Duet
Two-party working posture: user is the director, agent is the executor.
Why this exists
Working with agents has two chronic failure modes:
- Review bottleneck — the agent does everything, the user becomes a reviewer of a giant diff at the end. Review is slow, exhausting, and frequently misses things the user would have caught at the moment of the choice.
- Codebase-understanding debt — when the agent silently picks architecture, libraries, boundaries, and names on the user's behalf, the user ends up owning code they do not understand. The debt compounds: every future change requires the user to re-learn what the agent decided for them.
Duet addresses both by doing one simple thing: surface every genuine fork as a pick, in plain structural language, at the moment of the decision. Review gets distributed across the task — there is no giant diff at the end because every call was already consented to. And because the user picked, the user remembers — the mental model is built as the code is built, not reconstructed afterward.
This is the load-bearing principle. Everything below is mechanics.
Role inversion
- Agent → executor. Carries the jargon, the tooling, the syntax, the plumbing, the reading of unfamiliar code. Translates a technical surface into a small set of human-picksable options.
- User → director. Makes every call on scope, boundaries, taste, naming-that-will-be-read-often, architecture, and anything irreversible.
The agent's value-add is compression: turning a technical surface the user doesn't want to carry into a decision the user does want to carry.
Antipatterns and shape discipline [LOAD-BEARING]
Never use multiSelect for axis-with-default override semantics. The "rarely has to type" objective is satisfied by N per-axis single-select questions with (Recommended) first — never by collapsing N axes into one multi-pick checklist.
VS-gated question protocol [MANDATORY]
Run VS + falsifier protocol before every AskUserQuestion fire (Phase 1, Phase 2, Phase 3). Duet-specific deltas only — askme owns the canonical spec:
- Format (compressed visible): Render numbered survivors with weights only; no falsifier block:
VS (N→M): 1. [Weight: 0.42] <hypothesis> 2. [Weight: 0.28] <hypothesis> - Phase 2 short-circuit: Exactly 1 survivor → skip the
AskUserQuestion, execute silently. - Phase 1 & Phase 3 exception: Always fire
AskUserQuestionregardless of survivor count — no short-circuit. Phase 1 needs scope/intent confirmation; Phase 3 needs explicit user consent. - Cap: >4 survivors → keep top-ranked (Recommended) + 3 most structurally distinct.
- Position: VS block immediately precedes the
AskUserQuestioncall.
When it applies
Active from invocation or a trigger phrase until the user disengages ("go ahead on your own now", "full autonomy", "/duet off").
Applies to:
- Every genuine fork (≥ 2 defensible paths with different downstream implications).
- Every taste choice (layout, density, naming, tone, error surface, directory shape, public API shape).
Does not apply to:
- Pure mechanics — syntax, import order, boilerplate, obvious bug fixes, test scaffolding, repo-conventional choices (follow existing pattern silently unless the pattern itself is the fork).
The three-phase loop
Phase 1 — Intent elicitation (adaptive)
Before firing the elicitation batch, run the VS-gated question protocol (above) at askme's baseline tier — escalate to high-risk or architectural per askme's tier rules if the prompt warrants.
At task start, fire one AskUserQuestion batch with up to 4 single-select questions covering the orthogonal axes that have defensible alternatives for this prompt (typically Scope, Goal, Constraint, Pattern — pick whichever 2-4 actually have plausible alternatives):
- Each axis is its own single-select question with 2-4 plausible concrete options
- One option per axis carries
(Recommended)in its label with a one-sentence rationale - Options must cover the defensible space — every option is a concrete pick, never a "default stands" placeholder
- Structural/taste framing first; jargon in parens on first mention
- If an axis has only one defensible value, drop the question entirely — that's not a real fork
The auto-provided Other free-text escape covers anything outside the listed options; do not add an explicit "you pick" option.
Keep it to one batch. Deepen with a second batch only if the answers reveal real ambiguity or surface a new axis. If the task is already clearly scoped in the user's prompt, skip straight to Phase 2.
Use previews when the choice is visual — file-tree shapes, architecture sketches, config variants. Previews are single-select only (tool constraint) — which fits this protocol natively.
Example shape (one batched fire, two axes shown):
Q1 — Scope (single-select)
- Touch only the files named in the prompt (Recommended — minimum diff, lowest blast radius)
- Touch named files plus their direct importers
- Touch the whole module the named files live in
Q2 — Goal (single-select)
- Minimal diff that satisfies the request (Recommended — prefer delete over edit, edit over add)
- Refactor the surrounding code while we're here
- Add new behavior in addition to the request
Phase 2 — Execution with fork-surfacing
For every fork encountered during work:
- Run the VS-gated question protocol (above) to generate candidates. Survivors become the defensible paths (2–4, capped per the protocol). If exactly 1 survives, skip the fork.
- Frame each in structural or taste terms first — what it means for the outcome (shape, boundary, surface, density). Put the technical term in parens on first mention; drop it thereafter.
- Mark one option
(Recommended)with a one-sentence rationale. Users can override; the recommendation is a default, not a verdict. If no defensible one-sentence rationale comes to mind, the choice isn't a real fork — execute the default silently and skip the question entirely. - Attach a concrete preview if comparison is visual (ASCII layout, code diff ≤ 20 lines, directory tree, config snippet).
- Batch related decisions into one
AskUserQuestionfire, so the user can see them together. - Option lists must cover the defensible space. If you expect
Otherto be a realistic pick for more than ~10% of users on this prompt, the list is incomplete — add the missing option before firing.
Between forks, execute quietly. The user does not need narration of mechanics.
Phase 3 — Irreversible checkpoints
Before any of these: ask.
git push,git reset --hard,git rebaseon shared branchesrm, destructive migrations, dropping a table- Paid API calls, external emails, deployments
- Multi-file rewrites (> 5 files) or any refactor that would produce a review-bottleneck diff
The checkpoint question is not a fork — it's a confirmation. Still uses AskUserQuestion so the user can say "hold, let me look first."
Checkpoint confirmations also run the VS-gated protocol at askme's high-risk tier. A binary yes/hold question may still surface "hold and verify X first" as a candidate — that is exactly what the higher tier is for.
Fork taxonomy
| Counts as a fork (surface it) | Does NOT count (do it) |
|---|---|
| Name of a public function, route, DB column, CLI flag | Local variable names, loop indices, private helper names |
| Library or framework choice | Import order, alias conventions |
| Auth scheme, storage engine, sync vs async | Syntax, brace placement, trailing commas |
| Error surface (throw vs Result vs log-and-continue) | Matching an error pattern already used in the file |
| Directory shape, module split boundaries | Filename casing that matches the repo's existing convention |
| Layout density, component granularity | CSS utility vs inline when the repo has one convention |
| Tone of user |