warded director: consult-to-headless conversion flow (decision interview that upgrades tickets to dispatchable) #493

Closed
opened 2026-07-02 00:17:07 +00:00 by coilyco-ops · 5 comments
Member

Why

The 2026-07-01 launch triage proved the flow manually: two interview sessions converted ~60 consult/unlabeled tickets into headless-dispatchable ones. The mechanic is repeatable and belongs in the director: a consult ticket is almost always one extractable human decision away from headless - the expensive part is a human noticing that, framing the options, and recording the answer where the next agent finds it.

The manual prototype's loop, per ticket:

  1. Read the ticket; extract the blocking human decision(s) (design call, product stance, access gap, sequencing).
  2. Generate a concrete option set (2-4 options + a recommendation + consequences), batched a few tickets at a time.
  3. Interview the human; accept freeform answers too (several of tonight's best answers were off-menu).
  4. Bake the answer into the ticket as a DECISION comment with attribution and date, cross-linking affected tickets.
  5. Flip consult -> headless (or record why it stays: umbrella / gated / human-action / conflict).
  6. Surface merges, closes, moots, and new split tickets discovered along the way; execute on approval.

Deliverable

A director surface (subcommand or phase of the #350 g-brain loop) that runs this interview against a repo's consult + unlabeled queue: question generation, batched prompts, decision-comment writes, label flips, merge/close proposals. Fits #350's interactive phase ('backlog drained, what's next?' - this is one of the answers). The dispatch side pairs with #324 (backlog loop) and the mode-ceiling gate (agentic-os#246).

Done condition

A director session pointed at a repo ends with every consult ticket either headless-with-decision-comment, merged, closed, or consult-with-recorded-reason - reproducing the 2026-07-01 manual sessions without the human doing anything but answering questions.

See also

#350 (vision anchor), #324 (backlog loop, canonical for #295), #144 (triage-style exits), #176 (consult posture gap from ward dispatch retirement), #315 (broker-mediated file+label from explore).


Recorded by Claude Code (Fable) during the 2026-07-01 ward launch triage session with Kai.

## Why The 2026-07-01 launch triage proved the flow manually: two interview sessions converted ~60 consult/unlabeled tickets into headless-dispatchable ones. The mechanic is repeatable and belongs in the director: a consult ticket is almost always one extractable human decision away from headless - the expensive part is a human noticing that, framing the options, and recording the answer where the next agent finds it. The manual prototype's loop, per ticket: 1. Read the ticket; extract the blocking human decision(s) (design call, product stance, access gap, sequencing). 2. Generate a concrete option set (2-4 options + a recommendation + consequences), batched a few tickets at a time. 3. Interview the human; accept freeform answers too (several of tonight's best answers were off-menu). 4. Bake the answer into the ticket as a DECISION comment with attribution and date, cross-linking affected tickets. 5. Flip consult -> headless (or record why it stays: umbrella / gated / human-action / conflict). 6. Surface merges, closes, moots, and new split tickets discovered along the way; execute on approval. ## Deliverable A director surface (subcommand or phase of the #350 g-brain loop) that runs this interview against a repo's consult + unlabeled queue: question generation, batched prompts, decision-comment writes, label flips, merge/close proposals. Fits #350's interactive phase ('backlog drained, what's next?' - this is one of the answers). The dispatch side pairs with #324 (backlog loop) and the mode-ceiling gate (agentic-os#246). ## Done condition A director session pointed at a repo ends with every consult ticket either headless-with-decision-comment, merged, closed, or consult-with-recorded-reason - reproducing the 2026-07-01 manual sessions without the human doing anything but answering questions. ## See also #350 (vision anchor), #324 (backlog loop, canonical for #295), #144 (triage-style exits), #176 (consult posture gap from ward dispatch retirement), #315 (broker-mediated file+label from explore). --- Recorded by Claude Code (Fable) during the 2026-07-01 ward launch triage session with Kai.
Owner

Naming the loop: "interrogate until headless, then dispatch headless"

The core pattern this issue automates, stated as the director's operating loop (Kai's framing):

Take a vague / consult / underspecified work item. Interrogate it - clarifying questions to the human, extract the decisions, resolve the design forks, verify the assumptions against the live system - until it decomposes into concrete, self-contained issues that can carry themselves. Then dispatch those headless.

The endpoint is headless-dispatchable, not "still a consult". The interrogation is the value-add: it converts a non-dispatchable ticket into one (or several) dispatchable ones. The failure mode it replaces is skipping consult tickets - parking them because they are not fire-and-forget ready. That is backwards. The point is to drive them up to headless, not around them. A consult ticket is almost always one extractable decision (or one verified fact) away from headless.

Two moves inside the loop that this issue's interview mechanic should encode explicitly:

  • Interrogate the human for the decisions only they hold (the design forks, the go/no-go, the intent behind a vague ask).
  • Interrogate the system for the facts a human misremembers - grep the corpus, check whether a thing actually landed, clone-and-look. Half of what unblocks a consult ticket is not a human decision at all, it is a verified fact that dissolves a wrong assumption.

Worked example (a live run of the loop, 2026-07-03 director session)

A vague ask - "I specced something about filling the substrate with issue context" - was driven to dispatch by interrogation:

  1. Interrogate intent - "what does that mean?" -> it is the issue-corpus (a programmatically-rendered virtual git repo of full issue history), not prompt-stuffing.
  2. Interrogate the design - the "fill substrate" reframed to coilysiren/inbox double-duty (inbox is already private, already a corpus source, so it hosts the output too).
  3. Interrogate the system - "is it even running?" -> no. coilysiren/issue-corpus was never created (404), the cron is a green hourly no-op (token unset), the whole feature is dormant. That verified fact dissolved a wrong "it mostly landed" assumption and reshaped the work.
  4. Decompose -> three concrete headless issues: infrastructure#453 (keystone), agentic-os#324 (producer), ward#575 (consumer), in a clean dependency chain.
  5. Dispatch headless -> engineers away on #453 and #324, #575 held on its dependency.

That is the loop end to end. This ticket is the automation of it: run the interrogation against a repo's consult + untriaged queue, and dispatch what comes out the far side.


Comment by the read-only director surface session (claude-linux-cbbb76a95881-cb37-she-her) naming the loop and recording a live worked example, per Kai's "interrogate until headless, then dispatch headless" framing.

## Naming the loop: "interrogate until headless, then dispatch headless" The core pattern this issue automates, stated as the director's operating loop (Kai's framing): > Take a vague / consult / underspecified work item. **Interrogate** it - clarifying questions to the human, extract the decisions, resolve the design forks, verify the assumptions against the live system - **until it decomposes into concrete, self-contained issues that can carry themselves**. **Then dispatch those headless.** The endpoint is **headless-dispatchable**, not "still a consult". The interrogation is the value-add: it *converts* a non-dispatchable ticket into one (or several) dispatchable ones. The failure mode it replaces is **skipping** consult tickets - parking them because they are not fire-and-forget ready. That is backwards. The point is to drive them **up** to headless, not around them. A consult ticket is almost always one extractable decision (or one verified fact) away from headless. Two moves inside the loop that this issue's interview mechanic should encode explicitly: - **Interrogate the human** for the decisions only they hold (the design forks, the go/no-go, the intent behind a vague ask). - **Interrogate the system** for the facts a human misremembers - grep the corpus, check whether a thing actually landed, clone-and-look. Half of what unblocks a consult ticket is not a human decision at all, it is a verified fact that dissolves a wrong assumption. ## Worked example (a live run of the loop, 2026-07-03 director session) A vague ask - "I specced something about filling the substrate with issue context" - was driven to dispatch by interrogation: 1. **Interrogate intent** - "what does that mean?" -> it is the issue-corpus (a programmatically-rendered virtual git repo of full issue history), not prompt-stuffing. 2. **Interrogate the design** - the "fill substrate" reframed to `coilysiren/inbox` double-duty (inbox is already private, already a corpus source, so it hosts the output too). 3. **Interrogate the system** - "is it even running?" -> **no**. `coilysiren/issue-corpus` was never created (404), the cron is a green hourly no-op (token unset), the whole feature is dormant. That verified fact dissolved a wrong "it mostly landed" assumption and reshaped the work. 4. **Decompose** -> three concrete headless issues: infrastructure#453 (keystone), agentic-os#324 (producer), ward#575 (consumer), in a clean dependency chain. 5. **Dispatch headless** -> engineers away on #453 and #324, #575 held on its dependency. That is the loop end to end. This ticket is the automation of it: run the interrogation against a repo's consult + untriaged queue, and dispatch what comes out the far side. --- Comment by the read-only director surface session (claude-linux-cbbb76a95881-cb37-she-her) naming the loop and recording a live worked example, per Kai's "interrogate until headless, then dispatch headless" framing.
Author
Member

🔒 Reserved by ward agent --driver claude — container engineer-claude-ward-493 on host KAI-DESKTOP-TOWER is carrying this issue (reserved 2026-07-04T08:26:56Z). Concurrent ward agent runs are blocked until it finishes or the reservation goes stale (2h0m0s TTL); --force overrides.

Do not comment on or edit this issue to steer the run while it is reserved. The engineer seeded the body once at launch and never re-reads it, so a comment or edit reaches only human readers, never the running engineer. A correction goes to a new issue, dispatched fresh — that is the only channel that reaches a run in flight. Where the forge supports it, ward locks this conversation to make that a road-block rather than a convention (ward#494).

— Claude (she/her), via ward agent

<!-- ward-agent-reservation --> 🔒 Reserved by `ward agent --driver claude` — container `engineer-claude-ward-493` on host `KAI-DESKTOP-TOWER` is carrying this issue (reserved 2026-07-04T08:26:56Z). Concurrent `ward agent` runs are blocked until it finishes or the reservation goes stale (2h0m0s TTL); `--force` overrides. **Do not comment on or edit this issue to steer the run while it is reserved.** The engineer seeded the body once at launch and never re-reads it, so a comment or edit reaches only human readers, never the running engineer. A correction goes to a **new issue, dispatched fresh** — that is the only channel that reaches a run in flight. Where the forge supports it, ward locks this conversation to make that a road-block rather than a convention (ward#494). <!-- ward-agent-signature --> — Claude (she/her), via `ward agent`
Owner

⚠️ Reopened: this run's work did not land on main

An ephemeral ward container (claude mode) dispatched for this issue finished but its work was not merged to main, so the reaper preserved it on a branch before teardown and reopened the issue (its closes #493 never reached main). Recover from the salvage branch below.

  • Repo: coilyco-flight-deck/ward
  • Salvage branch: ward-salvage/ward-9b76e536
  • Reason: merge conflict integrating onto main
  • Container uptime at reap: 24m (age of the baked Forgejo PAT snapshot; a long-lived container is likelier to carry a rotated token)

Reap diagnostics

--- reap diagnostics ---
ward version:      v0.385.0
version source:    pinned via WARD_VERSION/--ward-version (v0.385.0)
HEAD:              0476291c7973
origin/main:       1f68ead98237
ancestry:          HEAD is NOT yet on origin/main - residual work remains to land
decision gate:     provenance missing or unreadable
reason:            merge conflict integrating onto main
provenance:        missing or unreadable
run-owned landed:  no
working tree:      9 dirty path(s)
container uptime:  24m (baked Forgejo PAT age proxy)
--- end reap diagnostics ---

Recover

git fetch https://forgejo.coilysiren.me/coilyco-flight-deck/ward.git ward-salvage/ward-9b76e536
git checkout -b ward-salvage/ward-9b76e536 FETCH_HEAD

Working tree at reap time

M  cmd/ward/agent_director.go
AM cmd/ward/agent_director_consult.go
A  cmd/ward/agent_director_consult_test.go
MM cmd/ward/forgejo_ops.go
M  docs/FEATURES.md
M  docs/README.md
AM docs/agent-director-consult.md
MM docs/agent-director.md
M  docs/director-startup-triage.md

— Claude (she/her), via ward agent

## ⚠️ Reopened: this run's work did not land on `main` An ephemeral `ward container` (claude mode) dispatched for this issue finished but its work was **not merged to `main`**, so the reaper preserved it on a branch before teardown and reopened the issue (its `closes #493` never reached `main`). Recover from the salvage branch below. - **Repo:** `coilyco-flight-deck/ward` - **Salvage branch:** `ward-salvage/ward-9b76e536` - **Reason:** merge conflict integrating onto main - **Container uptime at reap:** 24m (age of the baked Forgejo PAT snapshot; a long-lived container is likelier to carry a rotated token) ## Reap diagnostics ``` --- reap diagnostics --- ward version: v0.385.0 version source: pinned via WARD_VERSION/--ward-version (v0.385.0) HEAD: 0476291c7973 origin/main: 1f68ead98237 ancestry: HEAD is NOT yet on origin/main - residual work remains to land decision gate: provenance missing or unreadable reason: merge conflict integrating onto main provenance: missing or unreadable run-owned landed: no working tree: 9 dirty path(s) container uptime: 24m (baked Forgejo PAT age proxy) --- end reap diagnostics --- ``` ## Recover ```bash git fetch https://forgejo.coilysiren.me/coilyco-flight-deck/ward.git ward-salvage/ward-9b76e536 git checkout -b ward-salvage/ward-9b76e536 FETCH_HEAD ``` ## Working tree at reap time ``` M cmd/ward/agent_director.go AM cmd/ward/agent_director_consult.go A cmd/ward/agent_director_consult_test.go MM cmd/ward/forgejo_ops.go M docs/FEATURES.md M docs/README.md AM docs/agent-director-consult.md MM docs/agent-director.md M docs/director-startup-triage.md ``` <!-- ward-agent-signature --> — Claude (she/her), via `ward agent`
Author
Member

🔒 Reserved by ward agent --driver claude — container engineer-claude-ward-493 on host KAI-DESKTOP-TOWER is carrying this issue (reserved 2026-07-07T22:38:26Z). Concurrent ward agent runs are blocked until it finishes or the reservation goes stale (2h0m0s TTL); --force overrides.

Do not comment on or edit this issue to steer the run while it is reserved. The engineer seeded the body once at launch and never re-reads it, so a comment or edit reaches only human readers, never the running engineer. A correction goes to a new issue, dispatched fresh — that is the only channel that reaches a run in flight. Where the forge supports it, ward locks this conversation to make that a road-block rather than a convention (ward#494).

run seed context — what this run is carrying (ward#609)
  • Resolved: coilyco-flight-deck/ward#493 · branch issue-493 · driver claude · workflow direct-main
  • Run: engineer-claude-ward-493 · ward v0.422.0 · dispatched 2026-07-07T22:38:26Z
  • Comment thread: 2 included in the pre-flight read, 1 stripped (ward's own automated comments).

Issue body as seeded:

## Why
The 2026-07-01 launch triage proved the flow manually: two interview sessions converted ~60 consult/unlabeled tickets into headless-dispatchable ones. The mechanic is repeatable and belongs in the director: a consult ticket is almost always one extractable human decision away from headless - the expensive part is a human noticing that, framing the options, and recording the answer where the next agent finds it.

The manual prototype's loop, per ticket:
1. Read the ticket; extract the blocking human decision(s) (design call, product stance, access gap, sequencing).
2. Generate a concrete option set (2-4 options + a recommendation + consequences), batched a few tickets at a time.
3. Interview the human; accept freeform answers too (several of tonight's best answers were off-menu).
4. Bake the answer into the ticket as a DECISION comment with attribution and date, cross-linking affected tickets.
5. Flip consult -> headless (or record why it stays: umbrella / gated / human-action / conflict).
6. Surface merges, closes, moots, and new split tickets discovered along the way; execute on approval.

## Deliverable
A director surface (subcommand or phase of the #350 g-brain loop) that runs this interview against a repo's consult + unlabeled queue: question generation, batched prompts, decision-comment writes, label flips, merge/close proposals. Fits #350's interactive phase ('backlog drained, what's next?' - this is one of the answers). The dispatch side pairs with #324 (backlog loop) and the mode-ceiling gate (agentic-os#246).

## Done condition
A director session pointed at a repo ends with every consult ticket either headless-with-decision-comment, merged, closed, or consult-with-recorded-reason - reproducing the 2026-07-01 manual sessions without the human doing anything but answering questions.

## See also
#350 (vision anchor), #324 (backlog loop, canonical for #295), #144 (triage-style exits), #176 (consult posture gap from ward dispatch retirement), #315 (broke

… (truncated to 2000 chars; full body is on this issue)

Static container doctrine and seed boilerplate are identical every run and omitted here (they ride ward v0.422.0).

— Claude (she/her), via ward agent

<!-- ward-agent-reservation --> 🔒 Reserved by `ward agent --driver claude` — container `engineer-claude-ward-493` on host `KAI-DESKTOP-TOWER` is carrying this issue (reserved 2026-07-07T22:38:26Z). Concurrent `ward agent` runs are blocked until it finishes or the reservation goes stale (2h0m0s TTL); `--force` overrides. **Do not comment on or edit this issue to steer the run while it is reserved.** The engineer seeded the body once at launch and never re-reads it, so a comment or edit reaches only human readers, never the running engineer. A correction goes to a **new issue, dispatched fresh** — that is the only channel that reaches a run in flight. Where the forge supports it, ward locks this conversation to make that a road-block rather than a convention (ward#494). <details><summary>run seed context — what this run is carrying (ward#609)</summary> - **Resolved:** `coilyco-flight-deck/ward#493` · branch `issue-493` · driver `claude` · workflow `direct-main` - **Run:** `engineer-claude-ward-493` · ward `v0.422.0` · dispatched `2026-07-07T22:38:26Z` - **Comment thread:** 2 included in the pre-flight read, 1 stripped (ward's own automated comments). - included: @coilysiren (2026-07-03T18:15:54Z), @coilysiren (2026-07-04T08:51:34Z) - stripped: @coilyco-ops (2026-07-04T08:27:01Z) **Issue body as seeded:** ``` ## Why The 2026-07-01 launch triage proved the flow manually: two interview sessions converted ~60 consult/unlabeled tickets into headless-dispatchable ones. The mechanic is repeatable and belongs in the director: a consult ticket is almost always one extractable human decision away from headless - the expensive part is a human noticing that, framing the options, and recording the answer where the next agent finds it. The manual prototype's loop, per ticket: 1. Read the ticket; extract the blocking human decision(s) (design call, product stance, access gap, sequencing). 2. Generate a concrete option set (2-4 options + a recommendation + consequences), batched a few tickets at a time. 3. Interview the human; accept freeform answers too (several of tonight's best answers were off-menu). 4. Bake the answer into the ticket as a DECISION comment with attribution and date, cross-linking affected tickets. 5. Flip consult -> headless (or record why it stays: umbrella / gated / human-action / conflict). 6. Surface merges, closes, moots, and new split tickets discovered along the way; execute on approval. ## Deliverable A director surface (subcommand or phase of the #350 g-brain loop) that runs this interview against a repo's consult + unlabeled queue: question generation, batched prompts, decision-comment writes, label flips, merge/close proposals. Fits #350's interactive phase ('backlog drained, what's next?' - this is one of the answers). The dispatch side pairs with #324 (backlog loop) and the mode-ceiling gate (agentic-os#246). ## Done condition A director session pointed at a repo ends with every consult ticket either headless-with-decision-comment, merged, closed, or consult-with-recorded-reason - reproducing the 2026-07-01 manual sessions without the human doing anything but answering questions. ## See also #350 (vision anchor), #324 (backlog loop, canonical for #295), #144 (triage-style exits), #176 (consult posture gap from ward dispatch retirement), #315 (broke ``` … (truncated to 2000 chars; full body is on this issue) Static container doctrine and seed boilerplate are identical every run and omitted here (they ride ward v0.422.0). </details> <!-- ward-agent-signature --> — Claude (she/her), via `ward agent`
Author
Member

WARD-OUTCOME: done - warded director consult interview lands on main; consult tickets flip to headless-with-DECISION, merge, close, or keep-with-reason.

Retro, honestly: this one fought back mostly at the edges, not the core. The core was pleasant - startup triage (ward#397) is a near-perfect template, so the shape (candidate collection -> batched host one-shot -> parse -> write) fell out fast, and the disposition set maps cleanly onto the done condition (headless / merged / closed / kept). The dry-run reads the live queue end-to-end and tags consult vs untriaged correctly, which was a nice confidence check.

What surprised me: the doc-layout and code-comment caps (80 lines / 4000 chars per doc, 2-line comment blocks, 90-char comment lines). The comment hook passed on the first --all-files run only because the new file was untracked - it lit up the moment I staged it, and condensing ~24 comment blocks was the bulk of the fiddly work. Worth front-loading next time.

Confidence: high on the pure core (well-tested), medium on the interactive loop - consultOne does real forge writes so it isn't unit-tested (matching the triage house style, which also only tests pure functions), and I couldn't exercise a live write end-to-end without mutating a real ticket.

Rough edges / follow-ups worth filing:

  • The "interrogate the system" move from the thread (grep the corpus, clone-and-look) is only prompted here, not executed - the host one-shot reasons over issue text alone. The richer version belongs in the container surface with the clone. Follow-up candidate.
  • No split/decompose disposition yet (file N child issues from one consult ticket). It's in the "Why" but not the done-condition's terminal set, so I left it out; a lightweight version could file one follow-up issue.
  • Pre-existing, unrelated: TestReadBootstrapEnvDefaults fails on clean main (expects codex model gpt-5.4, fleet default is now gpt-5.4-mini). Stale test, not mine, but the suite is red on it.
WARD-OUTCOME: done - `warded director consult` interview lands on main; consult tickets flip to headless-with-DECISION, merge, close, or keep-with-reason. Retro, honestly: this one fought back mostly at the edges, not the core. The core was pleasant - startup triage (ward#397) is a near-perfect template, so the shape (candidate collection -> batched host one-shot -> parse -> write) fell out fast, and the disposition set maps cleanly onto the done condition (headless / merged / closed / kept). The dry-run reads the live queue end-to-end and tags consult vs untriaged correctly, which was a nice confidence check. What surprised me: the doc-layout and code-comment caps (80 lines / 4000 chars per doc, 2-line comment blocks, 90-char comment lines). The comment hook passed on the first `--all-files` run only because the new file was untracked - it lit up the moment I staged it, and condensing ~24 comment blocks was the bulk of the fiddly work. Worth front-loading next time. Confidence: high on the pure core (well-tested), medium on the interactive loop - `consultOne` does real forge writes so it isn't unit-tested (matching the triage house style, which also only tests pure functions), and I couldn't exercise a live write end-to-end without mutating a real ticket. Rough edges / follow-ups worth filing: - The "interrogate the system" move from the thread (grep the corpus, clone-and-look) is only *prompted* here, not *executed* - the host one-shot reasons over issue text alone. The richer version belongs in the container surface with the clone. Follow-up candidate. - No `split`/decompose disposition yet (file N child issues from one consult ticket). It's in the "Why" but not the done-condition's terminal set, so I left it out; a lightweight version could file one follow-up issue. - Pre-existing, unrelated: `TestReadBootstrapEnvDefaults` fails on clean main (expects codex model `gpt-5.4`, fleet default is now `gpt-5.4-mini`). Stale test, not mine, but the suite is red on it.
Sign in to join this conversation.
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
coilyco-flight-deck/ward#493
No description provided.