feat(agent): deprecate and completely remove cline agent support

- Delete adapters/cline.py and unregister from registry.py
- Remove cline branches from lib.sh and all 8 skill scripts (create, resume, stop, status, reconcile, update_yaml_resumed, resolve_session_id, orc_onboard)
- Narrow own-key mapping dictionaries across lib_py core modules to 4 supported agents
- Delete cline-exclusive tests and retarget shared fixtures to grok/hermes/claude
- Update skills documentation and installation guides (439 passed, 0 failures)
- Archive cline deprecation consensus and review reports
This commit is contained in:
2026-08-28 22:38:48 +09:00
parent e0c0c107f5
commit f57cd5cdde
40 changed files with 530 additions and 411 deletions
@@ -0,0 +1,109 @@
# 🏛️ Consensus Opinion — Should `cline` Agent Support Be Removed?
- **Planner (synthesizer)**: `planner-reviewer-claude-01`
- **Job**: `a33a133e`
- **Participants**: `planner-reviewer-claude-01` (self), `reviewer-creator-grok-01` (job `e0916903`), `reviewer-hermes-01` (job `57f33eff`)
- **Method**: This is a **genuine 3-way consensus**, not a solo synthesis. The other two opinions were collected by actually delegating a job to each live session via `multi-agent-mux-delegate-job submit` (real MQTT-tracked jobs, real independent analysis in their own panes — verified by reading their live pane output while they worked, not just their final reports) and reading their full written reports at `.mam/jobs/e0916903/grok-reports/report-final.md` and `.mam/jobs/57f33eff/hermes-reports/report-final.md`. No skill/framework code was modified by any participant.
- **Status**: Rev.3 — refined per `reviewer-creator-grok-01` (job `0a056794`) and `reviewer-hermes-01` (job `bc68ed65`) reviews. Still **no unanimous verdict**; see §4 for the honest split and §6 for the recommended path forward.
---
## 0. Changelog
### Rev.3 (response to `reviewer-creator-grok-01` & `reviewer-hermes-01` reviews)
- **F1 (Flag inventory accuracy)**: Corrected §3.2 and §6.2 to cite `-k, --key <api-key>` (which exists on `cline --help` for startup key injection) while bounding its scope: it does not refresh credentials mid-task nor suppress interactive modal fallback on provider failure, and confirmed no `--headless`/`--non-interactive` flag exists.
- **F2 (drift-C modernization status)**: Corrected §2 point 3 and §6.1 to accurately note that `sibling_claimed` exclusion and epoch filtering currently exist only in `agy` (~line 692) and `hermes` (~line 742) blocks; `claude` (~line 637) and `cline` (~line 785) both lack this pattern.
- **F3 (Consensus attribution)**: Clarified §2 point 3 and §6.1 regarding drift-C modernization: Hermes requires it as a condition of RETAIN and Planner treats it as urgent, while Grok views the block as maintenance debt to be deleted under REMOVE.
### Rev.2 (response to `creator-agy-01` challenge)
- §3.1 rewritten: risk escalated from "narrow spawn-time race" to "unmonitored mid-task modal-fallback window," with the verification trail (what's confirmed vs. inferred) laid out explicitly.
- §3.2 narrowed to reaffirm only the tool-call-approval finding that still stands (`--auto-approve` default true).
- New §3.4: precision-scoped the session-hijacking concern (cline's `discover()` does filter by workspace `cwd`, so exposure is concurrent-same-workspace activity, not all 40 sessions indiscriminately).
- §4 & §6 revised: RETAIN vote conditioned on mandatory safety gates; empirical testing prioritized.
---
## 1. Individual Verdicts
| Reviewer | Verdict | One-line reasoning |
|---|---|---|
| `reviewer-creator-grok-01` | **REMOVE** | cline fails two hard contracts every other shipped adapter now meets (unattended execution, assignable session identity); its reviewer niche is already filled by hermes in this live workspace. |
| `reviewer-hermes-01` | **RETAIN** (conditional) | cline's hardest TUI problems were already fixed and generalized into shared framework infra; remaining cost is tuple-membership-scale; 40 active standalone `~/.cline/data/sessions` folders show sustained real usage outside MAM. |
| `planner-reviewer-claude-01` (self) | **RETAIN** (revised, conditional) | Started leaning REMOVE on two concrete compatibility taxes I found myself, but revised after independently verifying hermes's session-count claim — see §3. |
**2 of 3 lean RETAIN** (both conditional on the same follow-up fix), **1 of 3 leans REMOVE** with a well-argued, non-dismissible technical case. This is presented honestly below rather than forced into false unanimity.
---
## 2. Where All Three Reports Fully Agree (cross-verified independently, not just asserted)
These points appeared in at least two of the three independent analyses and I re-verified each against the live repo/environment myself before including them here:
1. **The Node.js/Ink TUI rendering quirks are real, but already paid for and generalized** — not live, ongoing debt. Specifically:
- `lib.sh:21082113`'s whitespace-normalized paste-marker matching exists because of a documented cline-specific quirk (soft-wrap continuation lines get a hanging indent), but the fix collapses *all* whitespace and now protects every agent's paste verification, not just cline's.
- The "skip strict paste check" exemption list (`lib.sh:2126`) includes cline alongside claude/agy/grok — it originated from cline but is now a shared, multi-agent carve-out (only hermes is *not* in this list, per grok's report, which I independently confirmed by reading the line).
2. **`modal_tokens` is not cline-exclusive baggage.** `claude.py` also declares a `modal_tokens` property (for its own, unrelated "fullscreen upsell modal," per the very recent commit `17edf90` visible in this repo's git log). I confirmed this via `grep -n "modal_tokens" .agents/skills/lib_py/agents/adapters/*.py` — only `claude.py` and `cline.py` override it. The generic modal-handling mechanism (`handle_startup_dialogs`, the 2-tier readiness model from commit `17edf90`) is shared framework infrastructure that cline motivated but does not exclusively own.
3. **`reconcile.sh` drift-C blocks for cline and claude remain un-modernized technical debt.** Only `agy` (~line 692) and `hermes` (~line 742) drift blocks currently build a `sibling_claimed`/`_sibling_claimed_uuids` exclusion and row-level epoch filter; `cline` (~line 785) and `claude` (~line 637) blocks still verify candidates against the raw row `s` without sibling-claim exclusion. Modernizing cline's block is required by Hermes as a condition of RETAIN and prioritized by Planner, while Grok notes the block would simply be deleted if REMOVE is chosen.
4. **No cline session is currently running** in `.mam/agent-sessions.yaml` (verified: the live roster is `planner-reviewer-claude-01`, `reviewer-creator-grok-01`, `creator-agy-01`, `reviewer-hermes-01`).
5. **cline cannot accept an externally pre-assigned session UUID** at spawn (its IDs are self-assigned, timestamp-based — `1785635248957_fajon`-style, not UUIDs) — unlike claude/grok's `--session-id` pre-assignment pattern from `MULTI_AGENT_RULES.md` §2. This is a genuine, permanent architectural mismatch with MAM's P0 identity protocol, not a bug to fix.
6. **Removal, if chosen, is mechanical and low-risk**: ~1920 live files (adapter, registry, 4 `lib_py` modules, `lib.sh`, 89 skill scripts, 57 `SKILL.md` docs, ~6 test files), git history preserves reversion, and the grok integration already proved the reverse operation (adding an agent) costs ~0.51.5 days — so re-adding cline later, if ever needed, is a known, bounded cost. **Historical `.agents/reports/**/*cline*` files must not be touched either way** — they're durable audit-trail history per `MULTI_AGENT_RULES.md` §4, not live framework surface.
---
## 3. Where the Reports Diverge — the Actual Crux, and a Factual Correction
### 3.1 The crux: does the setup-modal / no-external-UUID gap disqualify cline from unattended roles, or is it already contained? (Rev.2: revised, risk escalated)
**Rev.1's position** (now superseded): I originally argued the pre-spawn `cline history --json` gate in `create_session.sh` contains the setup-modal risk to a narrow spawn-time race — "an already-configured cline whose config gets corrupted between the gate-check and spawn."
**Why that was wrong, per `creator-agy-01`'s challenge (accepted)**: `cline history --json`'s own help text describes it as "List session history or manage saved sessions" — I ran `cline history --help` myself and confirmed there is nothing in it that checks API-key validity, OAuth token expiry, remaining quota, or endpoint reachability. It only proves local session storage is readable. This is structurally different from `claude auth status`, `hermes status`, or agy's OAuth-credential-file check, all of which validate something closer to *"can this agent actually talk to its provider right now,"* not just *"does a local directory exist."* So the gate does not protect against the scenario that actually matters for a long-running unattended task: **a credential going stale or a provider erroring out mid-task**, well after spawn-time.
**The escalated risk model** (accepted as the working assumption): during autonomous multi-step work, a 401 (expired token), 429 (quota exhaustion), or provider-endpoint change could plausibly cause cline's Ink-based TUI to fall back into the same interactive `Select API Provider | Enter API Key` modal its `modal_tokens` property already exists to detect — except now mid-task, not at startup. I checked exactly where MAM watches for this modal (`lib.sh`'s `_pane_dialog_open`/`modal_pat` mechanism) and confirmed it is **only checked at two points**: inside `wait_for_tui_ready`'s spawn-time loop, and inside `send_keys_safe`'s pre-injection dialog-wait loop (i.e., only when MAM is about to send the *next* prompt). **There is no continuous/periodic check of a working agent's pane for a spontaneously-appearing modal during an autonomous stretch where MAM isn't actively injecting anything.** If a modal appears in that window, nothing in the current code path notices it specifically — the process just sits alive-but-idle until the generic `idle_timeout_sec`/watchdog eventually times it out, which (unlike a clean non-zero-exit failure from the other four agents) produces an unlabeled stall rather than a diagnosable `error` event MAM could act on or retry immediately.
**Honesty caveat**: I want to be precise about what's verified vs. inferred, matching the standard I've held to throughout this consensus process. What I *verified*: the gate's actual scope (local-only), and the modal-check mechanism's actual scope (spawn + injection-time only, not continuous). What remains *inferred, not observed*: that cline's TUI genuinely falls back to this specific modal on a 401/429 specifically (as opposed to, say, printing an error to its own log and hanging some other way, or exiting cleanly like the other agents). Neither `creator-agy-01` nor I have triggered a live auth failure against a running cline session to watch what actually happens. Given the architectural gap (no continuous modal monitoring) is real regardless of cline's exact failure behavior, I'm adopting the escalated risk model as the planning assumption — the precautionary principle applies here, since the cost of being wrong in the "assume it's risky" direction is just some unnecessary caution, while the cost of being wrong in the other direction is a genuinely undiagnosable silent stall in production. This is Rev.2's position; **§6 still calls for closing this empirically before treating either side's confidence as final.**
### 3.2 On tool-call approval and API key flags (Rev.3: corrected flag inventory)
I checked the actual installed `cline` CLI (v3.0.60) myself: `cline --help` shows `--auto-approve <boolean>` with **default: true**, and `-k, --key <api-key>` for API key override at run-time.
- On the **tool-call auto-approval** axis (bash/file-write "Allow this? [y/N]" prompts): cline is non-blocking by default.
- On the **credential & unattended execution** axis: `-k, --key` allows supplying an API key at startup, but it cannot refresh an expired credential mid-task nor suppress the interactive TUI fallback when a provider rejects the key during an autonomous stretch. Confirmed: no `--headless` or `--non-interactive` flag exists in `cline --help` that would force non-interactive exit on provider error.
### 3.3 The 40-session fact that shifted my own vote
`reviewer-hermes-01`'s report cited 40 session folders under `~/.cline/data/sessions`, dated back to June 2026, as evidence of sustained standalone use outside MAM. I independently verified this (`ls ~/.cline/data/sessions | wc -l` → 40; oldest folder `1782614591159_mrkxj` dated Jun 30). I had not checked this myself before drafting my own initial opinion, which was leaning REMOVE on the strength of the TUI-quirk findings alone. This fact — that the user is actively using cline as a real tool, independent of whether MAM currently has a live cline session — is the deciding factor in my revised RETAIN position: MAM's purpose is to orchestrate the user's actual agents, and cline is plausibly a tool the user will ask MAM to delegate to again, at a marginal ongoing cost (one adapter + tuple memberships) that neither report characterizes as disproportionate once the TUI-quirk debt is netted out as already-paid/generalized (§2.1).
### 3.4 A tension in my own Rev.1 reasoning, surfaced by the challenge (new in Rev.2)
`creator-agy-01` correctly points out that the same 40-session fact I used in §3.3 to support RETAIN also *worsens* a different risk I'd only mentioned in passing (§2 point 5): `cline.py::discover()` resolves an unknown session by sorting `~/.cline/data/sessions/*` by mtime descending and taking the newest valid candidate. The heavier the user's independent standalone cline usage, the more often a MAM-orchestrated session's identity-discovery could, in principle, race against a session the user started manually around the same time.
**Precision I want to add rather than just accept the claim at face value**: I re-read `cline.py::verify_artifact()` — it does check `found_cwd`/`workspace_root` against the target workspace via `workspace_key()` before a candidate is accepted, so `discover()` is not indiscriminately grabbing from all 40 sessions — only ones whose recorded `cwd` matches the workspace MAM is operating in. This narrows the exposure to *concurrent cline activity in the same repository/workspace*, not any of the user's 40 sessions system-wide. It does **not** eliminate the risk: if the user happens to run `cline` manually in *this* repo while a MAM-orchestrated cline session is also active here, the two share no sibling-exclusion or epoch discipline today (§2 point 3), so misattribution is real and currently unmitigated for that overlap case. Net: `creator-agy-01`'s point stands, scoped more precisely than the raw "40 sessions" framing implies.
---
## 4. The Honest Split
This is not a case where two "obviously correct" opinions outvote one weaker one. Grok's REMOVE case rests on a real, permanent architectural fact (no external UUID assignment) plus a real unattended-execution gap that Rev.2/Rev.3 characterizes sharply (§3.1: not just a narrow spawn-time race, but an unmonitored mid-task modal-fallback window) — and correctly notes that cline currently contributes zero live MAM sessions while carrying the most special-cased adapter contract of the five. Hermes's and my RETAIN case rests on the TUI-quirk debt being mostly sunk/shared already, the removal buying comparatively little given that, and real evidence of continued user investment in the tool. **Rev.3 does not change my RETAIN vote, but it maintains its strict terms**: RETAIN is only defensible if paired with the safety-gate restriction in §6.2. **Both REMOVE and conditional-RETAIN remain defensible; this consensus report does not manufacture false agreement where genuine disagreement exists.**
---
## 5. Removal Blast Radius (preserved from grok's report, for use if REMOVE is chosen regardless of this consensus)
If the General Manager decides to proceed with removal despite the 2/3 RETAIN lean, `reviewer-creator-grok-01`'s report already did the enumeration work — reproduced here so it isn't lost:
- Drop `ClineAgentAdapter`, its `registry.py` entry, and every `case`/tuple-membership site across `create_session.sh`, `resume_session.sh`, `resolve_session_id.sh`, `stop_session.sh`, `status.sh`, `reconcile.sh`, `update_yaml_resumed.sh`, `orc_onboard.sh`, `run_loop.sh`, `lib.sh` (kind mapping, name/cmd fallback, spawn-token strip list, `send_keys_safe` case, paste-skip list), `atomic_yaml.py`, `verify_session.py`, `workspace_uuid.py`.
- Remove the cline-only `^[0-9]{10,}_[0-9A-Za-z]+$` ID-format union in `orc_onboard.sh` and its `node`-as-argv0 ancestor-walk accommodation.
- **Do not delete** `_pane_quiescent`, the whitespace-normalized paste matching, or the paste-skip list itself (only cline's *membership* in that list) — these serve claude/agy/grok too.
- **Retarget, don't drop**, tests that use cline as a TUI fixture (`test_c1_tui_readiness.py`'s `wait_for_tui_ready dummy-sess cline` usage, `test_orc_onboard.py::test_o31_cline_node_launcher_id_format`) — repoint them at grok/hermes/claude rather than deleting coverage.
- Update the 57 `SKILL.md` docs' supported-agent lists.
- Single coordinated change, not a drive-by delete of `cline.py` alone — a partial removal will immediately fail `test_tier1_unit.py`'s 5-tuple whitelist assertions.
---
## 6. Recommended Path Forward (regardless of REMOVE vs. RETAIN)
1. **Modernize drift-C blocks in `reconcile.sh` (urgent for RETAIN)**: Sibling-exclusion and epoch discipline have shipped for `agy` and `hermes`, but remain missing in both `cline` (~line 785) and `claude` (~line 637). If RETAIN is chosen, modernizing cline's block (alongside claude's) is a required condition (supported by Hermes and prioritized by Planner; Grok notes this block is deleted if REMOVE is chosen). Rev.2's §3.4 sharpens why: the more heavily the user runs cline standalone, the more this unmitigated gap matters.
2. **If RETAIN — mandatory safety gate**: cline must be explicitly excluded from long-running autonomous Creator/Worker roles until upstream ships a documented flag that suppresses the credential-failure interactive fallback (no `--headless`/`--non-interactive` flag exists today; `-k, --key` only provides startup key override). Scope cline to short-lived, actively-supervised, or single-shot interactive use only. This should be written into `MULTI_AGENT_RULES.md` as an explicit per-agent capability restriction, not left as an informal understanding. Revisit after an observation window (e.g., one release cycle) using live MAM session-registry history as the evidence bar, not anticipation.
3. **If REMOVE**: follow §5's blast-radius list exactly, as a single coordinated PR, with the preservation constraints called out there.
4. **Close the open empirical question from §3.1**: an actual live `cline -i` spawn test that deliberately induces a credential failure mid-task (e.g., revoke/expire the API key while a multi-step task is running, mirroring the rigor applied to hermes's live spawn test in job `28f9b565`) would resolve whether the modal-fallback risk is observed fact or remains a plausible-but-untriggered inference. Neither side of this consensus has that data point yet — Rev.2/Rev.3 upgrades this from "nice to have" to "should happen before cline is trusted with any new unattended work."
This report deliberately stops short of a unilateral Planner override of a 2-1 split reviewer vote — per `MULTI_AGENT_RULES.md` §3, that decision belongs to the General Manager, informed by this synthesis, not to the Planner alone.
@@ -0,0 +1,18 @@
# Report: Job e30b9201 — Refined Cline Removal Plan (Rev.2) per `creator-agy-01` Challenge
**Durable output (updated in place)**: [.agents/reports/planner-reviewer-claude-01/plan-264c3b5d.md](../../../.agents/reports/planner-reviewer-claude-01/plan-264c3b5d.md)
## Summary
`creator-agy-01` challenged Rev.1's test-retargeting strategy (§5) and doc-scope decision (§6), identifying one core blind spot and two supporting gaps. **All accepted — no `[REBUT:]` filed**, after independently re-verifying each claim:
1. **Core blind spot**: Rev.1 proposed retargeting the 2-tier readiness tests by pre-exporting `MAM_STRONG_READY_TOKENS`/`MAM_WEAK_READY_TOKENS` env vars directly. I re-checked `lib.sh` myself and confirmed this would skip `wait_for_tui_ready`'s `python -m lib_py.agents facts <agent>` bridge call entirely (it's gated behind an `[ -z "$strong_tokens" ]` check) — since cline was the only real adapter with non-degenerate 2-tier values, this would leave the bridge's handling of genuinely-differentiated strong/weak tokens completely untested once cline is gone. **Fixed**: retarget to a test-local mock adapter instead, keeping the real facts bridge in the loop.
2. **`test_c8` gap**: Rev.1 only swapped the modal *text*, missing that the test's session name (`my-project-worker-cline-01`) drives dynamic agent resolution → `facts cline` — a synthetic modal string wouldn't fix that the session name itself would fail to resolve once cline's adapter is deleted. **Fixed**: retarget the whole test to `claude`, using claude's own real `modal_tokens` value (`Try the new fullscreen renderer?`, verified present in `claude.py`), preserving the complete E2E path with an agent that isn't being removed.
3. Added a new adapter-contract assertion (all 4 remaining agents' default 2-tier fields round-trip correctly through the facts bridge) to close the coverage gap explicitly.
4. **`docs/NEW_AGENT_INTEGRATION_GUIDE.md`**: I'd placed this in "do not touch," but verified myself it's a live copy-paste template with `cline` hardcoded into 5 sample sites (adapter dict, `lib.sh` kind mapping, binary tuple, diagram, test assertion) — moved into the update-scope in §6.
Full Rev.2 text with the corrected test-retargeting specifications is in the durable plan linked above.
**Process note**: still pending a fresh `[VERDICT: PASS]` before implementation begins, per `MULTI_AGENT_RULES.md` §3.1.
[VERDICT: N/A — planning artifact, review pending]
@@ -0,0 +1,25 @@
# Report: Job adad47d1 — Refined Consensus (Rev.2) per `creator-agy-01` Challenge
**Durable output (updated in place)**: [.agents/reports/cline_deprecation_opinion.md](../../../.agents/reports/cline_deprecation_opinion.md)
## Summary
`creator-agy-01` challenged Rev.1's §3.1 risk characterization of cline's unattended-execution gap, arguing it understated the real danger: `cline history --json` (the pre-spawn gate) only validates that local session storage exists, not credential/API validity — so it can't protect against a runtime auth failure (401/expired token/429 quota) mid-task, which could plausibly force cline's TUI into its interactive setup modal with no clean-exit fallback like the other four agents have. A second point noted the 40-session usage fact I'd used to support RETAIN also amplifies `discover()`'s session-hijacking exposure.
**Verification before accepting**: I ran `cline history --help` myself (confirmed: pure local listing, no auth semantics) and traced exactly where MAM checks for the setup modal in `lib.sh` (confirmed: only at spawn-time and at the moment of injecting a new prompt — never continuously during an autonomous work stretch). Both confirm the architectural gap is real. I was also careful to flag what remains *inferred* rather than *observed*: neither `creator-agy-01` nor I have actually triggered a live credential failure to watch cline's real behavior — I adopted the escalated risk model as the working precautionary assumption while saying so explicitly, rather than overstating certainty either side doesn't have.
**Disposition**: Accepted, with the honesty caveat above — no `[REBUT:]` filed.
## What changed in Rev.2
- §3.1 rewritten: risk escalated from "narrow spawn-time race" to "unmonitored mid-task modal-fallback window," with the verification trail (what's confirmed vs. inferred) laid out explicitly.
- §3.2 narrowed to reaffirm only the tool-call-approval finding that still stands (`--auto-approve` default true) — the credential-failure gap is a separate, more severe category, not covered by that flag.
- New §3.4: precision-scoped the session-hijacking concern (cline's `discover()` does filter by workspace `cwd`, so exposure is concurrent-same-workspace activity, not all 40 sessions indiscriminately) while accepting the core point.
- §4 revised: my RETAIN vote stands but its terms changed — now explicitly conditional on the new §6.2 safety gate, not an unconditional RETAIN as Rev.1 implied.
- §6 reprioritized: the reconcile.sh fix is now urgent; a new mandatory safety-gate requirement (exclude cline from long-running autonomous roles until upstream ships a real headless/non-interactive flag) is added for the RETAIN path; the empirical-verification ask is upgraded from "nice to have" to a prerequisite before trusting cline with new unattended work.
Full Rev.2 text is in the durable report linked above.
**Process note**: Still no unanimous verdict (2 RETAIN-conditional vs. 1 REMOVE) — this challenge sharpened the RETAIN side's conditions rather than flipping any vote. The REMOVE-vs-RETAIN decision still belongs to the General Manager per `MULTI_AGENT_RULES.md` §3.
[VERDICT: N/A — consensus opinion artifact, decision belongs to the General Manager]
@@ -0,0 +1,40 @@
# 🔍 Cross-Code Review — Cline Deprecation Consensus Opinion, Rev.3 (Job 073e27d4)
- **Reviewer**: `planner-reviewer-claude-01`
- **Target document**: `.agents/reports/cline_deprecation_opinion.md` (Rev.3 — response to `reviewer-creator-grok-01` (job `0a056794`) and `reviewer-hermes-01` (job `bc68ed65`) reviews)
- **Prior review context**: I also reviewed Rev.2 of this same document in job `b9a72dce`, flagging 3 findings (imprecise `§6.2`/`§4.2` cross-references, an overclaimed "proved ~0.51.5 days" statement, and a stale §1 summary line). Rev.3 does not appear to be a response to that review — its changelog (F1/F2/F3) addresses different findings from grok's and hermes's separate reviews. This review checks both: whether Rev.3's own new claims are accurate, and whether my earlier findings were carried forward.
- **Method**: read the full current file (109 lines, confirmed via `wc -l`; `git status --short` shows only this file as untracked, confirming the "no skill/framework code modified" constraint holds), and independently re-verified Rev.3's three new corrections (F1/F2/F3) against the live `cline --help` output and `reconcile.sh` source rather than accepting the changelog's claims at face value.
---
## 1. Constraint Compliance
`git status --short` → only `?? .agents/reports/cline_deprecation_opinion.md`. No skill/framework code touched. Diff header claims `+109` lines; live file is 109 lines — consistent.
## 2. Verification of Rev.3's Own New Claims (F1/F2/F3)
I did not take the changelog's self-description at face value — I re-derived each claim independently:
- **F1 (flag inventory)**: Ran `cline --help` myself. Confirmed line 25 of its output: `-k, --key <api-key> API key override for this run`. The report's bounded framing — this flag injects a key at startup but cannot refresh a credential mid-task or suppress the interactive modal fallback on a runtime provider failure, and no `--headless`/`--non-interactive` flag exists — is accurate; I found nothing in `cline --help` contradicting that scope-limiting claim. **Verified correct.**
- **F2 (drift-C modernization status, corrected line numbers)**: I grepped `reconcile.sh` for `sibling_claimed` and drift-C block headers. Initially my grep for `"drift C ("` missed claude's block because its header uses a different format (`# === drift C: claude ...` — colon, not a parenthesized agent name, unlike agy/hermes/cline's `# === drift C (agy): ...` style). On closer inspection, claude's block **is** at line 637 exactly as claimed, and it indeed calls `verify_session_uuid(cwd, 'claude', uuid, s, mode="discover")` with the raw row `s` — no `sibling_claimed` exclusion, matching cline's block at line 785. agy (line 692) and hermes (line 742) both build `s_eval['_sibling_claimed_uuids']`. **Verified correct** — this is a genuine improvement over Rev.1/Rev.2, which had incorrectly implied claude's block was already modernized (grouping it with agy/hermes).
- **F3 (consensus attribution)**: Cross-checked against hermes's original report (`.mam/jobs/57f33eff/hermes-reports/report-final.md`), which does state the drift-C fix as an explicit numbered condition of its RETAIN verdict, and grok's report, which lists the drift-C block within cline's maintenance-cost inventory (to be deleted under REMOVE) rather than as a standalone precondition. **Verified correct.**
All three of Rev.3's own corrections are accurate and represent genuine, verified improvements over Rev.2.
## 3. Findings Carried Forward — Unaddressed from My Rev.2 Review (job `b9a72dce`)
Rev.3's changelog responds to grok's and hermes's reviews, but none of the three issues I flagged in my own separate Rev.2 review were incorporated. Re-verified as still present in the live Rev.3 text:
- **Still present** (line 14, 85): `§6.2` cited as if it were a subsection heading. §6 (line 102) is still a flat `## 6.` heading followed by a plain numbered list (`1.`, `2.`) — no `### 6.1`/`### 6.2` headings exist anywhere in the document. Same issue as before, unfixed.
- **Still present** (line 49): "the grok integration already **proved** the reverse operation (adding an agent) costs ~0.51.5 days" — unchanged. As I found in the prior review, `grok.py` was added in a single squashed commit (`ad8201d`, 2026-08-26), which cannot establish actual wall-clock effort; the day-count traces to `new_agent_types_roadmap.md`'s a priori estimate for different hypothetical candidates, not a measured fact about grok. "Proved" still overstates this.
- **Still present** (line 34): "**2 of 3 lean RETAIN** (both conditional on the same follow-up fix)" — unchanged. As of Rev.2, my own RETAIN vote already carried an additional condition (the §6 item 2 safety gate) that hermes's original report never agreed to, so the two RETAIN votes are not conditioned on literally the same thing. This has been true since Rev.2 and remains uncorrected in Rev.3.
## 4. Minor Observation (not a defect)
The §0 changelog's Rev.2 entry was compressed from Rev.1/Rev.2's original 4-row table (which included per-point verification methodology, e.g. "I ran `cline history --help` myself") into shorter prose bullets during the Rev.3 restructuring. Some audit-trail granularity was lost from the changelog summary specifically, though the underlying detail still lives in the body sections (§3.1, etc.) it refers to. Not a correctness issue, just a slight reduction in the changelog's own self-sufficiency as a summary.
## 5. Verdict
Rev.3's own corrections (F1/F2/F3) are all independently verified accurate and are genuine improvements — in particular, F2 correctly identifies that claude's drift-C block is just as un-modernized as cline's, which earlier revisions had gotten wrong. However, three previously-identified, still-valid findings from my prior review of this same document were not carried forward into this revision. None of these — old or new — are severe enough to undermine the document's core methodology or conclusions; they remain small, mechanical precision fixes. Passing, with the expectation that a future revision finally closes out all outstanding findings from both review passes together rather than only the most recent one.
[VERDICT: PASS]
@@ -0,0 +1,48 @@
# 🔍 Cross-Code Review — Complete Cline Removal Implementation (Job 20d45d12)
- **Reviewer**: `planner-reviewer-claude-01`
- **Target diff**: implementation of `plan-264c3b5d.md` Rev.2 — 30 files (adapter deletion, registry, 4 `lib_py` modules, `lib.sh`, 9 skill scripts, 6 test files, 3 docs) plus 1 out-of-scope test-flakiness fix.
- **Method**: read every changed file's live post-diff state directly (not diff text alone), independently verified the two highest-risk items from my own Rev.2 plan (the `reconcile.sh` drift-C block boundary and the tiered-readiness/modal test retargeting), syntax-checked all 10 modified shell scripts, grepped the entire diff for any surviving `cline` reference, and ran the full test suite myself.
---
## 1. Fidelity to Rev.2 Plan — Verified, Not Assumed
I did not trust the implementation's own claim of compliance — I re-checked the specific corrections `creator-agy-01`'s challenge required in Rev.2 against the live diff:
- **Tiered-readiness tests (Rev.2's core correction)**: `test_c3_strong_token_and_hint_token_readiness_succeeds` now mocks `_delegate_py_bin`/`python -m lib_py.agents facts` to return a synthetic `mocktiered` agent with genuinely distinct `MAM_STRONG_READY_TOKENS='MockApp'` / `MAM_WEAK_READY_TOKENS='Use arrow keys'`, keeping the real facts-bridge call path exercised rather than bypassing it with raw env-var pre-injection — exactly what Rev.2 required. The other C4C7 tests that were already using pre-set env vars (not resolving through the bridge in the original cline-based version either) were correctly left as simple session-name swaps, since they were never testing the bridge to begin with.
- **`test_c8b` (modal test)**: retargeted fully to `claude`, using `'Try the new fullscreen renderer?'` as the injected screen text and `MAM_MODAL_TOKENS='Try the new fullscreen renderer\?'`, with session name `my-project-worker-claude-01` — this is claude's actual, verified `modal_tokens` value, exactly matching Rev.2's requirement to preserve the full session-name-resolution → `facts claude` → dialog-block path, not a synthetic placeholder.
- **New facts-bridge round-trip assertion**: `test_a4_adapter_contract.py::test_adapter_required_properties` gained `assert adapter.strong_ready_tokens == adapter.ready_tokens` / `assert adapter.weak_ready_tokens == ''` for all 4 remaining agents, and `test_facts_bridge_eval_contract` now additionally asserts `STRONG=`/`WEAK=` come through the real bash `eval` of the bridge's output — this is actually a **stronger** implementation than what I asked for (I only required the property-level check; this round-trips through the real subprocess + bash eval too).
- **`docs/NEW_AGENT_INTEGRATION_GUIDE.md`**: all 5 sites I flagged in Rev.2 (diagram, `_ADAPTERS` sample, `lib.sh` kind-mapping sample, binary-tuple sample, test-assertion sample) were updated — the architecture diagram box-drawing was even correctly realigned (`┬` connector fixed) after swapping `ClineAgentAdapter` for `GrokAgentAdapter` in that slot, not just text-deleted.
## 2. Independent Verification of the Highest-Risk Edit
I flagged the `reconcile.sh` cline drift-C block deletion as the highest-risk single edit in my own plan. Checked the live file directly: the block is cleanly gone, the preceding `hermes` drift-C block and the following `result = {...}` return statement are both intact and correctly adjacent with no orphaned fragments. Extracted and `ast.parse()`'d the actual `RECON_SRC` heredoc (lines 320794, not the other heredoc earlier in the file, which I made sure to distinguish) — valid Python. `bash -n` on the whole file — valid.
## 3. Completeness Check
`git diff | grep -n "^+.*[Cc]line"` (every added line, across the entire diff) returns **zero matches** — no newly-written line anywhere in this diff still references cline. Cross-checked a full-repo `cline` grep against `git status`: every remaining match is either inside `.agents/reports/**` (untouched, correct) or inside changelog-style docs (`VERSIONS.md`, `IMPROVEMENTS.md`) describing past releases in the past tense (correctly left alone, consistent with my plan's "spot-check, don't blanket-edit" guidance).
## 4. Findings
### 4.1 Minor: `MULTI_AGENT_RULES.md`/`.ko.md` line 21 slightly stale (Low, not blocking)
`"Newly spawned agents (e.g., antigravity, claude, cline, hermes) act as Team Leaders..."` — an illustrative `e.g.` list, not a hard enumeration, but it does still name cline as a live example post-removal. Low severity since the sentence's substance is about the *role concept*, not a supported-agent contract, and this file wasn't in either of our removal plans' scope. Worth a follow-up touch-up, not blocking.
### 4.2 Out-of-scope change present in the diff (informational, not a defect)
`tests/test_o2_race_free_lock.py` was modified — replacing a fixed `time.sleep(0.3)` in `acquire_bg()` with an active poll-until-marker-file-written loop (up to 2s, with early exit if the background process dies). This has nothing to do with cline removal; it's a flaky-test timing fix, most likely surfaced while chasing "100% pass, zero regressions" during implementation. I reviewed the change itself: it's strictly safer than what it replaces (removes a fixed-sleep race assumption, fails faster on a dead process) and doesn't touch cline-adjacent code. Flagging for transparency/scope-discipline reasons, not as a defect — I would not block on this alone.
## 5. Full Test Suite
```
.venv/bin/python -m pytest tests/ -q
→ 439 passed in 655.22s (0:10:55), exit code 0
```
Ran to completion myself (not the diff's own claim). **Zero failures, zero regressions.**
## 6. Verdict
Every site from my own Rev.2 plan was implemented faithfully and, in two places (the facts-bridge round-trip assertion, the architecture-diagram realignment), more thoroughly than the plan strictly required. No orphaned `cline` references anywhere in the diff. The highest-risk edit (`reconcile.sh`'s block deletion) is clean and syntactically valid. One low-severity doc staleness and one out-of-scope-but-safe test fix are noted, neither blocking.
[VERDICT: PASS]
@@ -0,0 +1,26 @@
# Re-review: `cline_deprecation_opinion.md` (Rev.3)
- **Reviewer**: `reviewer-creator-grok-01`
- **Job**: `d1fe1a99` (follow-up to `0a056794` `[VERDICT: NOT PASS]`)
- **Artifact**: `.agents/reports/cline_deprecation_opinion.md` (untracked; no skill/framework code in the diff)
## Prior blocking items
| ID | Rev.2 defect | Rev.3 |
|---|---|---|
| **F1** | Claimed no `--api-key`-override exists | **Fixed.** §3.2 and §6.2 cite `-k, --key <api-key>` as spawn/run-start injection, bound as not mid-task refresh / not modal suppression. `--headless`/`--non-interactive` absence still holds against live `cline --help`. |
| **F2** | Claimed claude drift-C has `sibling_claimed` | **Fixed.** §2.3 / §6.1: only agy (~692) and hermes (~742); claude (~637) and cline (~785) both lack it. |
| **F3** | “All three independently demand a drift-C fix either way” | **Fixed.** Hermes: condition of RETAIN. Planner: urgent if RETAIN. Grok: delete the block if REMOVE. |
## Remainder (non-blocking)
- Honest 21 split preserved. Grok **REMOVE** in §1/§4 is accurate (no assignable UUID; unattended/modal gap; no live cline session).
- §5 blast-radius constraints still match `e0916903` (keep `_pane_quiescent` / paste-normalize; retarget `test_c1_*`; no drive-by `cline.py` delete).
- §2 heading still says “fully agree” while point 3 now states a disposition split. Content is honest; heading is slightly loose. Not worth another loop.
- Groks **REMOVE** vote is unchanged. This PASS is on the synthesis, not a vote flip.
No `[ESCALATE: PLANNER]`. GM still owns REMOVE vs conditional-RETAIN.
---
[VERDICT: PASS]
@@ -0,0 +1,38 @@
# Review: Complete `cline` removal
- **Reviewer**: `reviewer-creator-grok-01`
- **Job**: `d78e819e`
- **Scope**: Diff vs `cline_deprecation_opinion.md` §5 blast radius. No skill/framework files were edited by this review.
## §5 checklist
| Requirement | Result |
|---|---|
| Delete `adapters/cline.py`, unregister in `registry.py` | **Met.** Adapters dir is `claude.py` / `agy.py` / `hermes.py` / `grok.py` only. |
| Remove cline from lib.sh + skill scripts | **Met.** Zero `cline` hits under `.agents/skills/` (kind map, spawn-token strip, `send_keys_safe` case, create/resume/stop/status/reconcile/orc_onboard/run_loop, atomic_yaml / verify_session / workspace_uuid own-keys). |
| Drop `cline_re` and cline `--id` / `CLINE_SESSION_ID` arms | **Met.** `is_valid_id` is UUID-only. |
| Keep `_pane_quiescent`, whitespace paste-normalize, paste-skip list (minus cline membership) | **Met.** Skip list is now `claude\|agy\|grok`. Hanging-indent comment depersonalized, logic kept. |
| Retarget, dont drop, TUI fixture tests | **Met for `test_c1_*`.** Strong/weak readiness uses a `mocktiered` facts stub; modal dialog test uses claudes fullscreen upsell; SKS suffix cases use grok/claude/hermes/agy. `test_o31` (node launcher + non-UUID id) was **deleted**, which is correct: that code path no longer exists and cannot be retargeted to a UUID agent. |
| Historical `.agents/reports/**/*cline*` untouched | **Met.** Diff does not touch those trees. `VERSIONS.md` / `IMPROVEMENTS.md` still mention cline as changelog history — leave them. |
| Single coordinated change | **Met.** 31 files, 411/+135. Not a `cline.py`-only delete. |
| Other agents unharmed | **Met.** hermes `--yolo --accept-hooks` spawn fallback, grok assigned-UUID YAML branch, claude/agy paths intact. |
## Tests
```
pytest tests/ → 439 passed in 614.62s
```
Zero failures. Adapter contract, TUI readiness, orc-onboard, tier1/tier2 all green after the 5-tuple shrink.
## Residual nits (do not block)
1. `create_session.sh` fallback `CMD_FULL` case still omits `grok` (primary path is `spawn-spec`). Pre-existing, not introduced by this removal.
2. Create `SKILL.md` `cmd_full` table still lists only claude/agy. Docs lag, not runtime.
3. Changelog files (`VERSIONS.md`, `IMPROVEMENTS.md`) still name cline; that is audit trail, not live surface.
No leftover live dispatch, no deleted shared TUI helpers, no regression in the remaining four agents. No `[ESCALATE: PLANNER]`.
---
[VERDICT: PASS]
@@ -0,0 +1,48 @@
# 📋 Code Review Report — Complete Removal of cline Agent Support (Job 2562f9c9)
- **Reviewer**: `reviewer-hermes-01` (role: reviewer)
- **Reviewed diff**: 31 files, +135/411 — full cline removal per the GM's REMOVE decision, executed against `.agents/reports/cline_deprecation_opinion.md` §5 blast radius and plan `.agents/reports/planner-reviewer-claude-01/plan-264c3b5d.md` (Rev.2)
- **Baseline for comparison**: merged HEAD `e0c0c10` (hermes modernization, previously reviewed by me in jobs b45fb1d4/ca4539e8)
- **Method**: full-diff read; framework-wide residual `grep -i cline` over `.agents/skills/`, `tests/`, `deploy/`, `docs/`; syntax checks on all 10 modified shell scripts + 11 Python files; test-count delta analysis; full suite execution. No code modified by this review.
---
## 1. Verification Evidence
| # | Check | Result |
|---|---|---|
| 1 | **Full test suite** | ✅ **439 passed, 0 failures** (607.5 s) |
| 2 | **Test-count sanity** (plan §8 requirement) | ✅ 441 → **439** collected (2 = exactly the two cline-only tests `test_o31_cline_node_launcher_id_format` and `test_o39_cline_orchestrator_exclusion` deleted per plan §5; all other retargeted tests preserved as renamed/redirected cases). Matches plan's "decreased by roughly the number deleted, not increased or unchanged" gate. |
| 3 | **Residual cline references** | ✅ **Zero** matches for `-i 'cline'` across `.agents/skills/`, `tests/`, `deploy/`, `docs/` (all file types) — the removal is total in live surface. (`.agents/reports/**` history correctly untouched per plan §6/§8 preservation constraint.) |
| 4 | **Syntax**: `bash -n` on all 10 modified/affected shell scripts; `py_compile` on registry/atomic_yaml/verify_session/workspace_uuid + 6 test files | ✅ all clean |
| 5 | Adapter + registry | ✅ `cline.py` deleted; `registry.py` no longer imports/registers it |
| 6 | 4 `lib_py` core modules | ✅ own-key tuples/dicts reduced to 4 agents in `atomic_yaml.py`, `verify_session.py`, `workspace_uuid.py` |
| 7 | `lib.sh` 5 mechanisms | ✅ kind mapping case + 2 grep fallbacks removed; binary-strip tuple → 4-agent; comment `~/.cline` dropped; `send_keys_safe` case arm removed; **paste-skip list retains claude/agy/grok membership** (mechanism preserved per plan §3); whitespace-normalization comment de-attributed from cline but **mechanism kept** |
| 8 | 9 skill scripts | ✅ all cline branches/cases/tuples removed exactly per plan §4 — spot-verified against the plan's line list: create (auth gate L122125, CMD_FULL L189, spawn dispatch L210, delegate_agent L319, child-PID L339, YAML entry L423), resume (RESOLVED_BIN branch L8991 collapsed correctly to a single `command -v` block), stop (own-id capture L291292), status (artifact check L9396), reconcile (drift-C block L785828 fully deleted, `id_name` tuple → `agent == 'claude'` per plan's style recommendation), orc_onboard (`cline_re` + argv parsing arm + env var + own-key list), run_loop (elif branch), update_yaml_resumed (PID capture + elif branch) |
| 9 | Docs | ✅ 5 SKILL.md/README + `deploy/INSTALL.md` + `implementation_plan.md` + `docs/NEW_AGENT_INTEGRATION_GUIDE.md` (all 5 hardcoded sample sites fixed: diagram, `_ADAPTERS` dict, kind mapping, binary tuple, test-assertion sample) |
| 10 | Test retargeting fidelity (the plan's core preservation principle) | ✅ verified concretely: `test_c3` now uses a test-local `mock_py` facts-bridge shim with genuinely differentiated strong/weak tokens ('MockApp' vs 'Use arrow keys') and `wait_for_tui_ready dummy-sess mocktiered` — this keeps the **real bridge call** in the loop exactly as the plan's Rev.2 challenge-fix required (not raw env-var injection, which would have bypassed the `[ -z … ]` gate); `test_c8b` retargeted end-to-end to claude's real modal (`Try the new fullscreen renderer?`) with a claude session name, preserving the session-name→facts→modal→RC=2 path; `test_o32` **strengthened** (cline-format IDs now asserted rejected: `"1785635248957_fajon"` added to the bad-list — correct since `cline_re` is gone); 2-tier round-trip assertions added to the facts-bridge contract test (`STRONG=`/`WEAK=` for all 4 remaining agents) |
| 11 | Unrelated-regression check on shared logic | ✅ `send_keys_safe`'s marker/paste/submit flow, `wait_for_tui_ready`'s tier decision logic, and `verify_session_uuid` are structurally untouched apart from cline membership removal |
## 2. Deviations from the plan (all assessed as acceptable or improvements)
1. **`stop_session.sh` usage() lost two lines not required by the plan** ("Stop is always graceful and always captures the conversation id. / (idempotent: …)"). These lines were *true before and after* this change — their deletion is unrelated to cline removal. It is a small drive-by docs deletion; content is still true and was mildly useful. Low-impact; flag for the committer to either restore the two lines or confirm intentional.
2. **`tests/test_o2_race_free_lock.py::acquire_bg` reworked** (fixed 0.3 s sleep → marker-file polling loop up to 2 s). Nothing to do with cline — this is a test-flakiness fix for a race between lock acquisition and the test's first assertion. Reasonable hardening, but it is an unrelated change riding in a cline-removal diff; it *is* defensible under "keep the suite green while removing cline" if the old timing proved flaky during this work (plausible — the lock script changed nothing, but sandbox timing may have). No action required; noted for change-transparency.
3. **`implementation_plan.md` team line** (`cline``hermes`, `grok`) and **`deploy/INSTALL.md`** agent list edits: the plan said "spot-check, don't blanket-edit" these docs for *actively misleading* mentions — an agent-listing that includes a now-removed agent qualifies as actively misleading, so these edits fall inside the plan's exception clause. Acceptable.
4. **`test_a4_adapter_contract.py` gained new assertions** (2-tier default contract, STRONG/WEAK facts-bridge round-trip) — these implement plan §5's Rev.2 addition; verified present and passing.
## 3. Non-blocking observations
1. **[Low — pre-existing, now moot for cline]** The claude drift-C block's missing sibling-exclusion (the Rev.3 consensus §6.1 item) is **not** addressed here — correctly, since the plan explicitly scoped it out as orthogonal to cline removal. It remains the one open modernization gap; recommend it be tracked as its own follow-up job rather than silently absorbed.
2. **[Info]** `docs/NEW_AGENT_INTEGRATION_GUIDE.md`'s ASCII diagram edit preserves alignment acceptably; no rendering breakage observed.
3. **[Info]** `test_tier2_component.py` usage-text loops now assert `grok` instead of `cline` — consistent with the scripts' actual usage() output (verified by the passing suite).
4. **[Info]** The consensus/plan/report artifacts (`clline_deprecation_opinion.md`, plan-264c3b5d, plan-a33a133e, report-073e27d4, report-d1fe1a99, report-2e6f01f4) are included in the diff as new tracked files under `.agents/reports/` — consistent with the durable-promotion convention in `MULTI_AGENT_RULES.md` §4.
## 4. Risk Assessment
The three plan-flagged risk items are all addressed: (a) the reconcile.sh drift-C block deletion's boundaries were verified against live code (block fully gone, adjacent hermes block intact, `result = {` assembly intact); (b) orc_onboard's ancestor-walk mechanism was kept generically with only cline's case arm removed (plan §4 recommendation followed); (c) the test-count sanity check passes exactly. The suite's 439/439 green plus zero residual references means no cline path remains reachable, and the other four agents' coverage is demonstrably unchanged in structure.
## 5. Verdict
The removal is complete, surgical, and matches the planned blast radius with only two minor unrelated edits (stop_session.sh usage lines, o2 flakiness fix) that are individually defensible but worth the committer's awareness. All preservation constraints from the consensus doc §5 were honored: generic mechanisms (whitespace-normalized paste matching, paste-skip list, `_pane_quiescent`, 2-tier readiness, modal contracts) retained; generic-mechanism tests retargeted rather than deleted, with the facts-bridge genuinely still exercised; historical `.agents/reports/**` untouched.
[VERDICT: PASS]
@@ -0,0 +1,43 @@
# 📋 Code Review Report — cline Deprecation Consensus Opinion Rev.3 (Job 2e6f01f4)
- **Reviewer**: `reviewer-hermes-01` (role: reviewer) — participant in the reviewed consensus (job `57f33eff`) and author of the prior NOT PASS review (job `bc68ed65`); this review judges only whether Rev.3 faithfully and accurately incorporates the two review rounds.
- **Reviewed diff**: one file — `.agents/reports/cline_deprecation_opinion.md` (Rev.2 → Rev.3, 105 → 109 lines; blob `f818c6e``229ad1e`, hash verified against working tree). No code changes.
- **Context**: my previous review (job `bc68ed65`) issued `[VERDICT: NOT PASS]` with two blocking factual defects; grok's review (job `0a056794`) raised its own points. This revision claims to fix both (F1, F2) plus an attribution refinement (F3).
---
## 1. Prior Blocking Defects — Correction Verification
### 1.1 F1 — flag inventory (my bc68ed65 §2.2) → **FIXED, accurately**
Rev.3 §3.2 now states: `cline --help` shows `--auto-approve <boolean>` (default true) **and** `-k, --key <api-key>` for startup key injection, bounding its scope explicitly — "cannot refresh an expired credential mid-task nor suppress the interactive TUI fallback"; and confirms no `--headless`/`--non-interactive` flag exists. §6.2's parenthetical is updated consistently ("-k, --key only provides startup key override").
My re-verification against the live CLI (v3.0.60): `--auto-approve` default true ✅; `-k, --key <api-key>` present ✅; no `--headless`/`--non-interactive` ✅. The corrected scoping is exactly the right technical characterization — it neither overstates nor understates what `-k` buys.
### 1.2 F2 — drift-C modernization status (my other blocking defect) → **FIXED, accurately**
§2 point 3 and §6.1 now state: sibling-exclusion + epoch discipline exist only in **agy (~692)** and **hermes (~742)**; **claude (~637)** and **cline (~785)** both lack it, and §6.1's urgent fix now covers **both** cline and claude blocks.
Line references re-verified against live code: block markers at 637/676/725/785; `sibling_claimed` present only at 692 (agy) and 742 (hermes) — claude's block (637665) verifies against raw `s`. All four line numbers in the document are correct. The omission that made Rev.2 not-passable is fully remediated.
### 1.3 F3 — consensus attribution (grok's review) → **FIXED**
§2.3/§6.1 now attribute positions correctly (Hermes: RETAIN-condition; Planner: urgent; Grok: delete-if-REMOVE). Grok's review job `0a056794` exists with the expected brief, and its original report (e0916903) indeed recommends REMOVE as summarized in §1's table.
## 2. Carry-Over Claims — Re-verified (no regression between Rev.2 → Rev.3)
| Claim | Status |
|---|---|
| §3.1 modal-check scope (spawn + injection-time only, never continuous) — lib.sh `_pane_dialog_open` consumers | ✅ re-confirmed (wait_for_tui_ready:1888, send_keys_safe:2081 are the only consumers) |
| §3.3 / §2.4: 40 standalone sessions, oldest `1782614591159_mrkxj` | ✅ re-confirmed live |
| §2.2: only claude.py + cline.py override `modal_tokens` | ✅ |
| §2.6 / §5: blast-radius enumeration (incl. 5-tuple whitelist assertion in test_tier1_unit.py:963, fixture names) | ✅ re-confirmed |
| §2.5 / §5: cline timestamp IDs, orc_onboard regex `^[0-9]{10,}_[0-9A-Za-z]+$` (L6970), argv0-aware ancestor walk + cline argv parsing (`--id`/`--session-id`, orc_onboard.sh:128129), per-family env vars (L147) | ✅ all confirmed — the §5 "node-as-argv0 ancestor-walk accommodation" claim is accurate: `detect_nearest_agent` (orc_onboard.sh:76164) walks ancestors via `ps -o command=`, basename-matches agent tokens, and parses cline's `--id` from the command line; `test_o31_cline_node_launcher_id_format` (exec -a argv0 rewrite) covers exactly this |
| §4 honest 21 split; final decision deferred to GM | ✅ preserved in Rev.3 |
## 3. Residual Findings (non-blocking)
1. **[Info — nuance, no doc change required]** claude's epoch story differs in kind from cline's: `claude.py::verify_artifact` still uses a file-level `getmtime` check (claude.py:54), but each claude session is one `.jsonl` file, so file-mtime **is** row-level there — unlike hermes's shared `state.db`, which is why hermes needed the row-level rewrite and claude does not. Rev.3's §6.1 correctly treats claude and cline as equally *structurally* un-modernized (both lack sibling exclusion in reconcile.sh); readers should note the adapter-level epoch mechanics differ (claude's per-file mtime is adequate; cline's per-file mtime likewise). This nuance does not change §6.1's operative instruction.
2. **[Info]** §6.2's `MULTI_AGENT_RULES.md` capability-restriction remains a protocol-document change requiring GM + Team Leader consent per that document's own amendment clause — correctly framed as a recommendation, not an enacted rule.
3. **[Info]** No lint issues (markdown-only diff).
## 4. Assessment
Both blocking defects from job `bc68ed65` are corrected faithfully — not merely acknowledged, but with correct line-level references and properly bounded flag semantics. The F3 attribution addition (grok's REMOVE-side framing of the drift block as deletable debt) is a genuine improvement in consensus fidelity. All checkable claims now survive direct verification against the live repo, live CLI, and live environment. The document maintains its honest 21 split presentation and correct GM deferral.
[VERDICT: PASS]
+8 -13
View File
@@ -606,18 +606,15 @@ print('\t'.join(env_flags) + '\n' + ' '.join(binary_tokens))
*-creator-claude|*-planner-claude|*-reviewer-claude) kind="claude" ;;
*-creator-agy|*-planner-agy|*-reviewer-agy) kind="agy" ;;
*-creator-hermes|*-planner-hermes|*-reviewer-hermes) kind="hermes" ;;
*-creator-cline|*-planner-cline|*-reviewer-cline) kind="cline" ;;
*-creator-grok|*-planner-grok|*-reviewer-grok) kind="grok" ;;
*)
if echo "$name" | grep -qi "agy"; then kind="agy"
elif echo "$name" | grep -qi "claude"; then kind="claude"
elif echo "$name" | grep -qi "hermes"; then kind="hermes"
elif echo "$name" | grep -qi "cline"; then kind="cline"
elif echo "$name" | grep -qi "grok"; then kind="grok"
elif echo "${final_cmd:-}" | grep -qi "agy"; then kind="agy"
elif echo "${final_cmd:-}" | grep -qi "claude"; then kind="claude"
elif echo "${final_cmd:-}" | grep -qi "hermes"; then kind="hermes"
elif echo "${final_cmd:-}" | grep -qi "cline"; then kind="cline"
elif echo "${final_cmd:-}" | grep -qi "grok"; then kind="grok"
fi
;;
@@ -632,7 +629,7 @@ try:
tokens = shlex.split(cmd)
if tokens:
first = tokens[0]
if first in ('claude', 'agy', 'hermes', 'cline', 'grok') or any(first.endswith('/' + a) for a in ('claude', 'agy', 'hermes', 'cline', 'grok')) or (kind and (first == kind or first.endswith('/' + kind))):
if first in ('claude', 'agy', 'hermes', 'grok') or any(first.endswith('/' + a) for a in ('claude', 'agy', 'hermes', 'grok')) or (kind and (first == kind or first.endswith('/' + kind))):
tokens = tokens[1:]
print(' '.join(shlex.quote(t) for t in tokens))
except Exception:
@@ -1660,7 +1657,7 @@ capture_conversation_id() {
# Config-home isolation (.mam/agent_homes/<uuid>/) and legacy isolation.root
# row consumers were completely deprecated and removed in favor of:
# 1. Universal Global Config: all agents read/write standard ~/.claude, ~/.gemini,
# ~/.hermes, ~/.cline user configuration and credential stores.
# ~/.hermes user configuration and credential stores.
# 2. Process Isolation: each agent-workspace pair runs in its own herdr pane.
# 3. Conversation Isolation: session UUIDs discriminate conversation history.
# ---------------------------------------------------------------------------
@@ -2061,7 +2058,6 @@ send_keys_safe() {
*-claude|*-claude-[0-9]*|claude|claude-[0-9]*) _sks_agent="claude" ;;
*-agy|*-agy-[0-9]*|agy|agy-[0-9]*) _sks_agent="agy" ;;
*-hermes|*-hermes-[0-9]*|hermes|hermes-[0-9]*) _sks_agent="hermes" ;;
*-cline|*-cline-[0-9]*|cline|cline-[0-9]*) _sks_agent="cline" ;;
*-grok|*-grok-[0-9]*|grok|grok-[0-9]*) _sks_agent="grok" ;;
esac
if [ -n "$_sks_agent" ]; then
@@ -2105,12 +2101,11 @@ send_keys_safe() {
# multi-byte UTF-8 char, e.g. Korean, producing a marker that can never match
# the properly-decoded rendered pane text) of the last non-empty line.
marker=$(printf '%s' "$text" | tr -d '\r' | awk 'NF {line=$0} END {print line}' | python3 -c "import sys; print(sys.stdin.read().rstrip('\n')[-24:], end='')")
# Whitespace-normalized copy for matching: some TUIs (e.g. cline's own
# message rendering) don't just soft-wrap with a bare newline — they add a
# leading-space "hanging indent" on the continuation line too, so removing
# only '\n' still leaves an extra space that breaks an exact literal match.
# Matching with all whitespace collapsed out sidesteps wrap formatting
# entirely, whatever shape it takes.
# Whitespace-normalized copy for matching: some TUIs don't just soft-wrap
# with a bare newline — they add a leading-space "hanging indent" on the
# continuation line too, so removing only '\n' still leaves an extra space
# that breaks an exact literal match. Matching with all whitespace collapsed
# out sidesteps wrap formatting entirely, whatever shape it takes.
marker_norm=$(printf '%s' "$marker" | tr -d '[:space:]')
local sks_buf="sks_${sess}_${job_id}_$$_${RANDOM}_$(date +%s%N 2>/dev/null || date +%s)"
@@ -2123,7 +2118,7 @@ send_keys_safe() {
return 3
fi
local was_popup=0
if [[ "$sess" =~ "cline" ]] || [[ "$sess" =~ "claude" ]] || [[ "$sess" =~ "agy" ]] || [[ "$sess" =~ "grok" ]]; then
if [[ "$sess" =~ "claude" ]] || [[ "$sess" =~ "agy" ]] || [[ "$sess" =~ "grok" ]]; then
# Skip strict paste check due to scrollout false-positives, proceed to C-m submission loop
true
else
@@ -1,112 +0,0 @@
import os, json, shutil, glob
from typing import Optional, Any
from lib_py.agents.base import BaseAgentAdapter, DiscoveryContext
from lib_py.verify_session import workspace_key
class ClineAgentAdapter(BaseAgentAdapter):
@property
def name(self) -> str:
return 'cline'
@property
def own_key(self) -> str:
return 'cline_conversation_id_own'
@property
def ready_tokens(self) -> str:
return 'Cline|history|Chat|What can I do|slash commands'
@property
def strong_ready_tokens(self) -> str:
return 'Cline|What can I do|slash commands'
@property
def weak_ready_tokens(self) -> str:
return 'history|Chat'
@property
def modal_tokens(self) -> Optional[str]:
return 'Select API Provider|Enter API Key|Select an API provider|API Provider|Cline API key'
@property
def exit_key(self) -> str:
return '/exit'
@property
def delegate_agent_key(self) -> str:
return 'cline-agent'
@property
def identity_cache_fields(self) -> tuple:
return ('session_id',)
@property
def input_prompt(self) -> str:
return ''
@property
def input_placeholder(self) -> str:
return 'Ask anything...'
@property
def input_rule_pattern(self) -> str:
return '{10,}'
def artifact_path(self, uuid: str, ctx: DiscoveryContext) -> str:
return f"{ctx.home_dir}/.cline/data/sessions/{uuid}/{uuid}.json"
def verify_artifact(self, uuid: str, ctx: DiscoveryContext) -> bool:
path = self.artifact_path(uuid, ctx)
if not os.path.exists(path):
return False
if ctx.epoch and os.path.getmtime(path) < ctx.epoch:
return False
try:
with open(path) as f:
sdata = json.load(f)
if sdata.get("session_id") != uuid:
return False
found_cwd = sdata.get("cwd") or sdata.get("workspace_root")
if found_cwd and workspace_key(found_cwd) != workspace_key(ctx.cwd):
return False
except Exception:
return False
return True
def purge_artifacts(self, uuid: str, ctx: DiscoveryContext) -> list:
purged = []
sessions_dir = f"{ctx.home_dir}/.cline/data/sessions/{uuid}"
if os.path.isdir(sessions_dir):
shutil.rmtree(sessions_dir)
purged.append(sessions_dir)
return purged
def spawn_spec(self, binary: str, session_uuid: str = "", use_wrapper: bool = False) -> str:
return f"{binary} -i"
def resume_spec(self, binary: str, session_uuid: str, materialized: bool = False) -> str:
if materialized and session_uuid:
return f"{binary} -i --id {session_uuid}"
return f"{binary} -i"
def auth_ok(self, run_cmd: Optional[Any] = None) -> bool:
return True
def discover(self, ctx: DiscoveryContext) -> list:
sessions_dir = f"{ctx.home_dir}/.cline/data/sessions"
if os.path.isdir(sessions_dir):
files = []
for folder in glob.glob(f"{sessions_dir}/*"):
if os.path.isdir(folder):
fn = os.path.basename(folder)
jf = f"{folder}/{fn}.json"
if os.path.exists(jf):
files.append(jf)
files.sort(key=os.path.getmtime, reverse=True)
candidates = []
for j in files:
cand = os.path.basename(j)[:-5]
if cand and self.verify_artifact(cand, ctx):
candidates.append(cand)
return candidates
return []
-2
View File
@@ -5,14 +5,12 @@ from lib_py.agents.base import BaseAgentAdapter
from lib_py.agents.adapters.claude import ClaudeAgentAdapter
from lib_py.agents.adapters.agy import AgyAgentAdapter
from lib_py.agents.adapters.hermes import HermesAgentAdapter
from lib_py.agents.adapters.cline import ClineAgentAdapter
from lib_py.agents.adapters.grok import GrokAgentAdapter
_ADAPTERS: Dict[str, BaseAgentAdapter] = {
'claude': ClaudeAgentAdapter(),
'agy': AgyAgentAdapter(),
'hermes': HermesAgentAdapter(),
'cline': ClineAgentAdapter(),
'grok': GrokAgentAdapter(),
}
+1 -1
View File
@@ -129,7 +129,7 @@ def atomic_dump_yaml_main():
if name in old_roles and s.get('role') != old_roles[name]:
raise SystemExit(f"VALIDATE: role of session {name!r} cannot be modified from {old_roles[name]!r} to {s.get('role')!r}")
running_keys = ['claude_session_id_own', 'agy_conversation_id_own', 'hermes_conversation_id_own', 'cline_conversation_id_own', 'grok_session_id_own']
running_keys = ['claude_session_id_own', 'agy_conversation_id_own', 'hermes_conversation_id_own', 'grok_session_id_own']
id_to_session = {}
for s in d.get('herdr_sessions', []):
if s.get('status') == 'running':
+1 -1
View File
@@ -66,7 +66,7 @@ def mam_orchestrator_uuids():
def mam_row_own_uuid(row):
if not isinstance(row, dict):
return None
for k in ["claude_session_id_own", "agy_conversation_id_own", "hermes_conversation_id_own", "cline_conversation_id_own", "grok_session_id_own"]:
for k in ["claude_session_id_own", "agy_conversation_id_own", "hermes_conversation_id_own", "grok_session_id_own"]:
v = row.get(k)
if v:
return v
+1 -2
View File
@@ -8,7 +8,6 @@ OWN_KEY = {
'claude': 'claude_session_id_own',
'agy': 'agy_conversation_id_own',
'hermes': 'hermes_conversation_id_own',
'cline': 'cline_conversation_id_own',
'grok': 'grok_session_id_own'
}
@@ -31,7 +30,7 @@ def find_workspace_uuid_main():
if s_item.get('status') == 'running':
if target and s_item.get('name') == target:
continue
for k in ['claude_session_id_own', 'agy_conversation_id_own', 'hermes_conversation_id_own', 'cline_conversation_id_own', 'grok_session_id_own']:
for k in ['claude_session_id_own', 'agy_conversation_id_own', 'hermes_conversation_id_own', 'grok_session_id_own']:
val = s_item.get(k)
if val:
running_ids.add(val)
@@ -129,7 +129,7 @@ herdr_sessions:
```bash
WORKSPACE=/path/to/project
AGENT=claude # claude | agy | hermes | cline | grok — always pass it explicitly
AGENT=claude # claude | agy | hermes | grok — always pass it explicitly
source .agents/skills/lib.sh
SESSION_NAME="$(derive_session_name "$WORKSPACE" "$AGENT")"
@@ -160,7 +160,7 @@ case "$AGENT" in
grok)
herdr new-session -d -s "$SESSION_NAME" -x 140 -y 40 -c "$WORKSPACE" "grok --permission-mode bypassPermissions"
;;
*) echo "ERROR: --agent must be claude, agy, hermes, cline or grok, got: $AGENT"; exit 2 ;;
*) echo "ERROR: --agent must be claude, agy, hermes, or grok, got: $AGENT"; exit 2 ;;
esac
# 3. Wait for agent TUI to be ready (varies: claude ~5s, agy ~3s)
@@ -1,7 +1,7 @@
#!/usr/bin/env bash
# create_session.sh — multi-agent-mux-create 의 부속 스크립트
# Usage:
# bash create_session.sh --workspace <path> --agent <claude|agy|hermes|cline> --role <role> [--session <name>] [--herdr-session <name>] [--wrapper]
# bash create_session.sh --workspace <path> --agent <claude|agy|hermes|grok> --role <role> [--session <name>] [--herdr-session <name>] [--wrapper]
#
# 동작:
# 1) preflight: herdr/claude/agy 가용성, workspace 존재
@@ -26,11 +26,11 @@ source "$_lib_sh"
usage() {
cat <<EOF
Usage: $0 --workspace <path> --agent <claude|agy|hermes|cline> --role <role> [options]
Usage: $0 --workspace <path> --agent <claude|agy|hermes|grok> --role <role> [options]
Options:
--workspace PATH project directory (required)
--agent AGENT claude | agy | hermes | cline (required)
--agent AGENT claude | agy | hermes | grok (required)
--role ROLE assigned role (required)
--session NAME herdr session name (default: derived from workspace)
--wrapper force use of ~/.local/bin/<session> wrapper even if not present
@@ -90,8 +90,8 @@ fi
[ -n "$WORKSPACE" ] || { echo "ERROR: --workspace required" >&2; usage; exit 2; }
[ -n "$AGENT" ] || { echo "ERROR: --agent required" >&2; usage; exit 2; }
case "$AGENT" in
claude|agy|hermes|cline|grok) ;;
*) echo "ERROR: --agent must be claude, agy, hermes, cline or grok, got: $AGENT" >&2; exit 2 ;;
claude|agy|hermes|grok) ;;
*) echo "ERROR: --agent must be claude, agy, hermes or grok, got: $AGENT" >&2; exit 2 ;;
esac
[ -n "$ROLE" ] || { echo "ERROR: --role required" >&2; usage; exit 2; }
[ -d "$WORKSPACE" ] || { echo "ERROR: workspace $WORKSPACE not a directory" >&2; exit 1; }
@@ -119,11 +119,6 @@ elif [ "$AGENT" = "hermes" ]; then
echo "ERROR: hermes is not functional. Run 'hermes setup' first." >&2
exit 1
fi
elif [ "$AGENT" = "cline" ]; then
if ! cline history --json >/dev/null 2>&1; then
echo "ERROR: cline is not functional or configured." >&2
exit 1
fi
elif [ "$AGENT" = "grok" ]; then
if [ -f "$HOME/.grok/auth.json" ] || [ -n "$XAI_API_KEY" ]; then
true
@@ -186,7 +181,6 @@ if [ -z "$CMD_FULL" ]; then
claude) CMD_FULL="${RESOLVED_BIN} --dangerously-skip-permissions --session-id ${SESSION_UUID}" ;;
agy) CMD_FULL="${RESOLVED_BIN} --dangerously-skip-permissions" ;;
hermes) CMD_FULL="${RESOLVED_BIN} --yolo --accept-hooks" ;;
cline) CMD_FULL="${RESOLVED_BIN} -i" ;;
esac
fi
@@ -207,10 +201,10 @@ spawn() {
HERDR_SESSION_NAME="$HERDR_SESSION_NAME" _herdr new-session -d -s "$SESSION_NAME" -x 140 -y 40 -c "$WORKSPACE" "$CMD_FULL"
fi
;;
agy|hermes|cline|grok)
agy|hermes|grok)
HERDR_SESSION_NAME="$HERDR_SESSION_NAME" _herdr new-session -d -s "$SESSION_NAME" -x 140 -y 40 -c "$WORKSPACE" "$CMD_FULL"
;;
*) echo "ERROR: --agent must be claude, agy, hermes, cline or grok, got: $AGENT" >&2; exit 2 ;;
*) echo "ERROR: --agent must be claude, agy, hermes or grok, got: $AGENT" >&2; exit 2 ;;
esac
}
@@ -316,7 +310,6 @@ if [ -n "$SUBMIT_JOB_PROMPT" ]; then
case "$AGENT" in
claude) delegate_agent="claude-code" ;;
hermes) delegate_agent="hermes-agent" ;;
cline) delegate_agent="cline-agent" ;;
agy) delegate_agent="antigravity-cli" ;;
grok) delegate_agent="grok-build" ;;
*) echo "ERROR: cannot resolve delegate agent key for '$AGENT'" >&2; exit 2 ;;
@@ -336,7 +329,7 @@ fi
# 모든 값은 환경변수로 전달 — heredoc interpolation 없음 (P1-B).
# 자식 pid 는 bash 에서 pgrep 으로 미리 구함 (P2: 도구명 필터).
CHILD_PID=0
if { [ "$AGENT" = "agy" ] || [ "$AGENT" = "hermes" ] || [ "$AGENT" = "cline" ]; } && [ -n "$PANE_PID" ]; then
if { [ "$AGENT" = "agy" ] || [ "$AGENT" = "hermes" ]; } && [ -n "$PANE_PID" ]; then
CHILD_PID=$(pgrep -P "$PANE_PID" -x "$AGENT" 2>/dev/null | head -1 || true)
CHILD_PID="${CHILD_PID:-0}"
fi
@@ -420,11 +413,6 @@ elif agent == 'hermes':
entry['child_pid'] = int(cp) if cp.isdigit() else 0
entry['hermes_conversation_id_own'] = None
entry['last_visible_status'] = "unverified"
elif agent == 'cline':
cp = os.environ.get('CHILD_PID', '0')
entry['child_pid'] = int(cp) if cp.isdigit() else 0
entry['cline_conversation_id_own'] = None
entry['last_visible_status'] = "unverified"
elif agent == 'grok':
assigned = os.environ.get('SESSION_UUID', '') or None
entry['grok_session_id_own'] = assigned
@@ -1,6 +1,6 @@
# multi-agent-mux-delegate-job 스킬
작업(Job)을 자율 에이전트(claude-code/hermes/agy/cline/codex/opencode/human)에게 위임하고 MQTT
작업(Job)을 자율 에이전트(claude-code/hermes/agy/grok-build/codex/opencode/human)에게 위임하고 MQTT
이벤트 채널로 비동기 관찰하는 범용 에이전트 협업 스킬. **시작점은 [`SKILL.md`](./SKILL.md).**
- 프로토콜/스키마: [`job-protocol.md`](./job-protocol.md)
@@ -1,6 +1,6 @@
---
name: multi-agent-mux-delegate-job
description: "Delegate a unit of work to any autonomous agent (claude-code, hermes, agy, cline, grok-build, codex, or a human) and observe it asynchronously over an MQTT event channel. Supported roles include orchestrator, worker, and reviewer."
description: "Delegate a unit of work to any autonomous agent (claude-code, hermes, agy, grok-build, codex, or a human) and observe it asynchronously over an MQTT event channel. Supported roles include orchestrator, worker, and reviewer."
version: 3.1.0
author: godopu
license: MIT
@@ -17,7 +17,7 @@ metadata:
Delegate a unit of work to any autonomous agent, then **observe** it asynchronously instead of blocking. Every job gets a unique ID and a registry record. The worker agent publishes lifecycle events (`started`, `permission_required`, `progress`, `completed`, `error`) to a per-job MQTT topic, and the delegator/orchestrator subscribes to verify the final state.
This skill allows any agent (`claude-code`, `hermes`, `agy`, `cline`, `grok-build`, etc.) to play any role: **Orchestrator/Delegator**, **Worker/Implementer**, or **Reviewer**.
This skill allows any agent (`claude-code`, `hermes`, `agy`, `grok-build`, etc.) to play any role: **Orchestrator/Delegator**, **Worker/Implementer**, or **Reviewer**.
---
@@ -36,7 +36,7 @@ The `multi-agent-mux-delegate-job` bash wrapper handles job registration, subscr
```bash
# 1) Submit a new job to a targeted agent session (e.g. herdr session name 'demo')
multi-agent-mux-delegate-job submit \
--agent <claude-code|hermes-agent|agy-agent|cline-agent|grok-build|human> \
--agent <claude-code|hermes-agent|agy-agent|grok-build|human> \
--agent-session herdr:<session_name> \
--prompt "Task description or instructions here" \
--role <Worker|Planner|Reviewer> \
@@ -289,7 +289,7 @@ print(','.join(reviewers))
"
}
# Resolve target agent type (claude, cline, agy) via load_state_json.
# Resolve target agent type (claude, hermes, agy) via load_state_json.
resolve_agent_type() {
local name="$1"
NAME="$name" MAM_STATE_JSON="$(load_state_json)" python3 -c "
@@ -307,8 +307,6 @@ if not agent:
segments = name.split('-')
if 'agy' in segments:
agent = 'agy'
elif 'cline' in segments:
agent = 'cline'
elif 'hermes' in segments:
agent = 'hermes'
else:
@@ -467,7 +467,7 @@ def _pin_and_verify_resume(s, agent, cwd, uuid, degraded=False):
else:
s['last_visible_status'] = f"resume dry-run failed: {res.stderr.strip() or res.stdout.strip()}"
id_name = 'session' if agent in ('claude', 'cline') else 'conversation'
id_name = 'session' if agent == 'claude' else 'conversation'
if not degraded:
drifts.append({'class': 'C', 'name': s['name'], 'msg': f"{s['name']}: {id_name} id materialized: {uuid}"})
else:
@@ -518,7 +518,7 @@ if herdr_confirmed:
agent = None
role = 'creator'
for r_name in ('creator', 'planner', 'reviewer'):
for a_name in ('claude', 'agy', 'hermes', 'cline', 'grok'):
for a_name in ('claude', 'agy', 'hermes', 'grok'):
if name.endswith(f"-{r_name}-{a_name}"):
role = r_name
agent = a_name
@@ -537,7 +537,7 @@ if herdr_confirmed:
if tok.startswith('MAM_MANAGED='):
managed_path = tok.split('=', 1)[1]
if os.path.realpath(managed_path) == os.path.realpath(workspace_root):
for a_name in ('claude', 'agy', 'hermes', 'cline', 'grok'):
for a_name in ('claude', 'agy', 'hermes', 'grok'):
if a_name in pm_check.get('cmd', '') or a_name in pm_check.get('cmd_full', ''):
agent = a_name
break
@@ -598,9 +598,6 @@ if herdr_confirmed:
elif agent == 'hermes':
entry['child_pid'] = 0
entry['hermes_conversation_id_own'] = None
elif agent == 'cline':
entry['child_pid'] = 0
entry['cline_conversation_id_own'] = None
d.setdefault('herdr_sessions', []).append(entry)
yaml_session_names.add(name)
drifts.append({'class': 'B', 'name': name,
@@ -611,7 +608,7 @@ def row_agent(s):
return agent_of_row(s)
OWN_KEY_BY_AGENT = {
a: _get_own_key(a) for a in ('claude', 'agy', 'hermes', 'cline', 'grok')
a: _get_own_key(a) for a in ('claude', 'agy', 'hermes', 'grok')
}
# === drift C0: 지정된 ID 는 발견이 아니라 '확인'만 필요하다 ===
@@ -782,51 +779,6 @@ for s in d.get('herdr_sessions', []):
else:
_pin_and_verify_resume(s, 'hermes', cwd, uuid, degraded=True)
# === drift C (cline): cline 새 session id materialize (per-row own id) ===
for s in d.get('herdr_sessions', []):
if row_agent(s) != 'cline':
continue
if s.get('status') != 'running':
continue
if s.get('cline_conversation_id_own'):
continue
cwd = (s.get('pane') or {}).get('cwd', '')
if not cwd:
continue
sessions_dir = f"{home}/.cline/data/sessions"
if not os.path.isdir(sessions_dir):
continue
candidates = []
for session_folder in glob.glob(f"{sessions_dir}/*"):
if os.path.isdir(session_folder):
folder_name = os.path.basename(session_folder)
json_file = f"{session_folder}/{folder_name}.json"
if os.path.exists(json_file):
candidates.append(json_file)
candidates.sort(key=os.path.getmtime, reverse=True)
valid_candidates = []
for j in candidates:
uuid = os.path.basename(j)[:-5]
if verify_session_uuid(cwd, 'cline', uuid, s, mode="discover"):
valid_candidates.append(uuid)
if len(valid_candidates) > 1:
drifts.append({'class': 'C-ambiguous', 'name': s['name'],
'msg': f"{s['name']}: {len(valid_candidates)} candidate transcripts newer than session epoch; not pinning"})
s['last_visible_status'] = f"ambiguous: {len(valid_candidates)} candidates"
actions.append(f"ambiguous candidates: {s['name']}")
if len(valid_candidates) == 1:
uuid = valid_candidates[0]
cmd = ['bash', '-c', f'source "{lib_sh}" && verify_tui_viewport "{s["name"]}" "cline" "{cwd}"']
rc = subprocess.run(cmd).returncode
if rc == 0:
_pin_and_verify_resume(s, 'cline', cwd, uuid, degraded=False)
elif rc == 1:
drifts.append({'class': 'C-warn', 'name': s['name'], 'msg': f"{s['name']}: TUI viewport mismatch for candidate {uuid} — not pinned, will retry next cycle"})
else:
_pin_and_verify_resume(s, 'cline', cwd, uuid, degraded=True)
result = {
'timestamp': now_iso,
'yaml_path': yaml_path,
@@ -8,7 +8,7 @@ platforms: [linux, macos]
environments: [terminal, herdr]
metadata:
hermes:
tags: [agent, herdr, claude, antigravity, agy, cline, hermes, grok, orchestrator, onboard, isolation]
tags: [agent, herdr, claude, antigravity, agy, hermes, grok, orchestrator, onboard, isolation]
related_skills: [multi-agent-mux-create, multi-agent-mux-resume, multi-agent-mux-monitor]
prereq_skills: [multi-agent-mux-create]
---
@@ -24,7 +24,7 @@ Registers an orchestrator session UUID into the `orchestrator_uuids` list of `.m
```
### Options
- `--uuid <uuid>`: Explicitly register the specified orchestrator session UUID (UUID format or cline ID format).
- `--uuid <uuid>`: Explicitly register the specified orchestrator session UUID.
- `--remove <uuid>`: Remove the specified UUID from the `orchestrator_uuids` list.
- `--list`: Display all currently registered orchestrator UUIDs.
- `--no-autodetect`: Disable process ancestry auto-detection when `--uuid` is not provided.
@@ -32,18 +32,16 @@ Registers an orchestrator session UUID into the `orchestrator_uuids` list of `.m
## Auto-Detection Hierarchy (Rev.2)
When `--uuid` is omitted, `orc_onboard.sh` inspects the process ancestry tree of the nearest agent ancestor (`claude`, `agy`, `hermes`, `cline`, `grok`) in the following order:
When `--uuid` is omitted, `orc_onboard.sh` inspects the process ancestry tree of the nearest agent ancestor (`claude`, `agy`, `hermes`, `grok`) in the following order:
1. **CLI `argv`**:
- `claude -r <uuid>` / `claude --session-id <uuid>`
- `agy --conversation <uuid>`
- `cline --id <uuid>` / `cline --session-id <uuid>`
- `grok --session-id <uuid>` / `grok --resume <uuid>`
2. **Family-Matched Environment Variables**:
- `claude``CLAUDE_CODE_SESSION_ID`
- `agy``ANTIGRAVITY_CONVERSATION_ID`
- `hermes``HERMES_SESSION_ID`
- `cline``CLINE_SESSION_ID`
- `grok``GROK_SESSION_ID`
3. **Fallback**:
- If no valid ID matching the nearest agent family is found, exits with status 3 (`Could not detect orchestrator ID`).
@@ -66,8 +66,7 @@ done
is_valid_id() {
local val="$1"
local uuid_re='^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$'
local cline_re='^[0-9]{10,}_[0-9A-Za-z]+$'
if [[ "$val" =~ $uuid_re ]] || [[ "$val" =~ $cline_re ]]; then
if [[ "$val" =~ $uuid_re ]]; then
return 0
fi
return 1
@@ -104,7 +103,7 @@ detect_nearest_agent() {
local base
base="$(basename "$tok")"
case "$base" in
claude|agy|hermes|cline|grok)
claude|agy|hermes|grok)
match="$base"
break
;;
@@ -125,9 +124,6 @@ detect_nearest_agent() {
hermes)
detected_id=$(echo "$cmd_line" | grep -oE '(--resume|--session)[[:space:]=]+[^[:space:]]+' | head -n 1 | sed -E 's/^(--resume|--session)[[:space:]=]+//' || true)
;;
cline)
detected_id=$(echo "$cmd_line" | grep -oE '(--id|--session-id)[[:space:]=]+[^[:space:]]+' | head -n 1 | sed -E 's/^(--id|--session-id)[[:space:]=]+//' || true)
;;
grok)
detected_id=$(echo "$cmd_line" | grep -oE '(--session-id|--resume)[[:space:]=]+[^[:space:]]+' | head -n 1 | sed -E 's/^(--session-id|--resume)[[:space:]=]+//' || true)
;;
@@ -144,7 +140,6 @@ detect_nearest_agent() {
claude) env_var="${CLAUDE_CODE_SESSION_ID:-}" ;;
agy) env_var="${ANTIGRAVITY_CONVERSATION_ID:-}" ;;
hermes) env_var="${HERMES_SESSION_ID:-}" ;;
cline) env_var="${CLINE_SESSION_ID:-}" ;;
grok) env_var="${GROK_SESSION_ID:-}" ;;
esac
@@ -206,7 +201,7 @@ except Exception:
target = sys.argv[1]
for s in d.get("herdr_sessions", []):
if s.get("status") == "running":
for k in ["claude_session_id_own", "agy_conversation_id_own", "hermes_conversation_id_own", "cline_conversation_id_own", "grok_session_id_own"]:
for k in ["claude_session_id_own", "agy_conversation_id_own", "hermes_conversation_id_own", "grok_session_id_own"]:
if s.get(k) == target:
sys.exit(1)
sys.exit(0)
@@ -49,7 +49,7 @@ ideal resume path:
`agent-sessions.yaml` and on-disk discovery are used to resolve the UUID in this order:
1. **`herdr_sessions[]` row's per-row own id** (`claude_session_id_own` / `agy_conversation_id_own` / `hermes_conversation_id_own` / `cline_conversation_id_own` / `grok_session_id_own`) — explicitly saved by `multi-agent-mux-stop` right before teardown (tier-1, race-free).
1. **`herdr_sessions[]` row's per-row own id** (`claude_session_id_own` / `agy_conversation_id_own` / `hermes_conversation_id_own` / `grok_session_id_own`) — explicitly saved by `multi-agent-mux-stop` right before teardown (tier-1, race-free).
2. **Workspace-scoped on-disk scan** (adapter `discover()`)
If both are empty → the workspace has no conversation yet. Fall back to `multi-agent-mux-create`.
@@ -58,7 +58,7 @@ If both are empty → the workspace has no conversation yet. Fall back to `multi
```bash
WORKSPACE=/path/to/project
AGENT=claude # claude | agy | hermes | cline | grok — pass it explicitly
AGENT=claude # claude | agy | hermes | grok — pass it explicitly
SESSION_NAME=<workspace>-creator-<agent> # same convention as multi-agent-mux-create
# Resolve the isolated herdr server name & load common utils
@@ -88,7 +88,6 @@ case "$AGENT" in
claude) CMD_FULL="claude --dangerously-skip-permissions $CLAUDE_ID_FLAG $UUID" ;;
agy) CMD_FULL="agy --dangerously-skip-permissions --conversation $UUID" ;;
hermes) CMD_FULL="hermes --resume $UUID --no-restore-cwd --yolo --accept-hooks" ;;
cline) CMD_FULL="cline -i --id $UUID" ;;
grok) CMD_FULL="grok --resume $UUID --permission-mode bypassPermissions" ;;
esac
@@ -100,7 +99,7 @@ case "$AGENT" in
# auto-handle trust / bypass dialogs
handle_startup_dialogs "$SESSION_NAME" 20
;;
agy|hermes|cline|grok)
agy|hermes|grok)
eval "herdr new-session -d -s \"$SESSION_NAME\" -x 140 -y 40 -c \"$WORKSPACE\" \"$CMD_FULL\""
;;
esac
@@ -1,7 +1,7 @@
#!/usr/bin/env bash
# resolve_session_id.sh — multi-agent-mux-resume 의 부속 스크립트
# Usage:
# bash resolve_session_id.sh --workspace <path> --agent <claude|agy|hermes|cline>
# bash resolve_session_id.sh --workspace <path> --agent <claude|agy|hermes|grok>
# 출력: stdout 으로 UUID 한 줄 (없으면 빈 줄 + exit 0)
#
# P0-C: 전역 agent_identities 를 즉시 반환하지 않는다. lib.sh::find_workspace_uuid
@@ -13,7 +13,7 @@ source "$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)/lib.sh"
usage() {
cat <<EOF
Usage: $0 --workspace <path> --agent <claude|agy|hermes|cline> [--session <name>]
Usage: $0 --workspace <path> --agent <claude|agy|hermes|grok> [--session <name>]
Outputs the resolved UUID on stdout (empty if not found).
--session prefers that registry row's recorded id; falls back to workspace-wide discovery if it does not verify.
EOF
@@ -36,8 +36,8 @@ done
[ -n "$WORKSPACE" ] || { echo "ERROR: --workspace required" >&2; exit 2; }
[ -n "$AGENT" ] || { echo "ERROR: --agent required" >&2; exit 2; }
case "$AGENT" in
claude|agy|hermes|cline|grok) ;;
*) echo "ERROR: --agent must be claude, agy, hermes, cline, or grok" >&2; exit 2 ;;
claude|agy|hermes|grok) ;;
*) echo "ERROR: --agent must be claude, agy, hermes, or grok" >&2; exit 2 ;;
esac
find_workspace_uuid "$WORKSPACE" "$AGENT" "$SESSION_NAME"
@@ -9,7 +9,7 @@ source "$LIB_SH"
usage() {
cat <<EOF
Usage: $0 --workspace <path> --agent <claude|agy|hermes|cline> --session <name> [options]
Usage: $0 --workspace <path> --agent <claude|agy|hermes|grok> --session <name> [options]
Options:
--herdr-session NAME specify isolated herdr session name (alias: --herdr-server)
@@ -42,7 +42,7 @@ done
[ -n "$WORKSPACE" ] || { echo "ERROR: --workspace required" >&2; exit 2; }
[ -n "$AGENT" ] || { echo "ERROR: --agent required" >&2; exit 2; }
case "$AGENT" in
claude|agy|hermes|cline|grok) ;;
claude|agy|hermes|grok) ;;
*) echo "ERROR: unsupported agent: $AGENT" >&2; exit 2 ;;
esac
[ -n "$SESSION_NAME" ] || { echo "ERROR: --session required" >&2; exit 2; }
@@ -86,15 +86,9 @@ fi
# Resolve absolute path of the agent command to prevent herdr PATH inheritance issues (especially on macOS)
RESOLVED_BIN="$AGENT"
if [ "$AGENT" = "cline" ]; then
if command -v cline >/dev/null 2>&1; then
RESOLVED_BIN="$(command -v cline)"
fi
else
if command -v "$AGENT" >/dev/null 2>&1; then
RESOLVED_BIN="$(command -v "$AGENT")"
fi
fi
# On macOS, clear quarantine attribute for the agent binary to prevent Gatekeeper hangs
if [ "$(uname)" = "Darwin" ] && [ -f "$RESOLVED_BIN" ]; then
@@ -108,7 +102,6 @@ if [ -z "$CMD_FULL" ]; then
claude) CMD_FULL="${RESOLVED_BIN} --dangerously-skip-permissions -r $UUID" ;;
agy) CMD_FULL="${RESOLVED_BIN} --dangerously-skip-permissions --conversation $UUID" ;;
hermes) CMD_FULL="${RESOLVED_BIN} --resume $UUID --no-restore-cwd --yolo --accept-hooks" ;;
cline) CMD_FULL="${RESOLVED_BIN} -i --id $UUID" ;;
grok) CMD_FULL="${RESOLVED_BIN} --resume $UUID --permission-mode bypassPermissions" ;;
*) echo "ERROR: unsupported agent: $AGENT" >&2; exit 2 ;;
esac
@@ -4,14 +4,14 @@
# resume UUID 를 per-row own id (claude_session_id_own / agy_conversation_id_own)
# 에 박는다 — agent_identities 전역은 더 이상 primary 아님 (cache 로 강등, P0-C/단계 e).
#
# Usage: bash update_yaml_resumed.sh --session <name> --uuid <id> [--agent claude|agy|hermes|cline]
# Usage: bash update_yaml_resumed.sh --session <name> --uuid <id> [--agent claude|agy|hermes|grok]
set -euo pipefail
source "$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)/lib.sh"
usage() {
cat <<EOF
Usage: $0 --session <name> --uuid <id> [--agent claude|agy|hermes|cline] [--herdr-session <name>]
Usage: $0 --session <name> --uuid <id> [--agent claude|agy|hermes|grok] [--herdr-session <name>]
EOF
}
@@ -85,7 +85,7 @@ NOW_EPOCH=$(date +%s)
PANE_PID=$(_herdr list-panes -t "$SESSION_NAME" -F '#{pane_pid}' 2>/dev/null | head -1 || true)
PANE_PID="${PANE_PID:-}"
CHILD_PID=0
if { [ "$AGENT" = "agy" ] || [ "$AGENT" = "hermes" ] || [ "$AGENT" = "cline" ]; } && [ -n "$PANE_PID" ]; then
if { [ "$AGENT" = "agy" ] || [ "$AGENT" = "hermes" ]; } && [ -n "$PANE_PID" ]; then
CHILD_PID=$(pgrep -P "$PANE_PID" -x "$AGENT" 2>/dev/null | head -1 || true)
CHILD_PID="${CHILD_PID:-0}"
fi
@@ -190,13 +190,6 @@ elif agent == 'hermes':
cp = os.environ.get('CHILD_PID', '0')
if cp.isdigit() and int(cp) > 0:
target['child_pid'] = int(cp)
elif agent == 'cline':
target['pane']['cmd'] = 'cline'
target['pane']['cmd_full'] = f'cline -i --id {uuid}'
target['cline_conversation_id_own'] = uuid
cp = os.environ.get('CHILD_PID', '0')
if cp.isdigit() and int(cp) > 0:
target['child_pid'] = int(cp)
snap = d.setdefault('snapshot', {})
snap['taken_at'] = now
@@ -53,12 +53,12 @@ def resume_on_disk(s):
name = s.get('name', '')
cwd = (s.get('pane') or {}).get('cwd', '')
agent = None
for a in ('claude', 'agy', 'hermes', 'cline', 'grok'):
for a in ('claude', 'agy', 'hermes', 'grok'):
if any(name.endswith(f'-{r}-{a}') for r in ('creator', 'planner', 'reviewer')) or name.endswith(f'-{a}'):
agent = a
break
if not agent:
for a in ('claude', 'agy', 'hermes', 'cline', 'grok'):
for a in ('claude', 'agy', 'hermes', 'grok'):
if f"-{a}" in name or f"_{a}" in name:
agent = a
break
@@ -90,11 +90,6 @@ def resume_on_disk(s):
except Exception:
return 'MISSING'
return 'no'
if agent == 'cline':
u = s.get('cline_conversation_id_own')
if u:
return 'yes' if os.path.exists(f"{home}/.cline/data/sessions/{u}/{u}.json") else 'MISSING'
return 'no'
if agent == 'grok':
u = s.get('grok_session_id_own')
if u:
+1 -1
View File
@@ -37,7 +37,7 @@ The stop command is always **graceful by default**:
```bash
SESSION_NAME=<workspace>-creator-<agent> # convention
AGENT=claude # claude | agy | hermes | cline | grok — always pass it
AGENT=claude # claude | agy | hermes | grok — always pass it
AGENT_SESSIONS_YAML=.mam/agent-sessions.yaml
# 1) Session is registered?
@@ -1,7 +1,7 @@
#!/usr/bin/env bash
# stop_session.sh — multi-agent-mux-stop 의 부속 스크립트
# Usage:
# bash stop_session.sh --session <name> [--agent claude|agy|hermes|cline] [--herdr-session <name>] \
# bash stop_session.sh --session <name> [--agent claude|agy|hermes|grok] [--herdr-session <name>] \
# [--reason <reason>] [--purge-conversation] [--yes]
#
# 동작: 항상 graceful stop 입니다. send-keys 로 정상 종료를 유도하고
@@ -12,7 +12,7 @@
#
# 옵션:
# --session <name> — 대상 세션 (필수)
# --agent <type> — claude | agy | hermes | cline
# --agent <type> — claude | agy | hermes | grok
# (권장: 항상 명시. 미지정 시 레지스트리 기록으로
# 해석 — agent 필드 → 세션명 접미사 → pane.cmd;
# 셋 다 실패하면 exit 2)
@@ -41,12 +41,12 @@ source "$_lib_sh"
usage() {
cat <<EOF
Usage: $0 --session <name> [--agent claude|agy|hermes|cline] [--herdr-session <name>]
Usage: $0 --session <name> [--agent claude|agy|hermes|grok] [--herdr-session <name>]
[--reason <reason>] [--purge-conversation] [--yes]
Arguments:
--session <name> — target session name (required)
--agent <type> — claude | agy | hermes | cline (recommended: always pass it)
--agent <type> — claude | agy | hermes | grok (recommended: always pass it)
(falls back to the registry record: agent field ->
session-name suffix -> pane.cmd)
--herdr-session <name> — specify isolated herdr session name (alias: --herdr-server)
@@ -58,9 +58,6 @@ Arguments:
--purge-conversation — also delete on-disk conversation artifacts;
status becomes terminated and resume is impossible
--yes — skip the --purge-conversation confirmation prompt
Stop is always graceful and always captures the conversation id.
(idempotent: stopping an already-stopped session is a no-op with exit 0)
EOF
}
@@ -94,8 +91,8 @@ while [ $# -gt 0 ]; do
done
if [ -n "$AGENT" ]; then
case "$AGENT" in
claude|agy|hermes|cline|grok) ;;
*) echo "ERROR: invalid agent type '$AGENT'. Allowed types are: claude, agy, hermes, cline, grok." >&2; exit 2 ;;
claude|agy|hermes|grok) ;;
*) echo "ERROR: invalid agent type '$AGENT'. Allowed types are: claude, agy, hermes, grok." >&2; exit 2 ;;
esac
fi
[ -n "$SESSION_NAME" ] || { echo "ERROR: --session required" >&2; usage; exit 2; }
@@ -288,8 +285,6 @@ if captured and not purge:
target['agy_conversation_id_own'] = captured
elif agent == 'hermes':
target['hermes_conversation_id_own'] = captured
elif agent == 'cline':
target['cline_conversation_id_own'] = captured
elif agent == 'grok':
target['grok_session_id_own'] = captured
target['resumable'] = True
+1 -1
View File
@@ -1,6 +1,6 @@
# 🛠️ Multi-Agent Mux (MAM) 설치 및 적용 가이드
MAM은 단일 워크스페이스 상에서 복수의 에이전트(Claude, Cline, Agy, Hermes 등)들이 서로의 상태를 오염시키지 않고 협업할 수 있도록 프로세스 격리 및 라이프사이클 관리를 제공하는 프레임워크입니다.
MAM은 단일 워크스페이스 상에서 복수의 에이전트(Claude, Agy, Hermes, Grok 등)들이 서로의 상태를 오염시키지 않고 협업할 수 있도록 프로세스 격리 및 라이프사이클 관리를 제공하는 프레임워크입니다.
이 가이드는 기존의 다른 프로젝트/레포지토리에 MAM을 신속하게 도입하고 적용하는 절차를 설명합니다.
+5 -8
View File
@@ -36,10 +36,10 @@ MAM은 에이전트별 동작 특성(TUI 프롬프트 패턴, 세션 복원 인
│ │ • spawn_spec(), resume_spec(), auth_ok(), discover() │ │
│ └────────────────────────────────────────────────────────────────┬─────────────────────────────────────────────────────────────────┘ │
│ │ │
│ ┌────────────────────┬──────────────────────────────────────────────────────────────┬─────────────────────┐ │
│ ┌────────────────────┬──────────────────────────────────────────────────────────────┬─────────────────────┐ │
│ ▼ ▼ ▼ ▼ ▼ │
│ ClaudeAgentAdapter AgyAgentAdapter ClineAgentAdapter HermesAgentAdapter [NewAgentAdapter] │
│ (Claude Code) (Antigravity) (Cline) (Hermes) (Grok / Codex / ...) │
│ ClaudeAgentAdapter AgyAgentAdapter HermesAgentAdapter GrokAgentAdapter [NewAgentAdapter] │
│ (Claude Code) (Antigravity) (Hermes) (Grok) (Codex / ...)
└──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
```
@@ -186,7 +186,6 @@ _ADAPTERS: Dict[str, BaseAgentAdapter] = {
'claude': ClaudeAgentAdapter(),
'agy': AgyAgentAdapter(),
'hermes': HermesAgentAdapter(),
'cline': ClineAgentAdapter(),
'grok': GrokAgentAdapter(), # 👈 추가
}
```
@@ -209,13 +208,11 @@ case "$name" in
*-creator-claude|*-planner-claude|*-reviewer-claude) kind="claude" ;;
*-creator-agy|*-planner-agy|*-reviewer-agy) kind="agy" ;;
*-creator-hermes|*-planner-hermes|*-reviewer-hermes) kind="hermes" ;;
*-creator-cline|*-planner-cline|*-reviewer-cline) kind="cline" ;;
*-creator-grok|*-planner-grok|*-reviewer-grok) kind="grok" ;; # 👈 추가
*)
if echo "$name" | grep -qi "claude"; then kind="claude"
elif echo "$name" | grep -qi "agy"; then kind="agy"
elif echo "$name" | grep -qi "hermes"; then kind="hermes"
elif echo "$name" | grep -qi "cline"; then kind="cline"
elif echo "$name" | grep -qi "grok"; then kind="grok" # 👈 추가
else kind="generic"; fi
;;
@@ -225,7 +222,7 @@ esac
#### 2) 바이너리 이름 중복 제거 튜플 (`lib.sh:385`)
`herdr agent start` 호출 시 첫 번째 인자로 전달되는 바이너리 중복을 방지하기 위해 등록합니다:
```bash
if [[ " claude agy hermes cline grok " =~ " ${cmd_binary} " ]]; then
if [[ " claude agy hermes grok " =~ " ${cmd_binary} " ]]; then
```
---
@@ -248,7 +245,7 @@ if [[ " claude agy hermes cline grok " =~ " ${cmd_binary} " ]]; then
def test_agent_adapter_registry():
"""모든 등록된 에이전트 어댑터 인스턴스 검증"""
adapters = get_all_adapters()
assert set(adapters.keys()) == {'claude', 'agy', 'hermes', 'cline', 'grok'}
assert set(adapters.keys()) == {'claude', 'agy', 'hermes', 'grok'}
def test_adapter_required_properties():
+1 -1
View File
@@ -1,7 +1,7 @@
# 🚀 MAM 메시징 백플레인 전환 실행 로드맵 (`implementation_plan.md`)
- **문서 버전**: v1.2.0
- **작성/관리 주체**: Multi-Agent Orchestration Team (`claude`, `agy`, `cline`)
- **작성/관리 주체**: Multi-Agent Orchestration Team (`claude`, `agy`, `hermes`, `grok`)
- **기준 커밋**: `916185c` (306/306 baseline tests passing)
- **문서 목적**: MAM의 메시징 인프라를 공개 HiveMQ 브로커에서 `nats-server` 전용 사설 브로커로 무중단 전환하기 위한 5개 트랙(Track 0~3, Track 1R)과 6단계 마일스톤(M0~M4, M2b)의 구체적 실행 지침 및 진행 상황 추적.
- **연계 문서**: [`NATS_REPORT.md`](nats-docker/NATS_REPORT.md), [`PRIVATE_SERVER.md`](nats-docker/PRIVATE_SERVER.md), [`IMPROVEMENTS.md`](IMPROVEMENTS.md)
+4 -28
View File
@@ -537,8 +537,7 @@ elif cmd1 == "agent":
buffer_content = {
"claude": "Anthropic Claude Ready",
"agy": "Antigravity Ready",
"hermes": "Hermes Ready",
"cline": "Cline Chat Ready"
"hermes": "Hermes Ready"
}.get(agent_type, "Ready")
# Look up cwd from target pane in state if not explicitly passed
@@ -577,8 +576,7 @@ elif cmd1 == "agent":
own_key_map = {
"claude": "claude_session_id_own",
"agy": "agy_conversation_id_own",
"hermes": "hermes_conversation_id_own",
"cline": "cline_conversation_id_own"
"hermes": "hermes_conversation_id_own"
}
own_key = own_key_map.get(agent_type)
if own_key:
@@ -629,15 +627,6 @@ elif cmd1 == "agent":
conn.execute("INSERT OR REPLACE INTO sessions (id, cwd) VALUES (?, ?)", (session_uuid, ws_abs))
conn.commit()
conn.close()
elif agent_type == "cline":
clin_base = os.path.join(home_dir, ".cline", "data", "sessions")
if "agent_homes" in home_dir:
clin_base = os.path.join(home_dir, "sessions")
session_dir = os.path.join(clin_base, session_uuid)
os.makedirs(session_dir, exist_ok=True)
json_file = os.path.join(session_dir, f"{session_uuid}.json")
with open(json_file, 'w') as jf:
jf.write(json.dumps({"id": session_uuid}))
state["agents"] = agents
save_state()
@@ -913,7 +902,7 @@ sys.exit(0)
@pytest.fixture
def mock_agents(mam_sandbox, monkeypatch):
"""
Generates mock agent binaries (claude, agy, hermes, cline)
Generates mock agent binaries (claude, agy, hermes)
and places them in the sandboxed bin folder to satisfy preflight checks.
"""
tmp_path = mam_sandbox
@@ -957,20 +946,7 @@ sys.exit(0)
""")
hermes_bin.chmod(0o755)
# 4. cline mock
cline_bin = bin_dir / "cline"
cline_bin.write_text("""#!/usr/bin/env python3
import sys
import json
args = sys.argv[1:]
if len(args) >= 1 and args[0] == "history":
print(json.dumps([]))
sys.exit(0)
sys.exit(0)
""")
cline_bin.chmod(0o755)
# 5. uuidgen mock to guarantee isolated creation UUIDs
# 4. uuidgen mock to guarantee isolated creation UUIDs
uuidgen_bin = bin_dir / "uuidgen"
uuidgen_bin.write_text("""#!/usr/bin/env python3
import uuid
+13 -41
View File
@@ -30,7 +30,6 @@ def test_agent_adapter_registry():
'claude': 'claude_session_id_own',
'agy': 'agy_conversation_id_own',
'hermes': 'hermes_conversation_id_own',
'cline': 'cline_conversation_id_own',
'grok': 'grok_session_id_own',
}
for agent, expected in EXPECTED_OWN_KEYS.items():
@@ -50,8 +49,8 @@ def test_agent_of_row_priority():
assert agent_of_row(row2) == 'hermes'
# Priority 3: pane.cmd exact match
row3 = {'pane': {'cmd': 'cline'}}
assert agent_of_row(row3) == 'cline'
row3 = {'pane': {'cmd': 'grok'}}
assert agent_of_row(row3) == 'grok'
def test_agent_of_row_pane_cmd_binary_path_and_failure():
# pane.cmd 가 절대 경로 형태여도 해석된다
@@ -73,7 +72,6 @@ def test_adapter_required_properties():
'claude': ('Anthropic|Assistant|Chat|Welcome|Claude Code|Opus|Sonnet|Haiku', '/exit', 'claude-code', ('session_id', 'session_jsonl', 'session_size_bytes', 'session_lines')),
'agy': ('Antigravity', 'Exit', 'antigravity-cli', ('conversation_id', 'conversation_db', 'conversation_brain_dir')),
'hermes': ('Hermes|Welcome to Hermes Agent|NOUS HERMES', '/exit', 'hermes-agent', ('session_id',)),
'cline': ('Cline|history|Chat|What can I do|slash commands', '/exit', 'cline-agent', ('session_id',)),
'grok': ('Grok|xAI|Assistant||>>>', '/exit', 'grok-build', ('session_id', 'session_jsonl')),
}
for agent, (toks, exitk, delk, cache_f) in expected.items():
@@ -83,13 +81,15 @@ def test_adapter_required_properties():
assert adapter.exit_key == exitk
assert adapter.delegate_agent_key == delk
assert adapter.identity_cache_fields == cache_f
# 2-Tier readiness default contract
assert adapter.strong_ready_tokens == adapter.ready_tokens
assert adapter.weak_ready_tokens == ''
def test_adapter_modal_tokens_contract():
"""F-8: Verify adapter-level modal_tokens contract (T-2d)."""
expected_modal = {
'claude': 'Try the new fullscreen renderer\\?',
'cline': 'Select API Provider|Enter API Key|Select an API provider|API Provider|Cline API key',
'agy': None,
'hermes': None,
'grok': None,
@@ -106,7 +106,7 @@ def test_facts_bridge_eval_contract():
env = os.environ.copy()
skills_dir = str(Path(__file__).resolve().parent.parent / ".agents" / "skills")
env["PYTHONPATH"] = f"{skills_dir}:{env.get('PYTHONPATH', '')}"
for agent in ('claude', 'agy', 'hermes', 'cline', 'grok'):
for agent in ('claude', 'agy', 'hermes', 'grok'):
res = subprocess.run([sys.executable, "-m", "lib_py.agents", "facts", agent], capture_output=True, text=True, env=env)
assert res.returncode == 0
facts_output = res.stdout
@@ -115,12 +115,12 @@ def test_facts_bridge_eval_contract():
bash_cmd = f"""
set -euo pipefail
eval {shlex_quote(facts_output)}
echo "AGENT=$MAM_AGENT_NAME|OWN=$MAM_OWN_KEY|TOK=$MAM_READY_TOKENS|EXIT=$MAM_EXIT_KEY|DEL=$MAM_DELEGATE_AGENT_KEY|PH=$MAM_INPUT_PLACEHOLDER"
echo "AGENT=$MAM_AGENT_NAME|OWN=$MAM_OWN_KEY|TOK=$MAM_READY_TOKENS|EXIT=$MAM_EXIT_KEY|DEL=$MAM_DELEGATE_AGENT_KEY|PH=$MAM_INPUT_PLACEHOLDER|STRONG=$MAM_STRONG_READY_TOKENS|WEAK=$MAM_WEAK_READY_TOKENS"
"""
res_bash = subprocess.run(["bash", "-c", bash_cmd], capture_output=True, text=True)
assert res_bash.returncode == 0, f"Bash eval failed for {agent}:\nStdout: {res_bash.stdout}\nStderr: {res_bash.stderr}"
if agent == 'cline':
assert "PH=Ask anything..." in res_bash.stdout
assert f"STRONG={get_adapter(agent).strong_ready_tokens}" in res_bash.stdout
assert "WEAK=" in res_bash.stdout
def shlex_quote(s):
import shlex
@@ -187,18 +187,7 @@ def test_purge_artifacts_composite(tmp_path):
assert conn.execute("SELECT count(*) FROM messages WHERE session_id='uuid-h'").fetchone()[0] == 0
conn.close()
# 4. Cline (sessions dir)
cline_adapter = get_adapter('cline')
cline_ctx = DiscoveryContext(workspace=ws, agent_name='cline', home_dir=home)
cline_dir = f"{home}/.cline/data/sessions/uuid-cl"
os.makedirs(cline_dir, exist_ok=True)
with open(f"{cline_dir}/uuid-cl.json", 'w') as f:
f.write('{"session_id": "uuid-cl"}')
purged_cl = cline_adapter.purge_artifacts('uuid-cl', cline_ctx)
assert len(purged_cl) == 1
assert not os.path.exists(cline_dir)
# 5. Grok (session directory containing chat_history.jsonl)
# 4. Grok (session directory containing chat_history.jsonl)
grok_adapter = get_adapter('grok')
grok_ctx = DiscoveryContext(workspace=ws, agent_name='grok', home_dir=home)
g_path = grok_adapter.artifact_path('uuid-g', grok_ctx)
@@ -227,11 +216,6 @@ def test_adapter_spawn_and_resume_specs():
assert hermes.resume_spec('hermes', 'u1', materialized=True) == 'hermes --resume u1 --no-restore-cwd --yolo --accept-hooks'
assert hermes.resume_spec('hermes', 'u1', materialized=False) == 'hermes --yolo --accept-hooks'
cline = get_adapter('cline')
assert cline.spawn_spec('cline', 'u1') == 'cline -i'
assert cline.resume_spec('cline', 'u1', materialized=True) == 'cline -i --id u1'
assert cline.resume_spec('cline', 'u1', materialized=False) == 'cline -i'
grok = get_adapter('grok')
assert grok.spawn_spec('grok', 'u1') == 'grok --session-id u1 --permission-mode bypassPermissions'
assert grok.spawn_spec('grok', '') == 'grok --permission-mode bypassPermissions'
@@ -264,9 +248,8 @@ def test_adapter_auth_ok(tmp_path, monkeypatch):
grok_auth.write_text("{}")
assert grok.auth_ok() is True
# Hermes & Cline always True
# Hermes always True
assert get_adapter('hermes').auth_ok() is True
assert get_adapter('cline').auth_ok() is True
def test_adapter_discover(tmp_path):
import sqlite3
@@ -314,16 +297,7 @@ def test_adapter_discover(tmp_path):
conn.close()
assert hermes.discover(ctx_h) == ['u-h1']
# 4. Cline
cline = get_adapter('cline')
ctx_cl = DiscoveryContext(workspace=ws, agent_name='cline', home_dir=home)
cl_sess = f"{home}/.cline/data/sessions/u-cl1"
os.makedirs(cl_sess, exist_ok=True)
with open(f"{cl_sess}/u-cl1.json", 'w') as f:
f.write('{"session_id": "u-cl1", "cwd": "' + ws + '"}')
assert cline.discover(ctx_cl) == ['u-cl1']
# 5. Grok
# 4. Grok
grok = get_adapter('grok')
ctx_g = DiscoveryContext(workspace=ws, agent_name='grok', home_dir=home)
g_sess = f"{home}/.grok/sessions/{grok._ws_dir(ctx_g)}/u-g1"
@@ -350,7 +324,7 @@ def test_cli_bridge_subcommands_and_quote_safety():
assert res.stdout.strip() == "/bin/claude --dangerously-skip-permissions --session-id uuid-test"
# 3. exit-key
for agent, expected_key in [('claude', '/exit'), ('agy', 'Exit'), ('hermes', '/exit'), ('cline', '/exit'), ('grok', '/exit')]:
for agent, expected_key in [('claude', '/exit'), ('agy', 'Exit'), ('hermes', '/exit'), ('grok', '/exit')]:
res = subprocess.run([sys.executable, "-m", "lib_py.agents", "exit-key", agent], capture_output=True, text=True, env=env)
assert res.returncode == 0
assert res.stdout.strip() == expected_key
@@ -361,7 +335,6 @@ def test_delegate_agent_resolution_and_fallback():
'claude': 'claude-code',
'agy': 'antigravity-cli',
'hermes': 'hermes-agent',
'cline': 'cline-agent',
'grok': 'grok-build',
}
# 1. Adapter property
@@ -379,7 +352,6 @@ def test_delegate_agent_resolution_and_fallback():
case "$AGENT" in
claude) delegate_agent="claude-code" ;;
hermes) delegate_agent="hermes-agent" ;;
cline) delegate_agent="cline-agent" ;;
agy) delegate_agent="antigravity-cli" ;;
grok) delegate_agent="grok-build" ;;
*) echo "ERROR: cannot resolve delegate agent key for '$AGENT'" >&2; exit 2 ;;
+34 -23
View File
@@ -144,14 +144,31 @@ if _pane_dialog_open dummy; then echo "DIALOG_OPEN"; else echo "DIALOG_CLOSED";
def test_c3_strong_token_and_hint_token_readiness_succeeds():
"""C-3: Strong ready token + hint token together must detect readiness immediately (rc 0)."""
screen = """Cline v3.0.0
screen = """MockApp v1.0.0
Use arrow keys to select a tool
"""
script = f"""
mock_py() {{
if [ "$1" = "-m" ] && [ "$2" = "lib_py.agents" ] && [ "$3" = "facts" ]; then
echo "MAM_AGENT_NAME='mocktiered'"
echo "MAM_OWN_KEY='mock_id_own'"
echo "MAM_INPUT_PROMPT=''"
echo "MAM_INPUT_PLACEHOLDER=''"
echo "MAM_INPUT_RULE_PATTERN=''"
echo "MAM_READY_TOKENS='MockApp|Use arrow keys|Ready'"
echo "MAM_STRONG_READY_TOKENS='MockApp'"
echo "MAM_WEAK_READY_TOKENS='Use arrow keys'"
echo "MAM_MODAL_TOKENS=''"
echo "MAM_EXIT_KEY='/exit'"
echo "MAM_DELEGATE_AGENT_KEY='mock-agent'"
return 0
fi
}}
_delegate_py_bin() {{ echo "mock_py"; }}
_pane_capture() {{ printf '%s' '{screen}'; }}
sleep() {{ :; }}
wait_for_tui_ready dummy-sess cline
wait_for_tui_ready dummy-sess mocktiered
"""
res = _run_lib_helpers(script)
assert res.returncode == 0, res.stderr + res.stdout
@@ -170,7 +187,7 @@ sleep() {{ :; }}
export MAM_STRONG_READY_TOKENS='NonExistentStrongToken'
export MAM_WEAK_READY_TOKENS=''
export MAM_INPUT_PLACEHOLDER='Ask anything...'
wait_for_tui_ready dummy-sess cline
wait_for_tui_ready dummy-sess custom
"""
res = _run_lib_helpers(script)
assert res.returncode == 0, res.stderr + res.stdout
@@ -197,7 +214,7 @@ export MAM_INPUT_PROMPT=''
export MAM_INPUT_RULE_PATTERN='{{10,}}'
_MAM_MODAL_TOKENS='NON_MATCHING_MODAL'
rc=0
wait_for_tui_ready dummy-sess cline || rc=$?
wait_for_tui_ready dummy-sess custom || rc=$?
echo "RC=$rc"
"""
res = _run_lib_helpers(script)
@@ -219,7 +236,7 @@ export MAM_INPUT_PLACEHOLDER='NonExistentPlaceholder'
export MAM_INPUT_PROMPT=''
export MAM_INPUT_RULE_PATTERN='{{10,}}'
rc=0
wait_for_tui_ready dummy-sess cline || rc=$?
wait_for_tui_ready dummy-sess custom || rc=$?
echo "RC=$rc"
"""
res = _run_lib_helpers(script)
@@ -247,7 +264,7 @@ export MAM_INPUT_PLACEHOLDER=''
export MAM_INPUT_PROMPT=''
export MAM_INPUT_RULE_PATTERN=''
rc=0
wait_for_tui_ready dummy-sess cline || rc=$?
wait_for_tui_ready dummy-sess custom || rc=$?
echo "RC=$rc"
"""
res1 = _run_lib_helpers(script1)
@@ -268,7 +285,7 @@ export MAM_INPUT_PLACEHOLDER=''
export MAM_INPUT_PROMPT=''
export MAM_INPUT_RULE_PATTERN=''
rc=0
wait_for_tui_ready dummy-sess cline || rc=$?
wait_for_tui_ready dummy-sess custom || rc=$?
echo "RC=$rc"
"""
res2 = _run_lib_helpers(script2)
@@ -343,7 +360,7 @@ export MAM_INPUT_PLACEHOLDER=''
export MAM_INPUT_PROMPT=''
export MAM_INPUT_RULE_PATTERN=''
rc=0
wait_for_tui_ready dummy-sess cline || rc=$?
wait_for_tui_ready dummy-sess custom || rc=$?
echo "RC=$rc"
"""
res = _run_lib_helpers(script)
@@ -368,29 +385,29 @@ echo "DIALOG=$_MAM_DIALOG_TOKENS"
def test_c8b_agent_modal_tokens_merged_into_dialog_open():
"""C-8b (T-2d / F-8): Per-agent modal_tokens from adapter are merged into _pane_dialog_open and block send_keys_safe."""
# 1. Direct _pane_dialog_open test
screen = """Please enter your Cline API key to continue:
screen = """Try the new fullscreen renderer?
"""
script1 = f"""
_pane_capture() {{ printf '%s' '{screen}'; }}
export MAM_MODAL_TOKENS='Cline API key'
export MAM_MODAL_TOKENS='Try the new fullscreen renderer\\?'
if _pane_dialog_open dummy; then echo "DIALOG_OPEN"; else echo "DIALOG_CLOSED"; fi
"""
res1 = _run_lib_helpers(script1)
assert res1.returncode == 0, res1.stderr + res1.stdout
assert "DIALOG_OPEN" in res1.stdout
# 2. Dynamic facts resolution via send_keys_safe on cline session
# 2. Dynamic facts resolution via send_keys_safe on claude session
script2 = f"""
_pane_quiescent() {{ return 0; }}
_pane_capture() {{ printf '%s' 'Select an API provider to get started:'; }}
_pane_capture() {{ printf '%s' 'Try the new fullscreen renderer? (y/n)'; }}
_sks_herdr() {{ return 0; }}
rc=0
SKS_DIALOG_TIMEOUT=1 send_keys_safe "my-project-worker-cline-01" "some text" || rc=$?
SKS_DIALOG_TIMEOUT=1 send_keys_safe "my-project-worker-claude-01" "some text" || rc=$?
echo "RC=$rc"
"""
res2 = _run_lib_helpers(script2)
assert res2.returncode == 0, res2.stderr + res2.stdout
assert "RC=2" in res2.stdout, f"Expected dialog blocking (RC=2) for cline modal: {res2.stdout}"
assert "RC=2" in res2.stdout, f"Expected dialog blocking (RC=2) for claude modal: {res2.stdout}"
@@ -414,7 +431,7 @@ def test_c9_create_session_timeout_alive_pid_preserves_session(mam_sandbox, mock
cmd = [
"bash", str(script_path),
"--workspace", str(mam_sandbox),
"--agent", "cline",
"--agent", "claude",
"--role", "creator",
"--session", "test-timeout-sess",
"--onboard"
@@ -454,7 +471,7 @@ def test_c10_create_session_timeout_dead_pid_kills_session(mam_sandbox, mock_her
cmd = [
"bash", str(script_path),
"--workspace", str(mam_sandbox),
"--agent", "cline",
"--agent", "claude",
"--role", "creator",
"--session", "test-dead-sess"
]
@@ -471,7 +488,7 @@ def test_c10_create_session_timeout_dead_pid_kills_session(mam_sandbox, mock_her
def test_adapter_strong_weak_partition():
"""N-D: Verify strong_ready_tokens and weak_ready_tokens partition ready_tokens for all adapters."""
from lib_py.agents.registry import get_adapter
for agent in ('claude', 'agy', 'hermes', 'cline', 'grok'):
for agent in ('claude', 'agy', 'hermes', 'grok'):
adapter = get_adapter(agent)
assert adapter is not None
ready_set = set(t for t in adapter.ready_tokens.split('|') if t)
@@ -502,26 +519,20 @@ check_sks() {
send_keys_safe "$sess" "hello" 2>&1 || true
}
echo "1: $(check_sks 'claude-reviewer-cline-01')"
echo "2: $(check_sks 'my-claude-creator-cline')"
echo "3: $(check_sks 'reviewer-creator-grok-01')"
echo "4: $(check_sks 'planner-reviewer-claude-01')"
echo "5: $(check_sks 'test-worker-hermes-02')"
echo "6: $(check_sks 'workspace-creator-agy-05')"
echo "7: $(check_sks 'claude')"
echo "8: $(check_sks 'cline-01')"
echo "9: $(check_sks 'grok-02')"
"""
res = _run_lib_helpers(script)
assert res.returncode == 0, res.stderr + res.stdout
assert "1: RESOLVED_AGENT=cline" in res.stdout
assert "2: RESOLVED_AGENT=cline" in res.stdout
assert "3: RESOLVED_AGENT=grok" in res.stdout
assert "4: RESOLVED_AGENT=claude" in res.stdout
assert "5: RESOLVED_AGENT=hermes" in res.stdout
assert "6: RESOLVED_AGENT=agy" in res.stdout
assert "7: RESOLVED_AGENT=claude" in res.stdout
assert "8: RESOLVED_AGENT=cline" in res.stdout
assert "9: RESOLVED_AGENT=grok" in res.stdout
+11 -1
View File
@@ -23,7 +23,17 @@ def run_lock_cmd(action, marker_path, env=None):
def acquire_bg(marker_path, sleep_secs=10):
cmd = f"source '{LOCK_SCRIPT}' && mam_acquire_loop_lock '{marker_path}' && sleep {sleep_secs}"
proc = subprocess.Popen(["bash", "-c", cmd], stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
time.sleep(0.3)
for _ in range(40):
time.sleep(0.05)
if marker_path.is_file():
try:
content = marker_path.read_text()
if f"pid={proc.pid}" in content:
break
except Exception:
pass
if proc.poll() is not None:
break
return proc
+2 -30
View File
@@ -13,7 +13,7 @@ ONBOARD_SH = REPO_ROOT / ".agents" / "skills" / "multi-agent-mux-orc-onboard" /
RECONCILE_SH = REPO_ROOT / ".agents" / "skills" / "multi-agent-mux-monitor" / "scripts" / "reconcile.sh"
SCRUBBED_ENV = dict(os.environ)
for k in ["CLAUDE_CODE_SESSION_ID", "ANTIGRAVITY_CONVERSATION_ID", "HERMES_SESSION_ID", "CLINE_SESSION_ID", "MAM_ORCHESTRATOR_UUIDS"]:
for k in ["CLAUDE_CODE_SESSION_ID", "ANTIGRAVITY_CONVERSATION_ID", "HERMES_SESSION_ID", "MAM_ORCHESTRATOR_UUIDS"]:
SCRUBBED_ENV.pop(k, None)
SCRUBBED_ENV["PYTHONPATH"] = f"{LIB_SH.parent}:{SCRUBBED_ENV.get('PYTHONPATH', '')}"
@@ -490,25 +490,10 @@ def test_o30_argv_precedes_env(mam_sandbox):
assert env_uuid not in res_list.stdout
# O-31: cline node launcher & non-UUID ID format (1785635248957_fajon)
def test_o31_cline_node_launcher_id_format(mam_sandbox):
cline_id = "1785635248957_fajon"
env = {
"AGENT_SESSIONS_YAML": str(mam_sandbox / ".mam" / "agent-sessions.yaml"),
"HOME_DIR": str(mam_sandbox)
}
cmd = f"exec -a 'cline --id {cline_id}' bash '{ONBOARD_SH}'"
res = subprocess.run(["bash", "-c", cmd], capture_output=True, text=True, env={**SCRUBBED_ENV, **env}, cwd=str(mam_sandbox))
assert res.returncode == 0
res_list = run_onboard(["--list"], env=env, cwd=str(mam_sandbox))
assert cline_id in res_list.stdout
# O-32: ID format strictness rejects invalid strings
def test_o32_id_format_strictness(mam_sandbox):
env = {"AGENT_SESSIONS_YAML": str(mam_sandbox / ".mam" / "agent-sessions.yaml"), "HOME_DIR": str(mam_sandbox)}
for bad in ["../../etc/passwd", "a b", "fajon", "1785635248957"]:
for bad in ["../../etc/passwd", "a b", "fajon", "1785635248957", "1785635248957_fajon"]:
res = run_onboard(["--uuid", bad], env=env, cwd=str(mam_sandbox))
assert res.returncode == 2
@@ -565,19 +550,6 @@ def test_o38_atomic_dump_yaml_initialization(mam_sandbox):
assert orc_uuid in yaml_path.read_text()
# O-39: cline agent family discovery exclusion in verify_session_uuid
def test_o39_cline_orchestrator_exclusion(mam_sandbox):
cline_id = "1785635248957_fajon"
c_dir = mam_sandbox / ".cline" / "data" / "sessions" / cline_id
c_dir.mkdir(parents=True, exist_ok=True)
(c_dir / f"{cline_id}.json").write_text(json.dumps({"session_id": cline_id}))
state = {"orchestrator_uuids": [cline_id], "herdr_sessions": []}
env = dump_state(mam_sandbox, state)
row = {"name": "subagent", "cline_conversation_id_own": None, "pane": {"cwd": str(mam_sandbox)}}
assert not run_verify_uuid(str(mam_sandbox), "cline", cline_id, row=row, mode="discover", env=env)
# O-40: Malformed orchestrator_uuids logs warning to stderr and degrades gracefully
def test_o40_malformed_orchestrator_uuids_degrades_open(mam_sandbox):
sub_uuid = "02222222-2222-2222-2222-222222222222"
+3 -3
View File
@@ -960,18 +960,18 @@ def test_grok_shell_and_scripts_integration(mam_sandbox):
lib_path = mam_sandbox / "skills" / "lib.sh"
lib_content = lib_path.read_text()
assert '*-creator-grok|*-planner-grok|*-reviewer-grok) kind="grok"' in lib_content
assert "'claude', 'agy', 'hermes', 'cline', 'grok'" in lib_content
assert "'claude', 'agy', 'hermes', 'grok'" in lib_content
assert '[[ "$sess" =~ "grok" ]]' in lib_content
# 2. create_session.sh validation
create_path = mam_sandbox / "skills" / "multi-agent-mux-create" / "scripts" / "create_session.sh"
create_content = create_path.read_text()
assert 'claude|agy|hermes|cline|grok)' in create_content
assert 'claude|agy|hermes|grok)' in create_content
# 3. stop_session.sh validation & state capture
stop_path = mam_sandbox / "skills" / "multi-agent-mux-stop" / "scripts" / "stop_session.sh"
stop_content = stop_path.read_text()
assert 'claude|agy|hermes|cline|grok)' in stop_content
assert 'claude|agy|hermes|grok)' in stop_content
assert "target['grok_session_id_own'] = captured" in stop_content
# 4. workspace_uuid OWN_KEY
+2 -2
View File
@@ -140,7 +140,7 @@ def test_comp_create_usage_matches_parser(mam_sandbox, mock_herdr, mock_agents):
assert res.returncode == 0
assert "--herdr-session" in res.stdout
assert "--herdr-server" in res.stdout
for agent in ("claude", "agy", "hermes", "cline"):
for agent in ("claude", "agy", "hermes", "grok"):
assert agent in res.stdout
# Test parser acceptance of valid flags vs unknown arg rejection
@@ -1158,7 +1158,7 @@ def test_comp_stop_usage_matches_parser(mam_sandbox):
assert dead not in res.stdout, f"usage() still advertises {dead}"
# 1b) 검증기가 받는 에이전트는 전부 도움말에 나온다 (Rev.2 M3)
for agent in ("claude", "agy", "hermes", "cline"):
for agent in ("claude", "agy", "hermes", "grok"):
assert agent in res.stdout, f"usage() omits supported agent {agent}"
# 2) 도움말이 광고하는 플래그는 전부 파서가 받는다