CAPSULE
Closed trusted-extension tree
- selected entrypoints and supported assets
- closed package dependency graph
- exact model metadata
- no symlink, hardlink, special-file, or import escape
- content-addressed read-only mount
20b76f2
2026-08-26An exploration of how to implement highly usable, Linux native, agentic workflows.
This page is a living document exploring the design space of the current iteration of my workflow experiments. It is very intentionally not publicly indexed on the general web or exposed through my blog for two reasons: first, it is rapidly changing and may be entirely different tomorrow; more importantly, in order to experiment rapidly without spending inordinate amounts of time on ephemeral documentation, many sections are regenerated with substantial LLM assistance. I have made the conscious decision that my website must be entirely human-made, or at least human-written, and therefore this document doesn't meet that standard.
I have become very interested in exploring what a dynamic system-programmable by the agent itself during its own lifetime-would look like, and this pi extension is me toying with that idea.
You will, of course, notice that it currently doesn't go very far on the self-modifying axis, and this is an intentional design choice (earlier versions did explore it): I am not entirely convinced that, with the current generation of LLMs, we want to give them this ability, and I think it may actually be worthwhile to impose the structure of execution.
Given this choice, the interesting research paths, in my opinion, are:
You will notice that security is conspicuously absent from the list-not because it is unimportant, but because it means nothing more than “being correctly isolated in a workspace” in my own use case and threat model. I use AI agents mostly by giving them access to a shell with the same user rights as I have, so before even starting, any security posture is already broken, and doing any “fancy” cgroup isolation or eBPF observation (which I did test before asking myself “what the hell are you doing?”) is security theatre more than actual security.
The first and the last points proved to be mostly straightforward, at least in the current imperfect iteration, and as such most of the document focuses on the second point: workspaces and checkpoints.
Creating workspaces is not that hard, and if you decide to constrain yourself to, for example, git repos, then you may already have natural solutions such as git worktrees; but designing a usable workspace system proved to be quite hard for me in order to meet the two arbitrary constraints I imposed on myself: I want to be able to use workflows everywhere, including outside of git repos, and I want checkpointing and workspace creation to happen in milliseconds.
It is entirely possible that both choices prove not to be particularly useful, but I think they are achievable (and achieved) by making use of a very powerful constraint: I don't care if the system works for anybody other than myself, so I can use modern Btrfs, systemd, and any other Linux technology I want.
Given that most of the complexity is in understanding what is happening with Btrfs volumes, you will find in the left margin an always-visible volume annotation reminder, and volume symbols are all interactive in the text.
I hope you find useful ideas to steal, enjoy the presentation, and send me recommendations.
Start with one small workflow and follow it outward: first the language and interface, then the running agents, their private workspaces, and finally the verified changes applied to the live project.
Q launch baseline
W scope workspace
Sₙ boundary checkpoint
P final proposal
C host projection
F verified projection
A user can start a fairly substantial coding procedure with one ordinary command:
/execute-plan Add empty-input handling to the parser and run focused checks
What appears on screen is neither a graph editor nor an undifferentiated stream of agent messages. The workflow creates a plan, shows the resulting worklist, runs an agent for each implementation step, and keeps the current step visible until it can return a final result.
● Plan r1 · 1/3 · active ✓ 1. Inspect parser behavior ● 2. Add empty-input handling and coverage ○ 3. Run focused checks
This small interaction already explains why the rest of the system exists. The interface can show a worklist because worklists are part of the workflow language, rather than something guessed from an agent’s prose. The run survives a closed terminal because the Linux service manager, systemd, owns the process. The implementation agents work on a private copy of the project, and when they finish, the host—not the workflow—decides whether their changes are safe to apply.
Taken in order, the design answers five fairly ordinary questions. Each answer creates the problem handled by the next one.
DEFINE
Authors use a restricted form of TypeScript, but keep the familiar parts: branches, bounded loops, helpers, and typed values. Anything that talks to a model, changes durable state, or reaches outside the workflow remains an explicit flow.* call.
DERIVE
The definition already contains enough information to build launch forms and basic status views. When the language has a real concept such as a worklist, experiment, or human question, the interface can show that richer meaning without asking the workflow to describe pixels or terminal rows.
RUN
When someone launches a reviewed workflow, the host saves everything needed to run it again. A coordinator owned by systemd then follows the TypeScript procedure and starts each model call in its own bounded service.
ISOLATE
Agents that run one after another continue in the same private workspace. Agents that run at the same time cannot safely do that, so each branch gets its own writable workspace. A read-only checkpoint records the exact files left at every completed boundary.
SETTLE
The workflow returns a typed result, but that result is not permission to overwrite the project. The host separately captures the final filesystem, selects the allowed changes, verifies them, and checks that the live project has not changed underneath the run.
The order matters. If we started with snapshots and databases, most of the machinery would look arbitrary. Starting with the workflow makes the need for it easier to see: as soon as agents can run for a long time or work in parallel, the runtime has to preserve their complete coding state without allowing one branch to erase another.
One word appears throughout the study: authority. Here it simply means a fact or capability that the host has checked and is willing to act on. An agent may describe a checkpoint, claim that a command succeeded, or recommend that a proposal be applied; none of that text becomes authority merely because it sounds convincing.
| layer | owns | deliberately does not own |
|---|---|---|
| Language | Procedure, schemas, declared outside calls, typed results | Privileged paths, process identities, UI pixels, final application |
| Interface | Launch forms, status, operation structure, specialized views, evidence | Execution, control decisions, repair, proposal acceptance |
| Runtime | Saved launch description, call order, coordinator and worker lifetime | Interpreting workflow source as root or deciding when live files may be overwritten |
| Workspace | The editable project, read-only checkpoints, isolated branches | Interpreting returned prose or deciding how concurrent work should be combined |
| Settlement | The complete proposal, fixed file selection, verification, conflict-safe application | Letting workflow code choose files or declare its own changes safe |
This distinction is the thread connecting the rest of the study. The next chapter starts where a workflow author starts, with the language. From there we can derive the interface, turn a reviewed definition into running agents, and only then introduce the workspace, recovery, and settlement machinery needed to keep those agents honest.
Workflow authors describe the procedure in restricted TypeScript. Static review works out what it may do, while the runtime supplies the workspaces, checkpoints, recovery, and final application that would otherwise clutter every definition.
Q launch baseline
W scope workspace
Sₙ boundary checkpoint
P final proposal
C host projection
F verified projection
A useful workflow can be very small. The example below declares one write-capable agent, calls it, and returns its typed output. There is no snapshot code, no workspace setup, and no “apply these files” step.
import { agent, schema as s, workflow } from "pi/workflows";
const implement = agent({
profile: "builtin:implementer",
access: "write",
network: "research",
output: s.object({
summary: s.string({ minLength: 1, maxLength: 4_000 }),
}),
});
export default workflow({
description: "Implement one reviewed objective.",
input: s.object({
objective: s.string({ minLength: 1, maxLength: 20_000 }),
}),
output: s.object({
summary: s.string({ minLength: 1, maxLength: 4_000 }),
}),
async run(flow, { objective }) {
const result = await flow.agent(implement, { prompt: objective });
return result.output;
},
});
The short source does not mean that the safety machinery disappeared. The runtime still creates a private workspace, remembers the state from which it started, checkpoints the agent’s result, freezes the final proposal, verifies it, checks for conflicts, and cleans up afterward. Workflow authors do not have to reproduce that sequence every time.
The language has to satisfy two audiences at once. Humans and agents should be able to read and write it without translating every idea into a scheduler graph, but the runtime still has to know—before anything starts—which calls may reach models, commands, networks, people, measurements, or writable project state.
The compromise is restricted TypeScript: ordinary computation remains ordinary, while every durable or external effect goes through the flow object.
| goal | language choice | practical consequence |
|---|---|---|
FAMILIAR |
Normal TypeScript expressions and control flow | Authors use variables, functions, branches, bounded loops, try/catch, and typed values rather than nodes and edges. |
EXPLICIT |
Effects are named flow.* operations |
Model calls, commands, concurrency, worklists, questions, measurements, and experiment disposition remain visible in source. |
REVIEWABLE |
Descriptors and effect sites are statically declared | Review derives capabilities and resource bindings without executing run() or trusting a future runtime branch. |
DURABLE |
The host owns operation completion | Returning from a source-level call means its result, evidence, and required physical boundary have settled—not merely that a promise resolved. |
This is deliberately less permissive than general TypeScript and less ceremonial than an authored graph. A graph is useful when the graph itself is the product, but it is awkward when the procedure is naturally “loop over these plan points until we need to replan.” Unrestricted JavaScript would feel pleasant at first, yet it would also make ambient I/O, dynamic loading, and runtime effect discovery part of the security boundary. The restricted language keeps normal control flow while closing those escape routes.
The API exposes the things an author actually wants to do—run an agent, ask a question, measure a proposal—rather than the storage operations needed underneath. The author states the intent; the host decides what evidence is required before the call can count as complete.
| operation | author declares | host completion boundary | authority source |
|---|---|---|---|
agent |
One typed model task with profile, access, network, and output schema. | Validated finish_work, process absence, canonical evidence, and an immutable checkpoint. |
Reviewed agent descriptor and the current workspace authority. |
command |
One admitted executable profile and its reviewed arguments. | Supervised host result and durable effect evidence. | Static executable and effect descriptor. |
parallel / map |
Keyed lanes and their operation closures. | Terminal lane records and one complete ordered join record. | Reviewed source site plus runtime lane keys. |
unroll / item |
A bounded ordered worklist with stable item keys. | Committed manifest and monotonic item outcomes. | Reviewed source site and item keys. |
ask |
A typed question and response schema. | Challenge-bound response committed while the run waits. | Static response schema and human or remote procedure call (RPC) control. |
measureProposal |
Evaluator, metrics, sampling, and admitted write paths. | Immutable workspace checkpoint plus durably recorded samples, metrics, policy, and evidence. | Pinned measurement profile and write-path authority. |
settleExperiment |
One measurement reference, disposition, and semantic report. | Accepted state or complete checkpoint-restoration evidence after rejection. | Host-minted measurement and statically derived write authority. |
This is what makes the operations pleasant to combine. An author can place an agent inside parallel, or follow it with measureProposal, without threading workspace IDs, checkpoint paths, and recovery tokens through the source.
run()Before a workflow can run, static review reads its module shape and every declared flow.* call. It does not execute run() and hope to discover dangerous behavior only when a particular branch happens to be taken.
| domain | conclusion | reviewed facts |
|---|---|---|
MODULE SHAPE |
Restricted source |
|
EFFECT GRAPH |
Known calls and contexts |
|
CAPABILITIES |
Five derived grants |
|
RESOURCES |
Exact launch binding |
|
The result is a finite description of what the workflow may do: where effects can occur, which capabilities they require, and which resources they may use. Input can choose among options that were already reviewed, but it cannot add a new kind of effect after launch.
Definitions live in builtin:, user:, or trusted project: namespaces, but installation alone does not put a workflow in front of the model. A strict registry.json chooses the names exposed through the model-facing workflow tool; other valid definitions can still be launched by a person.
Drafting is separate again. The workflow_draft tool can create, replace, and validate inert source, but a person must answer a review challenge before that source can be promoted or exposed. A model can therefore propose new automation without quietly granting its own proposal permission to run.
Control branches and loop bounds must derive from input, pure computation, or restored operation results. The control realm excludes ambient files, processes, network, environment, clock, randomness, timers, dynamic evaluation, and workflow launch, but no analyzer can make arbitrary JavaScript logic deterministic.
The table above is the exact reference, but the source is easier to understand as a story. This sketch asks two reviewers to work in parallel, walks through a durable worklist, lets an implementer edit the project, measures the result, keeps or rejects the experiment, and optionally waits for a person.
import { agent, schema as s, workflow } from "pi/workflows";
const inspect = agent({ profile: "builtin:reviewer",
access: "read", output: Finding });
const implement = agent({ profile: "builtin:implementer",
access: "write", output: Result });
export default workflow({
async run(flow, input) {
const views = await flow.parallel({
design: () => flow.agent(inspect, { prompt: input.goal }),
tests: () => flow.agent(inspect, { prompt: input.goal })
});
await flow.unroll(input.points, async () => {
for (const point of input.points)
await flow.item(point.key, async () => { /* ordered work */ });
});
const done = await flow.agent(implement, {
prompt: `Integrate ${Object.keys(views).length} reviewed views`
});
const measurement = await flow.measureProposal(…);
await flow.settleExperiment({ measurement, … });
if (needsInput) await flow.ask({ … });
return done.output;
}
});
HOST-SUPPLIED CONTRACT
None of those calls derives a workspace, captures a checkpoint, mounts predecessor evidence, verifies a final proposal, applies it to the live project, or cleans up. Static review and the runtime attach those mechanisms to the operations instead of asking each workflow to reimplement them.
Optimization is a useful stress test because it repeatedly edits the project, measures the result, and then either keeps the change or rolls it back. optimize.flow.ts expresses that loop with ordinary agents, immutable measurements, and one explicit accept-or-reject decision.
const baseline = await flow.measureProposal(
args.evaluator, args.metrics, sampling, args.writePaths,
);
await flow.settleExperiment({
measurement: baseline,
disposition: "accepted",
report: { conclusion: "Established the immutable baseline." },
});
let best = baseline;
for (let iteration = 0;
iteration < maximum && !best.evaluation.targetReached;
iteration++) {
const trial = await flow.agent(experiment, {
prompt: "Form one hypothesis and make one bounded workspace change.",
});
const measured = await flow.measureProposal(
args.evaluator, args.metrics, sampling, args.writePaths,
);
const disposition = measured.evaluation.acceptable
? "accepted" : "rejected";
await flow.settleExperiment({
measurement: measured,
disposition,
report: {
hypothesis: trial.output.hypothesis,
intervention: trial.output.changeSummary,
conclusion: trial.output.conclusion,
},
});
if (disposition === "accepted") best = measured;
}
The loop reads like a normal experiment notebook: establish a baseline, ask an agent to try one bounded change, measure it, and either keep it or reject it. The source does not mention the full workspace restoration needed after a rejection because that is part of settleExperiment, not part of every optimization workflow.
The runtime can keep this loop reproducible, but it cannot make the experiment scientifically sound. The evaluator may be poor, the sample may be too small, and repeated trials may overfit the metric; none of those problems disappears because the workflow is durable.
The six built-ins feel quite different in use. Research fans out into independent lines of inquiry, coding makes one implementation pass, and optimization repeats measured trials. Underneath those different procedures, however, the same rules govern workspaces, evidence, recovery, authority, and final application.
| workflow | procedure | runtime pattern |
|---|---|---|
builtin:research Independent evidence lanes |
Map keyed research angles, collect failures, synthesize, critique, and revise. | Read-only branches return terminal checkpoints to synthesis and review. |
builtin:package-audit Sequential pipelines in mapped items |
Inventory, risk analysis, and test planning per package, followed by portfolio synthesis. | Each package lane preserves sequential continuity; the parent receives terminal lane evidence. |
builtin:coding One implementation pass |
A write agent implements the objective and returns a concise summary. | One private project workspace followed by automatic verification and live application. |
builtin:goal Bounded handoffs and corrections |
Read-only inquiry can hand off to shared-workspace implementation; independent review can request corrections. | Sequential checkpoints preserve every handoff while one proposal accumulates accepted work. |
builtin:execute-plan Stable worklist with replanning |
Create bounded plan points, execute them in order, replan from evidence, and run final checks. | unroll and item provide a semantic worklist; write agents continue one workspace. |
builtin:optimize Evaluator-governed experiments |
Measure a baseline, try bounded interventions, retain acceptable state, and restore after rejection. | Every measurement binds a checkpoint; rejection restores the accepted best workspace. |
Adding a seventh workflow should not require inventing a seventh storage model. What changes is the procedure; the physical guarantees remain the same. That shared vocabulary also gives the interface something stable to present, which is where the next chapter begins.
The workflow definition already contains much of what an interface needs. Saved run records fill in what actually happened, and a few runtime-owned concepts—worklists, experiments, and human questions—support richer views without putting UI instructions in the workflow.
Q launch baseline
W scope workspace
Sₙ boundary checkpoint
P final proposal
C host projection
F verified projection
Once workflows have a real language, the interface no longer has to start from an empty screen. The definition tells it what may run—through schemas, agent descriptions, and declared operations—while the run database tells it what actually happened. In this chapter, projection simply means turning those checked facts into a read-only view.
Some useful views need more structure than a list of generic calls. A worklist has ordered items, an experiment has a baseline and accepted result, and a human question has one current response challenge. The operations unroll and item, measureProposal and settleExperiment, and ask make those concepts part of the runtime contract. The interface calls the resulting specialized view a facet.
There is one important constraint: a run can detach, recover after a crash, and outlive the terminal that launched it. The interface must therefore rebuild every view from saved state instead of depending on events that happened to be seen in memory.
“Derived UI” can easily promise too much. The source cannot predict which side of a branch will run or how many loop iterations will occur; it can only describe the possible operations. The records created during execution fill in the rest.
| source | facts available | interface use | limit |
|---|---|---|---|
| Reviewed definition | Name, description, input and output schemas, descriptors, known effect sites | Exact model-tool union, /flow explain, launch fields, resource summaries |
Cannot know which runtime branches or loop iterations will occur |
| Generic run records | Lifecycle, operation instances, control branches, attempts, progress, resources, artifacts, checkpoints, attention | Status cards, operation topology, current work, receipts, inspector pages | Cannot infer domain terms such as “plan item” or “accepted experiment” |
| Semantic protocols | Worklist manifests, experiment measurements and settlements, challenge-bound questions | Typed worklist, experiment, and human-interaction views | Exists only for concepts with host validation and deterministic state reduction |
Every workflow gets the middle layer, even if it has no special UI at all. The last layer is deliberately rarer: a concept earns a facet only when it is useful in the workflow language itself, not merely because a renderer would like another card.
Two validated read models
Two read-only presentation surfaces
The diagram shows both views coming from the same database snapshot. The generic side can report lifecycle, current work, attempts, resources, artifacts, checkpoints, cross-run lineage, and anything that needs attention. A facet adds domain-specific meaning, such as the active plan item or the best experiment so far.
That richer path is intentionally expensive. A new facet needs code to validate its records, reduce them to current state, expose a bounded projection, render it, and test the whole chain. Letting a workflow emit a convenient string would be much easier, but the interface could no longer know whether the string was true.
Today, only three concepts clear that bar. Each starts with operations written by the workflow author, but the UI reports only what the host has actually checked and saved.
| protocol | author operation | saved facts | what the UI may say |
|---|---|---|---|
| Worklist | unroll + item |
Manifest, stable order, active item, monotonic terminal states | Pending, active, completed, failed, skipped, and exact omitted windows |
| Experiment series | measureProposal + settleExperiment |
Metrics, baseline, accepted reference, samples, disposition, report, restoration | Baseline, best, latest, gain, target, guardrails, active progress, accepted/rejected history |
| Human interaction | ask |
Challenge-bound pending request and typed response | Actionable waiting state without inventing domain meaning |
This is why the console can call an experiment “rejected” rather than “failed,” or identify one worklist item as active. Those are facts recorded by the protocol, not labels guessed from log messages.
execute-plan.flow.ts uses unroll to declare an ordered list and item to identify the point currently being executed. Titles and labels belong naturally in that definition; glyphs, colors, columns, and terminal widths do not.
await flow.unroll(
remaining.map(point => ({
key: point.id,
label: point.objective,
})),
async () => {
for (const point of remaining) {
await flow.item(point.id, async () =>
await flow.agent(executePoint, {
title: `Point ${point.id}`,
prompt: point.objective,
}));
}
},
{ title: `Plan r${planRevision}` },
);
● Plan r1 · 1/3 · active ✓ 1. Inspect parser behavior ● 2. Add empty-input coverage ○ 3. Run focused checks
The same worklist can appear as one line in a live tool widget, a compact receipt after completion, or a detailed section in /flow open. The amount of detail changes, but the item order and state do not.
Experiments make the same point more forcefully. A value such as “12.8” says almost nothing unless we also know what was measured, whether lower or higher is better, which unit and aggregation were used, how many samples were taken, and what the value should be compared with.
Latency · median · 3 samples + 1 warmup – Candidate 2 rejected 2 candidates · 1 accepted · 1 rejected Baseline 12.8 ms · Latest 11.6 ms Best 10.9 ms · Gain +14.8438% Target · relative gain 10% · reached Guardrails Memory · reference best · maximum regression 5%
A completed measurement therefore stores the metric definition alongside the observed values. The runtime also checks the sample count, aggregate, evaluator profile, environment, writable paths, and whether this result can honestly be compared with earlier ones.
| role | decision effect | projected meaning |
|---|---|---|
PRIMARY |
Decides improvement and stopping | Baseline, latest, best, gain, target, and sampling policy |
GUARDRAIL |
Constrains feasible regression | Reference, exact limits, current value, and violations |
OBSERVE |
Reports only | Secondary context that cannot change disposition |
REPORT |
Records authored learning | Hypothesis, intervention, and conclusion, separate from host evaluation |
Only the primary metric and its guardrails decide whether a candidate is acceptable. Extra observations provide context, and the report records what the agent thinks it learned, but neither can quietly turn a rejected result into an accepted one.
NO GENERIC UI EVENT BAG
There is intentionally no generic flow.emitUi(). If workflows could emit arbitrary tables or untyped UI events, every renderer would have to interpret author-defined meaning and decide whether it was still valid after recovery. Rich views are allowed only where the runtime already owns the underlying concept.
Once saved facts become UI, four simple rules keep the result trustworthy.
COHERENT One database snapshotOperations, progress, worklists, measurements, and settlements come from one database snapshot. The UI cannot accidentally combine an old operation list with a newer experiment result.
BOUNDED Exact omissionLarge runs must be shortened for display, but the current item, reference state, exact counters, and recent history remain visible. When details are omitted, the UI says how much it left out.
FAIL-CLOSED Corruption becomes an issueIf saved records contradict one another, the UI omits that facet and reports the problem. It does not invent the most plausible history.
PRESENTATION-ONLY No execution authorityColors, widths, rows, sparklines, and elapsed-time text remain presentation choices. Changing them cannot change workflow identity or saved domain state.
With those rules enforced earlier, the shared renderer can focus on presentation: fitting the real terminal width, clipping by visible character width, sanitizing control characters, preserving units, and keeping rejected experiments distinct from failed runs. Tool receipts and the console use the same underlying status logic, just at different densities.
Detached runs create one small but important usability problem: the model session that started the work still needs to hear when it finishes. While the run is active, the interface keeps one stable live widget. When the run ends, that widget disappears and Pi queues one short follow-up message after the current foreground work has settled.
The notification belongs to the interface, not to the workflow. Losing or repeating it must not change the result, resume the run, or require another prompt from the user.
We now know what the interface can honestly report, but not yet what keeps the work alive after Pi detaches. For that we have to leave the UI and look at the processes underneath it.
A reviewed definition still has to become real processes. systemd keeps coordinators and agents alive; SQLite records which workflow calls and scheduled launches those processes are allowed to complete.
Q launch baseline
W scope workspace
Sₙ boundary checkpoint
P final proposal
C host projection
F verified projection
A reviewed definition and a derived interface still do not run anything. To start real work, the host must save the exact definition and arguments, prepare the project state, start a coordinator under systemd, and give every agent its own bounded service.
Some of those steps genuinely require root privileges: snapshots have to be created, filesystems mounted, identities assigned, and services started. Rather than pretending that this privilege can disappear, the implementation keeps the root-facing interface deliberately boring. Each process gets enough access for one job and, just as importantly, no access to the neighboring jobs.
The caller may be a model, a person, or a timer, but the resulting run has the same shape. In every case, the launcher resolves one reviewed definition and saves enough information for the run to be understood and recovered later.
| origin | public surface | initial authority |
|---|---|---|
| Model session | workflow |
One exact member of the session-local union of exposed reviewed definitions |
| Human or RPC | /flow run |
One installed human-callable definition plus schema-valid arguments |
| Schedule occurrence | systemd-backed /schedule definition |
One reviewed launch template whose saved permission has not expired |
Once the caller-specific checks pass, all three paths converge.
RESOLVE
Resolve the exact namespace, definition, input schema, and caller authority. Missing, ambiguous, untrusted, or unexposed definitions fail before a run exists.
FREEZE
Save the source, arguments, project, runtime API, profiles, model route, resources, safety policy, and hashes. Recovery reads this frozen description instead of whatever Pi happens to be configured to use later.
START
The fixed privileged launcher receives only the run ID. It prepares the project state and starts pi-workflow-coordinator-<id>.service, which systemd continues to own after the initiating Pi session has detached.
EXECUTE
The coordinator starts a restricted control worker, which re-executes the reviewed TypeScript and reads completed calls back from SQLite. It asks for new outside work only when the procedure reaches a call that has not already completed.
SPAWN
An agent operation becomes pi-workflow-agent-<id>.service. That service receives one project view, its predecessor evidence, the selected provider and tools, one credential channel, and fixed limits on resources and filesystem visibility.
SETTLE
The runtime validates the terminal result, proves that the worker and all of its child processes have stopped, captures the evidence and checkpoint, and only then marks the workflow call complete.
A live process identifier (PID) is therefore not enough to describe a run. systemd can say which service and control group (cgroup) exist; SQLite says which workflow call that process belongs to and whether it is still allowed to complete it.
| role | identity | authority | explicit exclusion |
|---|---|---|---|
| Pi extension | interactive user process | Registry, launch request, UI, controls, drafts, schedules | Does not own run lifetime. |
| Privileged launcher / broker | fixed root services | Static re-review, snapshots, mounts, projection, publication, retirement, systemd | No workflow evaluation or model command policy. |
| Coordinator | systemd-owned storage identity | SQLite transitions, recovery, effects, workspace graph | No arbitrary privileged filesystem API. |
| Control worker | restricted child realm | Pure TypeScript control and host calls | No ambient I/O, time, randomness, or process access. |
| Agent worker | UID/GID 1000 service | One project view, evidence, provider, admitted tools | No live project, home, D-Bus, mounts, capabilities, or broker socket. |
| Collector / publisher | short-lived UID/GID 2000 helpers | Bounded inventory and publication roles | No general write API or model-selected coding role. |
The exclusions are the important part of the table. The process that understands workflow source is not root, while the root services do not interpret TypeScript or execute shell commands chosen by a model.
Calls across the root boundary are intentionally uninteresting: an operation version followed by one validated run ID.
launch-v1 flow_<32 lowercase hex> storage-v1 flow_<32 lowercase hex> delete-v1 flow_<32 lowercase hex>
The root service looks up everything else itself: paths, reviewed capabilities, storage policy, service properties, identities, and allowed executables. Those values come from the sealed release, the saved launch description, and fixed host configuration—not from extra arguments supplied by the caller. There is no place to smuggle in “just one more” path or executable.
An agent still needs extensions, provider access, and a useful process environment. The runtime assembles those resources explicitly instead of letting the worker discover whatever happens to be installed in the interactive user’s home directory.
CAPSULE
CREDENTIAL
SERVICE
Together, these pieces make the worker environment predictable. Trusted extension code comes from the read-only capsule, one provider credential arrives through a private channel, and the service limits what either can reach. The same boundary also makes checkpointing possible: before taking a snapshot, the runtime can prove that no child process is still changing the workspace.
The implementation requires Linux, cgroup v2, systemd, Btrfs copy-on-write storage, idmapped mounts, fixed system executables, and a reviewed root installation. There is intentionally no portable filesystem or process-supervision fallback.
Closing a terminal should not silently kill a workflow that was meant to continue. Pi can detach once a boundary has been saved because the coordinator owns the run and systemd owns the process. Pause, resume, stop, and human responses are written as durable requests rather than sent as fragile signals to whichever process happens to be alive.
| surface | boundary | behavior |
|---|---|---|
AWAITED |
Durable run boundary | The model tool or /flow run returns when the run is waiting, paused, completed, failed, or stopped. |
BACKGROUND |
Presentation detaches | Launch returns after publication. Closing or reloading Pi removes polling; it does not stop the coordinator. |
CONTROL |
Durable request queue | Pause, resume, stop, stop-effect, and ask-response requests commit before waking the coordinator. |
INSPECTION |
Read-only projection | /flow open and protocol envelopes inspect bounded run facts and evidence. |
“Background” is therefore a presentation choice, not a weaker kind of execution. The same service continues to run, and reconnecting simply rebuilds its view from saved facts.
Human question handoff. flow.ask() commits a waiting interaction. A response command binds the current challenge, schema-valid value, actor, run transition, and wakeup. A stale response cannot resume a newer question.
A schedule is best understood as an interactive launch saved for later. It points to the same reviewed definition and arguments, but a person grants permission for several future launches rather than just one. Because that is a substantial delegation of authority, /schedule is available only to a human or RPC caller, never to a workflow worker.
The following command prepares a recurring builtin:research job. It fixes the workflow, arguments, project, calendar and timezone, catch-up behavior, and two limits on how long the permission remains valid.
/schedule create nightly-research builtin:research \
--args '{"question":"Summarize changes since yesterday"}' \
--calendar "*-*-* 09:00:00 UTC" \
--timezone UTC \
--catch-up once \
--max-runs 30 \
--valid-until 2028-01-01T00:00:00.000Z
/schedule confirm create sha256:<exact-review-hash>
/schedule resume <schedule-id>
Creation does not enable the timer immediately. It returns an exact review challenge and stores the schedule paused; after confirmation, a separate resume command checks it once more before future activations are enabled. Replacing or renewing a schedule sends it through the same paused, reviewed, then resumed sequence.
The schedule creates a persistent systemd user timer. For the example above, the relevant properties are equivalent to:
[Timer] OnCalendar *-*-* 09:00:00 UTC Unit pi-workflow-schedule@<schedule-id>.service Persistent yes AccuracySec 1min RandomizedDelaySec 0 DeferReactivation yes WakeSystem no
Persistent=yes implements catch-up once, while catch-up none uses Persistent=no. Notice what the timer does not contain: no workflow source, arguments, project path, executable choice, or general permission to launch work. It can start only the fixed schedule worker for one schedule ID.
A timer wakeup is only a reminder to check the schedule. Under a machine-wide lock, the schedule database verifies that the saved permission is still valid and that another run is allowed; only then does it create an ordinary workflow run.
WAKE
The timer starts the fixed schedule service with one validated schedule ID. systemd gives that service a lifetime and an invocation identity, but does not decide whether a workflow may launch.
LOCK
The worker takes the schedule and machine-wide admission locks, repairs any occurrence left halfway through preparing, and reads one coherent schedule revision.
ADMIT
SQLite checks that the schedule is enabled, still refers to the reviewed definition and project, has not exhausted its lease, is not launching too soon or overlapping itself, and does not duplicate an occurrence already admitted. It also enforces the machine-wide limit on scheduled runs.
MATERIALIZE
An accepted occurrence gets a fresh run ID and project baseline, while retaining the exact schedule revision that authorized it. From that point onward, its coordinator follows the same path as an interactive run.
REPORT
If a run is created successfully, the launch-failure counter resets even if the workflow itself later fails. Repeated launch failures can block the schedule; a workflow that launched and then failed or started waiting instead produces an attention item for the project.
| policy | purpose | rules |
|---|---|---|
LEASE |
Standing authority expires |
|
OVERLAP |
One schedule never overlaps itself |
|
FAILURE |
Launch and run failure differ |
|
INBOX |
Attention is a projection |
|
The command surface exposes those saved objects instead of hiding everything behind the timer:
/schedule list a1b2c3d4 · nightly-research · enabled · *-*-* 09:00:00 UTC /schedule inspect a1b2c3d4 /schedule run-now a1b2c3d4 /schedule inbox /schedule doctor
run-now is not an escape hatch: it passes through the same lease, spacing, overlap, and machine-wide limits as a timer occurrence. Pausing or deleting a schedule prevents future launches but does not revoke a run that has already been created. When the user manager starts, a reconciler repairs incomplete timers and half-prepared occurrences, then relaunches coordinators that should be active; waiting and paused runs remain dormant.
The practical result is a recurring job whose permission is visible and finite. It expires, cannot overlap itself, survives a closed terminal, and keeps three very different events separate: a timer wakeup, a failed launch, and a workflow that launched correctly but later needs attention.
We now know who may start a process and how long that process can live. The next problem is more physical: how can sequential agents share their files while concurrent agents remain isolated from one another?
Sequential agents continue in one private workspace, with cheap read-only checkpoints recording what each one left behind. Parallel agents get separate branches and return them as evidence rather than asking the host to merge their work blindly.
Q launch baseline
W scope workspace
Sₙ boundary checkpoint
P final proposal
C host projection
F verified projection
Long-running agents create a simple physical problem: they need the complete project, but they should not edit the live copy or erase one another’s work. The easiest way to understand the solution is to follow one workspace. Agent A edits it, the runtime takes a checkpoint, and Agent B carries on in the same place. The checkpoint remembers what A completed without forcing B into another freshly copied workspace.
Three symbols describe project state while the workflow is running. The important difference is simple: agents can change W, while Q and S are frozen.
The runtime calls each independent strand of workflow control a scope. The root run is one scope; every parallel lane or mapped item gets another. Each scope owns one W, although sequential agents inside that scope use the workspace one after another.
| symbol | object | mutability | role |
|---|---|---|---|
| Q | launch baseline | immutable | Exact project state captured when the run launches |
| W | scope workspace | writable | The only project filesystem a write agent can modify |
| Sₙ | boundary checkpoint | immutable | Exact state captured at an agent, fork, or measurement boundary |
Q gives the run a known starting point, W carries the ongoing work, and S records a completed boundary. Taking S does not replace W; the next sequential agent simply continues in the same workspace.
That small detail—S branches off while W continues—keeps the common case cheap. A Btrfs snapshot records the exact files, directories, modes, and project roots as immutable Sₐ, while W remains the place where Agent B works. Creating another writable workspace after every sequential step would add machinery without providing any additional isolation.
New writable workspaces are reserved for cases that actually need a branch: parallel work, a clean retry, restoration after a rejected experiment, disposable evaluation, or an explicit restore. There is one final snapshot at successful write completion, when the root W becomes immutable proposal P. A read-only run keeps its useful checkpoints but can discard the private workspace.
A project is not always one convenient directory-shaped Btrfs subvolume. It may begin partway through a larger subvolume, and it may include approved auxiliary roots on other Btrfs filesystems. The runtime combines those pieces into one project view and carries the whole verified set through checkpoints, cross-run import, final application, and cleanup. Sibling directories and storage-control data remain outside the agent’s view.
The details below all support the same rule: every writable scope receives the complete project view, but workflow code never chooses the physical storage objects behind it.
| mechanism | host enforcement | rejected authority |
|---|---|---|
| Scope ownership | Workspace-generation and scope-workspace records bind one active W to one semantic scope. | Sibling scopes cannot share writable authority. |
| Checkpoint authority | Root records bind Btrfs UUIDs, lineage, read-only state, and receipt hashes. | Names and model claims do not establish authority. |
| Project view | Verified project roots are mounted at fixed worker destinations. | Callers cannot choose mount targets or expose ancestor and storage-control paths. |
| Git metadata | .git is read-only; optional locks and system Git configuration are disabled. |
Index refresh cannot alter the baseline-to-proposal comparison. |
Notice what does not appear in the authority column: a path invented by workflow code, a claim in model output, or the current contents of a mutable Git index. All three can be useful information, but none proves which filesystem state the runtime should trust.
DECISION 01
Carry exact project state between agents, with boundary capture cheap enough to use after every call.
Each scope owns W; each agent boundary captures S; causal successors receive verified evidence mounts.
Artifact manifests and per-path writer protocols omit unselected state and make coding continuity depend on manual mutation choreography.
This moves substantial responsibility into the runtime: Btrfs identity, mount isolation, process absence, quota policy, and complete physical cleanup all have to work.
Snapshots are cheap, not free, so the workspace model also needs a storage limit. The runtime never turns on Btrfs quotas by itself. If traditional quota groups (qgroups) are already enabled, the broker creates one predictable level-1 qgroup for the run, limits its exclusive use to 4 GiB, and assigns every run-owned filesystem object to it.
If quotas are disabled, a run can proceed without its own qgroup, but the host still keeps admission and emergency free-space reserves and checks checkpoint size. If quota information is inconsistent, being rescanned, changes during inspection, or already exceeds the limit, the runtime refuses to create the checkpoint rather than promising storage it cannot retain.
Inside this boundary, the worker should still feel like a normal coding agent. It gets Pi’s usual filesystem tools and full Bash, but only inside its isolated W. Nothing becomes checkpoint evidence until the worker, and every process it started, has exited.
The namespace makes that separation visible: one place to edit, one place for private notes, and several read-only locations containing the task and evidence left by earlier agents.
/workspace
/evidence/
index.json
current/
instruction.md
invocation.json
notes/
incoming/
<causal-boundary>/
boundary.json
handoff.md
result.json
instruction.md
transcript.jsonl
artifacts/
workspace/ # optional, immutable
/outputs
/runtime/capsule
/state
/run/pi-workflow/agent.sock
WRITE PLANE Only W and private notesA write agent can modify /workspace and its current notes. Incoming evidence and resource capsules are read-only.
TOOL PLANE Native Pi behaviorProfiles admit the ordinary read, search, edit, write, deletion, workspace, and mediated-research tools needed for normal coding semantics. Workflow launch tools stay outside the worker so delegated work cannot expand orchestration authority.
NETWORK PLANE Provider plus mediationThe selected provider transport remains available. Research access is coordinator-mediated; host commands run in separate networkless services.
EVIDENCE PLANE Lazy and completePrompts name the evidence contract. Complete predecessor boundaries remain inspectable on demand, avoiding lossy artifact selection and large injected summaries.
An agent therefore needs no special “artifact API” to preserve its work; it edits /workspace normally. Evidence remains available when needed without being mixed into the proposed project changes.
When the agent is done, it makes one terminal call. The call contains a typed result for the workflow and a short handoff for the next agent.
await finish_work({
output: typedOutput,
handoff: "Concise downstream orientation: decisions, paths, checks, and remaining risk.",
});
output drives the workflow, while handoff helps the next agent find the relevant decisions, paths, checks, and risks. Neither piece of text proves what happened to the files. The immutable checkpoint is the project state actually left by the agent.
Receiving valid JSON from the model is only the first step. Before the call can count as complete, the runtime must prove that the worker is gone, freeze the filesystem, assemble the evidence, and save the handoff—in that order.
FINISH
The exact typed output schema and complete finish-wrapper schema are checked and hashed separately.
QUIESCE
The service and every descendant must exit; the cgroup must be inactive and empty.
SNAPSHOT
Every project root is inspected, snapshotted read-only, and bound to deterministic operation and scope identity.
MATERIALIZE
Instruction, invocation, output, handoff, transcript, tools, resources, activities, notes, artifacts, causal index, and checkpoint become one canonical boundary.
COMMIT
The handoff row commits the boundary hash and checkpoint before the agent operation can become a completed call.
Only then can the next workflow operation see the call as complete. This ordering ties the typed result to the exact files that accompanied it.
READ-ONLY AGENTS ALSO LEAVE S
A read agent cannot modify its workspace, but a successful read still leaves a checkpoint and handoff. Treating read and write agents alike here gives every completed call the same recovery and evidence shape.
Sequential calls can share W because only one uses it at a time. Concurrent calls cannot, so every lane receives its own writable branch while the parent waits at the fork.
The key phrase in the diagram is “mount, do not merge.” When the lanes finish, the parent workspace resumes unchanged and each lane appears beside it as read-only evidence. A joining agent—not a blind text merge in the host—decides whether to combine that work, use it only as advice, or ignore it.
OUTCOME Every terminal lane is representedSuccessful, failed, and cancelled terminal checkpoints can be mounted as outcome-bearing evidence. Failed state remains observational and never becomes continuation state implicitly.
NESTING The same rule recursesParallel branches and mapped items own child scopes, local cursors, workspace generations, terminal keys, and exact join edges.
Forking creates two matching records. One says which lanes existed and how they ended; the other identifies the workspaces and checkpoints belonging to those outcomes.
| boundary | semantic record | physical record |
|---|---|---|
| Fork | Owner operation, child scopes, lane keys | Parent checkpoint and one derived W per lane |
| Lane | Local cursor chain and terminal key | Local W and terminal checkpoint |
| Join | Ordered lane outcomes and join key | Join-evidence edges to exact terminal S records |
Neither record is enough by itself. A complete list of lane keys cannot prove that their checkpoints still exist, while a pile of snapshots cannot explain which lanes they belong to or how the join ordered them.
DECISION 02
Keep lane edits separate while giving the parent everything needed to understand and combine their results.
Mount each finished branch read-only and let a joining agent inspect and combine it with ordinary tools.
A host text merge cannot know whether changes should combine, whether one lane only informs another, or which semantic result is intended.
The join records every lane key, outcome, checkpoint, and evidence link, including lanes that failed.
The same replacement mechanism supports a clean retry, a rejected experiment, an explicit restore, and cross-run replay, where a new run imports a completed prefix from an older one. In every case, the new workspace is saved before control continues, so the workflow cannot advance while still pointing at the old files.
The optimization loop from the language chapter repeatedly edits, measures, accepts, and rejects project state. Underneath that simple source, every measurement evaluates an immutable S rather than a workspace that might still be changing.
BASELINE
The first measurement establishes metric definitions, samples, environment, write paths, and the accepted reference checkpoint.
INTERVENE
The implementer is instructed to use declared write paths. Measurement detects any changed path outside them and makes the trial unacceptable.
MEASURE
The host snapshots exact state, runs warmups and samples, aggregates outputs, and applies primary and guardrail policy.
Measurement disposition
ACCEPT
An acceptable trial becomes the latest accepted best and the next experiment’s reference.
REJECT
Write paths constrain admission, not rollback. The runtime replaces the complete workspace generation from the accepted checkpoint and records the physical receipt.
Acceptance is cheap because W already contains the measured state. Rejection does more work: the runtime replaces the whole workspace from the best accepted checkpoint, so an undeclared leftover from the rejected trial cannot leak into the next one.
DECISION 03
Preserve valid experimental evidence without allowing a non-improving trial to become the next workspace state.
Partial rollback could retain undeclared changes from a rejected trial; deriving all of W from accepted S restores one complete known state.
Treating rejection as workflow failure would conflate a valid measured outcome with broken execution and obscure the experiment series.
A failed measurement, broken restore, or contradictory disposition is an actual workflow or integrity failure.
The workspace layer now preserves the files, but files alone cannot tell us which workflow call produced a checkpoint or which call should run next. That is the job of the control journal.
A checkpoint preserves files, but the runtime also needs to remember which workflow call produced them. The journal ties those two facts together so a restart can reuse finished work and a new run can import only a complete prefix.
Q launch baseline
W scope workspace
Sₙ boundary checkpoint
P final proposal
C host projection
F verified projection
The workspace layer preserves files across agent calls, but recovery has an extra complication: after a crash, the TypeScript procedure starts again from the beginning while the retained workspace does not rewind. The runtime needs a way to recognize work that already finished and reconnect it to the repeated procedure.
A checkpoint answers “which files existed?” but not “where was the workflow?” To resume safely, the runtime must know which scope produced the checkpoint, which call it belongs to, what request that call made, and which completed call came before it. Together those fields give the checkpoint a stable place in program order.
NEXT OPERATION = (scope, cursor, previous call key)
The root workflow, every parallel branch, and every mapped item has its own sequence. A cursor identifies calls made one after another, while explicit lane keys identify concurrent work without depending on which worker happened to finish first.
The fields overlap on purpose because they catch different mistakes. A source location can identify the wrong request, a matching request can run under different resources, and a result can exist without a completed checkpoint. One convenient hash would hide those distinctions rather than remove them.
| field | purpose | durable check | not sufficient alone |
|---|---|---|---|
| scope + cursor | Program-order address | Contiguous local sequence | The operation kind and request must also match. |
| source site | Static admission token | Compiler-inserted method/site mapping | Source location is excluded from result equivalence. |
| semantic input | Canonical request identity | Kind-specific hash | Late-bound route, resource, and workspace context also matter. |
| semantic key | Context-bound operation identity | Descriptor, resources, authority, ancestry | It does not prove a terminal outcome. |
| call key | Committed predecessor for the next call | Outcome, result hash, authority, checkpoint | It is a local integrity link, not a signature. |
When the TypeScript procedure reaches a call for the second time, these fields let the runtime answer a precise question: “Is this the same call, under the same reviewed conditions?” The answer still does not prove that the call finished; for that, the runtime also needs its saved result and checkpoint.
Each operation has different completion rules, as the operation table showed, but they all enter the journal in the same order. First, the operation adapter saves the outside work’s result or failure; this is effect settlement. The engine then records that settled result at the current place in program order and commits the call.
Why split this into two commits? Because a crash can happen after “the outside work finished” but before “the workflow moved on.” If the result was already settled, the restarted procedure can validate it and commit the call without launching the outside work a second time.
BEFORE EFFECT SETTLEMENT
If an external service has neither idempotency nor a recoverable receipt, a crash can leave ambiguity. Local journaling cannot manufacture exactly-once semantics.
AFTER EFFECT SETTLEMENT
The result or failure, the evidence needed for recovery, and the checkpoint identity are already saved.
AFTER CALL COMMIT
The next operation’s predecessor binds this exact terminal outcome and local sequence.
This sequence reduces the uncertain window; it cannot abolish it. Once effect settlement is durable, recovery only has to validate saved evidence. Before that point, an external service still needs its own idempotency key or some receipt that can be recovered after a crash.
Progress is observable, not semantic. Warmup/sample counters and host messages wake projections but never enter semantic keys, result hashes, or replay decisions.
Suppose the coordinator crashes after saving the agent’s result and checkpoint but before committing the call. On restart, it reaches the same call, validates the saved boundary, commits it, and continues without launching another agent.
The coordinator runs twice; the agent runs once.
This is the point of “two control passes, one agent launch” in the diagram. Repeating the TypeScript is harmless; repeating the agent and its filesystem changes may not be.
RECOVERY VALIDATION
A saved result is not reusable if its checkpoint has disappeared or changed. Conversely, a perfectly valid snapshot is not enough if it belongs to another operation, source definition, handoff, or parallel join.
| domain | boundary | required evidence |
|---|---|---|
SEMANTIC |
Journal boundary |
|
PHYSICAL |
Workspace boundary |
|
CAUSAL |
Evidence boundary |
|
Recovery and replay both reuse earlier work, but only recovery stays inside the same run. Recovery asks whether the current run can continue from its own saved state. Replay creates a new run and asks whether it may import a completed prefix from an older one.
SAME-RUN RECOVERY Validate existing authorityRe-execute the exact admitted source, consume this run’s effect-settled calls, validate W generations, checkpoints, joins, handoffs, and physical identities, then continue.
CROSS-RUN REPLAY Import a selected prefixCreate a new run, import a completed source prefix, remint its checkpoints as target-owned objects, replace target root W at the selected boundary, then execute later calls fresh.
The new ownership is the important part. Replay preserves the earlier typed results and records where they came from, but the new run creates its own checkpoints and runs the remaining suffix under its own current configuration and authority.
A replay point must include a complete root boundary. A caller cannot select one attractive parallel lane while omitting its siblings and the join that gave the lane its meaning.
ADMIT
Verify source completion, root ownership, operation boundary, source definition, resources, and structural closure.
IMPORT
Copy attempts, typed results, handoffs, transcripts, artifacts, and causal evidence while recording the source run they came from.
REMINT S
Derive new immutable checkpoints from the source checkpoints. The files and logical outputs remain the same, but the physical receipts now belong to the new run.
CUT OVER
Create a fresh writable workspace from the imported checkpoint, install it as the new root W, record the replay link, and retire the workspace it replaces.
CONTINUE
Run every later call normally against the imported workspace and the new run’s current configuration.
Replay is therefore more than copying files. The completed calls, their evidence, the checkpoints, and the new writable workspace must all agree before fresh execution begins.
After the ordinary recovery checks pass, replay adds four rules that are specific to importing another run.
| rule | reject when |
|---|---|
PREFIX CLOSURE |
A lane is selected alone, or any lane outcome, terminal checkpoint, key, or join edge is missing. |
REMINT S |
The source object is unavailable or changed, remint lineage is incomplete, or source identity is presented as target authority. |
CUT OVER W |
The root set differs, the exchange is not durable, or displaced mutable authority remains active. |
FRESH SUFFIX |
A source suffix outcome or source physical receipt is reused as target continuation. |
The rules forbid four tempting shortcuts: selecting a lane by itself, pretending the old run’s snapshot already belongs to the new one, replacing W only partially, or quietly reusing calls beyond the selected prefix.
The imported checkpoint receipts are not part of logical result hashing because the new run owns different Btrfs objects. Source provenance is stored separately, preserving the original result without pretending that the old physical identity carried over.
Completed runs keep their immutable checkpoints, and the associated quota charge, until they are explicitly deleted. If another retained run cites one of those checkpoints as replay evidence, the source run cannot yet be deleted. Reuse therefore creates a real storage dependency, not just a link in the UI.
No single file or process can prove an entire run valid. SQLite records where the procedure is, Btrfs identifies the project state, proposal receipts describe final application, and systemd identifies the processes that performed the work.
| authority | canonical form | consumer | not inferred from |
|---|---|---|---|
| Semantic | run.sqlite records and call keys |
Coordinator and read-model projector | Filesystem names or UI rows |
| Physical | Btrfs UUIDs, parent lineage, read-only state, root receipts | Privileged broker and workspace graph | SQLite IDs alone or model text |
| Proposal settlement | Run-local proposal.sqlite and terminal settlement receipts |
Proposal-settlement and live-apply services | Workflow return value |
| Execution | systemd unit, invocation ID, cgroup | Coordinator and worker supervisors | A live PID alone |
Checking all four is more work, but it prevents a convenient record from standing in for evidence it does not contain. A SQLite row cannot resurrect a missing snapshot, and a correctly named snapshot cannot prove that the workflow ever reached it.
A run leaves behind databases, explanatory files, and Btrfs objects. They are all evidence in a broad sense, but they do different jobs: databases enforce the current workflow and settlement state, bounded files explain what happened at agent boundaries, and Btrfs preserves the actual project files. The inventory groups them by purpose rather than pretending that they form one literal directory tree.
| layer | durable objects | purpose | authority rule |
|---|---|---|---|
| Run authority | source.flow.tsrun.sqliteproposal.sqlite |
Bind admitted source to semantic, workspace, and proposal-settlement state. | Atomic publication prevents execution against partial invocation data. |
| Launch context | context/invocation.jsonruntime-api.jsonproposal-project.json |
Retain arguments, schemas, project binding, launch origin, and exact public API. | Replay inputs are explicit and hashed rather than ambient configuration. |
| Resource capsule | resources/capsule_<hash>/ |
Provide one content-addressed trusted-extension closure. | Interactive home state, credentials, and mutable package discovery stay outside the capsule. |
| Agent boundary | evidence/agents/operation_…/boundary.json + bounded records |
Explain instruction, output, handoff, transcript, tools, resources, notes, and artifacts. | One canonical boundary hash names complete but separately inspectable evidence. |
| Project objects | Btrfs-managed project root sets | Retain mutable continuation and immutable project-state boundaries. | UUIDs, lineage, read-only state, and receipts—not SQLite names alone—establish physical authority. |
| Settlement evidence | proposal-evidence/inventory + projection + verification receipts |
Explain baseline, proposal, projection, verification, application, and cleanup. | Settlement records remain inspectable without becoming project proposal content. |
When investigating a run, the useful order is to start with the reviewed source and current database state, follow an agent boundary when an explanation is needed, and consult the Btrfs receipts when the question depends on exact project files.
DATABASE MODEL
SQLite makes one final distinction that is easy to miss. The rows that enforce what is true now are separate from the history explaining how the run arrived there.
| role | model | contract |
|---|---|---|
ENFORCED STATE |
Normalized current-state tables | Rows, constraints, and revisions define current run authority and which transition may commit next. |
CAUSAL PROVENANCE |
Ordered events and provenance records | Events, attempts, artifacts, receipts, and boundary hashes explain how state arose without superseding current-state authority. |
An event log is excellent for explanation, but it does not override the tables that enforce current state. The interface reads both and reports an integrity problem if they disagree.
At this point the runtime can tell which work completed, which files belong to it, and whether recovery or replay may continue. One question remains: how does a successful private workspace become a safe change to the live project?
A successful write workflow is not finished until the host has frozen its proposal, selected and verified the allowed changes, checked for concurrent live edits, and retired every mutable workspace.
Q launch baseline
W scope workspace
Sₙ boundary checkpoint
P final proposal
C host projection
F verified projection
An agent may report that it changed three files, but the host does not use that report as the change list. It compares the complete starting state Q with the final workspace W, derives the proposal from the files themselves, and applies only the result allowed by fixed host policy.
The workspace chapter introduced Q, W, and S and briefly showed P at successful completion. Settlement finishes that sequence. P is the frozen proposal left in W; C is a temporary workspace containing only the changes selected by host policy; F is the immutable result after that projection has passed verification.
| symbol | object | mutability | role |
|---|---|---|---|
| P | final proposal | immutable | Final root W captured after semantic success |
| C | fixed host projection | temporary, writable | Host-owned projection of every selected non-protected change from Q to P |
| F | verified projection | immutable | Exact clean state authorized for conflict-safe application |
AUTHORITY RULE
A reviewed project agent receives read-project; declaring access: "write" also requires the derived proposal-write capability. A reviewed settleExperiment call needs the same write capability. The workspace graph decides which private W the agent may use, while proposal-write permits project changes and triggers automatic settlement at successful completion. Neither a persuasive workflow name nor a privileged-looking namespace can create those permissions.
The full protocol has seven stages. Read the diagram from top to bottom: each stage answers one question before the next stage is allowed to act.
Follow the spine downward. Each stage establishes the boundary required by the next.
W · QUIESCEProve agent services and descendants absent; reconstruct and validate the complete workspace graph.
P · CAPTURECreate one immutable final proposal root set after the TypeScript program returns successfully.
Δ Q→P · INVENTORYBound paths, inode kinds, bytes, hashes, protected state, and auxiliary roots without trusting model prose.
C · PROJECTThe host selects every non-protected change. Workflow code cannot select or hide proposal paths.
F · VERIFYProject C, run the pinned verification profile in a disposable derivative, and capture immutable F.
LIVE · APPLYLock the project, compare affected live paths with Q and F, preserve concurrent third-party work, apply only exact safe changes, and record the application.
CLOSED · RETIREUnmount views and retire W, branch workspaces, and temporary C/F objects according to terminal retention policy.
The preceding chapters used the word “settlement” for three related but different transitions.
One operation’s result or failure becomes durable before the workflow call commits.
settleExperiment records whether one measured trial is accepted or rejected.
The host runs the terminal Q→P→C→F→live protocol after a successful write-capable run.
Completing one agent call does not apply a proposal, and rejecting one valid experiment does not mean that the workflow failed.
Verification proves that F passed the configured checks, but it does not make an overwrite safe. Someone may have edited the live project since Q was captured, so the apply service compares every selected path with both Q and F.
| condition | live state | action |
|---|---|---|
UNCHANGED PATH |
Live still equals Q | The verified F postimage may replace the accepted preimage. |
ALREADY APPLIED |
Live already equals F | Recovery recognizes the exact postimage without writing it again. |
CONFLICT |
Live equals neither Q nor F | Application stops. Concurrent third-party work is never silently overwritten. |
OUTSIDE MANIFEST |
Path is absent from the host-generated selection manifest | Live apply neither classifies nor rewrites it. |
The rule is deliberately conservative. If the live path still matches Q, replacing it with F cannot erase an intervening edit. If it already matches F, recovery can recognize that the change was applied earlier. If it matches neither, the host stops instead of guessing.
An empty proposal is a no-op only when the complete inventories of Q and P are equal. “The agent says there were no changes” is not enough. Conversely, a write-capable workflow cannot report success while leaving an unclassified mutable proposal behind.
Proposal settlement is automatic because every write workflow should finish under the same application rules. Letting each author invent approval and apply steps would make “completed” mean something different for every workflow. This consistency still does not establish production safety: productionAdmission is false, and testing is limited to reviewed guest or disposable hosts.
That completes the path from a TypeScript procedure to verified live files. The remaining chapters are reference material: first the systems and papers that influenced the design, then the much less tidy question of what the prototype has actually proved.
The implementation is coherent enough to study, but nowhere near proven enough to call production-ready. This final chapter separates what the source shows, what still needs serious testing, and what the runtime can never prove by itself.
Q launch baseline
W scope workspace
Sₙ boundary checkpoint
P final proposal
C host projection
F verified projection
It is tempting to end an architecture study with a tidy verdict; however, I do not currently have one. I am still in the early testing phase to determine if the current shape of this experiment actually accomplishes its ultimate goal: being useful.
In order to answer decisively, I still need to accomplish three distinct things:
The second and last points in particular are the more uncertain ones: while proving that the code actually works and does what it is supposed to do is indeed not trivial and most definitely time-consuming, it is still a fairly well-defined exercise.
However, despite all the effort I've put into implementing what I think to be a good workflow model, I am still not entirely convinced that they actually help, other than potentially achieving some increased speed through parallelization and burning through tokens in the process.
There is even some evidence that the optimize//goal workflow may not even achieve better performance compared to a normal agentic loop.
Benchmarks are therefore the only way to answer that question, and benchmarks are hard to design and implement correctly.
The current design was not my first attempt. I built and exercised four earlier approaches, then abandoned them when their handoff models became too awkward or simply failed to meet the constraints I cared about. “Tested” is doing fairly modest work here: it means that I used each design enough to learn something from it, not that every generation received a controlled benchmark.
The dates show how the implementation moved, but the recurring question matters more: what exactly can one agent hand to the next without losing useful state or quietly granting too much authority?
| trial | what it tested | why we abandoned it |
|---|---|---|
| Claude Code-like generated workflows May–July · 486a8cf |
The main agent generated inline JavaScript workflow code on demand for each task. That code launched subagents; sequential agents used the shared project, while parallel editors could return patches and bounded ignored-file artifacts from disposable Git worktrees. | It made orchestration flexible, but every control program was generated anew instead of selected from a reviewed reusable definition. Shared edits lacked proposal isolation, worktree patches represented only selected filesystem changes, and a saved model result could not prove that matching project state still existed. |
| Durable candidates on per-file reflinks July · 7eeeba3 · measurements |
Strict TypeScript re-execution, SQLite scopes and cursors, structural concurrency, typed receipts, and disposable candidates. Launch capture walked the project, reflinked every regular file, and hashed and verified the resulting tree. | It was abandoned first and foremost because it was slow. Verified Q creation measured 55 ms for two files but 10.6 s median for 1,000 files / 4 MiB; cost scaled with file count. That was incompatible with the decision to target workspace creation and checkpointing in milliseconds. |
| Native Btrfs candidates and mutation ledger July–August · 71f7ec6 |
Kernel subvolume snapshots replaced per-file reflinks and brought checkpoint creation from an existing boundary to a measured 12.2 ms median. Workspace epochs, per-path mutations, and mutation heads still tracked candidate state. | The Btrfs storage result met the latency goal and survived. The candidate-era semantic model did not: SQLite path state remained a second representation that had to stay consistent with complete snapshots, and continuity still depended on candidate-specific disposition and replay rules. |
| Writer-mediated proposal and authored receiver August · 454de26 |
Q/W/P/C/F settlement, fixed privileged operations, a dedicated UID-2000 writer, producer handoff, and receiver-owned curation. | The proposal boundary became stronger, but normal editing, Bash, and restoration crossed a second mutation protocol. Writer-side JIT and Git-index failures combined with artifact bundles, finalizers, and receiver choreography to make coding continuity and recovery too indirect. |
Not every idea was discarded. Strict TypeScript control, SQLite call settlement, systemd-owned lifetime, Btrfs identity, and Q→P→C→F settlement all survived. The decisive change was the handoff: instead of asking each workflow to describe selected artifacts, complete W and S filesystems now carry coding continuity and the host owns checkpointing and final settlement.
COMPATIBILITY POLICY
The runtime accepts one database, release-manifest, draft, and workflow API format.
Supporting several executable authority models would make every recovery and security rule depend on the historical format of the run.
Compatibility adapters could interpret older formats, but every adapter would then become trusted recovery and proposal-settlement code.
Runs in older formats remain useful historical evidence, but this runtime does not execute or resume them.
IMPLEMENTATION ANCHORS
The source at commit 20b76f2 is the primary evidence for this study. Design documents explain intent, but the strict schemas and state transitions decide what the implementation actually enforces. Two details are especially easy to miss when reading only the prose: temporary C remains writable until immutable F has been captured, and the worker receives one pinned provider credential in memory rather than mounted Pi credential or model files.
GOAL.md · WORKSPACE.md · ORCHESTRATION.mdSECURITY.md · runtime/release-manifest.tsworkflow-api.d.ts · definition/workflow-*.tsruntime/semantic-engine.ts · executable-runtime.tsworkspaces/workspace-graph.ts · btrfs-project-storage.tsevidence/agent-evidence.ts · agents/sdk-worker.tsruntime/causal-replay.ts · replay-boundary.tsproposals/settlement-*.ts · live-apply.tsproposals/experiment-domain.ts · experiment-effects.tsUI.md · projection/run-projection.ts · ui/workflow-facet-renderer.ts