docs(issue-42): stages 1-2 - SKILL.md code-block inventory; remove blocks the scripts already do #46
No files matched your search
@@ -161,7 +161,7 @@ bash .agents/skills/multi-agent-mux-create/scripts/create_session.sh --workspace
|
||||
| `--role` | Required. Assigned role |
|
||||
| `--session` (`--name`) | herdr session name (default: `derive_session_name`) |
|
||||
| `--wrapper` | claude only: use the `~/.local/bin/<session>` wrapper even if it is not present |
|
||||
| `--dry-run` | Print what would be spawned; spawn and write nothing |
|
||||
| `--dry-run` | Print what would be spawned; no spawn and no registry (YAML/DB) update. The checks before it still run, and on macOS they remove the quarantine attribute from the agent binary |
|
||||
| `--herdr-session` (`--herdr-server`) | Isolated herdr server name |
|
||||
| `--herdr-workspace` | herdr workspace label recorded in the registry (a label only, never a socket) |
|
||||
| `--submit-job PROMPT` | Register a delegated job with this prompt and send it to the new agent |
|
||||
@@ -176,7 +176,7 @@ bash .agents/skills/multi-agent-mux-create/scripts/create_session.sh --workspace
|
||||
| 2 | Bad arguments; control-plane binding refused for a worktree; whitespace in a control-plane path that must be forwarded |
|
||||
| 3 | A herdr session of that name already exists (`multi-agent-mux-resume`, or `multi-agent-mux-stop` first) |
|
||||
| 4 | The YAML already has a `running` row of that name. The YAML write raises it after spawn, and the herdr session is rolled back |
|
||||
| 42 | The TUI never became ready and the process is gone |
|
||||
| 42 | The TUI never became ready and the process is gone, or it is alive and `MAM_KILL_ON_READY_TIMEOUT=1` (rolled back) |
|
||||
| 43 | The rollback could not kill the herdr session; it is still running |
|
||||
| other | A later step failed and its code is passed through. The EXIT trap (`cleanup_herdr_on_error`) rolls back the herdr session and closes the onboarding job as `cancelled`; if that kill fails, the exit is 43 instead |
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ Three cases this skill handles:
|
||||
|
||||
1. **herdr is dead, conversation lives** — `agent-sessions.yaml` has the UUID. The JSONL/db is on disk. Re-spawn the herdr session + run `claude -r <id>` / `agy --conversation <id>`.
|
||||
2. **herdr is alive but empty** — You started a session with `multi-agent-mux-create` but haven't sent a message yet (so no session id was assigned). The user can either send their first message (and the id is auto-assigned), or you can read the *workspace's* most recent conversation from `$HOME_DIR/.gemini/antigravity-cli/cache/last_conversations.json` (defaults to `~/.gemini/...`) for agy, or the latest `*.jsonl` in `$CLAUDE_PROJECT_DIR/<workspace-key>/` (defaults to `~/.claude/projects/`) for claude.
|
||||
3. **herdr is alive AND the agent inside is already running** — `resume_session.sh` only updates the row and exits 0. It does not attach and does not check the agent's permission flags (#45); attach with `herdr agent attach <name>`.
|
||||
3. **herdr is alive AND the agent inside is already running** — `resume_session.sh` only updates the row and exits 0. It does not attach and does not check the agent's permission flags (#45); attach with `herdr --session <server> agent attach <name>`, where `<server>` is the row's `herdr_session` (the server it was resumed on).
|
||||
|
||||
### Resuming a `stopped` session (`stopped → running`)
|
||||
|
||||
@@ -60,7 +60,7 @@ If both are empty → for Class A agents (`agy`, `hermes`, `opencode`, `codex`),
|
||||
bash .agents/skills/multi-agent-mux-resume/scripts/resume_session.sh --workspace <path> --agent <agent> --session <name>
|
||||
```
|
||||
|
||||
`resume_session.sh` does the whole flow: original-cwd check, control-plane binding, session-id resolution (`resolve_session_id.sh`), the herdr-alive check, the agent command (adapter `resume-spec`, or `spawn-spec` for a Class A fresh spawn), spawn, start-up dialogs, and the YAML update (`update_yaml_resumed.sh`, which also publishes a `progress "resumed"` event when the row has a `delegate_job_id`). It never attaches; attaching is a separate step: `herdr agent attach <name>`.
|
||||
`resume_session.sh` does the whole flow: original-cwd check, control-plane binding, session-id resolution (`resolve_session_id.sh`), the herdr-alive check, the agent command (adapter `resume-spec`, or `spawn-spec` for a Class A fresh spawn), spawn, start-up dialogs, and the YAML update (`update_yaml_resumed.sh`, which also publishes a `progress "resumed"` event when the row has a `delegate_job_id`). It never attaches; attaching is a separate step that names the server, because the server the script exports does not reach the calling shell and the native `herdr` binary does not route on environment variables: `herdr --session <server> agent attach <name>` (`<server>` = the `--herdr-session` you passed, or the row's `herdr_session`).
|
||||
|
||||
| Option | Meaning |
|
||||
|---|---|
|
||||
@@ -70,7 +70,7 @@ bash .agents/skills/multi-agent-mux-resume/scripts/resume_session.sh --workspace
|
||||
| `--herdr-session` (`--herdr-server`) | Isolated herdr server name |
|
||||
| `--herdr-workspace` | herdr workspace label |
|
||||
| `--auto-trust` / `--no-auto-trust` | Answer a start-up directory-trust dialog or leave it to a human (`MAM_AUTO_TRUST`; default: answer) |
|
||||
| `--dry-run` | Resolve the command and print it; write nothing |
|
||||
| `--dry-run` | Resolve the command and print it; no spawn and no registry (YAML/DB) update. The checks before it still run, and on macOS they remove the quarantine attribute from the agent binary |
|
||||
|
||||
| Exit | Meaning |
|
||||
|---|---|
|
||||
@@ -101,8 +101,12 @@ A session is resumed only in the directory it ran in (its row's `pane.cwd`): the
|
||||
## Verification
|
||||
|
||||
```bash
|
||||
# The server the session runs on (its row's herdr_session); the native herdr
|
||||
# binary needs it as --session, an exported variable is not enough.
|
||||
SERVER=$(python3 -c "import yaml; d = yaml.safe_load(open('.mam/agent-sessions.yaml')); print([s for s in d['herdr_sessions'] if s['name'] == '$SESSION_NAME'][0].get('herdr_session') or 'default')")
|
||||
|
||||
# 1. herdr alive with the right cmd (real native command, no lib.sh needed)
|
||||
herdr agent get "$SESSION_NAME" | python3 -c "
|
||||
herdr --session "$SERVER" agent get "$SESSION_NAME" | python3 -c "
|
||||
import sys, json
|
||||
a = json.load(sys.stdin)['result']['agent']
|
||||
print(f\"cmd={a['agent']} cwd={a['cwd']}\")
|
||||
@@ -119,7 +123,7 @@ print(f' pane.cmd_full: {s[\"pane\"][\"cmd_full\"]}')
|
||||
|
||||
# 3. TUI shows resumed conversation (real native command, no lib.sh needed)
|
||||
sleep 5
|
||||
herdr agent read "$SESSION_NAME" --source visible --lines 30
|
||||
herdr --session "$SERVER" agent read "$SESSION_NAME" --source visible --lines 30
|
||||
# look for the previous message at top of the buffer (claude) or last_visible_status set (agy)
|
||||
```
|
||||
|
||||
@@ -132,4 +136,4 @@ herdr agent read "$SESSION_NAME" --source visible --lines 30
|
||||
|
||||
- **No saved session yet** → `multi-agent-mux-create`
|
||||
- **Killing an existing session** → `multi-agent-mux-stop`
|
||||
- **Just attaching** → `herdr agent attach <name>` (no skill needed)
|
||||
- **Just attaching** → `herdr --session <server> agent attach <name>` (no skill needed; `<server>` = the row's `herdr_session`)
|
||||
@@ -58,7 +58,7 @@ bash .agents/skills/multi-agent-mux-stop/scripts/stop_session.sh \
|
||||
|
||||
| Exit | Meaning |
|
||||
|---|---|
|
||||
| 0 | Stopped; or already `stopped` (no-op) |
|
||||
| 0 | Stopped; or already `stopped` (no-op, except with `--purge-conversation`, which still purges) |
|
||||
| 1 | From the checks before the stop, nothing was changed: YAML not found, or the session is not registered. **The registry update after the stop also exits 1** (the row disappeared from the YAML during the run) |
|
||||
| 2 | Bad arguments (including the retired `--mode` / `--capture-id` / `--graceful`), or `--agent` omitted and not resolvable |
|
||||
| 3 | `--purge-conversation` without `--yes` |
|
||||
|
||||
@@ -339,9 +339,9 @@
|
||||
{
|
||||
"skill": "multi-agent-mux-resume",
|
||||
"start": 103,
|
||||
"end": 124,
|
||||
"end": 128,
|
||||
"lang": "bash",
|
||||
"sha": "f4bddf5ba4d1",
|
||||
"sha": "8a468dfa43bd",
|
||||
"class": "b",
|
||||
"kind": "verification",
|
||||
"evidence": [],
|
||||
@@ -492,7 +492,8 @@
|
||||
],
|
||||
"replacement": "bash .agents/skills/multi-agent-mux-resume/scripts/resume_session.sh --workspace <path> --agent <agent> --session <name>",
|
||||
"script": "multi-agent-mux-resume/scripts/resume_session.sh",
|
||||
"note": "Workflow re-wrote resume_session.sh in bash"
|
||||
"note": "Workflow re-wrote resume_session.sh in bash",
|
||||
"base_fingerprint": "cd4000a4afae"
|
||||
},
|
||||
{
|
||||
"skill": "multi-agent-mux-stop",
|
||||
@@ -511,7 +512,8 @@
|
||||
],
|
||||
"replacement": "bash .agents/skills/multi-agent-mux-stop/scripts/stop_session.sh",
|
||||
"script": "multi-agent-mux-stop/scripts/stop_session.sh",
|
||||
"note": "pre-flight (registered? already stopped?); replaced by one prose line, the Workflow block already calls the entry point"
|
||||
"note": "pre-flight (registered? already stopped?); replaced by one prose line, the Workflow block already calls the entry point",
|
||||
"base_fingerprint": "f2e5380dbaa4"
|
||||
},
|
||||
{
|
||||
"skill": "multi-agent-mux-create",
|
||||
@@ -531,7 +533,8 @@
|
||||
],
|
||||
"replacement": "bash .agents/skills/multi-agent-mux-create/scripts/create_session.sh --workspace <path> --agent <agent> --role <role>",
|
||||
"script": "multi-agent-mux-create/scripts/create_session.sh",
|
||||
"note": "pre-flight; replaced by prose, the has_real_herdr vs command -v pitfall kept as prose"
|
||||
"note": "pre-flight; replaced by prose, the has_real_herdr vs command -v pitfall kept as prose",
|
||||
"base_fingerprint": "6caf3573a587"
|
||||
},
|
||||
{
|
||||
"skill": "multi-agent-mux-create",
|
||||
@@ -554,7 +557,8 @@
|
||||
],
|
||||
"replacement": "bash .agents/skills/multi-agent-mux-create/scripts/create_session.sh --workspace <path> --agent <agent> --role <role>",
|
||||
"script": "multi-agent-mux-create/scripts/create_session.sh",
|
||||
"note": "Workflow re-wrote create_session.sh in bash"
|
||||
"note": "Workflow re-wrote create_session.sh in bash",
|
||||
"base_fingerprint": "bc8eec474b36"
|
||||
},
|
||||
{
|
||||
"skill": "multi-agent-mux-create",
|
||||
@@ -569,7 +573,8 @@
|
||||
"mismatch": [],
|
||||
"replacement": "bash .agents/skills/multi-agent-mux-create/scripts/create_session.sh --workspace <path> --agent <agent> --role <role>",
|
||||
"script": "multi-agent-mux-create/scripts/create_session.sh",
|
||||
"note": "manual \"append this row\" step; the script writes the row. The lib.sh shim note (base 248-251) moved to prose under Output format"
|
||||
"note": "manual \"append this row\" step; the script writes the row. The lib.sh shim note (base 248-251) moved to prose under Output format",
|
||||
"base_fingerprint": "b8ff0badf537"
|
||||
}
|
||||
],
|
||||
"entry_points": {
|
||||
@@ -620,5 +625,19 @@
|
||||
"C6: create_session.sh header lists exit 4 \"YAML append failure\"; there is no literal `exit 4`, but the YAML write raises SystemExit(4) for a duplicate running row (create_session.sh:515) and it passes through, so 4 IS a real exit code (the plan assumed it was not)",
|
||||
"R6: resume's \"The script above re-passes them\" and \"as done in Workflow above\" pointed at the removed block; reworded (same for create's Verification note)",
|
||||
"R7 (review 08e1f1b2): stop's table said exit 1 is always pre-stop; the registry update after the kill chain also raises SystemExit(1) (stop_session.sh:310) when the row disappeared"
|
||||
],
|
||||
"superseded": [
|
||||
{
|
||||
"skill": "multi-agent-mux-create",
|
||||
"base_start": 263,
|
||||
"base_end": 266,
|
||||
"lang": "bash",
|
||||
"base_sha": "ceb96a8c1293",
|
||||
"class": "c",
|
||||
"kind": "invocation",
|
||||
"replacement": "bash .agents/skills/multi-agent-mux-create/scripts/create_session.sh",
|
||||
"note": "The base call example under 'Updating agent-sessions.yaml' (with the nonexistent agent-sessions-yaml-edit script above it, prose_mismatches C4) is folded into the Workflow call block; no information lost.",
|
||||
"base_fingerprint": "b6a8a25279c5"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -4,8 +4,10 @@ tests/data/skill_code_blocks.json lists every fenced block of the eight
|
||||
SKILL.md files with its class - (b) a procedure no script offers yet, (c) an
|
||||
example, call or diagram - and the (a) blocks removed because the entry script
|
||||
already does them. These tests fail when a SKILL.md block changes without the
|
||||
inventory, when an (a) block comes back, or when an exit-code table drifts from
|
||||
the entry script.
|
||||
inventory, when a deleted block's body comes back (by exact sha or by a
|
||||
whitespace-insensitive fingerprint, whatever the inventory says about it), or
|
||||
when an exit-code table drifts from the entry script. A (c) block folded into
|
||||
another call is recorded under "superseded".
|
||||
"""
|
||||
import hashlib
|
||||
import json
|
||||
@@ -127,6 +129,55 @@ def test_no_duplicate_block_left():
|
||||
assert r["class"] == "a", r
|
||||
|
||||
|
||||
def fingerprint(body):
|
||||
"""A deleted body's identity that survives reindenting and reflowing."""
|
||||
return hashlib.sha256(" ".join(body.split()).encode()).hexdigest()[:12]
|
||||
|
||||
|
||||
def _returned_bodies(texts, gone):
|
||||
"""(skill, start, which) for each current block whose body is a deleted one."""
|
||||
hits = []
|
||||
for skill, text in texts.items():
|
||||
for start, _end, _lang, sha, body in parse_blocks(text):
|
||||
for g in gone:
|
||||
if sha == g["base_sha"] or fingerprint(body) == g["base_fingerprint"]:
|
||||
hits.append((skill, start, f"{g['skill']}:{g['base_start']}"))
|
||||
return hits
|
||||
|
||||
|
||||
# 2b. A deleted body stays deleted - even when the inventory is updated to list
|
||||
# it again under another class (review R3).
|
||||
def test_no_deleted_body_came_back():
|
||||
gone = DATA["removed"] + DATA["superseded"]
|
||||
texts = {s: skill_text(s) for s in SKILL_NAMES}
|
||||
assert _returned_bodies(texts, gone) == []
|
||||
|
||||
|
||||
def test_a_returned_body_is_caught_reindented_and_with_the_inventory_updated():
|
||||
"""The guard itself: a current block posing as a deleted one is found by its
|
||||
exact sha and, reindented, by its fingerprint; listing it in the inventory
|
||||
does not hide it (that check never reads DATA["blocks"])."""
|
||||
skill = "multi-agent-mux-stop"
|
||||
start, _end, _lang, sha, body = parse_blocks(skill_text(skill))[0]
|
||||
fake = {"skill": skill, "base_start": 1, "base_sha": sha, "base_fingerprint": fingerprint(body)}
|
||||
assert _returned_bodies({skill: skill_text(skill)}, [fake]) == [(skill, start, f"{skill}:1")]
|
||||
reindented = "\n".join(" " + line for line in body.split("\n"))
|
||||
other = {"skill": skill, "base_start": 1, "base_sha": "0" * 12, "base_fingerprint": fingerprint(reindented)}
|
||||
assert _returned_bodies({skill: skill_text(skill)}, [other]) == [(skill, start, f"{skill}:1")]
|
||||
|
||||
|
||||
# 2c. A deleted (c) block folded into another call is recorded too (review C1):
|
||||
# with it the 37 base blocks are all accounted for.
|
||||
def test_superseded_blocks_are_recorded_and_replaced():
|
||||
assert DATA["superseded"], "the folded create call is recorded"
|
||||
for r in DATA["superseded"]:
|
||||
assert r["class"] == "c" and r["kind"] in KINDS, r
|
||||
bodies = [b[4] for b in parse_blocks(skill_text(r["skill"]))]
|
||||
assert any(r["replacement"] in body for body in bodies), r
|
||||
for r in DATA["removed"] + DATA["superseded"]:
|
||||
assert re.fullmatch(r"[0-9a-f]{12}", r["base_fingerprint"]), r
|
||||
|
||||
|
||||
# 3. A removed block is replaced by the entry-point call.
|
||||
@pytest.mark.parametrize("removed", DATA["removed"], ids=lambda r: f"{r['skill']}:{r['base_start']}")
|
||||
def test_removed_block_replaced_by_entry_call(removed):
|
||||
|
||||
Reference in new issue
Block a user