Three reviewers examined fc0bd96..8def235. Codex and Claude each reproduced a defect; all three findings were re-verified here before being accepted, and none were rebutted. R1 (codex) — the shell helpers ran `python -m lib_py.delegation.cli`. For -m, sys.path[0] is the working directory, which therefore beats PYTHONPATH: with cwd inside a tree containing lib_py, the live package won even with the frozen path listed first. The documented B-13 guarantee for this path did not hold. Reproduced, then replaced with an explicit bootstrap that inserts SKILL_DIR itself. cwd is left alone, because the registry and audit-log paths are relative to it. The file-based shim path was never affected. R2 (codex) — the persistent connection was reused on host and port alone. Two jobs sharing an endpoint but carrying different credentials or TLS settings silently published over the first connection. Reproduced (the client factory ran once, under the first account, with TLS off). The guard now compares an immutable snapshot of the whole connection identity and names only the differing field, never its value. F-1 (claude) — paho queues a publish issued while disconnected and raises RuntimeError from wait_for_publish. RuntimeError is deliberately outside the retry tuple, so the one state a persistent connection exists to survive, the gap between a drop and paho's reconnect, failed on the first attempt. Confirmed against paho 2.1.0. That single condition is now translated into a retryable error; the tuple is unchanged, so genuine programming errors are still not retried. Two fixtures were hiding these: FakeInfo had no rc and never raised. It now mirrors paho's contract, which is what makes the new tests meaningful. Also folds in the duplication both Claude (O-1) and I had flagged: the CLI path kept its own copy of connect-publish-disconnect plus its own timeout constants. _publish_once now delegates to OneShotPublisher.publish_raw, so a transport fix lands once. Having two copies is exactly why these bugs existed in one of them. Documentation corrected rather than defended: the benchmark's rationale for keeping three forks was imprecise (the || true already guarantees rc 0; the lookup buys quietness and legacy parity), SKILL.md no longer claims PYTHONPATH decides the frozen path, and the daemon's directory creation is recorded as a deliberate difference. Trailing whitespace inherited by the relocated file is stripped, so the commit range is now clean too. Verified: each regression fails against the pre-fix code and passes after (R1 1, R2 4, F-1 1). 66 phase-1 tests pass. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UrDNJkHmm18uu7n7JwV5Qd
139 lines
8.2 KiB
Markdown
139 lines
8.2 KiB
Markdown
---
|
|
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.3.0
|
|
author: godopu
|
|
license: MIT
|
|
platforms: [linux, macos, windows]
|
|
environments: [terminal, herdr]
|
|
metadata:
|
|
hermes:
|
|
tags: [agent, herdr, multi-agent, delegate, mqtt, async, job]
|
|
related_skills: [multi-agent-mux-create, multi-agent-mux-resume, multi-agent-mux-loop]
|
|
prereq_skills: [multi-agent-mux-create]
|
|
---
|
|
|
|
# multi-agent-mux-delegate-job — Async Job Delegation over MQTT
|
|
|
|
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`, `grok-build`, etc.) to play any role: **Orchestrator/Delegator**, **Worker/Implementer**, or **Reviewer**.
|
|
|
|
---
|
|
|
|
## Roles in Multi-Agent Mux
|
|
|
|
- **Orchestrator (Delegator)**: Initiates the job, coordinates other agents, handles loops and reviews, and commits final changes.
|
|
- **Worker (Implementer)**: Receives the brief file or task prompt, performs the implementation, and emits started/completed/error events.
|
|
- **Reviewer**: Evaluates git diffs or artifacts produced by the worker, and responds with a `completed` event containing `"PASS"` or feedback.
|
|
|
|
---
|
|
|
|
## Core Commands (CLI)
|
|
|
|
The `multi-agent-mux-delegate-job` bash wrapper handles job registration, subscriber management, agent session targeting, and validation hooks:
|
|
|
|
```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|grok-build|human> \
|
|
--agent-session herdr:<session_name> \
|
|
--prompt "Task description or instructions here" \
|
|
--role <Worker|Planner|Reviewer> \
|
|
--timeout 3600 --idle-timeout 120
|
|
|
|
# 2) Submit a job with a feedback loop (Worker-Reviewer Loop)
|
|
multi-agent-mux-delegate-job submit \
|
|
--agent <worker_agent> --agent-session herdr:<worker_session> \
|
|
--type loop --reviewer <reviewer_agent> --reviewer-session herdr:<reviewer_session> \
|
|
--prompt "Task description"
|
|
|
|
# 3) Check job status and audit logs
|
|
multi-agent-mux-delegate-job status --job <JOB_ID>
|
|
multi-agent-mux-delegate-job logs <JOB_ID> # Chronological log of events
|
|
multi-agent-mux-delegate-job list # Summary of all registered jobs
|
|
|
|
# 4) Verify job artifacts with a validation script
|
|
multi-agent-mux-delegate-job verify --job <JOB_ID> --validate ./validate.sh
|
|
```
|
|
|
|
---
|
|
|
|
## Task Delegation Types
|
|
|
|
Supported job types include:
|
|
- `direct` (default): Single agent execution (direct tasking).
|
|
- `loop` (Worker-Reviewer Loop): Alternates worker execution and reviewer evaluation until reviewer approves (`PASS`) or iterations run out.
|
|
- `discuss` (Research & Discussion): Collaboration between two agents to reach a consensus (e.g., agreeing on a design or plan).
|
|
|
|
For detailed state machine diagrams and configurations, see [DELEGATION_TYPES.md](./DELEGATION_TYPES.md).
|
|
|
|
---
|
|
|
|
## Where the Implementation Lives (v4.3.0)
|
|
|
|
The implementation moved to the `lib_py.delegation` package in issue #5 phase 1.
|
|
`scripts/{mqtt_common,registry,publish_event,job_subscriber}.py` remain as
|
|
compatibility shims and **their paths are unchanged** -- every command in this
|
|
document still works verbatim. Each shim binds the `lib_py.delegation` package
|
|
to the tree next to itself, once per process, without touching `sys.path`, so
|
|
`import mqtt_common` yields the same module object as
|
|
`lib_py.delegation.mqtt_common`.
|
|
|
|
Which tree wins, by execution context:
|
|
|
|
| Context | Resolved by | Tree that wins |
|
|
|---|---|---|
|
|
| Loop process and its children (frozen `lib.sh` sourced) | the helper's explicit bootstrap from `SKILL_DIR` | the frozen snapshot |
|
|
| Agent session running a **frozen** script path while `PYTHONPATH` still points live | the shim, from its own location | the frozen snapshot (B-13 preserved) |
|
|
| Live helpers (create/stop/resume/reconcile) | `PYTHONPATH` or the shim (same tree) | the live tree |
|
|
| Standalone, no `PYTHONPATH` | the shim's fallback append | the shim's own tree |
|
|
|
|
`lib.sh` calls the package through `_delegation_py`, which inserts its own
|
|
`SKILL_DIR` at the front of the import path. It deliberately does **not** use
|
|
`python -m`: for `-m` the current directory precedes `PYTHONPATH`, so a frozen
|
|
loop whose working directory happened to contain a `lib_py` package would have
|
|
silently executed live code. The working directory is left untouched, because
|
|
the registry and audit-log paths are relative to it.
|
|
|
|
Setting `MAM_DELEGATION_PY=0` switches the shell helpers back to their preserved
|
|
legacy bodies with no redeploy.
|
|
|
|
One in-process caveat: the package binds once per process and is never rebound,
|
|
so do not import a frozen shim after a live `lib_py.delegation` is already bound
|
|
inside the same interpreter. Production never does this — each delegated job is
|
|
a fresh interpreter — but a test harness could.
|
|
|
|
---
|
|
|
|
## The Event Protocol Contract
|
|
|
|
Every agent participating in the delegation contract must follow the same lifecycle publishing protocol using `publish_event.py`:
|
|
|
|
1. **On Start**: Publish `started` event.
|
|
`python3 .agents/skills/multi-agent-mux-delegate-job/scripts/publish_event.py --job "$JOB_ID" --event started`
|
|
2. **On Tool/Permission Prompt**: Publish `permission_required` event.
|
|
`python3 ... --job "$JOB_ID" --event permission_required --detail "<tool>:<reason>"`
|
|
3. **On Progress Update (Optional)**: Publish `progress` event.
|
|
`python3 ... --job "$JOB_ID" --event progress --detail "<status_update>"`
|
|
4. **On Success**: Publish `completed` event.
|
|
`python3 ... --job "$JOB_ID" --event completed --detail "<summary>"` (Reviewer should include `"PASS"` in the detail to approve).
|
|
5. **On Failure/Feedback**: Publish `error` event.
|
|
`python3 ... --job "$JOB_ID" --event error --detail "<reason_or_feedback>"`
|
|
|
|
---
|
|
|
|
## Audit Logs
|
|
|
|
Job lifecycle execution events are persistently mirrored to an append-only log under `.mam/delegate_job_logs/<job_id>/` (containing `meta.json`, `events.ndjson`, and `status.json`). Use `multi-agent-mux-delegate-job logs <job_id>` to view the timeline.
|
|
|
|
---
|
|
|
|
## Best Practices and Pitfalls
|
|
|
|
- **Subscribe-Before-Publish**: The subscriber must be running before the agent starts publishing. The `submit` command handles this automatically by launching the subscriber in the background first.
|
|
- **Fresh job_id Propagation**: Make sure the worker agent receives the correct `JOB_ID` generated for the current run, rather than reusing stale IDs from previous sessions.
|
|
- **Brief delivery via file path**: For long or complex prompts, write the instructions to a file (e.g. `/tmp/task-brief.md`) and pass a short prompt pointing to the file path to prevent terminal buffer overflows.
|
|
- **Prompts injected into a live agent session MUST be English, ASCII-only, and short** — this is exactly what `--prompt`/the `instructions` string sent to `run_agent()` end up as. Any Korean (or other non-ASCII) content the task needs to convey must go in a markdown brief file (e.g. `.mam/jobs/<id>/brief.md`, written in Korean is fine) that the injected prompt merely tells the agent to read. Two independent bugs in `send_keys_safe`'s paste-verification (in `lib.sh`) made this matter in practice: (a) its marker was taken with a byte-based `tail -c 24`, which can slice a multi-byte UTF-8 (e.g. Korean) character in half; (b) the rendered pane soft-wraps long lines at the terminal width, which can split the marker across two visual lines. Both are now fixed at the source (character-safe truncation + newline-stripped matching before comparison), but keeping injected prompts short/English/file-referencing is still the cheapest way to avoid ever exercising this edge case at all — it's also simply what `submit`'s own default instruction template already does (see `Core Commands` above).
|
|
- **Batch Grouping**: Group non-overlapping tasks into batches to parallelize execution across multiple agent sessions, reducing overhead.
|