chore(release): bump framework and 8 skills to v4.1.1 (PATCH — agent creation & runtime reliability fixes)
This commit is contained in:
@@ -0,0 +1,58 @@
|
||||
# 🛠️ Implementation Plan: Agent-Creation Reliability Fixes (Job `c38ddfcf`)
|
||||
|
||||
- **Author**: `planner-reviewer-claude-01` (Planner)
|
||||
- **Source**: `mam-agent-creation-fix-report.md` (untracked, root of repo) — a field report describing 4 issues observed while creating `reviewer-opencode-01` and other sessions. At the time this plan was written, **none of the 4 fixes it describes were actually present in the code** (verified directly: `.agents/skills/lib_py/agents/adapters/opencode.py`'s `ready_tokens` was still `'OpenCode|Chat'`, and none of the `lib.sh` diffs it shows existed on disk) — the report documents intended fixes, not landed ones.
|
||||
- **Branch**: `fix/agent-creation-issues`
|
||||
- **Status**: Plan executed in this same job — code changed, tests updated/passing, version bumped (see §5).
|
||||
|
||||
---
|
||||
|
||||
## 1. Problem 1 — OpenCode TUI ready-token mismatch
|
||||
|
||||
- **File**: `.agents/skills/lib_py/agents/adapters/opencode.py`
|
||||
- **Root cause**: `ready_tokens` was `'OpenCode|Chat'`, which never appears in OpenCode's actual TUI (the banner renders as an ASCII block, not text matching those words). `wait_for_tui_ready()` (`.agents/skills/lib.sh:1830`) therefore ran the full 30s timeout loop before declaring readiness (or failing outright), even though the process was healthy.
|
||||
- **Fix**: extend `ready_tokens` to include real, observed TUI surface text: `'OpenCode|Chat|Ask anything|tab agents|ctrl\+p|Build auto|commands'`. The `\+` escaping is required because `wait_for_tui_ready` matches this string with `grep -E` (confirmed at `lib.sh:1901`); an unescaped `+` would be interpreted as an ERE quantifier.
|
||||
- **Why safe**: `OpenCodeAgentAdapter` does not override `strong_ready_tokens`/`weak_ready_tokens`, so the base class's default (`strong = ready_tokens`, `weak = ''`) still applies — the 2-tier `S ∨ (W ∧ C)` decision in `wait_for_tui_ready` degrades to plain `S`, matching every other single-tier adapter (`agy`, `hermes`, `grok`).
|
||||
- **Test impact**: `tests/test_a4_adapter_contract.py::test_adapter_required_properties` hardcodes the exact `ready_tokens` string per agent — updated in lockstep. `tests/test_c1_tui_readiness.py::test_adapter_strong_weak_partition` is schema-agnostic (splits on `|` and checks set algebra) — unaffected.
|
||||
|
||||
## 2. Problem 2 — Missing `PYTHONPATH` in the herdr shim
|
||||
|
||||
- **File**: `.agents/skills/lib.sh` (the `<<'EOF'` heredoc that `_init_herdr_isolation()` writes to `$WORKSPACE_ROOT/.mam/shim/herdr`)
|
||||
- **Root cause**: the shim script (once written to disk) directly calls `python3 -m lib_py.layout` (line ~682 in the heredoc) and imports `from lib_py.agents.sanitize import ...` (line ~528). These only resolve if `PYTHONPATH` includes `.agents/skills`. `lib.sh` itself exports `PYTHONPATH` when *sourced* (line 25), but the shim is a **separate executable** invoked directly via `$PATH` by herdr/other processes — it does not always inherit that export (e.g. a freshly spawned pane's shell never sourced `lib.sh`).
|
||||
- **Fix**: added a self-locating `PYTHONPATH` export at the very top of the heredoc, right after `set -euo pipefail`:
|
||||
```bash
|
||||
_SHIM_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
_SKILL_DIR="$(cd "$_SHIM_DIR/../../.agents/skills" 2>/dev/null && pwd || true)"
|
||||
[ -n "$_SKILL_DIR" ] && export PYTHONPATH="$_SKILL_DIR:${PYTHONPATH:-}"
|
||||
```
|
||||
Because the heredoc is single-quoted (`<<'EOF'`), `${BASH_SOURCE[0]}` is **not** expanded at write time — it's copied verbatim and resolves at the shim's own runtime, correctly pointing at wherever `.mam/shim/herdr` actually lives (`$WORKSPACE_ROOT/.mam/shim/../../.agents/skills` = `$WORKSPACE_ROOT/.agents/skills`).
|
||||
- **Test impact**: `tests/test_herdr_shim_contract.py::test_h19_single_resolver_helper_used_by_all_branches` already asserts `bash -n` on both the generated shim and `lib.sh` — passes. No test previously asserted `PYTHONPATH` presence in the shim; none needed updating.
|
||||
|
||||
## 3. Problem 3 — `herdr agent start` timeout treated as unconditional failure
|
||||
|
||||
- **File**: `.agents/skills/lib.sh`, the `agent) start)` case arm (~line 756–end of the W5/W6 backoff loop)
|
||||
- **Root cause**: on a slow TUI render, herdr can return `"timed out waiting for agent startup"`. The existing loop deliberately does **not** treat that string as success (see the pre-existing comment: *"Do NOT treat 'timed out waiting for agent startup' as success: herdr returns that for a dead process too"*) — a correct design constraint I preserved rather than overrode, because herdr genuinely returns identical text for a live-but-slow process and a dead one.
|
||||
- **Fix**: rather than trusting the ambiguous *text*, added a post-loop branch that re-queries herdr directly for ground truth:
|
||||
1. If `_herdr_agent_get_scoped "$agent_name"` now resolves a pane_id (i.e. herdr's own registry shows the agent alive), treat as success.
|
||||
2. Otherwise, fall back to injecting the launch command directly into the pane (`pane send-text` + `pane send-keys ... Enter`, mirroring the existing `paste-buffer` pattern at `lib.sh:1040`), wait 1s, and re-check liveness once more.
|
||||
3. Only if still unresolved does the existing `exit 1` failure path trigger.
|
||||
- **Why this doesn't reintroduce the bug the comment warns about**: the original concern was blindly promoting the *timeout string* to success. This fix never inspects the string as a truth signal for liveness — it always asks herdr's live registry, which is authoritative for both the dead-process and slow-render cases.
|
||||
- **Test impact**: `tests/test_b19_headless_reconcile_fixes.py::test_agent_start_success_tokens_exclude_startup_timeout` (D-1) asserts the *original* success-token grep line still excludes the timeout string — untouched, still passes, since the new logic is a separate branch appended after the loop, not a change to that line.
|
||||
|
||||
## 4. Problem 4 — `_resolve_herdr_pane_id` lacks a pane_id fast-path and mismatches on workspace label
|
||||
|
||||
- **File**: `.agents/skills/lib.sh`, `_resolve_herdr_pane_id()` (inside the shim heredoc)
|
||||
- **Root cause (fast-path)**: every call went through `agent get` + `pane list` resolution even when the caller already had a real pane_id (`wN:pM`) in hand — wasted round-trips, and a pointless second point of failure.
|
||||
- **Root cause (label matching)**: the pane-list-based workspace filter compared `target_ws` directly against each pane's `workspace_id`. If a caller passed a workspace **label** (e.g. `$MAM_WS_LABEL`, such as `"mam-agents"`) instead of the raw id, the filter always failed to match, since panes never carry a label field — only `herdr workspace list` returns `{workspace_id, label}` pairs (confirmed against `tests/conftest.py`'s mock herdr, which is the closest ground truth available for herdr's real JSON schema in this repo).
|
||||
- **Fix**:
|
||||
1. Added a fast-path at the top of the function: if `$target` already matches `^w[A-Za-z0-9]+:p[A-Za-z0-9]+$`, return it immediately.
|
||||
2. Added a `target_ws` normalization step: call `herdr workspace list`, and if `target_ws` isn't already a live `workspace_id`, look it up as a workspace `label` and substitute the resolved id. Falls back permissively to the original value on any lookup failure or no-match (preserves all prior behavior when `target_ws` is already a raw id, which is the common case).
|
||||
- **Why the fallback is safe under `set -e`**: mirrors the exact pattern already used by `_herdr_agent_get_scoped` a few lines above it — the inner Python explicitly `sys.exit(1)` (printing nothing) on any non-match/exception, and the **outer** `|| echo "$target_ws"` supplies the single fallback line. (An earlier draft of this fix had the Python script itself print-then-succeed in the fallback case, which double-emitted the value once `pipefail` propagated the upstream `_real_herdr` failure — corrected before landing, verified via `test_resolve_pane_id_fails_cleanly_under_set_e`.)
|
||||
- **Test impact**: `tests/test_b19_headless_reconcile_fixes.py::test_no_substring_matching_remains_in_lib_sh` (D-6) asserts the pane_id regex literal exists inside the function body — still true (now appears twice: once in the new fast-path, once in the pre-existing final validation). `tests/test_b19_headless_reconcile_fixes.py::test_resolve_pane_id_fails_cleanly_under_set_e` (D-4) and `tests/test_herdr_shim_contract.py::test_h20_pane_id_regex_accepts_alphanumeric_workspace` (H-20) both exercise this function with an all-failing mocked `_real_herdr` — both still pass, confirming the new lookup degrades gracefully rather than aborting the shim under `set -euo pipefail`.
|
||||
|
||||
## 5. Verification & Versioning
|
||||
|
||||
- Targeted tests (`test_a4_adapter_contract.py`, `test_c1_tui_readiness.py`, `test_b19_headless_reconcile_fixes.py`, `test_herdr_shim_contract.py`, `test_version_consistency.py`): **78 passed**.
|
||||
- Full suite (`pytest tests/ -q`): run and confirmed green before the version bump landed (see job report for the exact count/duration).
|
||||
- **SemVer classification**: PATCH (`v4.1.0` → `v4.1.1`). None of the 4 fixes add or change any public CLI flag, YAML schema key, or agent-facing contract — they are internal reliability/robustness fixes to code already shipped in `v4.1.0` (TUI-detection tuning, shim environment robustness, a startup-race fallback, and a pane-resolution robustness improvement). This matches the `v3.0.1` precedent (*"Herdr Shim Routing Contract Refactor, Multi-Workspace Isolation"*) — the closest prior release of the same shape.
|
||||
- 3-way lockstep bump applied: `lib.sh:MAM_VERSION`, `VERSIONS.md` (header + line-24 prose + 8-row matrix + new changelog section), 8× `SKILL.md` frontmatters.
|
||||
+66
-1
@@ -29,7 +29,7 @@ export WORKSPACE_ROOT
|
||||
# Framework semantic version. Single runtime source of truth; kept in lockstep
|
||||
# with VERSIONS.md and the 8 SKILL.md frontmatters by tests/test_version_consistency.py.
|
||||
# NOTE: unlike other MAM_* variables this one is intentionally NOT env-overridable.
|
||||
MAM_VERSION="4.1.0"
|
||||
MAM_VERSION="4.1.1"
|
||||
export MAM_VERSION
|
||||
AGENT_SESSIONS_YAML="${AGENT_SESSIONS_YAML:-$WORKSPACE_ROOT/.mam/agent-sessions.yaml}"
|
||||
|
||||
@@ -154,6 +154,15 @@ _init_herdr_isolation() {
|
||||
# Herdr-to-Herdr translation shim wrapper
|
||||
set -euo pipefail
|
||||
|
||||
# This shim can run as a standalone subprocess (e.g. spawned directly by
|
||||
# herdr) without inheriting the parent shell's PYTHONPATH export from
|
||||
# lib.sh, which breaks the `python3 -m lib_py...` / `from lib_py...` calls
|
||||
# below. Re-derive it from the shim's own on-disk location instead of
|
||||
# assuming inheritance.
|
||||
_SHIM_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
_SKILL_DIR="$(cd "$_SHIM_DIR/../../.agents/skills" 2>/dev/null && pwd || true)"
|
||||
[ -n "$_SKILL_DIR" ] && export PYTHONPATH="$_SKILL_DIR:${PYTHONPATH:-}"
|
||||
|
||||
# Dynamic real herdr path resolution to prevent infinite recursion
|
||||
_resolve_real_herdr() {
|
||||
local dir save_ifs="$IFS" real_path=""
|
||||
@@ -339,7 +348,39 @@ sys.exit(1)
|
||||
# and return 1. Callers MUST wrap with `|| true` (shim runs under set -e).
|
||||
_resolve_herdr_pane_id() {
|
||||
local target="$1"
|
||||
# Fast path: caller already passed a real pane_id (e.g. "w1E:p1") — skip
|
||||
# agent-get/pane-list resolution entirely.
|
||||
if [[ "$target" =~ ^w[A-Za-z0-9]+:p[A-Za-z0-9]+$ ]]; then
|
||||
printf '%s\n' "$target"
|
||||
return 0
|
||||
fi
|
||||
local target_ws="${2:-$(_herdr_ws_scope)}"
|
||||
# A caller may pass a workspace *label* (e.g. $MAM_WS_LABEL) rather than
|
||||
# the raw workspace_id — panes only carry workspace_id, so translate a
|
||||
# label to its id via `herdr workspace list` before filtering. Falls
|
||||
# back to the original value on any failure/no-match (permissive; keeps
|
||||
# prior behavior when $target_ws is already a raw id).
|
||||
if [ -n "$target_ws" ]; then
|
||||
local resolved_ws
|
||||
resolved_ws=$(_real_herdr workspace list 2>/dev/null | TARGET_WS="$target_ws" python3 -c "
|
||||
import sys, json, os
|
||||
tws = os.environ.get('TARGET_WS', '')
|
||||
try:
|
||||
wss = json.load(sys.stdin).get('result', {}).get('workspaces', [])
|
||||
ids = {w.get('workspace_id') for w in wss}
|
||||
if tws in ids:
|
||||
print(tws)
|
||||
sys.exit(0)
|
||||
hit = next((w.get('workspace_id') for w in wss if w.get('label') == tws), '')
|
||||
if hit:
|
||||
print(hit)
|
||||
sys.exit(0)
|
||||
except Exception:
|
||||
pass
|
||||
sys.exit(1)
|
||||
" 2>/dev/null || echo "$target_ws")
|
||||
[ -n "$resolved_ws" ] && target_ws="$resolved_ws"
|
||||
fi
|
||||
local sat pid=""
|
||||
sat=$(_sanitize_herdr_agent_name "$target")
|
||||
|
||||
@@ -780,6 +821,30 @@ except Exception:
|
||||
fi
|
||||
done
|
||||
|
||||
if [ "$success" -ne 1 ]; then
|
||||
# A "timed out waiting for agent startup" result is ambiguous — herdr
|
||||
# returns the identical text whether the process died or the TUI
|
||||
# render was just slow (see the comment above the retry loop). Rather
|
||||
# than trust that text either way, re-query herdr directly: if the
|
||||
# agent is actually registered and alive, treat this as success; if
|
||||
# not, fall back to sending the launch command straight into the
|
||||
# pane before giving up.
|
||||
if echo "$res" | grep -qiE "timed out waiting for agent startup"; then
|
||||
if [ -n "$(_herdr_agent_get_scoped "$agent_name" 2>/dev/null || true)" ]; then
|
||||
success=1
|
||||
else
|
||||
if [ -n "$final_cmd" ]; then
|
||||
_real_herdr pane send-text "$target_pane" "$final_cmd" >/dev/null 2>&1 || true
|
||||
_real_herdr pane send-keys "$target_pane" Enter >/dev/null 2>&1 || true
|
||||
fi
|
||||
sleep 1
|
||||
if [ -n "$(_herdr_agent_get_scoped "$agent_name" 2>/dev/null || true)" ]; then
|
||||
success=1
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ "$success" -ne 1 ]; then
|
||||
echo "$res" >&2
|
||||
exit 1
|
||||
|
||||
@@ -21,7 +21,7 @@ class OpenCodeAgentAdapter(BaseAgentAdapter):
|
||||
|
||||
@property
|
||||
def ready_tokens(self) -> str:
|
||||
return 'OpenCode|Chat'
|
||||
return 'OpenCode|Chat|Ask anything|tab agents|ctrl\\+p|Build auto|commands'
|
||||
|
||||
@property
|
||||
def exit_key(self) -> str:
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
name: multi-agent-mux-create
|
||||
description: "Create a new agent session (claude, antigravity/agy) in a dedicated herdr session for context-preserving long-running work. Always creates a herdr session — never backgrounds with nohup/disown. Writes the new session to .mam/agent-sessions.yaml. Use when you want to start a fresh agent (no prior UUID) for a new project workspace."
|
||||
version: 4.1.0
|
||||
version: 4.1.1
|
||||
author: godopu
|
||||
license: MIT
|
||||
platforms: [linux, macos]
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
name: multi-agent-mux-delegate-job
|
||||
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: 4.1.0
|
||||
version: 4.1.1
|
||||
author: godopu
|
||||
license: MIT
|
||||
platforms: [linux, macos, windows]
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
name: multi-agent-mux-loop
|
||||
description: "Run an autonomous planning-execution-review loop using multiple agents (Planner, Creator, Reviewers) in the workspace. Automatically orchestrates plan discussion, code changes, and peer reviews until a unanimous PASS is achieved or the maximum iteration limit is reached."
|
||||
version: 4.1.0
|
||||
version: 4.1.1
|
||||
author: godopu
|
||||
license: MIT
|
||||
platforms: [linux, macos]
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
name: multi-agent-mux-monitor
|
||||
description: "Run a long-lived reconciler that watches .mam/agent-sessions.yaml against the actual herdr/agent runtime state and reconciles them. Use when you want live visibility into which agent sessions are running, which are dead, which have stale YAML entries, and which have new session ids that haven't been recorded yet. Runs as a persistent loop (`reconcile.sh --subscribe`) that keeps going until it times out, idles out, or is interrupted."
|
||||
version: 4.1.0
|
||||
version: 4.1.1
|
||||
author: godopu
|
||||
license: MIT
|
||||
platforms: [linux, macos]
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
name: multi-agent-mux-orc-onboard
|
||||
description: "Register current or specified orchestrator session UUID into agent-sessions.yaml orchestrator_uuids list to prevent sub-agent discovery capture."
|
||||
version: 4.1.0
|
||||
version: 4.1.1
|
||||
author: godopu
|
||||
license: MIT
|
||||
platforms: [linux, macos]
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
name: multi-agent-mux-resume
|
||||
description: "Resume an existing agent (claude, antigravity/agy) conversation by UUID into a herdr session. Reads .mam/agent-sessions.yaml for the saved session/conversation id, spawns (or reuses) a herdr session of the matching name, and runs `claude -r <id>` or `agy --conversation <id>` inside. Use when you want to reattach to a previous session's context, or revive a session whose herdr died but the agent's conversation is still on disk."
|
||||
version: 4.1.0
|
||||
version: 4.1.1
|
||||
author: godopu
|
||||
license: MIT
|
||||
platforms: [linux, macos]
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
name: multi-agent-mux-status
|
||||
description: "Read-only instant snapshot of all agent herdr sessions — name, YAML status, herdr alive, pane cmd/cwd, resume UUID on disk, and any drift. No mutation. Reuses reconcile.sh --dry-run for the diff logic. Use when you want to know 'what's running RIGHT NOW' without spinning up the monitor loop."
|
||||
version: 4.1.0
|
||||
version: 4.1.1
|
||||
author: godopu
|
||||
license: MIT
|
||||
platforms: [linux, macos]
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
name: multi-agent-mux-stop
|
||||
description: "Stop an agent herdr session (claude, antigravity/agy) and update .mam/agent-sessions.yaml. Default stops gracefully and marks status=stopped with conversation preserved for resume. Does NOT delete on-disk conversation artifacts (jsonl/db) — those are preserved unless --purge-conversation is passed. Use when ending a work session, switching to a different one, or cleaning up before a fresh start."
|
||||
version: 4.1.0
|
||||
version: 4.1.1
|
||||
author: godopu
|
||||
license: MIT
|
||||
platforms: [linux, macos]
|
||||
|
||||
Reference in New Issue
Block a user