Splits publish_event.main into three pieces with no behaviour change:
prepare_event load the job, consume the seq, build the signed payload,
decide retain. The seq is burned here, before any transport
runs, so a failed publish can never leave a number a replayed
packet could reuse.
record_outcome audit record, registry event log and status sync -- all three
best effort, all three regardless of the publish result. That
is exactly the contract G-1/G-2/G-4 pin down, now in one place.
main parse, prepare, publish with retry, record.
publisher.py holds the two transports behind one interface. OneShotPublisher is
the current behaviour. PersistentPublisher keeps a single connection and lets
paho reconnect underneath, raising on an unacknowledged publish so the caller's
retry policy stays the only thing deciding attempt counts. It refuses an event
for a different broker rather than publishing to the wrong place.
Nothing in production uses the persistent path: the CLI and shell helpers exit
after one publish. The consumer is the phase 2a engine, which pays the CONNACK
cost on every event today -- this job's own started event took ~12.5s because
of one CONNACK timeout plus reconnect.
bench.py measures both arms. The fake transport is deterministic and is what
the tests assert on; the real-broker arm is informational, since the PoC
default is a public broker whose latency is not ours to control.
Verified: N publishes cost N connections one-shot and 1 persistent; the two
transports leave field-for-field identical registry and audit records including
the HMAC signature and retain flag; an unacknowledged publish retries on the
same connection and records published=false; a second broker is refused. No
test opens a socket.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UrDNJkHmm18uu7n7JwV5Qd
Adds the python side of the delegation helpers and switches lib.sh over to it.
daemon.py port of start_watchdog. Same monitor path, same pgrep pattern, so
a daemon started by either implementation is found by the other;
same stderr wording and exit 1 when reconcile.sh is missing; same
"print the PID" contract, multiple pgrep matches included, since
the shell echoed them all and trimming would be a silent change.
start_new_session detaches it, and SIGHUP is ignored across the
exec so it outlives its parent exactly as nohup did.
cli.py thin router. submit/publish forward to the existing main() of the
module that already owned the behaviour, so exit codes, stdout
shape and side effects cannot drift between the shell path and
the script path. Only ensure-daemon carries logic of its own.
service.py DelegationService facade, with an optional persistent connection
for the phase 2a engine. CLI and shell always use one-shot.
lib.sh keeps the three legacy bodies verbatim as _*_legacy behind
MAM_DELEGATION_PY=0, which is the instant rollback switch. PYTHONPATH is passed
explicitly so `-m` resolves lib_py from the tree this lib.sh belongs to, which
is what makes a frozen loop run frozen delegation code. start_watchdog survives
as an alias for ensure_subscriber_daemon.
The wrapper and run_loop.sh are untouched; reconcile.sh gains only the
ensure_env_loaded line from the previous commit.
Verified: C-01..C-09 pass against both the legacy and the python bodies, the
daemon dedups and survives its parent, the cli reproduces the legacy failure
line exactly, and publish failure semantics (status synced, seq consumed,
audit records published=false) are identical through the shim and the cli.
tier1+tier2 109 passed; bash -n clean on all six shell files.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UrDNJkHmm18uu7n7JwV5Qd
The four delegation scripts become a real package. Their old paths stay as
shims because those paths are a wire contract: agent instructions, the bash
wrapper, SKILL.md, BOOTSTRAP.md, deploy/install.sh and three test modules all
call them literally.
The shim binds the lib_py.delegation *package* to the tree beside it, once per
process, and never touches sys.path. Two cheaper designs were rejected with
evidence (the context matrix now lives in the test suite):
* inserting the sibling skills dir at sys.path[0] makes the first sandbox
copy imported under pytest shadow lib_py.agents/layout/state for the whole
session, and the identity assertion cannot see it -- `is` proves same
object, not same tree.
* loading only the named module by file path leaves a module that imports
siblings pulling them from whichever tree is on the path, so a frozen
publish_event ran against live dependencies and lost half of B-13.
Binding the package fixes both: one tree for all delegation modules, the parent
lib_py untouched, and a sys.path append only when no lib_py exists anywhere
(true standalone). Bind-once rather than nearest-wins, because rebinding would
split existing references and break monkeypatching.
Also moves the env-file load off import (_load_dotenv -> ensure_env_loaded,
called by each process entry point including reconcile.sh's embedded
subscriber), so importing the library no longer mutates os.environ.
Verified: PV-1 identity+provenance, PV-4's ten import scenarios plus K1/K3/K6
in fresh interpreters, PV-5 shim byte-identity, an AST check that the package
imports no sibling lib_py module, and --help output diffed against the
pre-move capture.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UrDNJkHmm18uu7n7JwV5Qd
- Implement CodexAdapter in lib_py/agents/adapters/codex.py with safe flags and TUI readiness
- Extend create_session, resume_session, stop_session, and reconcile for codex CLI
- Support codex agent delegation and loop integration across all multi-agent skills
- Add unit, component, and contract test suites for codex integration
- Verify all 462 tests passing cleanly
- Add --yolo and --accept-hooks headless auto-approval flags to spawn_spec and resume_spec
- Define Hermes TUI input delimiters (input_prompt='❯', input_rule_pattern='─{10,}')
- Refactor verify_artifact and discover in hermes.py using session started_at timestamps
- Fix HERDR_EPOCH capture timing before spawn in create_session.sh to prevent epoch race
- Update reconcile.sh for hermes drift-C multi-candidate and sibling claimed exclusion
- Add Hermes contract, epoch filtering, and C-ambiguous unit tests (140 passed)
- Add agent evaluation report and final review reports for Hermes support
- Separate TUI dialog detection into blocking gate layer (_MAM_MODAL_TOKENS) and recovery layer (_MAM_HINT_TOKENS) to eliminate idle dialog starvation
- Add modal_tokens contract across agent adapters (claude, cline) with dynamic scope facts resolution in send_keys_safe
- Implement fail-closed resolution for ambiguous same-kind panes (R-1) and enforce WORKSPACE_ROOT export (R-2)
- Preserve session on TUI readiness timeout when PID is alive for diagnostics
- Add comprehensive test suite in tests/test_c1_tui_readiness.py and adapter contract assertions in test_a4
- Change N=1->2 split policy from down to right to establish 2 full-height columns
- Share single decision table across GUI and headless modes
- Add _full_height_pane guard to prevent half-height column splits
- Quadruple-wire default max_columns=2 and max_rows=2 across layout.py, lib.sh, and .mam.env.example
- Update unit and integration tests to enforce deterministic 2x2 grid trajectory
- In stop_session.sh and update_yaml_resumed.sh: standardize explicit --agent option and use resolve_agent_type_from_registry to read agent type from YAML/DB state rather than brittle suffix-only regex inference.
- Update multi-agent-mux-stop/SKILL.md, multi-agent-mux-resume/SKILL.md, multi-agent-mux-create/SKILL.md, and deploy/INSTALL.md to standardize passing --agent explicitly.
- Fix J-1 in layout.py: refactor _env_int(*names, default=None) to take an explicit default parameter, eliminating the falsy-zero trap so MAM_MIN_PANE_COLS=0 is respected.
- Add regression and contract tests: test_j1_env_zero_min_cols_matches_flag_zero, test_j1_env_zero_min_rows_matches_flag_zero, test_comp_stop_agent_fallback_*, test_comp_docs_stop_examples_pass_agent.
- Verified 100% UNANIMOUS PASS from Planner claude and Reviewers claude and cline.
- I-2: add 5.0s upper-bound execution time assertion in test_bug4_headless_unobservable_fast_path to contractually guard SKS_EMPTY_GIVEUP early-exit latency
- I-3: clean up PaneInfo.focused, wire MAM_MAX_PANE_COLS/MAM_MAX_COLS environment variables and CLI --max-cols flag
- C-1: apply max_columns growth guard to headless 0x0 layouts, mirroring GUI behavior
- Promoted Planner consensus plan Rev.2 (plan-fea5f1b2.md) and unanimous PASS reports from Reviewers Claude (report-55d1a1d9.md) and Cline (report-6f18ba0f.md)
- Verified all 333 test cases pass with exit code 0
- Remove dead agent_identities read path and PyYAML import from workspace_uuid.py
- Defer eager PyYAML import in verify_session.py to lazy YAML fallback branch
- Simplify UUID resolution to 2-tier model (tier-1 own row ID -> tier-2 adapter scan)
- Clean up unused Drift D and ghost cache clearing in reconcile.sh and stop_session.sh
- Add 3 regression guards in tests/test_tier1_unit.py (266/266 PASS)
- Update IMPROVEMENTS.md, VERSIONS.md, and SKILL.md files
- Option B / C-3b: Deprecate isolation.root and remove its 4 legacy consumers in lib.sh, workspace_uuid.py, verify_session.py, and stop_session.sh.
- M2~M7 Migration: Implement artifact_path, verify_artifact, purge_artifacts, spawn_spec, resume_spec, auth_ok, discover, ready_tokens, exit_key, and delegate_agent_key across BaseAgentAdapter and all 4 adapters (Claude, Agy, Hermes, Cline).
- Migrate shell duplications in lib.sh, create_session.sh, resume_session.sh, stop_session.sh, and reconcile.sh to python -m lib_py.agents facts.
- Hardening & Corrections: Fix Cline resume_spec flag to '-i --id', apply shlex.quote across all facts variables with MAM_ prefix, add safe fallback in wait_for_tui_ready.
- Add comprehensive contract tests in tests/test_a4_adapter_contract.py and sync IMPROVEMENTS.md / LOG.md.
- Verified: 100% PASS across unit, component, contract, and integration tests.
- Replace legacy s[:16]-s[-15:] truncation with 8-char SHA-1 hash suffix in sanitize.py and lib.sh to prevent workspace name collisions
- Align mock Herdr error messages in tests/conftest.py to real Herdr 0.8.0 output format
- Add missing required/invalid_agent_name to early abort regex in lib.sh
- Remove legacy heuristics in lib.sh has-session and unify mock agent lookups
- Add unit tests in tests/test_sanitize_and_mock_errors.py (256/256 passed)