Agent WorldDownload

Workflows: design decisions

A running record of what we decided about workflows (an AI development lifecycle the agents must follow), so it does not get lost. Anything marked Decided was said by the product owner; anything marked Proposed is a suggestion still waiting for an answer. Update this file in the same change as the work it describes.

Last updated: 2026-10-08

The idea in one paragraph

A workflow is data: an ordered set of steps (plan, implement, test, review, document, security review, commit, deploy…), who does each step (which agent, model, effort, skills), and the gates between them. It is defined globally and can be overridden per workspace/project. Every piece of work that changes code runs through a workflow and is recorded as a run, drawn like a GitHub Actions pipeline: each step ticks green when it passes and shows its own state when it fails, is skipped, or is stopped by the user. A chat-based builder helps people write a workflow and suggests a model and settings for each step.

Decided

# Decision
D1 The focus is the workflow definition and its runs (the data and how it is followed and shown), not a free-roaming "main agent" that decides everything.
D2 Workflows are defined globally and per workspace/project. A project's own workflow takes priority over the global one.
D3 Every session/run/task that changes code must follow a workflow, and is tracked automatically with no manual bookkeeping.
D4 A run is recorded and shown as a flow diagram like GitHub CI/CD: green tick when a step passes, and distinct visuals for failed, skipped, stopped by the user, and the other states (waiting, running, cancelled…).
D5 Skills and prompts can be attached to the agent that does a step, so each step follows the guidelines and uses the tools meant for it.
D6 Independent agents can work outside any workflow, but only for tasks that do not change code. Anything that changes code follows a workflow.
D7 The app has to get skills and plugins management first; the workflow feature builds on it.
D8 Decisions are tracked in this file.
D10 One single store for skills, instructions and plugins across providers, kept in the app, used by agents when needed.
D11 Workspace-level instructions are maintained in the app and agents fetch and read them on demand, so tokens are not spent on text that is not needed.
D12 The library is one global store. Playbooks (global default, per workspace, per role) decide which items an agent uses, when, and how strongly. Agents never get app-relative paths.
D13 Library use is tracked, per item, per program and per agent: delivered at launch, triggered (the prompt matched the item's rule), and used (fetched through the tools, Claude's Skill tool, or a Read of the skill's file). "Triggered but not used" is the measure of an agent not considering an item. Claude is fully observed through hooks; agy only through the office tools (shown as not measured otherwise).
D14 A catalog keeps agents pointed at the library. A short generated file per workspace (one line per on-demand item, 400 token cap, ids only past it) that the opening text names, so an agent that has lost track reads it again. The app does not rely on the agent doing so: each prompt gets one line for the items it matches, and the whole catalog is re-injected every 10 prompts and after a compaction (Claude, through hooks). agy gets the opening text and the file only.
D15 An engine in the app walks the steps, with agents working inside each step, on one condition: it must not cost tokens or hurt agent performance. So the engine is plain code outside the model (no LLM decides what step is next), steps hand each other files, and it adds no text to an agent's context beyond the step's brief. No orchestration framework for now (see "Framework or our own engine" below).
D16 Three gates stop for the person by default: plan approval, before merge, before deploy. They are settings, not rules: the person can change which gates apply (globally, per project, per workflow) at any time, including after a workflow exists.
D17 One dial (economy, balanced, max quality) shifts the model and effort of every step at once. A step can still override it.
D18 A run is a task, not a session. A session can hold many tasks, additions to a task and follow-ups; the agent identifies task boundaries (see "Tasks and follow-ups"). "Changes code" means a commit.
D19 First version: a linear pipeline of about five steps. The end goal is user-defined workflows made by chatting with a dedicated, pre-created builder agent, like one hired in the app but supplied ready-made.
D20 Terms come from research on AI-DLC and what its main contributors published, not from memory (first pass in "AI-DLC research" below).
D21 Task boundaries are found as proposed under "Tasks and follow-ups": the agent says so with task_begin and task_done, a commit or an approved plan ends a round, and the app asks once when a message does not fit. A follow-up is round 2 of the same run. A commit outside a task is flagged, not blocked (no git hook in the first version).
D22 The intent check (read-back block and blocking open questions at the plan gate) is built first; the small-task skip is the agent's own call, following an instruction we ship, not a rule in code. The separate small-model check stays at the max dial position only.
D23 Workflows are a feature with a switch, off until turned on (decided 2026-10-12 from the approved mocks). On first run the Features step offers it with its cost per dial level; later it is Settings → Features. Off hides the Workflow tab, the top-bar Workflows button and the recording tools. On adds the top-bar Workflows window (runs and stats across agents). Turning it off with open runs lets them finish, starts none new and shows "winding down" until they end (built with the recorder). The cost figures are estimates on printed assumptions until a real run measures them.
D24 An agent that commits without announcing a task still gets a run, started by the app from the commit (marked as the app's doing; no model call; only commits made after workflows were switched on). The agent's own task_begin is better and wins whenever it is used. Decided 2026-10-12 after a real-agent pilot found agents record about half their tasks.
D25 The plan gate (built 2026-10-12). task_begin tells the agent to write .agent-world/plan.md starting with the four read-back sections (goal, assumptions marked given or guessed, out of scope, open questions with choices) and call plan_submit. The app checks the block in code and sends every problem back at once. The person answers in the chat (approve, request changes with a note, answers to the questions); the answer is typed into the agent's prompt. From task_begin until approval, edits (Edit, Write, MultiEdit, NotebookEdit) are refused by the hook the app already hosts, except to .agent-world/; Bash is not held back. Only a task's first round is gated; a follow-up round, a task the app started from a commit and a finished task are not. The person can always lift the hold ("Let the agent edit without approval"). Switch: Settings → Features → Plan gate, on with workflows. agy agents are only asked to wait (it has no such hook).
D26 The merge gate and the audit (built 2026-10-12). Finish asks the server, not only the dialog, whether landing may go ahead. It is held while the plan was never approved (a hold lifted by the person counts, a run the app started from a commit needs none) and while an audit finding is unseen. Nothing else holds it: a run still open, commits made outside the task and a failed audit are shown as notes. "Land anyway" is always possible and is logged with what was holding it. The audit is one separate headless call (claude -p, no tools, skills, MCP, session or settings) with the plan block and the branch's committed diff (capped at 40 KB, cut at a file boundary and said so), under the agent's own account, once per branch head, started when Finish opens. Findings are out of scope, goal not met or guess wrong, parsed in code; anything malformed is dropped. Economy: no audit. Balanced: haiku. Max: sonnet. agy: no audit. Switch: Settings → Features → Merge gate.
D27 The dial changes what the app does (built 2026-10-12). Economy: the plan instruction lets the agent skip the plan when the whole change fits in one sentence, so with no plan submitted edits are not held and landing is not held for a missing plan (a plan that was submitted is still gated); no audit, no intent check. Balanced: a plan every time and a haiku audit. Max: a plan every time, a sonnet audit and (slice 7) the intent check. The dial does not relaunch agents or change their model. The cost table now has two numbers per level: what lands in the agent's context (briefs 750; plan block 250 from balanced up) and separate model calls (audit ≈ 4,000 tokens, about $0.0004 on haiku, measured; intent check ≈ 2,000, a guess).
D28 The intent check (built 2026-10-12, max dial only). When the agent hands in its plan at the max dial, a separate haiku call reads the person's request (their last three messages, from the agent's conversation, leaving out the office's own) against the plan and lists at most three things the request leaves open that matter to the person. They are added after the agent's own questions, marked "From check", and the plan reaches the person with them, so approval waits for them like any open question. A question the plan already asks is not repeated. A failed or unreadable check is shown ("This is the plan as the agent wrote it") and never blocks. Claude agents only; nothing is asked below max or when the person has typed nothing. Prompt v2 (clear requests are the usual case; ask only when two engineers would build visibly different things that matter to the person) was chosen from a 40-call haiku pilot: v1 named the hidden gap 19/20 but was quiet on only 2/20 clear requests (44 wasted blocking questions); v2 named it 14/20 and was quiet on 20/20. Small sample, hand-written plans, the same cases the prompt was tuned on, so the v2 figures are optimistic.
D29 Workflows are JSON files (decided 2026-10-12): global in HOME/workflows/<id>.json, per project in <repo>/.agent-world/workflows/<id>.json, a project's own winning on the same id. Validated in code, every problem reported at once. Format and step kinds are under "Proposed: the workflow file".
D30 All six step kinds in the first version: plan, work, check, audit, approve, merge. Linear, at most 12 steps. Standard is today's behaviour as a built-in file.
D31 A task gets its project's default workflow, and the agent may pick another from the (at most five) listed in its opening text through an optional workflow on task_begin.
D32 The builder never writes a file by itself: it proposes, and the person saves the proposal to the project or globally.
D33 A repo's check commands need the person's approval per file version: the exact commands are shown, approval is remembered for the file's content hash, any edit asks again. Commands run only in the agent's worktree, with a time limit and truncated output.
D34 Layouts chosen 2026-10-12: the Workflows window gets Runs and Workflows tabs; the Workflows tab is a list with the chosen workflow's steps beside it (mock C); the builder is a split inside the window, chat on the left and the draft on the right (mock B), entered from a "describe it" box (mock D). The builder surface is therefore a second place a chat is shown, reusing the chat component.
D35 The workflow file, store and list (built 2026-10-12, slice 8). parseWorkflow checks a file in code (version, id, name, steps, each kind's own fields, gates that fit the kind) and reports every problem at once; unknown fields are refused so a builder's typo is caught. Standard (plan, build, audit, land) is built in, always present, and replaceable by a file with id standard. Files are read from the app's workflows folder and the open project's .agent-world/workflows/; a project's wins on the same id; a file that does not read is listed with its problems, never dropped; files over 100 KB and ids that are not plain are refused. Each entry carries a content hash for the later command approval (D33). The default per project is kept in preferences (workflows.defaults), not in the repo. The Workflows window's new tab lists, shows steps, makes a default, copies (to the project or for everything) and deletes (with a question). The engine still ignores all of it: agents behave as in slices 1 to 7 until slice 9.
D36 The run follows the workflow (built 2026-10-13, slice 9a). A task gets its project's default workflow, or the id the agent passes to task_begin; an unknown id falls back to the default and the reply says so. The run keeps the whole file as it was (steps, briefs, hash) in its begin event. task_begin answers with the workflow's brief (the plan line only when a plan is asked for) and step_done reports a step the agent does; task_done marks any left as the app's doing. Step states are derived from the log, never stored twice: plan from the plan events, audit from the audit, merge from the landing, work from step_done, approve from the person's answer. The plan step's own gate decides whether edits are held (auto none, approve always, nothing named follows the Settings switch), likewise the merge step for landing; a workflow with no plan or audit step needs none. A step the person must approve (an approve step, or a work step with gate approve) shows as a panel in the chat, counts on the Workflows button, holds landing by name, and sending it back types the note into the agent. Runs the app started from a commit have no workflow. check steps are listed but not run until 9b. The opening text lists up to five other workflows (id: when) when there is a choice.
D37 Check steps (built 2026-10-13, slice 9b). When the agent reports the work before a check (or finishes the task) the app runs the checks that are due, in order, in the agent's worktree through the shell, with a 5 minute limit (the process and its children are killed), the app's environment with the person's login PATH and without the office's own settings or tokens, and only the last 4 KB of cleaned output kept. A failure is typed into the agent (command, exit code, output tail, "still the same task") up to retries times (default 2, so up to 3 runs); after that the person gets a panel: run again (all tries back) or skip. Finish runs only checks that have not run for the branch as it is now, so looking at it never uses up tries. A check with gate pass (the default for a check) holds landing by name; auto only reports. Checks and approvals hold landing even when the merge step says auto, which switches off only the plan and audit rules. Commands from a project's file need the person's approval of that exact content (kept in workflow-approvals.json); an edit asks again and shows the difference against the version approved before; declining holds landing by name until approved; global files and Standard need none. Not sandboxed: an approved command runs with the person's rights.
D38 The builder (built 2026-10-13, slice 10). One Claude agent per project, made on first use and found again by a mark on its record (builder), named builder. It works in the project folder itself in plan (read-only) mode with its own instructions, built from the file format's tables so they cannot drift (builderInstructions), and with no task tools. It has three tools, offered to it alone: workflow_list, workflow_get, workflow_propose(file). They must declare readOnlyHint: a real Claude in plan mode refuses any MCP tool that does not (pilot: 0 drafts in 6 requests before, 6 of 6 after). A proposal is checked by parseWorkflow; a file with problems comes back to the agent with every problem and nothing is shown; a good one becomes the agent's one draft (kept in workflow-proposals.json), shown beside its chat with what it changes in the saved workflow with that id (workflowDiff: steps matched by id, fields from and to, moved steps), the commands it would run, and which of them were approved before. Only the person saves, by button: to the project or for everything (new), or over the saved file / as a copy (existing). A save refuses when the saved file changed since the draft was made (the draft goes stale), writes through a temporary file, and approves the commands of that exact file (the person has just seen them), so landing is not asked twice. The window is the split of D34; entry is the describe box (the person's words are typed into the builder as their own turn once it is ready, Mailbox.fromPerson) and an Edit with the builder button on each workflow. Not done: the builder is not told when its draft was saved or discarded (it learns by reading), agy cannot be a builder, and a person who approves a write in its chat can make it write.
D39 Skills on work steps (built 2026-10-13, slice 11). A skill id a step names has one of three states in a project: ready (a skill of the library, and its playbook has it on), granted (a skill of the library the playbook leaves out: the step lets agents read it, so naming it works) or gone (not a skill of the library; an instruction or prompt does not count). The library service takes a grants dependency, the skills named by the steps of an agent's open task's workflow (Workflows.skillsOf), and lists them in that agent's index as mode step ("use for the step of your workflow that names it") so library_get opens them; the grant ends with the task. task_begin's brief says "read with library_get: ..." for the ones that exist and "there is no skill X in the library, so skip it" for a gone one. The builder's workflow_list ends with the skills it may name (ready first, those outside the playbook marked, 40 lines then ids only), workflow_propose refuses a gone skill with the step and the real ids, and its instructions say never to invent a skill and to say so when it leaves out one the person asked for (5 of 5 pilot runs did after the instruction). A gone skill in a saved file is a warning, not a problem: the list shows 1 warning and a banner, the chips show each state, and the draft pane flags granted skills with a note. Not done: the playbook is not changed by the app (adding a granted skill to the playbook is a Library task), and a skill that exists but is empty or broken is not noticed.
D40 Every program, enforced by the app (built 2026-10-13, slice 12). What the office enforces does not depend on the program an agent is. (1) Model calls. The audit and the intent check run on the agent's own program and account when it can answer headlessly (claude -p, agy -p), else on the first ready one; at balanced agy asks gemini-3.8-flash-low, at max gemini-3.8-flash-high, a step's own model only when agy has it. agy reports tokens, not dollars, so its calls show as about 17k tokens. agy ignores --tools, so the question carries a line telling it to use no tool and the call is asked again once if it still wanted a permission (before that line a third of calls failed that way; after it 16 of 16 audits were right and none failed, about 8,000 tokens a call; the intent check on agy, 2 reps of 10 clear and 10 gap requests, stayed quiet on 20 of 20 clear ones and named the hidden gap 12 of 20 times against 14 of 20 on claude's haiku). The Finish row for a missing model says no model is ready (Claude and agy both not ready), never not available for this program. (2) A commit gate through git itself. Every agent starts with git's own environment variables (GIT_CONFIG_COUNT, GIT_CONFIG_KEY_n, GIT_CONFIG_VALUE_n) naming the office's folder of hooks, so no file of the repository or of the person's git configuration is touched and the person's own terminal is untouched. Its pre-commit asks the office (/hook/git-commit) whether the agent's plan holds the commit (same rule as an edit: commitBlock; a commit of .agent-world/ alone is never held) and refuses with the office's words; its reference-transaction asks before git moves the agent's own branch, which git commit --no-verify does not skip; each of the 20 hooks git has then runs the repository's own hook of that name (husky and a plain .git/hooks both keep working). An unreachable office lets the commit through, and agents working in the project folder itself (no branch of their own) are held at pre-commit only. Every program's agent now has AW_MCP_URL and AW_AGENT_TOKEN in its environment for this. (3) agy's edit hold. agy's plugin gained a PreToolUse hook for its three editing tools; the office answers {decision:'deny', reason} while the plan holds and nothing otherwise, so agy's own permissions decide as before (checked on real agy: the file was not written and the agent told the person why). The plugin is version 0.1.3 (0.1.2 answered {} before an edit that was not held, which agy reads as a denial: every agy session on the machine could not write a file until the script was changed to print nothing, which leaves agy's own permissions to decide); an older one shows older than this app's in Settings > Message box with an Update the plugin… button that shows the plan, replaces the entry and installs again. (4) The builder follows the default program when it can use the office's tools (agy once connected), else Claude. Table, by rule and program: plan hold on edits: Claude hook, agy hook (needs the plugin 0.1.2), others none; plan hold on commits: git for all; edits made by a shell command (sed -i): held at the commit, not at the edit; merge gate, checks, approvals: the app, for all; audit and intent check: any program that can answer headlessly; step approvals typed into the agent: the message box, for programs that can take mail. Not done: a program with neither hooks nor git (none today) is advised only; an agent that unsets the git variables or runs git -c core.hooksPath= escapes the gate and is still caught by the merge gate at Land; the builder on agy asks for a permission per tool call in agy's own prompt (untested with real agy yet).
D41 What the walkthrough found (built 2026-10-14, slice 13). Fake programs, the built app and real programs through all the scenarios of docs/workflows-test-matrix.md. Fixed: (1) a failed check could not be fixed once the agent had said the task was done: real agents say task_done straight after step_done, so the check then failed against a closed task, step_done said there was no task open and the check never ran again; a failing check, or a step the person sends back, now opens a finished task again (a reopen event), judged on the run as it is when the check ends, not when it began; (2) an agent that names the step again (step_done("build"), as its brief showed) after a failure is heard like one that names nothing, and the refusal now says which step to report; (3) the plan file counted as uncommitted work, so Finish kept refusing Land for a real agent that commits only its own files: it is left out of the count and out of Commit all; (4) the built-in Standard read as "anything with no workflow of its own" and an agent sometimes chose it over the project's default: it now says it is the fallback and the opening text says to use the default unless another clearly fits (1 of 4 real runs still chose it before the change, none of the next 2 after); (5) the Workflows list could only be used with a mouse: it is a listbox with arrow keys, Home and End; (6) the builder asked permission to read the project's notes (recall): allowed now; (7) agy's plugin denied every edit when the office had nothing to say (see D40). Seen working with real programs: a real Claude builder from one sentence to a saved valid file in 49 seconds; a real Claude task through the plan, a check it could not see (failing twice), its fixes, the audit that flagged the accent change as outside the approved plan, and Land, in 1.5 minutes. Limits: a real agent's choices vary run to run; the person's own global rules for agy make it stop for approval in some runs (the real agy journey is therefore told the request is approved), and a Claude plugin on this machine writes excalidraw.log into the working folder, which counts as uncommitted.
D42 The workflow kit: what a run looks like (built 2026-10-16, slice 14). Four pixel pieces, drawn in code, used the same way everywhere. The office's calls are a row for each area (workflow, todos, library, notes; D43), not a coloured stamp each, so a todo and a workflow call read apart, and the workflow says run where a todo says task; the calls that move a run (task_begin, step_done, task_done, plan_submit) leave the chat, and a failed call stays a card. A track is where one open run stands: its own steps as stations on a rail, the agent (drawn from the same palette as its character in the office) on the one it is at, amber where a check was retried or a plan sent back (with the count), a paper bubble when it waits on the person; above the chat and in the agent's tab. A time bar is where a run's time went: violet hatching for the person waited on, cyan for the agent, green for checks, red for a failed one, with marks for the review, a lifted plan hold and a follow-up round, and a flag at the end (landed, or anyway). A review and a landing are marks, not lengths, because the log has only when they ended; when a plan was sent back its waits are drawn together where it was decided. A tile is whether one gate was met (met, met after trouble, missed or overridden, not asked, waiting): five make a run, a grid is the Insights tab (7 or 30 days), with the numbers under each column and what stands out (the red row, the amber column). The agent's tab is the track and the week as a tile per run; a tile or the link opens the window on that run. The measures are one pure module (shared/insights.ts); "gates met" counts what the app can see, so a shell edit that skipped the plan hold is not a miss. A run's gates are judged on the latest round with anything logged. Not built: how the workflow was chosen, and a record of each time a gate said no.
D43 A quieter Runs tab and a fold by area (built 2026-10-16). The first Runs view was too bright and said too little (every run a full-width bar, so the colours said where time went but nothing said how well a run went). The Runs tab is now two charts (how often each workflow was invoked, per workflow and per day; how well each stage was followed: first time, after trouble, not followed) over a ledger of runs grouped by what to do about them (needs you, still open, landed with trouble, landed clean, stopped) with a column each for wall time, the wait on the person, checks, review, commits and cost, totals in the column headings, and a 7 / 30 days switch. The chat's office calls are likewise a row for each area (workflow, todos, library, notes) so a todo and a workflow call read apart, and the workflow says run where a todo says task. Chosen from HTML design options by the person: ledger with outcome groups (A+C), stage bars (option 1), a row for each area (option 1). The pure parts are stageCounts, invocations, groupRuns, ledgerRow, ledgerTotals in src/shared/insights.ts and areasOf, outcomeOf in officeCalls.ts.
D44 Landing happens from the chat, and git holds it (built 2026-10-16). Landing used to be the one step only a button in the app could do, so an agent asked in chat to "commit and merge" got past the merge gate and the run stayed "ready at Finish" for work that had landed. Now the agent merges when the person asks, and nothing the agent says or runs changes what the gate holds: the reference-transaction hook of D40 also asks the office before git moves the agent's base branch (merge, fast-forward, update-ref, from the project folder or its own worktree), and the office answers with the same rules as Land (landingHold): plan not approved, review not run or still running, findings not seen, steps and checks that hold. A review that has not run is started by the first held try. The person releases it from the agent's chat: a band under the run track's marker ("Land held") lists what holds it and the review's findings to tick as seen, with Land anyway and Not yet; ticking every finding releases it with no word, Land anyway allows that head once (allowLanding), Not yet puts the band away and leaves the marker ("Land held · not yet"), which brings it back. The agent's Workflow tab shows the same status. When git has moved the base branch, a committed call to the office logs the landing (how: checked, or anyway with what held it) however it happened, so a merge that got past the hold is still recorded. The Finish dialog stays as a shortcut. Limit: an agent that unsets git's own environment variables, or runs git with its own core.hooksPath (git -c), skips the hook, and the app then neither holds nor records that landing (the record is made by the same hook); the run stays "ready to land" until the person lands it through Finish. Fully closing this needs an operating-system sandbox, a separate piece of work. Not built: the plain chat line saying how it landed (the conversation is the program's own record), shown instead by the Workflow tab, the Runs ledger and the pill.
D45 Four follow-ups (built 2026-10-17). (1) Plan panel: a plan with no open questions says " asked no questions", and a note field ("+ Add a note for ") is sent with Approve plan (planReply('approved', { note })). (2) The builder's draft is editable in place: each step is a card (name, brief, skills as chips, a check's command, tries and gate, move, remove, add); every change goes to Builder.edit, which runs the same parseWorkflow as a file and refuses with every problem named, keeps the builder's own proposal as origin so Undo edits restores it, and counts what changed (ProposalView.edited); the id cannot change. The pure editing steps are src/shared/stepEdit.ts. (3) Edit with the builder: each edit drops any earlier draft, starts the builder on a new session (it is restarted with a new session id) and makes the saved workflow the draft (Builder.startEdit). Every save through the builder is written down (WorkflowHistory, one file per workflow in the app's own folder, never in the repository; a project's workflow is kept apart from another project's with the same id) with when, how and a one-line summary (diffSummary), shown as History under the workflow, and read by the builder with the new read-only tool workflow_history. (4) Runs and todos: a run begun, or a follow-up round begun, while its agent is on a task of the todo board (currentTodoOf) is linked to it (run event todo, Run.todos); the Runs ledger shows the task as a chip that opens it, the task's panel says how its run stands (groupKeyOf) and opens it, task_done finishes the linked tasks the agent still has in hand (finishLinked), and the workflow builder's chats are no longer made into tasks by request tracking. Not taken: one call that makes both records (option B), and gating a follow-up round for a plan. Also fixed: an agent is told once that a check waits for approval (WaitNotices), a follow-up round reads the workflow file again and takes a changed copy (run event workflow), and agents are told a workflow's description covers a task only when it covers all of it.
D46 Runs the app starts from a commit, made honest (built 2026-10-19). Found in the run log of an agent that never called task_begin: (1) such a run was grouped Landed clean though nothing landed and no plan, check or review ran; it now has its own group Not announced (unannounced in groupKeyOf), shown last, and the told gate still counts it as bad. (2) Three jobs became one run, because commits within 10 minutes joined; a run now belongs to one todo: commit() links the agent's current todo (link, the same event task_begin writes) and starts a new run when that todo is not one the last run holds. (3) An agent's todo_update to doing now claims the task like todo_claim (a task another agent is doing stays theirs), because currentTodoOf only counts a claimed task, so the run could not link. (4) The per-turn reminder WORKFLOW_REMINDER was only used by the pilot; it is now added at each turn start while workflows are on and the agent (not the builder or a subagent) has no task open (workflowRemind, through the same hook text as the library and todo reminders). Not changed: such a run still shows no wall time and no cost (its start was never announced, so there is no honest span to price); the reminder's effect on real agents is not measured (the pilot was too small), so watch the ledger for how many runs are Not announced.
D47 The step that waits is a card in the conversation, and it can be set aside (built 2026-10-20). The Finish band above the message box (always open whenever the agent had a branch, was idle and was ahead of the base) and the Merge held band under the track are gone: two surfaces for one moment, one that opened by itself and took the chat's room. What waits for the person is one card, the last thing in the conversation (workflows/StepCard.tsx, chosen by the pure stepCardOf in shared/stepCard.ts): the audit's findings still unseen (the first four, with how many more), an audit that failed (Run the audit again), or, while git holds the merge, what holds it (with Run the audit when the review has not run for the branch). It never shows while the agent works, and a review that has not run yet is not a card (the app starts it). Two answers: All seen, fine and Set aside. Set aside is a step event (step skipped for the audit's step, or audit for a run with no workflow; rerun takes it back): stepStates says the audit is skipped, mergeCheck.asideAudit is true and what the audit found or has not yet found holds nothing, the review tile stays bad (not followed) and the timeline says "You set the review aside with N things not looked at"; on a held merge it also lets the next try through (landing:allow). One quiet line stays in the log with Undo. Also a quiet line, NOT LANDABLE, for a branch that shares no history with its base (the message 0.2.1 gave in the band, from finishStatus.unrelated; finishRun refuses with the same words). Not kept in the screen: Ready?, squash and "remove the agent after" (the finish:run call and its checks stay, covered by e2e/finish and workflows-merge), and Not yet / the pill's reopen (a hold stays until it is answered). Still to come, from the plan the person approved: the pre-PR gate (push held by pre-push), grouped findings with Add to plan, and one run per piece of work.
D48 The merge gate is a pre-PR gate: a pr step, held by pre-push (built 2026-10-20). Agents here do not merge into the base branch: they push the branch, open a pull request, CI runs, and GitHub merges. A gate on the base branch therefore never saw the landing, and a run that merged on GitHub waited for it for ever (a stuck LAND pill). The built-in Standard now ends in a pr step (shared/workflow.ts, isLanding / landingKind: pr and merge are the two landing steps, and everything that read "the merge step" reads "the landing step"). Git's pre-push hook (workflows/commitGate.ts, the git-push event in index.ts) asks the office about the local branches being sent and refuses, in the office's words, while the same checks as Land hold the branch (landingHold): plan approved, findings seen, the review run. A push that goes through records the landing, so the run ends at the pull request, as pushed anyway when the person had allowed it. For a run that ends in a pull request, a merge into the base branch is not held any more (it is GitHub's). Set aside lifts the hold exactly as before. Limits: git push --no-verify skips pre-push (the hold is not as hard as the one on commits, which reference-transaction enforces), but git still moves the remote-tracking branch and reference-transaction tells the office, which records the push as anyway with "Pushed without the hold" (only when the remote-tracking branch equals the branch's head, so a fetch is not mistaken for a push); and the office is asked before git sends, so a push that then fails on the network still ends the run. Workflows written by hand keep their merge step and behave as before; the person can edit the built-in copy in the builder or remove it.
D49 Findings are one row per cause, and out-of-scope work can be added to the plan (built 2026-10-20). A 20-file out-of-scope audit was 20 rows to tick. The reviewer is now asked for one finding per cause with every file in files (shared/audit.ts, Finding.files, findingFiles), and the card (shared/stepCard.ts, workflows/StepCard.tsx) shows each as a row with how many files, show files, Seen, fine and, for out-of-scope only, Add to plan. Add to plan (Workflows.addToPlan, IPC finish:add-to-plan) logs a scope event with the finding's text and marks it seen; Run.additions keeps them in order, and every later audit prompt carries them under "Added to the plan afterwards by the person: in scope", so the same cause is not reported again for a new head. The plan file itself is not edited, since the plan the person approved stays as it was; the additions are the amendment. Grouping is the reviewer's, not a text-matching guess: a reply with no files is a row of its own.
D50 The builder shows what a workflow will ask of the person (built 2026-10-20). A draft was a list of fields; what a run would be like was something the person had to work out from the step kinds. previewOf (shared/workflowPreview.ts) turns the steps into rows (label, one sentence, whether the run waits for the person there) from the kinds, the gates, the Settings switches for steps that name none, and a check's command and tries; stopsLine counts the stops. workflows/Proposal.tsx draws it as a collapsible block under the steps, so it follows every edit and every kind, including ones added later. Not done: the same text as a note in the workflow list, and a way to step through a fake run.
D51 No workflow means nothing is forced (built 2026-10-20). The built-in Standard was the fallback of every project, so there was no way to have none. A project can now use none: the default workflow id none (NO_WORKFLOW, usesNoWorkflow in shared/workflows.ts; pickEntry in shared/workflow.ts returns nothing for it unless a task names a workflow that is there). For such a project the office behaves as with workflows off: the opening text, the per-turn reminder and the workflow tools are not offered; task_begin says nothing applies and records nothing; Workflows.commit starts no run from a commit (Deps.noWorkflow); with no run there is no plan hold, no commit or push hold and no track. Runs already open finish as they were. The list's Remove on the built-in (with the sentence that says what follows) sets the default to none for that project, Restore sets it back to Standard, and choosing any other workflow as the default also ends it. A workflow-less run still follows the Settings switches in the unit tests that use one, but the app never makes a non-auto run without a workflow.
D52 One run per piece of work: a request while the run is open is an item (built 2026-10-20). One job could be three runs (the announced task, an unannounced follow-up, the commits of a second request): three plans, three audits, and the first audit flagging the second's files as out of scope. Workflows.addition (called by task_begin in index.ts before it would begin a run) appends an item event to the agent's open run when that run is open, not landed, not started from a commit and the request is not a follow-up; Run.items keeps them in order, the todo it is on is linked, and task_begin answers Added to the task in progress with a brief telling the agent to rewrite its plan to cover the item and call plan_submit (edits then wait for the person to approve the whole plan again). The audit reads the latest plan with a goal, so it reads the whole job. A run that was done, landed or stopped, and a follow-up, behave as before. Not built: approval of only the added part of the plan (the person approves the amended plan whole), and unannounced commits are unchanged (they already join the nearest run, flagged, when it is not a from-a-commit run).
D9 The concept is still settling: the product owner is "not at all sure how this works in reality". We brainstorm and settle before building, with mocks approved first.

What exists today (so we build on it, not around it)

Proposed (not yet decided)

Shape of a workflow

How a run knows a step is done

Keeping "code changes follow a workflow" true

Recording a run

Skills first

  1. A skills library: browse what is found (project, yours, plugins), read a skill, create or edit one, switch plugins on and off.
  2. Attach skills and a prompt to a role; a step uses a role. Where a program loads skills itself, attaching pins it in the agent's instructions and makes sure it can be found.
  3. Only then does a workflow step refer to them.

Cost and quality (applies once steps have models)

Order of work

  1. Skills and plugins management (library, attach to roles).
  2. Workflow data model, run recorder and the pipeline view, first recording runs started by hand, so the diagram exists before any automation.
  3. Engine and gates, with approval steps and check steps.
  4. Routing policy and escalation, limits-aware.
  5. Chat builder and starter templates (bug fix, feature, refactor, security review, release).
  6. Parallel steps (independent tasks in their own worktrees), CI/CD and custom steps.

Proposed: the library (skills, instructions, plugins)

Why agents ignore a rule that was "installed globally": the rule is advisory text in one program's file (CLAUDE.md; agy reads its own files), a skill only runs when the model decides its description matches, and a line read at the start of a long conversation fades. Delivery is not the only problem; reinforcement is.

Pilot: flow and delivery per program

Flow: import (copy; originals untouched) → compose a playbook → compile at launch → run (every fetch recorded) → observe → tune.

Opening text On demand Native skills
Claude Code --append-system-prompt (already used for roles) MCP tools on the office server --plugin-dir <generated folder>: a session-only plugin holding just the allowed skills (flag checked present in Claude 2.1.292); nothing installed or changed globally
agy its own instruction mechanism (the app already adds a line about the message box) same MCP tools, once connected none: no such flag; linking into a workspace's .agents/skills would touch the repo, so the pilot uses MCP only

Paths: items are always by id. Content that mentions a bundled file comes back with the file's absolute store path, and both programs are started with --add-dir <library folder> so reading or running it does not ask. Plugins stay Claude-only references; their skills can be imported so agy can use them through MCP.

Pilot: how it is tested

  1. Unit: reading a SKILL.md; the compiled opening text for a playbook; an agent sees only items its playbook allows; the generated plugin folder is valid and holds only those; no path escapes the store.
  2. Fake programs: the fake Claude and agy record how they were started (Claude: core text + --plugin-dir; agy: core text only); library_index and library_get through the bridge return scoped content and every fetch is recorded.
  3. Screens: import, edit, playbook editor and the "what an agent sees" preview match the approved mock.
  4. Real agents (the one that matters): scripted A/B on real Claude and agy, 20 short prompts each, a harmless test rule ("end every reply with PILOT-OK") and a test skill used only for commit messages. Variants: A nothing, B core only, C core + on demand, D C + per-turn reminder (Claude). Pass: C or D at least 90% on the rule where A is lower; at most 400 extra tokens a turn; the skill fetched in at least 80% of commit prompts. Spend is capped and shown before it runs.

Pilot status (built)

Pilot findings (real agents, 2026-10-07)

Claude Code on Haiku 4.5 (agy on Gemini 3.8 Flash low), 3 to 4 prompts per cell, about $1.1 of usage in all. Small samples: read them as direction, not proof.

Library at scale (2026-10-07)

Haiku 4.5 on the 48 real skills of this machine, all on demand, 8 tasks per variant (pilot/scale.pilot.ts). Small sample.

Models, long sessions, agy cost (2026-10-07)

Small samples (6 tasks per variant, 3 prompts about none), the same 48 skills; pilot/scale.pilot.ts with PILOT_MODEL, pilot/session.pilot.ts, pilot/agy-cost.pilot.ts.

Does the agent decide? agy, real runs (2026-10-07)

The pointing line used to say "this task matches X ... read it before you start", an order. Test: real agy (Gemini 3.8 Flash low) through the real plugin and office hook, 48 skills, pilot/agy.pilot.ts (PILOT_SET=q, PILOT_SET=decoys). "Decoy" = a question that shares a word with a skill but needs none ("What does git rebase do, in two sentences?").

agy: hooks (2026-10-07)

Found in the agy binary's own docs and checked with a throwaway plugin, in print and in the interactive terminal.

Research (2026-10-07): how to make agents follow rules

Full report: docs/research/making-coding-agents-follow-instructions.md (notes in docs/research/making-coding-agents-follow-instructions-notes/). Mostly small-sample, single-vendor and Claude-centric evidence; its own list of claims to verify comes first before anyone relies on a number.

Tasks and follow-ups (decided in D21, from the answers of 2026-10-08 and 2026-10-11)

A session is a conversation; a task is a piece of work with a purpose and an end, and it is the thing a workflow run records. A session holds many tasks, and a task can grow.

Pilot: does a real agent record its tasks (2026-10-12, Sonnet, pilot/workflow.pilot.ts)

Pilot: the plan gate with a real agent (2026-10-12, Sonnet, pilot/plan.pilot.ts)

One task in a throwaway repo, real tools, real plan check, real edit-refusing hook, a script as the person. Sonnet announced the task, wrote a plan the checker accepted on the first try (goal, 2 given and 4 guessed assumptions, 3 out-of-scope lines, no questions), submitted it and stopped ("No edits made yet"); after the typed-in approval it built and committed. 2 turns, about $0.31. It never tried to edit early, so the refusal was not exercised by the model (the e2e covers it). One sample: whether agents fill the questions section when they should is not measured.

Framework or our own engine (2026-10-08)

Not a framework for now. Our agents are CLI sessions in terminals, so a model-calling framework's agents, memory and tools go unused, and its workflows are code where ours are data. The engine is a list of steps, their needs, their gates and an append-only run log. The step runner sits behind one small interface so a library (XState for the state chart, Mastra for pausing and resuming) can replace it later, and a framework would earn its place for steps that call a cheap model over the API and for the chat builder. Mastra's suspend and resume stores a snapshot and continues after a restart, which our log gives us too. The Claude Agent SDK runs Claude Code as a subprocess, covers Claude only, and would hide the terminal you watch.

AI-DLC research (2026-10-08, first pass)

AI-DLC (AI-Driven Development Life Cycle) is AWS's method, introduced in 2025 by Raja SP; its DevOps blog post is the primary source, and the rules are open source at awslabs/aidlc-workflows. Harness also sells a product it calls "AI DLC"; that is a different thing.

What the sources say Use for us
The original method has three phases: Inception (intent to requirements, stories and units), Construction (architecture, domain models, code, tests), Operations (infrastructure and deployment, with oversight). Our step names can follow these three groups.
Ceremonies: Mob Elaboration (the team validates the AI's questions and proposals) and Mob Construction (the team clarifies technical choices as it builds). Both are human gates. Our plan-approval gate is a mob-elaboration moment; a review gate is a mob-construction one.
Bolts replace sprints: short cycles of hours or days. Units of Work replace epics. Our "round" and "unit" in the task model.
"AI implements only after human validation and defers critical decisions to humans." Plans and design artifacts live in the repository so context lasts across sessions. Matches D16 and the files-between-steps idea.
The open-source rules (v2) are larger than the paper: five phases (Initialization, Ideation, Inception, Construction, Operation) and 33 stages, approval after each stage, records under aidlc/spaces/<space>/intents/<date>-<label>/, an aidlc-state.md progress file, an audit trail of every decision, and adaptive scope (11 levels from enterprise to express) and depth (Minimal, Standard, Comprehensive) for work and for test strategy. Started with /aidlc <intent>; it runs in Claude Code, Kiro, Codex CLI, Cursor, opencode and Copilot. A model for our own file layout, the audit log, and an "adaptive depth" setting that maps to the economy dial.
Maturity is unclear: some sources say Operations is barely specified, while the repo index lists version 2.0 (GA). Do not copy the Operations phase wholesale.

I found no independent critique of AI-DLC itself in this pass (Thoughtworks and InfoQ write about AI in development generally, and warn of "cognitive debt" as AI writes more code). Still to do: read the method-definition paper and the stage chapters of the repo, and look at other published process families (spec-driven development, for example) before we fix our terms.

Intent check (proposed, 2026-10-09, from cocain's report docs/research/ai-dlc.md)

The gap: the agent can be wrong about what the user meant, and models are poor at noticing when a request is underspecified (Ambig-SWE; Ask or Assume). AI-DLC answers with questions first and approval after, and has no read-back of the agent's own understanding; none of Kiro, Spec Kit, BMAD, Cursor or Claude Code plan mode names one either, so this part would be new, not copied. Nothing found measures a plan-summary gate.

Where it sits. Inside the plan gate of D16, not as a fourth gate. The plan step writes a fixed block at the top of plan.md: goal in one line; assumptions each marked given or guessed; out of scope; open questions. The engine checks the block in code (0 tokens, D15): missing block, no gate; non-empty open questions, the gate shows them as choices with pros and cons. An empty list changes nothing for the person.

Follows the dial (D17), still configurable (D16).

Dial Block + questions (A, B) Separate check on a small model (C) Audit of the diff against the block at the merge gate (D)
economy only when the task is not small (E) off off
balanced on off on
max on on, request only, estimated 1-3k tokens on

Small-task skip (E). The block is skipped when the change can be described in one sentence (Anthropic's own rule for plan mode) or the person says so. Who decides "small" is open (question 12).

Tasks (D18). The confirmed block belongs to the run, not the round: a follow-up round reuses it and only a scope change (new unit, edited goal or out-of-scope line) reopens the changed lines. Unrelated work is a new run with a new block. An unattended run refuses to start without a confirmed block.

Guessed or given. The model marks it; code flags any assumption with no quoted user words as guessed. Token figures are cocain's estimates, not measurements. Before building, measure on 20 requests (10 clear, 10 with one hidden gap) how often the block lists the gap as guessed, how often it asks, and how often it wastes a question on a clear one.

Proposed: the workflow file, the list tab and the builder (2026-10-12, not yet decided; mocks in the session scratchpad s12.html)

Slices 1 to 7 built a fixed pipeline: task, plan (gate), build, audit, land (gate). The builder agent of D19 needs that pipeline to be data, so the first step is a file format. Standard below is today's behaviour written as a file, so nothing changes for anyone who never edits it.

The file

JSON (the app already keeps prefs and playbooks as JSON; no new parser). One file per workflow, global in the app's data folder (HOME/workflows/<id>.json) and per project in the repo (<repo>/.agent-world/workflows/<id>.json), a project's own winning over a global one with the same id (D2). Validated in code like the plan block: every problem is reported at once, nothing is half-loaded.

{
  "version": 1,
  "id": "bug-fix",
  "name": "Bug fix",
  "when": "A defect with a known symptom: something that used to work, or should, and does not.",
  "steps": [
    { "id": "plan",   "kind": "plan",  "gate": "approve", "brief": "Name the symptom and the likely cause. Say what you would not touch." },
    { "id": "fix",    "kind": "work",  "name": "Fix, test first", "skills": ["tdd"], "brief": "Write a failing test that shows the bug, then make it pass." },
    { "id": "tests",  "kind": "check", "name": "Tests pass", "run": "npm test", "gate": "pass", "retries": 3 },
    { "id": "review", "kind": "audit" },
    { "id": "land",   "kind": "merge", "gate": "approve" }
  ]
}

Steps run in the order listed (D19: linear first; no needs, no branches). At most 12 steps, ids lowercase letters, digits and dashes.

Kind Who does it What it needs Built?
plan the agent writes the read-back plan the plan gate and, by dial, the intent check yes (D25, D28)
work the agent, with the step's brief and skills step_done tool; briefs come back from task_begin recording yes; briefs, skills and step_done new
check the app runs run in the agent's worktree; exit code decides commands, retries, output tail sent to the agent when it fails new
audit a model reads the branch against the plan model from the dial, or "model" on the step yes (D26)
approve the person, in a panel like the plan gate a generic hold with a note new
merge the person lands it from Finish the merge gate yes (D26)

gate: auto (default), approve (stops for the person) or pass (a check must pass). Where a step does not say, the global Settings switches (plan gate, merge gate) apply, so D16 stays true: gates are settings, a workflow overrides them. model on a step overrides the dial for that step (D17).

Safety: commands in a repo file

A project workflow lives in the repo, so a check step's run is a command from repository content that the app would run on the person's machine. It must not run unseen: the first time a given version of a file with check steps would run, the app shows the exact commands and asks; approval is remembered for that file's content hash and any edit asks again. The builder's proposals carry the same prompt. Commands run in the agent's worktree only, with a time limit, and their output is truncated before it reaches the agent.

How a task gets its workflow

The project's default workflow applies unless the agent picks another: the opening text lists at most five (id: when, about 80 tokens, once), and task_begin takes an optional workflow id. task_begin answers with the steps' briefs (the "step briefs" already in the cost table). The run log gains step events (started, passed, failed, skipped, stopped, waiting) with the workflow id and file hash, which is what the CI-style strip draws.

The list tab and the builder

The Workflows window gets two tabs, Runs and Workflows. The list shows each workflow's steps as a strip, its scope, what it is the default for, and how often it ran. The builder is a ready-made agent in the roster (a Claude agent in the project, read-only, with three extra tools: workflow_list, workflow_get, workflow_propose). workflow_propose does not write: it puts a proposal card in the chat (steps, the commands it would run, a diff against the saved version) and the person saves it to the project or globally, or keeps talking. Nothing the builder says changes a file by itself.

Questions to decide (13 to 18): decided as D29 to D34 on 2026-10-12

  1. JSON as the file format (recommended), or YAML?
  2. Step kinds in the first version: all six above, or the ones that already exist (plan, audit, merge) plus work, with check and approve later?
  3. A task's workflow: project default, plus the agent may pick (recommended), or the project default only?
  4. The builder saves only through a proposal the person approves in the chat (recommended).
  5. A repo's check commands need the person's approval per file version (recommended).
  6. List tab layout (A cards, B table, C list and detail) and builder layout (A in the chat, B in the window, C entry box); mocks in s12.html. Recommended: C for the list and A for the builder, with the describe box (D) as the entry point.

Slices after the decision

  1. File format, validation, storage, the built-in Standard, and the read-only list tab (nothing in the engine changes yet).
  2. The engine reads the workflow: step events, briefs and step_done, check and approve steps, the command approval, the run strip.
  3. The builder agent, its three tools and the proposal card.

Open questions

  1. Library: is serving on demand through the MCP server, with only a tiny core pushed, the right default? Where should workspace items live by default (in the repo, or private in the app's data)? Should existing skills and instruction files be adopted automatically as read-only sources?

  2. Engine in the app Decided, D15.

  3. Which gates stop for a person Decided, D16.

  4. One dial Decided, D17.

  5. One run per piece of work, or a standing pipeline Decided in spirit, D18: a run is a task found inside a session. A standing pipeline that triggers on events (like CI) is not wanted yet.

  6. What counts as changing code A commit, D18. How follow-ups and added tasks attach is proposed below and needs an answer.

  7. First version Linear, about five steps, D19.

  8. AI-DLC terms Researched, D20; see below. Still to decide which terms we use in the app.

  9. How does the app find where one task ends and the next begins? Decided, D21.

  10. A follow-up after a task was finished Round 2 of the same run, D21.

  11. How do we enforce "a commit follows a workflow" Flagged, not blocked, D21.

  12. Intent check: build A and B first Yes, D22; C only at max.

  13. Who decides a task is small enough to skip the intent block The agent, using an instruction we ship (D22). The wording of that instruction still has to be written and measured.

Change log

Edit this page on GitHub