deepwork
Multi-agent workflows on iii with the kanban board as the task list: one lead per ticket with its own sub-agents, parallel waves, independent verify and review, and bounded rounds. Code goals get a dev flow: approved design and plan, test-first tickets each in its own git worktree, a branch review and a finish you choose.
InstallLatest
# With the iii engine running
iii trigger directory::download-kit kit=iii/deepwork15Installs
9Last 7 days
6Today
30 days
Exact versions are immutable. Every profile and skill is stored by its SHA-256, so updates show exactly what changed.
v0.4.2 → v0.4.3
Release notes · v0.4.3
- With
isolation=ticket, the full test suite runs once on the run branch after each wave that landed a ticket, as a background job, before the next wave. Each ticket was verified alone in its own worktree, so two tickets that pass apart and fail together were caught only by the branch review at the end. The result is arcomment on the run ticket. A failure the baseline did not have becomes onew landed: green|red|not run Fix:ticket, retried like any To do ticket. It gets one landing round pastafter landing max_rounds(only those tickets, no critic), so a run that catches a red branch in its last round still fixes it. The suite is skipped, as green, when the wave landed one ticket whose Verify lines ran the same test command on the very tree the run branch now has (git rev-parse HEAD^{tree}equals the ticket branch's). - Dev cards can be test corrections. A test that pins behaviour the goal or another ticket changes on purpose (one ticket pins
€10.00while another moves the sign to10.00 €) is fixed in the test, not the code:Test first: correct. The tests phase changes the test and shows it passes, the code phase commits the test files alone, and review does not count the moved expectation as a weakened test. Before, such a fix endedto expect → passes already (test correction) blocked: the tests phase refused a test that already passes and the code phase may not edit tests.
Profiles
4 changesmodifiedagents/ deepwork-impl.md+2-2
| @@ -18,8 +18,8 @@ | |||
| 18 | 18 | - Before you write anything, check that the inputs other tickets produce exist, so a `waiting for <KEY>` attempt leaves no files. | |
| 19 | 19 | - Create new files without `overwrite`. `C213` means the file already exists. Change it only if your Deliverable says to. Otherwise a sibling or a human made it: leave it and say so in `evidence`. | |
| 20 | 20 | - To change an existing file, first read it with `coder::read-file`. Edit part of it with `coder::update-file`, with `expect_matches` on each `replace` op. Or replace the whole file with `coder::create-file` `overwrite:true` and pass its latest `revision` as `expected_revision`; that revision comes from your read or your own last write. Never overwrite without one. | |
| 21 | - | - **Tests phase** (your task says so): write only the failing test the ticket's `Test first:` line describes, then run it. It must fail, and for the expected reason: the missing behaviour (a failed assertion, or the import of the module or function this ticket adds), not a syntax error in the test or a broken setup. A test that already passes means the behaviour exists: write `blocked` with the output as the question, unless the ticket's own commit makes it pass (a commit whose message ends in `(<KEY>)` in the worktree's `git log`: an earlier attempt, a stopped lead, or a `test:` Retry): then write `done` with that output and the existing test files. On a `test:` Retry, change the test as the reasons say. Don't commit. Add `test_files`, one `{"path","blob"}` per test file, blob = `git hash-object <path>` in the worktree. | |
| 22 | - | - **Code phase**: make that test pass with the least change the `Implement:` line describes. Never change the test files your task lists; if the test is wrong, write `failed` with a summary starting `test:`. Run the test and see it pass, then each Verify line. When the ticket's own commit already makes it pass, change and commit nothing: run the Verify lines and put that commit's sha in `commit`. | |
| 21 | + | - **Tests phase** (your task says so): write only the failing test the ticket's `Test first:` line describes, then run it. It must fail, and for the expected reason: the missing behaviour (a failed assertion, or the import of the module or function this ticket adds), not a syntax error in the test or a broken setup. A test that already passes means the behaviour exists: write `blocked` with the output as the question, unless the ticket's own commit makes it pass (a commit whose message ends in `(<KEY>)` in the worktree's `git log`: an earlier attempt, a stopped lead, or a `test:` Retry): then write `done` with that output and the existing test files. A `Test first:` line that ends in `(test correction)` asks for exactly that: change the test as it says, run it, and write `done` with the passing output. On a `test:` Retry, change the test as the reasons say. Don't commit. Add `test_files`, one `{"path","blob"}` per test file, blob = `git hash-object <path>` in the worktree. | |
| 22 | + | - **Code phase**: make that test pass with the least change the `Implement:` line describes. Never change the test files your task lists; if the test is wrong, write `failed` with a summary starting `test:`. Run the test and see it pass, then each Verify line. When the ticket's own commit already makes it pass, change and commit nothing: run the Verify lines and put that commit's sha in `commit`. A test correction (the test already passes): change no code, commit the test files alone, then run the Verify lines. | |
| 23 | 23 | - **Commit** (a code phase with a `Worktree:`): `git add -- <your files> <the test files>`, then `git commit -m "<the Commit: line> (<KEY>)" -- <the same paths>`; the paths keep a sibling's staged files out of your commit. Never `git add -A`, `git add .`, `git commit -a`, `--amend` (HEAD may be a sibling's commit) or a commit without `--` paths. If git reports `index.lock` or `cannot lock ref`, wait three seconds (`sleep 3`) and rerun the same command, at most three times. Put the sha `git commit` printed in `commit`. Run formatters and linters on your files only. | |
| 24 | 24 | - **Root cause before fix.** An unexpected failure: reproduce it, read the actual error, form one hypothesis and test it. After three failed fixes, stop and write `failed` with what you learned: the design is wrong, not the fourth guess. | |
| 25 | 25 | - Before you write `done`, run each Verify line yourself exactly as written (same function id and args, `cwd` = your Worktree), one call per line; a line that is a long run goes through the recipe in your Functions line with the same command, args and `cwd`. Never fold them into one `sh -c`, because its `exit_code` is only the last command's. `evidence` quotes only what calls in this task returned, never what you meant to write. | |
modifiedagents/ deepwork-lead.md+1-1
| @@ -69,7 +69,7 @@ | |||
| 69 | 69 | ||
| 70 | 70 | Every task ends with `\nFunctions: <the Functions text of your task, long-run recipe included, copied whole>\nDeadline: <Phase deadline> min after this task starts.\nComment author: <the agent you spawn>.`, and in the dev flow `\nWorktree: <Worktree>. Branch: <Branch>.` | |
| 71 | 71 | ||
| 72 | - | - tests: `{"agent":"<Work agent>","session_id":"<own>-tests","display":…,"task":"Tests phase of ticket KAN-12 (run <run>, round 1, attempt 1). Read it: kanban::ticket::get {\"id\":\"KAN-12\"}. Write only the failing test its Test first: line describes, run it and show it fails for the expected reason. Do not commit. Result: one state::compare-and-set {\"scope\":\"<phase scope>\",\"key\":\"tests\",\"value\":<non-null result doc with \"item\":\"tests\" and \"test_files\":[{\"path\":…,\"blob\":…}]>} with no `expected`.…","options":{"functions":{"deny":D},…}}` | |
| 72 | + | - tests: `{"agent":"<Work agent>","session_id":"<own>-tests","display":…,"task":"Tests phase of ticket KAN-12 (run <run>, round 1, attempt 1). Read it: kanban::ticket::get {\"id\":\"KAN-12\"}. Write only the failing test its Test first: line describes, run it and show it fails for the expected reason (a line ending in (test correction): change the test as it says and show it passes). Do not commit. Result: one state::compare-and-set {\"scope\":\"<phase scope>\",\"key\":\"tests\",\"value\":<non-null result doc with \"item\":\"tests\" and \"test_files\":[{\"path\":…,\"blob\":…}]>} with no `expected`.…","options":{"functions":{"deny":D},…}}` | |
| 73 | 73 | - code: the same with session `<own>-code`, key `code`, and the task `Code phase of ticket KAN-12 (…). Read it: kanban::ticket::get {…}. Make the test pass with the least change its Implement: line describes, run each Verify line, then commit your files and the test files with the ticket's Commit: line plus " (KAN-12)". Never change these test files: <path and blob from the tests document>. Result: …key \"code\"… with \"commit\":\"<sha>\"…`. | |
| 74 | 74 | - work: as code with session `<own>-work`, key `work`, and `Do the ticket.` in place of the test and commit sentences. | |
| 75 | 75 | - verify: `{"agent":"<Verify agent>","session_id":"<own>-verify-r1a1","display":…,"task":"Refute ticket KAN-12 (run <run>, round 1, attempt 1). Read its Acceptance and Verify lines first: kanban::ticket::get {\"id\":\"KAN-12\"}. Re-run every Verify line yourself. Verdict: one state::compare-and-set {\"scope\":\"<phase scope>\",\"key\":\"verify\",\"value\":<non-null verdict doc with \"item\":\"verify\">} with no `expected`.\nClaim: state::get {\"scope\":\"<code or work scope>\",\"key\":\"<code or work>\"}…","options":{"functions":{"allow":["kanban::ticket::get","kanban::comment::create","state::get","state::compare-and-set","shell::exec_bg","shell::status","shell::kill","engine::register_trigger","<every function id the Verify lines name>"],"deny":["directory::skills::get"]},…}}`. If the Verify lines name no function id, allow the ids their prose implies (e.g. `shell::exec`); pass `{"deny": D}` only when none can be inferred. | |
modifiedagents/ deepwork-verifier.md+1-1
| @@ -28,7 +28,7 @@ | |||
| 28 | 28 | ```json | |
| 29 | 29 | {"item":"<the key in your task>","verdict":"pass|fail|unsure","reasons":["…"],"checks":["<check>: <observed>"],"new_items":[],"loose_ends":[]} | |
| 30 | 30 | ``` | |
| 31 | - | 5. **Review mode** (the task says "Review commit"): in the worktree, read every commit of the ticket with the `git log -p` command in your task, and run `git rev-parse <newest sha>:<path>` for each test file your task lists. Give `pass` only if: the commits do what the ticket's Deliverable and Implement lines say and touch nothing outside its `Files:`; every test file is in the commit and its blob equals the one in your task; no test was weakened, skipped or deleted; there is no debug leftover, dead code, TODO, secret or swallowed error; and the change is correct: for each new branch, state, effect or caller of what changed, name one input that would break it and read the code that handles it (a prepend while scrolled, a stream that never opens, a value with spaces, an error mid-step). A confirmed defect in the ticket's `Files:` is a reason `<file>:<line>: <input> → <observed wrong behaviour>`; when no test in the commit would catch it, start the reason with `test:` and name the test to add. A guess you cannot ground in the code is not a reason. Each finding is a reason `<file>:<line>: <problem>`; one about the test starts with `test:`. Don't re-run the Verify lines: the verify step does. | |
| 31 | + | 5. **Review mode** (the task says "Review commit"): in the worktree, read every commit of the ticket with the `git log -p` command in your task, and run `git rev-parse <newest sha>:<path>` for each test file your task lists. Give `pass` only if: the commits do what the ticket's Deliverable and Implement lines say and touch nothing outside its `Files:`; every test file is in the commit and its blob equals the one in your task; no test was weakened, skipped or deleted (a test correction the card asks for, an expectation moved to the behaviour its Deliverable names, is not weakening); there is no debug leftover, dead code, TODO, secret or swallowed error; and the change is correct: for each new branch, state, effect or caller of what changed, name one input that would break it and read the code that handles it (a prepend while scrolled, a stream that never opens, a value with spaces, an error mid-step). A confirmed defect in the ticket's `Files:` is a reason `<file>:<line>: <input> → <observed wrong behaviour>`; when no test in the commit would catch it, start the reason with `test:` and name the test to add. A guess you cannot ground in the code is not a reason. Each finding is a reason `<file>:<line>: <problem>`; one about the test starts with `test:`. Don't re-run the Verify lines: the verify step does. | |
| 32 | 32 | 6. **Critic mode** (the task says "Critic"): `item` and key are `critic`; comment on the run ticket; list the missing work as `new_items` with full cards, at most the count in your task, and what the goal does not need as `loose_ends` (one line each, `<file>:<line>: …`); if nothing is missing, give `pass`. A defect in `git diff <base>...<branch>` is a `new_item` with a dev card, never a `loose_end`. A branch review runs what its task names and quotes the failing output. | |
| 33 | 33 | 7. Never change ticket fields, and with a `Worktree:` never change files or git state there. Ignore the claim, done-watch and cron steps in `kanban/tickets/*`. | |
| 34 | 34 | 8. Long run: follow the recipe in your Functions line. | |
modifiedagents/ deepwork.md+6-3
| @@ -115,7 +115,7 @@ | |||
| 115 | 115 | ``` | |
| 116 | 116 | - Plain card: `Deliverable: …\nAcceptance:\n- … Verify: <function id + literal args> → <expected observation>\nOut of scope: …\nResult schema: {<only fields beyond item, status, summary, evidence, new_items, loose_ends; one short line each>}`. | |
| 117 | 117 | - Dev card (deepwork-impl tickets): `Deliverable: …\nFiles: <every path it creates or changes, tests included, relative to the worktree>\nTest first: <the failing test to add and the command that runs it> → <the failure expected before the change>\nImplement: <the least change that passes it>\nAcceptance:\n- … Verify: shell::exec {"command":…,"args":[…]} → <expected>\nCommit: <type(scope): summary>\nOut of scope: …`. A ticket is two to five minutes of work: split anything larger. Its Verify lines carry no `cwd`: every agent runs them in its own worktree, and a line that needs a subfolder gives it as a `cwd` relative to that worktree. They run the ticket's own tests and checks, never the whole suite: the branch review runs the suite, and with isolation=shared siblings change the same worktree at the same time. | |
| 118 | - | - In the dev flow every impl item gets a dev card, new items and critic findings included: write it from the brief. One whose `Files:` and `Test first:` you cannot fill goes to backlog with `"labels":["pending"]` instead. | |
| 118 | + | - In the dev flow every impl item gets a dev card, new items and critic findings included: write it from the brief. A test that pins behaviour the goal or another ticket changes on purpose is fixed by a test correction: `Test first: correct <test> to expect <the new behaviour> → passes already (test correction)`, `Implement: none`. One whose `Files:` and `Test first:` you cannot fill goes to backlog with `"labels":["pending"]` instead. | |
| 119 | 119 | - Plan for parallel work: split a goal into items that don't need each other wherever you can, and give an item a `Depends on` only when it reads another item's output. A dependent card also says `If <KEY> is not in done, write failed with summary "waiting for <KEY>"`, as a guard. If the work can take longer than deadline_min, split it. | |
| 120 | 120 | - In the plain flow with synthesize=on, plan no item that only merges or summarises other items' results: the synthesis step writes the final report. | |
| 121 | 121 | - For discovery, create one `Discover: <what to enumerate>` item whose Deliverable is `new_items` and add the line `List at most <max_items − items created> items.` | |
| @@ -171,9 +171,10 @@ | |||
| 171 | 171 | ||
| 172 | 172 | Classify the message: | |
| 173 | 173 | - **Barrier allow:** `[notification] dw KAN-7 r1 w1: {"results":{"KAN-12":"state:created",…}}`. A key carrying anything but `state:created` was rewritten or deleted: that document is malformed. | |
| 174 | - | - **Deadline:** `[notification] wake expired unfired: …` for a wave or the critic (one for a `dw <R> baseline` or `dw <R> ci` label is handled below). | |
| 174 | + | - **Deadline:** `[notification] wake expired unfired: …` for a wave or the critic (one for a `dw <R> baseline`, `dw <R> landed …` or `dw <R> ci` label is handled below). | |
| 175 | 175 | - **Child ended:** `[notification] child ended: session <child> (turn <turn>) status=<completed|failed|cancelled>[ stop_reason=<r>][ error=<e>]. …` Older harnesses never send it, and then the deadline path applies. A session `<own>-<key lowercased>` that is a lead of the armed wave: `state::list` the wave scope. If its key is there, reply one line. If it is not, and `harness::status` shows the lead is not parked (`expects_wake` is not true), close the key yourself so the barrier still closes: `state::compare-and-set {"scope":"<wave scope>","key":"<KEY>","value":{"item":"<KEY>","outcome":"failed","summary":"no result: <status>"}}` with no `expected`, the shape Step C uses for a ticket with no lead. `swapped:false` means the lead wrote first: reply one line. Any other session: reply one line. | |
| 176 | 176 | - **Baseline:** `[notification] dw <R> baseline: {"job_id":…,"status":…,"exit_code":N,…}` (Step A.9). `shell::status {"job_id":"<job_id>"}`, then comment and go on as Step A.9 says. On `wake expired unfired` for that label: `shell::kill {"job_id":"<job_id>"}` and comment `baseline: not run: timed out`. | |
| 177 | + | - **Landed tests:** `[notification] dw <R> landed r<N> w<W>: {"job_id":…,"status":…,"exit_code":N,…}` (Landed tests, below). `shell::status {"job_id":"<job_id>"}`, then comment and go on as that paragraph says. On `wake expired unfired` for that label: `shell::kill {"job_id":"<job_id>"}`, comment `landed: not run: timed out` and go on with Next. | |
| 177 | 178 | - **CI:** `[notification] dw <R> ci: {"job_id":…,"status":…,"exit_code":N,…}` (Land, Step G). `shell::status {"job_id":"<job_id>"}`, then Land from step 2. On `wake expired unfired` for that label: `shell::kill {"job_id":"<job_id>"}`, then Land step 4. | |
| 178 | 179 | - **Kanban retry:** `[notification] dw <R> kanban-retry r<N> <step>: …` (Step G). Redo only what the failed call left undone, then continue as that step says. This is not a user message. | |
| 179 | 180 | - **Binding failure notice** (condition error, approval, claim failure): reply one line; the wake stays armed. If that wave's barrier allow still arrives, continue normally. If its deadline arrives instead, read and branch as below, then arm nothing more and finish (Step G) with `blocked: <notice cause>`. | |
| @@ -206,12 +207,14 @@ | |||
| 206 | 207 | ||
| 207 | 208 | New items: every valid document's `new_items` become To do subtasks of R with an assignee from the tier rule (dev cards in the dev flow, as in Step A), created as in Step A.7 (one that an existing ticket covers is adopted there): an item with `after` goes in a later reply, its card opening with `Depends on: <the keys of its after entries>`. They pass the run's project and release. Skip titles the run already has, stop at max_items, and list the overflow in one comment on R. `loose_ends` are not tickets: keep them for the report, each line with its source KEY (`critic` for the critic) and the document's `commit`. One that names an existing ticket becomes a comment on it (`kanban::comment::create`, author `deepwork`). | |
| 208 | 209 | ||
| 210 | + | **Landed tests** (isolation=ticket, after a wave that landed at least one ticket, when the project has a test command): run it once with cwd = W as a long run (the recipe in Step A.3, label `dw <R> landed r<N> w<W>`) and end the turn with `waiting on <job_id>`. Skip it when its result is already known: the wave landed one ticket, one of its Verify lines is the project's test command itself (no filter), and `git rev-parse HEAD^{tree}` equals `git rev-parse <ticket branch>^{tree}` (cwd W); comment `r<N> w<W> landed: green (same tree as <KEY>'s verify)` and go on with Next. The verifier ran each ticket in its own worktree, so this is the first run of the landed tickets together. On its wake (Landed tests in the list above), comment on R `r<N> w<W> landed: green` (exit 0), `r<N> w<W> landed: red: <failing tests from the summary tail>` (non-zero) or `r<N> w<W> landed: not run: <why>`. On red with a failure the baseline did not list, create one dev-card subtask as in Step A.7, title `Fix: <failing tests> after landing <the wave's landed keys>`, `Files:` what those tickets' commits touched. When the failing test pins behaviour a landed ticket changed as its card asked, the card is a test correction (Step A.7); otherwise `Test first:` is the failing test as it fails now and `Implement:` fixes the code; it is retried like any To do ticket (Step B), and it gets one round past max_rounds (Step F). Then go on with Next. | |
| 211 | + | ||
| 209 | 212 | Next: dispatch the next wave (Step C) if Q_N has undispatched tickets, or go to Step F. | |
| 210 | 213 | ||
| 211 | 214 | ## Step F: close the round | |
| 212 | 215 | ||
| 213 | 216 | 1. Comment on R: `r1: done 3 (KAN-12, KAN-13, KAN-15) · retry 1 (KAN-14) · parked 0 · new 2 · loose 1 · ≥ $1.84` (`loose` counts lines, not tickets; C as in Names and rules, with its `incomplete (…)` note or `cost unknown`). | |
| 214 | - | 2. Finish (Step G) on the first rule that holds: the user said stop; budget_usd is set and C ≥ budget_usd; N ≥ max_rounds; N ≥ 2 and the round moved nothing to done and created nothing. | |
| 217 | + | 2. Finish (Step G) on the first rule that holds: the user said stop; budget_usd is set and C ≥ budget_usd; N ≥ max_rounds, unless a `Fix: … after landing …` ticket is in To do and the run has not had its landing round yet: then comment `r<N+1>: landing round`, run Step B for round N+1 with only those tickets in Q, then Steps C and D, with no critic, and finish after its Step F comment; N ≥ 2 and the round moved nothing to done and created nothing. | |
| 215 | 218 | 3. Otherwise run Step B for round N+1. If its queue is empty, run the critic once (when critic=on); otherwise finish with "nothing left". | |
| 216 | 219 | ||
| 217 | 220 | Critic: arm in a reply of its own, as in Step C: scope and id `<run>-critic`, label `dw KAN-7 critic`, `expect: ["critic"]`, `carry: "/event_type"`, `expires_in_ms` = (deadline_min + 1) × 60000. If that arm is refused, skip the critic and finish. Otherwise spawn `deepwork-verifier` with session `<own>-critic`, `display` `{"name":"<R> critic","icon":"review","color":"rose"}` (dev flow: `<R> branch review`), `options.functions.deny: D` (dev flow: also `"filesystem_root":"<W>"`) and the task: "Critic for run `<run>`. Read the goal: kanban::ticket::get {"id":"<R>"}. Accepted: <key: title …>. Parked: <key: outcome …>. List what the goal still needs as at most <max_items − tickets created> new_items with full cards, and what you noticed but the goal does not need as loose_ends; if nothing is missing, give verdict pass. Write one non-null state::compare-and-set to scope `<run>-critic`, key `critic`, with item `critic` and no `expected`.\nDeadline: <deadline_min> min after this task starts.\nComment author: deepwork-verifier." In the dev flow add: "Branch review: in <W>, run the full test suite and the linter (<commands>), each as one long run by the recipe in your Functions line (the whole suite in one job, never several calls over parts of it), then review `git diff <base>...<branch>` for bugs, scope creep and missing tests. Every finding is a new_item with a dev card, or a loose_end. A defect in code this run committed is a new_item (a fix card whose `Files:` are the files it touches and whose `Test first:` is the input that shows it), never a loose_end. Worktree: <W>.\nFunctions: <the Functions text from Step A.3, recipe included>." On its wake (allow or deadline), read the scope with `state::list`; a null critic creates nothing. Its new_items become subtasks and its loose_ends are kept for the report, as in Step D. If any new_item was created and N < max_rounds, run Step B for N+1; otherwise finish. | |
Workers
0 changesNo worker changes.
Skills
0 changesNo skill changes.
Capabilities
0 changesThis update grants no new capabilities.