workflow

git clone https://git.godosa.eu/workflow

master

raw · 8326 bytes

Lanes, Model line, slice jobs, area maps

Reference (released 2026-10-05). Lanes split work by task size and pick policy: a fast lane clears small, unblocking work and slices big tasks; a slow lane takes 1h slices; more lanes by config. The model is a per-task attribute (Model: line): interactive sessions take tasks with Model ≤ their own, orchestrated workers run on the task's Model. Area maps (code map + test recipe per area) are checked and refreshed by auto-added tasks.

0. The Model line

  • Task body line Model: haiku|sonnet|opus (2-space indent like Ref:; - Model: also parsed), in the body tail before After: / Ref:. No line → opus. Order haiku < sonnet < opus.
  • Set by the task's writer at wf add --model M; wf set ID --model M; --model "" removes it (→ opus).
  • haiku: mechanical edit, exact instructions, no judgement.
  • sonnet: exact Done criteria + an existing pattern to copy + a real-data pass/fail test.
  • opus: design forks, debugging, reverse engineering / oracles, cross-source analysis, anything unclear.
  • Wrong level found mid-task: wf set ID --model <higher> + wf note ID "<why>" + wf status ID clear.
  • wf check: unknown value or two Model lines → error; extra words after the value → warning; missing → fine.

1. Lane config

workflow.toml, optional; absent → built-in default equal to:

slice_above = "1h"          # effort above this → slice job (§3); one of EFFORTS

[lanes.fast]
efforts = ["<1h"]           # efforts this lane implements
order   = "unblock"         # pick policy (§2)
slices  = true              # this lane also takes slice jobs (exactly one lane may set it)

[lanes.slow]
efforts  = ["1h"]
order    = "priority"
fallback = "fast"           # nothing pickable here → pick from that lane
  • Lane name: [a-z][a-z0-9-]*; used in worktree .worktrees/<lane>, branch <lane>/<id>, registry file.
  • efforts: subset of EFFORTS, ≤ slice_above; lanes' efforts are disjoint; every effort ≤ slice_above belongs to some lane. Tasks without effort → the lane holding <1h.
  • order: unblock | priority. fallback: another lane name, optional, no cycles.
  • slices = true on exactly one lane; default: the lane holding <1h.
  • Validation errors in config.load (unknown keys are errors). Defining [lanes.*] replaces the whole default set (no merge).
  • Extra lanes: add tables (e.g. [lanes.long] efforts = ["5h"] with slice_above = "5h"). Several workers in one lane = existing <lane>-2, -3 worktrees.

2. Pick order

Pickable set (Pending, not blocked, no open slices, all After: archived, no live foreign claim), filtered to the lane's efforts (plus slice jobs on the slices lane) and to Model ≤ session model (interactive only). Effective priority = best (lowest P) of the task's own and that of every open task waiting on it, transitively ("waits on" = After: [[x]], or a parent waiting on its open slices). Keys: - unblock: (1) waited-on count > 0 first, (2) more open tasks waiting on it (transitively) first, (3) effective priority, (4) file order. - priority: (1) effective priority, (2) waited on by another lane first, (3) waited-on count, (4) file order (section order, then position).

3. Slice jobs

  • A Pending task with effort > slice_above and no open slices = slice job. It is not pickable as an implementation task in any lane; it is pickable in the slices lane, ranked by that lane's order.
  • Shown with slice: prefix in wf next, wf list --runner, wf lanes counts (2 pickable (1 slice)).
  • wf next prints for it: Slice job: wf add "<title>" -e <1h|1h> --parent <id> --model M per slice, each with Steps/Done/Ref; no code; wf note <id> "sliced into …"; wf status <id> clear. Not wf done (parent stays open until its slices are done).
  • Model of a slice job = the task's Model (default opus; slicing is judgement).
  • A task with open slices is never a slice job (already sliced). All slices done → parent pickable only if its effort ≤ slice_above; otherwise wf done of the last slice prints parent <id>: all slices done → wf done <id> or add slices (it is not offered as a slice job again while it has done slices: rule = no open slices AND no archived slices).

4. Sessions, registry, model

  • wf next [--lane L] [--as M]: --as = the session's model (haiku|sonnet|opus); none → haiku, with a hint line. --lane = lane name; no --lane → all lanes merged, priority order, slice jobs included.
  • Registry .wf/sessions/<lane>.json ({"lane", "model", "socket", "pid", "session", "at"}, from env CLAUDE_CODE_MESSAGING_SOCKET, CLAUDE_PID, CLAUDE_CODE_SESSION_ID; skipped without them); no --lane → all.json (all is reserved). .wf/.gitignore = *. Newest wins per lane unless another live session holds it: then kept, and wf next first prints another live <lane> session holds this lane: uds:…. Alive = pid and socket file exist. wf lanes --unregister drops this session's records.
  • Claims: wf status ID progress … writes .wf/claims/ID.json ({"id", "lane", "model", "socket", "pid", "session", "at"}); status clear|blocked and wf done remove it. wf next skips a task whose claim is alive, not mine and still in progress.
  • wf lanes [--lane L]: one line per configured lane: fast: 3 pickable (1 slice) · 1 waiting · session uds:… (alive) / … · no session.
  • Waiting block: t-y waits on t-x (slow lane) → message uds:…: "t-x blocks my t-y, please take it" / … → no slow session: tell the owner — lane of a task = lane by effort (slice lane for slice jobs).
  • wf done: tasks of other lanes that became pickable → notify <lane> uds:…: now pickable t-y / <lane> work now pickable: t-y (no session: tell the owner).
  • Messages go with the agents' SendMessage tool, to = uds:<socket>.
  • wf list: model and lane columns; filters --model M, --lane L; --runner --lane L = ids in pick order for that lane (no session model filter; the orchestrator spawns per task model).

5. Orchestrator, batch, worker

  • docs/orchestrator.md, shared/skills/wf-orchestrate: lanes from wf lanes; per lane wf list --runner --lane <lane> → first id; spawn wf-worker with model: <task's Model>; worktree .worktrees/<lane>, branch <lane>/<id>. At most one worker per lane worktree; lanes in parallel.
  • wf batch N [--lanes fast,slow] (default: all configured lanes).
  • shared/agents/wf-worker.md: prompt line Lane: <lane> Model: <model>; slice job → slice only, no code.
  • wf usage --log … --lane L: cost line per worker; --report rows by lane × model × effort.

6. Area maps

  • Notes file: config areas = "<path>", default project CLAUDE.md. Section ## Areas, one ### <area> each with lines (all optional):
  • - Code map: … — anchors = backticked tokens (`parse_item`).
  • - Test recipe: …
  • - Paths: <globs, space-separated> — default: whole repo.
  • - Checked: <short sha> — last refresh; set by wf areas --mark.
  • wf areas [AREA]: per area: missing anchors (git grep -F -q <token> -- <paths> fails), commits touching Paths since Checked (git rev-list --count <sha>..HEAD -- <paths>), stale yes/no. Exit 0.
  • wf areas --mark AREA: write Checked: <HEAD short sha> into the section (add the line if missing).
  • Stale = any missing anchor, or Checked set and commits ≥ area_stale_commits (config, default 20). No Checked → only the anchor test.
  • wf check: missing anchor → warning area <a>: anchor <tok> not found. Unknown Checked sha → warning.
  • wf done: after bookkeeping, for each stale area with no open task whose id is t-map-<slug> → add t-map-<slug> P2 <1h Model: sonnet, title Refresh <area> area map, Steps: wf areas <area>; fix missing anchors and test recipe from changed files (git log --stat <Checked>..HEAD -- <paths>); wf areas --mark <area>. Done: wf areas <area> shows not stale. Printed added t-map-<slug> (area map stale). Id taken by an archived task → t-map-<slug>-2, … (ids never reused).
  • No ## Areas section → feature silent. Template templates/CLAUDE.md area block has Paths:/Checked:.