Skip to content

Commit cfe8922

Browse files
WatchAndyTWclaude
andcommitted
fix(docs): resolve audit mismatches + writable mode resolves conflicts itself
Two changes, both on the existing v0.8.0 (no version bump — marketplace.json just catches up to the other spots). Docs (from the Fusion docs-vs-reality audit): - marketplace.json: 0.7.2 -> 0.8.0 (the 5th version spot, previously missed). - README/CLAUDE: drop the phantom `MMT_LIVE=1 npm test` live-test gate (no such gate exists in test/); state the suite is fully offline + live work is smoke-tested by hand. Fix the matching backends.mjs comment. - PROBES.md: add SUPERSEDED annotations — winpty -> node-pty/ConPTY is the live agy fix; codex prompt is delivered on STDIN (`-`), not a positional arg; remove/annotate deleted bash-era refs (backends.sh/route.sh/run.sh/ python/jq); correct the `--dangerously-bypass-approvals-and-sandbox` "not used" line (it IS used by --writable). Probe findings preserved. - roster.json: _comment backends.sh -> src/lib/backends.mjs; codex _note now describes the stdin invocation + writable_extra. - dir maps (README+CLAUDE): add src/lib/validate-config.mjs + docs/INTERFACES.md. - docs/REASONING.md: "zero runtime deps" -> stdlib-only for the reasoning path (node-pty is lazy-loaded only by the agy lane). Writable mode — orchestrator resolves conflicts (was: report + leave for user): - workflows/team.mjs Integrate stage: on a merge conflict the integration agent (now opus, with Read/Edit/Write) RESOLVES it inside the dedicated integration worktree — reads both sides, edits to a correct combined result, removes all markers, completes the merge — so the user gets ONE finished, conflict-free `mmt/team-<slug>` branch. Only a genuinely-unreconcilable conflict is aborted + left as `unresolved` (worktree kept). Schema: `conflicts` -> `resolved` + `unresolved`. Safety rule intact: all merges/ edits happen in INT_WORKTREE; the user's current branch is never touched. - Docs (README/CLAUDE/commands/team.md) + tests updated to match. 99/99 offline tests green. Codex-verified: the conflict-resolution change passed codex review (safety rule intact, safe resolution, bounded unresolved escape hatch, correct cleanup); one [LOW] log-wording nit fixed. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 48ff6f0 commit cfe8922

10 files changed

Lines changed: 132 additions & 66 deletions

File tree

.claude-plugin/marketplace.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
{
1111
"name": "multi-model-team",
1212
"description": "Delegates token-heavy, self-contained tasks to local CLI backends chosen by task size and type, with credit-exhaustion fallback through the backend chain to native Claude and a glanceable statusline HUD. Routing and shipped backend behavior are configured in config/roster.json; adding a new backend kind requires invoker and health support in code. /team runs a decompose → dispatch → verify → fix → synthesize pipeline; an optional config-gated UserPromptSubmit hook can proactively nudge delegation.",
13-
"version": "0.7.2",
13+
"version": "0.8.0",
1414
"author": {
1515
"name": "WatchAndyTW",
1616
"email": "watchandytw@gmail.com"
@@ -29,5 +29,5 @@
2929
]
3030
}
3131
],
32-
"version": "0.7.2"
32+
"version": "0.8.0"
3333
}

CLAUDE.md

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@ glanceable statusline HUD.
1010
else is Node stdlib. Cross-platform (Windows/Linux/macOS). `package.json` `"type":"module"`.
1111

1212
**Status:** built, adversarially reviewed, and green. `npm test` passes **99/99** offline
13-
(plus live agy + codex smoke tests under `MMT_LIVE=1`). Two live backends: **agy** (Gemini)
13+
(no backend calls; live agy/codex behaviour is smoke-tested by hand, not via a `npm test` gate).
14+
Two live backends: **agy** (Gemini)
1415
and **codex** (OpenAI Codex CLI); opencode remains a config-only stub. codex also serves as the
1516
**`/team` verifier**. See `README.md` (user-facing), `PROBES.md` (grounded CLI findings), and
1617
`docs/PLAN.md` (original design plan).
@@ -108,6 +109,7 @@ src/lib/team-spec.mjs /team cap-spec parser (replaces team_spec.py)
108109
src/lib/team-plan.mjs plan.json → per-subtask files (replaces team_plan.py)
109110
src/lib/reason-spec.mjs /reasoning panel-spec parser: expandPanel / parsePanel / splitPanel
110111
src/lib/gen-agents.mjs regenerate agents/*.md from roster.json (replaces gen_agents.py)
112+
src/lib/validate-config.mjs roster.json validator (unique route names, valid tiers/backends/agents)
111113
src/bin/route.mjs task → decision JSON CLI (replaces route.sh)
112114
src/bin/run.mjs executor + fallback chain + HUD state (replaces run.sh)
113115
src/bin/team.mjs scripted CLI-backend fan-out (replaces team.sh)
@@ -125,6 +127,7 @@ workflows/reasoning.mjs Ultracode Fusion workflow: Panel → Judge → S
125127
test/*.test.mjs offline test suite (npm test — node --test)
126128
docs/PLAN.md original implementation plan (historical)
127129
docs/REASONING.md design contract for the /reasoning Fusion pipeline
130+
docs/INTERFACES.md module interface contract (binding signatures for the Node ESM port)
128131
```
129132

130133
---
@@ -190,8 +193,10 @@ PR** (back-compat). *writable (`--writable`):* each subtask gets its **own git w
190193
HEAD; the agent makes **real changes** there (CLI **full-auto** via `run.mjs --cwd <wt> --writable`,
191194
selecting the backend's `writable_extra` instead of `extra`); then a deterministic **Setup → Integrate**
192195
pair of Bash sub-agents (the Workflow runtime has no fs/git) create the worktrees and **merge each into
193-
one integration branch `mmt/team-<slug>`** off HEAD — **conflicts reported, never auto-resolved**; the
194-
user's branch is untouched (no auto-merge) and **no `gh` PR** is created. Worktrees live under
196+
one integration branch `mmt/team-<slug>`** off HEAD — **the orchestrator resolves any merge conflicts
197+
itself and completes the merge**, so you get one finished, conflict-free branch (only a conflict it
198+
genuinely can't reconcile is left as `unresolved`); the user's branch is untouched (no auto-merge) and
199+
**no `gh` PR** is created. Worktrees live under
195200
`.mmt/worktrees/<slug>/` (gitignored). Enable per-invocation (`--writable` / Workflow `args.writable`)
196201
or via roster `team.mode:"writable"`. The full-auto sandbox is config-tunable: each backend's
197202
`writable_extra` in `roster.json` (codex `--dangerously-bypass-approvals-and-sandbox`; agy keeps its
@@ -349,11 +354,11 @@ fallback hop.
349354

350355
```bash
351356
npm test # offline: 99/99 routing + unit tests (no backend calls)
352-
MMT_LIVE=1 npm test # + live agy + codex smoke tests (network required)
353357
```
354358

355359
Keep the suite green. Add cases for any routing or behavior change. Tests live in `test/*.test.mjs`
356-
and run with `node --test`.
360+
and run with `node --test`. The suite is fully offline — there is no `MMT_LIVE` live-test gate; verify
361+
agy/codex behaviour by hand against the installed CLIs.
357362

358363
---
359364

PROBES.md

Lines changed: 53 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,16 @@
11
# Grounded probe findings — agy v1.0.8 (Windows)
22

3+
> **Historical capture — read with the SUPERSEDED notes.** This file records the raw probe results
4+
> from the original **bash** implementation (2026-06-13/14): the actual CLI bytes, flags, the
5+
> `isatty` discovery, and the model list — all still factually true about the CLIs. But the *plugin's
6+
> implementation* has since moved to **Node ESM** with **node-pty/ConPTY** (not winpty) and a
7+
> **stdin-delivered** codex prompt. Where a section describes the old bash/winpty mechanism, a
8+
> **SUPERSEDED** note points to the live code (`src/lib/backends.mjs`); `backends.sh`, `route.sh`,
9+
> `run.sh`, and the python/jq tooling referenced below were all deleted in the rewrite. `CLAUDE.md`
10+
> is the current source of truth for the implementation.
11+
312
Captured 2026-06-13 against the real `agy` CLI. These resolve the plan's open probes
4-
P1/P2 and the non-obvious headless behavior. Source of truth for `backends.sh` / `clean()`.
13+
P1/P2 and the non-obvious headless behavior.
514

615
## Binary
716
- Path: `C:\Users\WatchAndyTW\AppData\Local\agy\bin\agy.exe`
@@ -13,12 +22,21 @@ P1/P2 and the non-obvious headless behavior. Source of truth for `backends.sh` /
1322
Subcommands: `models`, `update`, `install`, `plugin`/`plugins`, `changelog`, `help`.
1423
**No `--output-format`** — text output only (matches plan).
1524

16-
## THE KEY FINDING — agy needs a TTY (use winpty)
25+
## THE KEY FINDING — agy needs a TTY
1726
`agy` gates its output on `isatty(stdout)`. When invoked through a normal pipe
1827
(Claude Code Bash/PowerShell tool, hook runner, subagent shell) it **exits 0 and prints
19-
NOTHING**. It only produces output when attached to a real console.
28+
NOTHING**. It only produces output when attached to a real console. *(This finding is still true
29+
and is the whole reason the agy lane needs a pseudo-terminal.)*
30+
31+
> **SUPERSEDED — the live fix is node-pty/ConPTY, not winpty.** The plugin now runs agy under a real
32+
> pseudo-terminal via **node-pty** (`backends.mjs runPty`): **ConPTY** on Windows, **forkpty** on
33+
> POSIX — so `isatty(stdout)` is true with **no visible console**, working from a fully headless
34+
> parent. winpty could NOT allocate a console with non-zero dims from a console-less parent (the
35+
> `winpty.cc:924` assertion below), so it was dropped from the agy path; `platform.ptyWrap` (winpty/
36+
> `script`) survives only as the **POSIX-without-node-pty** fallback. The winpty recipe below is the
37+
> historical bash approach, kept for the byte-level findings — not the current mechanism.
2038
21-
Fix: wrap every invocation in **winpty** (`/usr/bin/winpty`, present on this msys/git-bash):
39+
Historical bash fix (superseded): wrap every invocation in **winpty** (`/usr/bin/winpty`):
2240

2341
```
2442
winpty -Xallow-non-tty -Xplain <agy.exe> --print "<prompt>" --model "<name>" \
@@ -39,8 +57,10 @@ With the recipe above, `agy -p "Say exactly: HELLO"`:
3957
appears AFTER output, does **not** change the exit code (still 0 on success).
4058
- **exit**: 0 on success.
4159

42-
=> `clean()`: take stdout only, strip `\r`, trim trailing newline/whitespace.
43-
Defensive: also drop any line matching winpty's `Assertion failed:.*winpty\.cc`.
60+
=> `clean()` (now `backends.mjs clean()`): take stdout only, strip `\r` + ANSI/CSI/OSC, trim trailing
61+
whitespace. *(The winpty `Assertion failed:.*winpty\.cc` line filter still exists in `clean()` for
62+
the POSIX winpty fallback, but is dead on the primary node-pty/ConPTY path — ConPTY emits no such
63+
assertion.)*
4464
=> HUD token totals are **char estimates** (no usage emitted) — label them `~`.
4565

4666
### `--model` argument format (RESOLVED)
@@ -65,11 +85,17 @@ Not yet hit a real limit. Shipping default `quota_patterns`; will harden on firs
6585
exhaustion. winpty's assertion line is NOT a quota pattern, so no false positive.
6686

6787
## Tooling on this machine (bash/msys)
68-
- `jq`: **missing** -> statusline parses state.json with pure bash/sed (no jq dep).
69-
- `python3`: 3.12.12 (stdlib `json`) -> used by `route.sh`/`run.sh` to parse `roster.json`.
70-
- `winpty`, `timeout`, `sed`, `grep`, `awk`, `perl`: present.
71-
- agy is **not** on bash PATH in this session (added after Claude Code started); scripts
72-
resolve the binary themselves (env override -> `command -v` -> `$LOCALAPPDATA` path).
88+
> **SUPERSEDED.** This captured the *bash-era* tool deps. The plugin is now **Node ESM with one
89+
> native dep (`node-pty`)** — no `jq`, no `python3`, no `route.sh`/`run.sh`. `roster.json` is parsed
90+
> by `JSON.parse` (`src/lib/config.mjs`); the statusline parses `state.json` line-by-line in Node
91+
> (`statusline/statusline.mjs`); routing/execution are `src/bin/route.mjs` / `src/bin/run.mjs`.
92+
93+
- `jq`: **missing** -> (bash era) statusline parsed state.json with pure bash/sed. *(Now: Node, no jq.)*
94+
- `python3`: 3.12.12 -> (bash era) `route.sh`/`run.sh` parsed `roster.json` with it. *(Now: `JSON.parse`; those scripts are deleted.)*
95+
- `winpty`, `timeout`, `sed`, `grep`, `awk`, `perl`: present. *(Now off the hot path — only the POSIX winpty/`script` fallback remains.)*
96+
- agy is **not** on bash PATH in this session (added after Claude Code started); the binary is
97+
resolved by the plugin itself (env override -> PATH scan -> `$LOCALAPPDATA` path) — still true
98+
(`platform.resolveBinary`).
7399

74100
---
75101

@@ -81,10 +107,14 @@ section above; contrasts noted explicitly.
81107
## Binary
82108
- Path: `~/AppData/Roaming/npm/codex` (on PATH; resolved via `command -v codex`).
83109
- Version: `codex --version` -> `codex-cli 0.139.0`. Not TTY-gated — runs fine headless.
84-
- Non-interactive entrypoint: **`codex exec [OPTIONS] [PROMPT]`** (alias `codex e`).
85-
The prompt rides as the positional arg. If stdin is piped AND a prompt arg is given,
86-
codex appends stdin as a `<stdin>` block — so invoke with stdin = `/dev/null` (or
87-
the equivalent held-closed) to avoid spurious context injection.
110+
- Non-interactive entrypoint: **`codex exec [OPTIONS] [PROMPT]`** (alias `codex e`). codex also
111+
reads the prompt from **stdin** via the `-` sentinel: `codex exec … -`.
112+
> **SUPERSEDED — the live invoker delivers the prompt on STDIN, not as a positional arg.**
113+
> `backends.mjs invokeCodex` builds `codex exec … -` and writes the prompt to stdin. This was a
114+
> deliberate fix: on Windows codex is a `.cmd` shim spawned via `cmd.exe`, which **truncates a
115+
> multi-line prompt at the first newline** (and expands `%VAR%`) when it rides as an argv element.
116+
> stdin sidesteps both. The "prompt as positional arg + stdin=`/dev/null`" recipe below is the
117+
> historical pre-fix approach.
88118
89119
## THE KEY DIFFERENCE vs agy — no winpty, no TTY gating
90120

@@ -117,16 +147,21 @@ No `winpty`, no open-stdin FIFO, no output-scrubbing gymnastics required.
117147
| `-m <MODEL>` | Model selector (omitted → codex's own `config.toml` default). |
118148
| `--add-dir <DIR>` | Extra readable directory to expose. |
119149

120-
`--dangerously-bypass-approvals-and-sandbox` exists but is NOT used — `read-only` is
121-
safer for relay use.
150+
`--dangerously-bypass-approvals-and-sandbox` is the read-only DEFAULT's opposite: it is **used** by
151+
the `/team --writable` lane (`roster.json` codex `writable_extra`) so codex can write files + run
152+
commands in its worktree (full-auto). The default `extra` keeps `-s read-only` for the safe text-relay
153+
path. *(Original note said this flag was "not used" — true in the read-only-only era, now stale.)*
122154

123155
## Health check
124156

125157
`codex --version` (not `codex exec`) prints the version line to stdout, exit 0.
126-
Not TTY-gated. Suitable for a fast health gate in `backends.sh`.
158+
Not TTY-gated. Suitable for a fast health gate — now in `backends.mjs health()` (was `backends.sh`).
127159

128160
## Invocation recipe
129161

162+
> **SUPERSEDED** — the live invoker is `backends.mjs invokeCodex` (prompt on stdin via `-`; writable
163+
> mode swaps the sandbox flags). Historical read-only recipe (positional prompt + `/dev/null`):
164+
130165
```bash
131166
codex exec \
132167
-s read-only \

README.md

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -90,11 +90,13 @@ work runs across parallel agents — never one inline session.
9090
- **writable (`--writable`):** each subtask gets its **own git worktree + branch** off your current
9191
`HEAD`; the assigned agent makes **real file changes** there (CLI backends run **full-auto** in the
9292
worktree). The orchestrator then **merges every subtask branch into one integration branch
93-
`mmt/team-<slug>`** (off `HEAD`), **reports any merge conflicts** instead of guessing at them, and
94-
leaves that branch for you. **Your current branch is never touched** (no auto-merge) and **no
95-
GitHub PR is created** — you inspect / merge / open a PR for the integration branch yourself
96-
(`git log mmt/team-<slug>`). Per-subtask worktrees live under `.mmt/worktrees/` (gitignored);
97-
conflicted ones are kept so you can resolve them. The full-auto sandbox per backend is tunable via
93+
`mmt/team-<slug>`** (off `HEAD`) and **resolves any merge conflicts itself** — reading both sides and
94+
editing to a correct combined result, then completing the merge — so you get **one finished,
95+
conflict-free branch**, not a pile of worktrees to reconcile. **Your current branch is never touched**
96+
(no auto-merge onto it) and **no GitHub PR is created** — you merge / open a PR for the integration
97+
branch when ready (`git log mmt/team-<slug>`). Only a conflict the orchestrator genuinely can't
98+
reconcile is left for you (reported as `unresolved`, its worktree kept). Per-subtask worktrees live
99+
under `.mmt/worktrees/` (gitignored). The full-auto sandbox per backend is tunable via
98100
`writable_extra` in `roster.json`. Enable per-invocation with `--writable`, or set
99101
`team.mode: "writable"` in the roster.
100102

@@ -271,6 +273,7 @@ src/lib/team-spec.mjs /team cap-spec parser
271273
src/lib/team-plan.mjs plan.json → per-subtask files
272274
src/lib/reason-spec.mjs /reasoning panel-spec parser
273275
src/lib/gen-agents.mjs regenerate agents/*.md from the roster
276+
src/lib/validate-config.mjs roster.json validator (route names, tiers, backends, agents)
274277
src/bin/route.mjs task → decision JSON CLI
275278
src/bin/run.mjs executor + fallback chain + HUD state (file relay transport: --call-file)
276279
src/bin/team.mjs scripted CLI fan-out for /team
@@ -287,6 +290,7 @@ workflows/reasoning.mjs Ultracode Fusion workflow: Panel → Judge → Synt
287290
test/*.test.mjs offline test suite
288291
docs/PLAN.md original implementation plan (historical)
289292
docs/REASONING.md design contract for the /reasoning Fusion pipeline
293+
docs/INTERFACES.md module interface contract (Node ESM port signatures)
290294
```
291295

292296
---
@@ -295,9 +299,11 @@ docs/REASONING.md design contract for the /reasoning Fusion pipeline
295299

296300
```bash
297301
npm test # offline: 99/99 routing + unit tests (no backend calls)
298-
MMT_LIVE=1 npm test # also run live agy + codex smoke tests (network required)
299302
```
300303

304+
The suite is fully offline — no backend calls. Live agy/codex behaviour is verified by hand (run a
305+
real `node src/bin/run.mjs --call-file=…` against the installed CLIs), not by a `npm test` gate.
306+
301307
> **Why Node ESM?** The original bash hooks forked ~6–7 processes per invocation under a 10 s msys
302308
> timeout and were intermittently killed ("hooks not triggering sometimes"). Each hook is now **one
303309
> fork-free Node process** — read payload, gate with real `JSON.parse`, route in-process, emit.

commands/team.md

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -63,9 +63,11 @@ otherwise **read-only mode** (the default).
6363
- **writable (`--writable`):** each subtask gets its **own git worktree + branch** off the current HEAD;
6464
the assigned agent (CLI **full-auto** via `run.mjs --cwd <worktree> --writable`, or a native solver
6565
writing in the worktree) makes **real file changes** there; then the orchestrator **merges every
66-
subtask branch into one integration branch `mmt/team-<slug>`** off HEAD, **reports conflicts** (never
67-
blind-resolves them), and leaves that branch for you. Your current branch is **untouched** (no
68-
auto-merge), and **no GitHub PR is created** — you inspect/merge/PR the integration branch yourself.
66+
subtask branch into one integration branch `mmt/team-<slug>`** off HEAD and **resolves any merge
67+
conflicts itself** (reading both sides, editing to a correct combined result, completing the merge) —
68+
so you get **one finished, conflict-free branch**, not a pile of worktrees. (Only a conflict it
69+
genuinely can't reconcile is left `unresolved` for you.) Your current branch is **untouched** (no
70+
auto-merge onto it), and **no GitHub PR is created** — you merge/PR the integration branch when ready.
6971

7072
In the Ultracode path, pass `writable: true` in the Workflow args for writable mode (the workflow runs
7173
the Setup → Dispatch → Integrate stages). In the Task-agent path, follow the writable steps marked
@@ -265,9 +267,10 @@ override (`verifier`, `caps`, `verify`, `maxFixLoops`). With no `teamConfig`, th
265267
apply (all three backends eligible; codex verify). Read its returned `{ plan, mode, backends, caps,
266268
counts:{byBackend,…}, verifier, writable, results, final }` and present `final`, noting `counts.failed`
267269
if non-zero. **In writable mode** (`mode:"writable"`), also surface `writable.integration_branch` and
268-
`writable.integration` — tell the user the changes are on that branch (their current branch is
269-
untouched), list any `writable.integration.conflicts` that still need manual resolution, and how to
270-
inspect it (`git switch <integration_branch>` / `git log <integration_branch>`). (`args.task` is passed
270+
`writable.integration` — tell the user the changes are on that **finished, conflict-free** branch (their
271+
current branch is untouched), note any `writable.integration.resolved` conflicts the orchestrator
272+
reconciled (so they can review the resolution) and flag any `writable.integration.unresolved` ones that
273+
still need their hand, and how to inspect it (`git log <integration_branch>`). (`args.task` is passed
271274
as a JSON value, not shell — injection-safe.)
272275

273276
A trivial single task needs no fan-out: one subtask reduces this to a plain verified dispatch.

0 commit comments

Comments
 (0)