refactor: rename metadata directory .hermes to .mam in backplane scripts and documents

This commit is contained in:
2026-06-22 14:06:13 +09:00
parent 30e447189e
commit 9735258bc5
22 changed files with 81 additions and 81 deletions
+4 -4
View File
@@ -16,7 +16,7 @@
SKILL_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" SKILL_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
WORKSPACE_ROOT="$(cd "$SKILL_DIR/../.." && pwd)" WORKSPACE_ROOT="$(cd "$SKILL_DIR/../.." && pwd)"
AGENT_SESSIONS_YAML="${AGENT_SESSIONS_YAML:-$WORKSPACE_ROOT/.hermes/agent-sessions.yaml}" AGENT_SESSIONS_YAML="${AGENT_SESSIONS_YAML:-$WORKSPACE_ROOT/.mam/agent-sessions.yaml}"
# Workspace-relative defaults with environment overrides (Phase Z) # Workspace-relative defaults with environment overrides (Phase Z)
HOME_DIR="${HOME_DIR:-$WORKSPACE_ROOT}" HOME_DIR="${HOME_DIR:-$WORKSPACE_ROOT}"
@@ -439,7 +439,7 @@ def db_exists(uuid):
def hermes_exists(uuid): def hermes_exists(uuid):
hdb = f"{home}/.hermes/state.db" hdb = f"{home}/.mam/state.db"
if not os.path.exists(hdb): if not os.path.exists(hdb):
return False return False
try: try:
@@ -529,7 +529,7 @@ elif agent == 'agy':
if cand and db_exists(cand): if cand and db_exists(cand):
emit(cand) emit(cand)
elif agent == 'hermes': elif agent == 'hermes':
hdb = f"{home}/.hermes/state.db" hdb = f"{home}/.mam/state.db"
if os.path.exists(hdb): if os.path.exists(hdb):
cand = None cand = None
try: try:
@@ -724,7 +724,7 @@ start_watchdog() {
local job_id="$1" local job_id="$1"
local workdir="${2:-$PWD}" local workdir="${2:-$PWD}"
local watchdog_script="$workdir/.agents/skills/tmux-agent-orchestrate-monitor/scripts/watchdog.sh" local watchdog_script="$workdir/.agents/skills/tmux-agent-orchestrate-monitor/scripts/watchdog.sh"
local log_file="$workdir/.hermes/jobs/${job_id}.watchdog.log" local log_file="$workdir/.mam/jobs/${job_id}.watchdog.log"
if [ ! -x "$watchdog_script" ]; then if [ ! -x "$watchdog_script" ]; then
echo "ERROR: watchdog not found or not executable: $watchdog_script" >&2 echo "ERROR: watchdog not found or not executable: $watchdog_script" >&2
@@ -1,6 +1,6 @@
--- ---
name: tmux-agent-orchestrate-create name: tmux-agent-orchestrate-create
description: "Create a new agent session (claude, antigravity/agy) in a dedicated tmux session for context-preserving long-running work. Always creates a tmux session — never backgrounds with nohup/disown. Writes the new session to .hermes/agent-sessions.yaml. Use when you want to start a fresh agent (no prior UUID) for a new project workspace." description: "Create a new agent session (claude, antigravity/agy) in a dedicated tmux session for context-preserving long-running work. Always creates a tmux 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: 1.0.0 version: 1.0.0
author: godopu author: godopu
license: MIT license: MIT
@@ -16,7 +16,7 @@ metadata:
# Multi-Agent Create — Start a Fresh Agent in a tmux Session # Multi-Agent Create — Start a Fresh Agent in a tmux Session
> **Companion skills**: `tmux-agent-orchestrate-resume` (resume an existing UUID), `tmux-agent-orchestrate-stop` (terminate), `tmux-agent-orchestrate-monitor` (live status). > **Companion skills**: `tmux-agent-orchestrate-resume` (resume an existing UUID), `tmux-agent-orchestrate-stop` (terminate), `tmux-agent-orchestrate-monitor` (live status).
> **Single source of truth**: `./.hermes/agent-sessions.yaml` (this skill writes to it; never read it ad-hoc — go through this skill). > **Single source of truth**: `./.mam/agent-sessions.yaml` (this skill writes to it; never read it ad-hoc — go through this skill).
## What this skill does ## What this skill does
@@ -137,7 +137,7 @@ TMUX_EPOCH=$(tmux list-sessions -F '#{session_created}' -t "$SESSION_NAME" 2>/de
## Registering the session in agent-sessions.yaml ## Registering the session in agent-sessions.yaml
After spawn, append a new `tmux_sessions[]` entry to `.hermes/agent-sessions.yaml`: After spawn, append a new `tmux_sessions[]` entry to `.mam/agent-sessions.yaml`:
```yaml ```yaml
- name: <SESSION_NAME> - name: <SESSION_NAME>
@@ -200,7 +200,7 @@ tmux list-panes -t "$SESSION_NAME" -F 'cmd=#{pane_current_command} cwd=#{pane_cu
# 3. agent-sessions.yaml has the new entry # 3. agent-sessions.yaml has the new entry
python3 -c " python3 -c "
import yaml import yaml
d = yaml.safe_load(open('.hermes/agent-sessions.yaml')) d = yaml.safe_load(open('.mam/agent-sessions.yaml'))
names = [s['name'] for s in d['tmux_sessions']] names = [s['name'] for s in d['tmux_sessions']]
assert '$SESSION_NAME' in names, 'session not registered' assert '$SESSION_NAME' in names, 'session not registered'
print('OK:', names) print('OK:', names)
@@ -7,5 +7,5 @@
- 브로커 PoC→운영 전환: [`mqtt-broker-setup.md`](./mqtt-broker-setup.md) - 브로커 PoC→운영 전환: [`mqtt-broker-setup.md`](./mqtt-broker-setup.md)
- 레지스트리 포맷/동시성: [`registry.md`](./registry.md) - 레지스트리 포맷/동시성: [`registry.md`](./registry.md)
- 참조 구현: [`tmux-agent-orchestrate-delegate-job`](./tmux-agent-orchestrate-delegate-job) (bash wrapper), [`scripts/publish_event.py`](./scripts/publish_event.py), [`scripts/job_subscriber.py`](./scripts/job_subscriber.py), [`scripts/registry.py`](./scripts/registry.py), [`scripts/mqtt_common.py`](./scripts/mqtt_common.py) - 참조 구현: [`tmux-agent-orchestrate-delegate-job`](./tmux-agent-orchestrate-delegate-job) (bash wrapper), [`scripts/publish_event.py`](./scripts/publish_event.py), [`scripts/job_subscriber.py`](./scripts/job_subscriber.py), [`scripts/registry.py`](./scripts/registry.py), [`scripts/mqtt_common.py`](./scripts/mqtt_common.py)
- 영구 감사 로그: `.hermes/delegate_job_logs/<job_id>/` (`meta.json`·`events.ndjson`·`status.json`) - 영구 감사 로그: `.mam/delegate_job_logs/<job_id>/` (`meta.json`·`events.ndjson`·`status.json`)
`tmux-agent-orchestrate-delegate-job logs <id>` 또는 `tmux-agent-orchestrate-delegate-job logs --list`로 조회 (SKILL.md "Audit Logs" 참조) `tmux-agent-orchestrate-delegate-job logs <id>` 또는 `tmux-agent-orchestrate-delegate-job logs --list`로 조회 (SKILL.md "Audit Logs" 참조)
@@ -27,7 +27,7 @@ canonical concrete instance.
The model is deliberately small. A **job** is one delegated task. An **agent** The model is deliberately small. A **job** is one delegated task. An **agent**
is a worker (a claude-code tmux session, a codex run, a human). The **registry** is a worker (a claude-code tmux session, a codex run, a human). The **registry**
(`.hermes/jobs/<id>.json`) holds everything about a job so nothing important (`.mam/jobs/<id>.json`) holds everything about a job so nothing important
lives in environment variables — which means one tmux session can process many lives in environment variables — which means one tmux session can process many
jobs sequentially, and many sessions can fan out in parallel, with no env jobs sequentially, and many sessions can fan out in parallel, with no env
collisions. The **event channel** is one MQTT topic per job carrying JSON collisions. The **event channel** is one MQTT topic per job carrying JSON
@@ -83,7 +83,7 @@ tmux-agent-orchestrate-delegate-job submit \
# subscriber pid: … # subscriber pid: …
# agent launched in tmux session: demo # agent launched in tmux session: demo
# subscriber output: <one line per event> # subscriber output: <one line per event>
# /path/to/project/.hermes/delegate_job_logs/<JID> ← audit log dir # /path/to/project/.mam/delegate_job_logs/<JID> ← audit log dir
# 2) at any time, query the job or its audit log # 2) at any time, query the job or its audit log
tmux-agent-orchestrate-delegate-job status --job <JID> tmux-agent-orchestrate-delegate-job status --job <JID>
@@ -148,9 +148,9 @@ One topic per job: `python/mqtt/jobs/<job_id>/events`. Payload (JSON, UTF-8,
## Registry Format ## Registry Format
``` ```
.hermes/jobs/<id>.json # metadata record (single source of truth) .mam/jobs/<id>.json # metadata record (single source of truth)
.hermes/jobs/<id>.events.log # append-only JSON-lines log (debug, optional) .mam/jobs/<id>.events.log # append-only JSON-lines log (debug, optional)
.hermes/jobs/.lock # fcntl advisory lock for the registry .mam/jobs/.lock # fcntl advisory lock for the registry
``` ```
The record holds `status`, `prompt`, `agent`, `agent_session`, a `broker` block, The record holds `status`, `prompt`, `agent`, `agent_session`, a `broker` block,
@@ -163,13 +163,13 @@ the atomic rename trick, and multi-session job claiming are in
## Audit Logs ## Audit Logs
Every job's lifecycle is mirrored to a **persistent, append-only audit log** Every job's lifecycle is mirrored to a **persistent, append-only audit log**
under `.hermes/delegate_job_logs/` (override with `DELEGATE_JOB_LOGS_DIR`; under `.mam/delegate_job_logs/` (override with `DELEGATE_JOB_LOGS_DIR`;
default `<cwd>/.hermes/delegate_job_logs`). Unlike the registry — live state default `<cwd>/.mam/delegate_job_logs`). Unlike the registry — live state
mutated in place and liable to be cleaned up — the audit log is durable mutated in place and liable to be cleaned up — the audit log is durable
history you can replay after the fact. It is git-ignored. history you can replay after the fact. It is git-ignored.
``` ```
.hermes/delegate_job_logs/<job_id>/ .mam/delegate_job_logs/<job_id>/
meta.json # registration snapshot: prompt, agent, broker, timeouts, … meta.json # registration snapshot: prompt, agent, broker, timeouts, …
events.ndjson # append-only, one JSON event per line, in time order events.ndjson # append-only, one JSON event per line, in time order
status.json # current status only (fast point-query) status.json # current status only (fast point-query)
@@ -371,7 +371,7 @@ has been verified (2026-06-21, 6-batch refactoring sprint):
- [ ] `publisher.py`/`subscriber.py`/`README.md` demo on `python/mqtt/sample` - [ ] `publisher.py`/`subscriber.py`/`README.md` demo on `python/mqtt/sample`
still works unchanged (regression). still works unchanged (regression).
- [ ] **audit log integrity** — for a completed job, - [ ] **audit log integrity** — for a completed job,
`.hermes/delegate_job_logs/<JID>/events.ndjson` contains `registered` → `.mam/delegate_job_logs/<JID>/events.ndjson` contains `registered` →
`received started` → `published completed` (in that order), and `received started` → `published completed` (in that order), and
`status.json.status == "completed"` matches the registry record. A `status.json.status == "completed"` matches the registry record. A
logging failure (e.g. read-only log dir) does not break the publish or logging failure (e.g. read-only log dir) does not break the publish or
@@ -15,13 +15,13 @@ Reference implementation: [`./scripts/registry.py`](./scripts/registry.py)
## 1. Directory layout ## 1. Directory layout
``` ```
.hermes/jobs/ .mam/jobs/
<job_id>.json # job metadata record (schema below) <job_id>.json # job metadata record (schema below)
<job_id>.events.log # append-only JSON-lines event log (debug, optional) <job_id>.events.log # append-only JSON-lines event log (debug, optional)
.lock # shared advisory lock (fcntl) for the whole registry .lock # shared advisory lock (fcntl) for the whole registry
``` ```
`registry_dir` defaults to `.hermes/jobs` and is overridable everywhere via `registry_dir` defaults to `.mam/jobs` and is overridable everywhere via
`--registry-dir`. `--registry-dir`.
--- ---
@@ -143,13 +143,13 @@ that session.
## 7. Persistent audit log ## 7. Persistent audit log
Separate from the registry, every job is also mirrored to a durable append-only Separate from the registry, every job is also mirrored to a durable append-only
audit log at `.hermes/delegate_job_logs/<job_id>/` (override with audit log at `.mam/delegate_job_logs/<job_id>/` (override with
`DELEGATE_JOB_LOGS_DIR`, default `<cwd>/.hermes/delegate_job_logs`). The registry `DELEGATE_JOB_LOGS_DIR`, default `<cwd>/.mam/delegate_job_logs`). The registry
is **live state** mutated in place; the audit log is **history** that survives is **live state** mutated in place; the audit log is **history** that survives
even after the registry dir is cleaned up. It is git-ignored. even after the registry dir is cleaned up. It is git-ignored.
``` ```
.hermes/delegate_job_logs/<job_id>/ .mam/delegate_job_logs/<job_id>/
meta.json # registration snapshot (the full job record at register time) meta.json # registration snapshot (the full job record at register time)
events.ndjson # append-only, one JSON event per line, time-ordered events.ndjson # append-only, one JSON event per line, time-ordered
status.json # current status only (fast point-query) status.json # current status only (fast point-query)
@@ -71,11 +71,11 @@ _load_dotenv()
# Constants # Constants
# -------------------------------------------------------------------------- # --------------------------------------------------------------------------
SCHEMA_VERSION = 1 SCHEMA_VERSION = 1
DEFAULT_REGISTRY_DIR = ".hermes/jobs" DEFAULT_REGISTRY_DIR = ".mam/jobs"
DEFAULT_TOPIC_ROOT = "python/mqtt/jobs" DEFAULT_TOPIC_ROOT = "python/mqtt/jobs"
LOCK_FILENAME = ".lock" LOCK_FILENAME = ".lock"
# Persistent audit-log layout: .hermes/delegate_job_logs/<job_id>/{meta,events,status}. # Persistent audit-log layout: .mam/delegate_job_logs/<job_id>/{meta,events,status}.
# This is a *separate* artifact from the registry: the registry is the live job # This is a *separate* artifact from the registry: the registry is the live job
# record (mutated in place), the audit log is an append-only history that # record (mutated in place), the audit log is an append-only history that
# survives even if the registry dir is cleaned up. # survives even if the registry dir is cleaned up.
@@ -86,15 +86,15 @@ STATUS_FILENAME = "status.json"
def _default_logs_dir() -> str: def _default_logs_dir() -> str:
"""Audit-log root. Overridable with ``DELEGATE_JOB_LOGS_DIR``; otherwise """Audit-log root. Overridable with ``DELEGATE_JOB_LOGS_DIR``; otherwise
``<cwd>/.hermes/delegate_job_logs`` — we keep audit logs next to the ``<cwd>/.mam/delegate_job_logs`` — we keep audit logs next to the
live registry (``.hermes/jobs/``) so the two runtime artifacts sit live registry (``.mam/jobs/``) so the two runtime artifacts sit
under the same parent dir and follow the same ``.gitignore`` rule. under the same parent dir and follow the same ``.gitignore`` rule.
The cwd of whichever process emits events (the bash wrapper and The cwd of whichever process emits events (the bash wrapper and
scripts) is used as the anchor.""" scripts) is used as the anchor."""
env = os.environ.get("DELEGATE_JOB_LOGS_DIR") env = os.environ.get("DELEGATE_JOB_LOGS_DIR")
if env and env.strip(): if env and env.strip():
return env return env
return os.path.join(os.getcwd(), ".hermes", "delegate_job_logs") return os.path.join(os.getcwd(), ".mam", "delegate_job_logs")
LOGS_DIR = _default_logs_dir() LOGS_DIR = _default_logs_dir()
@@ -376,7 +376,7 @@ def _utcnow_precise() -> str:
# -------------------------------------------------------------------------- # --------------------------------------------------------------------------
# Persistent audit log (.hermes/delegate_job_logs/<job_id>/...) # Persistent audit log (.mam/delegate_job_logs/<job_id>/...)
# #
# Every function here is idempotent, concurrency-safe, and *best-effort*: a # Every function here is idempotent, concurrency-safe, and *best-effort*: a
# logging failure is swallowed with a logger.warning and never propagated, so it # logging failure is swallowed with a logger.warning and never propagated, so it
@@ -222,7 +222,7 @@ def _build_parser() -> argparse.ArgumentParser:
help="summarise every job under the logs dir instead") help="summarise every job under the logs dir instead")
p_logs.add_argument("--logs-dir", default=None, p_logs.add_argument("--logs-dir", default=None,
help="override the audit-log root (default: $DELEGATE_JOB_LOGS_DIR " help="override the audit-log root (default: $DELEGATE_JOB_LOGS_DIR "
"or <cwd>/.hermes/delegate_job_logs)") "or <cwd>/.mam/delegate_job_logs)")
p_logs.add_argument("--tail", type=int, default=0, p_logs.add_argument("--tail", type=int, default=0,
help="show only the last N events (0 = all)") help="show only the last N events (0 = all)")
p_logs.add_argument("--json", action="store_true", p_logs.add_argument("--json", action="store_true",
@@ -37,7 +37,7 @@ pick_python() {
echo "$py_bin" echo "$py_bin"
} }
REGISTRY_DIR_DEFAULT=".hermes/jobs" REGISTRY_DIR_DEFAULT=".mam/jobs"
usage() { usage() {
cat <<'EOF' cat <<'EOF'
@@ -1,6 +1,6 @@
--- ---
name: tmux-agent-orchestrate-monitor name: tmux-agent-orchestrate-monitor
description: "Run a long-lived Kanban worker that polls .hermes/agent-sessions.yaml against the actual tmux/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. Designed to be dispatched as a Kanban goal_mode task (--goal) so it keeps running until the user stops it." description: "Run a long-lived Kanban worker that polls .mam/agent-sessions.yaml against the actual tmux/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. Designed to be dispatched as a Kanban goal_mode task (--goal) so it keeps running until the user stops it."
version: 1.0.0 version: 1.0.0
author: godopu author: godopu
license: MIT license: MIT
@@ -16,7 +16,7 @@ metadata:
# Agent Sessions Monitor — Live Reconciliation via Kanban Worker # Agent Sessions Monitor — Live Reconciliation via Kanban Worker
> **Companion skills**: `tmux-agent-orchestrate-create` / `tmux-agent-orchestrate-resume` / `tmux-agent-orchestrate-stop` (mutators); this skill is the **observer**. > **Companion skills**: `tmux-agent-orchestrate-create` / `tmux-agent-orchestrate-resume` / `tmux-agent-orchestrate-stop` (mutators); this skill is the **observer**.
> **Single source of truth**: `./.hermes/agent-sessions.yaml`. > **Single source of truth**: `./.mam/agent-sessions.yaml`.
## What this skill does ## What this skill does
@@ -68,7 +68,7 @@ hermes kanban create \
--body "$(cat <<'EOF' --body "$(cat <<'EOF'
You are the agent-sessions monitor. Every 30 seconds, do: You are the agent-sessions monitor. Every 30 seconds, do:
1. Read .hermes/agent-sessions.yaml 1. Read .mam/agent-sessions.yaml
2. Run `tmux ls` and `tmux list-panes -F 'session=#{session_name} pid=#{pane_pid} cmd=#{pane_current_command} cwd=#{pane_current_path}'` 2. Run `tmux ls` and `tmux list-panes -F 'session=#{session_name} pid=#{pane_pid} cmd=#{pane_current_command} cwd=#{pane_current_path}'`
3. For each session in the YAML, check the corresponding tmux state 3. For each session in the YAML, check the corresponding tmux state
4. For each tmux session matching `*-creator-claude` or `*-creator-agy` that's not in the YAML, register it 4. For each tmux session matching `*-creator-claude` or `*-creator-agy` that's not in the YAML, register it
@@ -12,7 +12,7 @@ fi
JOB_ID="$1" JOB_ID="$1"
WORKDIR="$2" WORKDIR="$2"
LOG_DIR="$WORKDIR/.hermes/jobs" LOG_DIR="$WORKDIR/.mam/jobs"
mkdir -p "$LOG_DIR" mkdir -p "$LOG_DIR"
@@ -49,7 +49,7 @@ except Exception:
( (
cd "$WORKDIR" && timeout 120 .venv/bin/python .agents/skills/tmux-agent-orchestrate-delegate-job/scripts/job_subscriber.py \ cd "$WORKDIR" && timeout 120 .venv/bin/python .agents/skills/tmux-agent-orchestrate-delegate-job/scripts/job_subscriber.py \
--job "$JOB_ID" --timeout 120 --idle-timeout 999999 --registry-dir .hermes/jobs > "$LOG_FILE" 2>&1 --job "$JOB_ID" --timeout 120 --idle-timeout 999999 --registry-dir .mam/jobs > "$LOG_FILE" 2>&1
echo "[$(date -u +'%Y-%m-%dT%H:%M:%SZ')] subscriber exited" >> "$LOG_FILE" echo "[$(date -u +'%Y-%m-%dT%H:%M:%SZ')] subscriber exited" >> "$LOG_FILE"
) & ) &
@@ -1,6 +1,6 @@
--- ---
name: tmux-agent-orchestrate-resume name: tmux-agent-orchestrate-resume
description: "Resume an existing agent (claude, antigravity/agy) conversation by UUID into a tmux session. Reads .hermes/agent-sessions.yaml for the saved session/conversation id, spawns (or reuses) a tmux 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 tmux died but the agent's conversation is still on disk." description: "Resume an existing agent (claude, antigravity/agy) conversation by UUID into a tmux session. Reads .mam/agent-sessions.yaml for the saved session/conversation id, spawns (or reuses) a tmux 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 tmux died but the agent's conversation is still on disk."
version: 1.0.0 version: 1.0.0
author: godopu author: godopu
license: MIT license: MIT
@@ -17,7 +17,7 @@ metadata:
> **Companion skills**: `tmux-agent-orchestrate-create` (start a fresh agent), `tmux-agent-orchestrate-stop` (terminate), `tmux-agent-orchestrate-monitor` (live status). > **Companion skills**: `tmux-agent-orchestrate-create` (start a fresh agent), `tmux-agent-orchestrate-stop` (terminate), `tmux-agent-orchestrate-monitor` (live status).
> **Tmux Isolation**: `TMUX_SERVER_NAME` env var를 create에서 설정한 경우, 동일 서버에서 동작합니다. 자세한 격리 패턴은 [tmux-agent-orchestrate-create/SKILL.md](../tmux-agent-orchestrate-create/SKILL.md) 참조. > **Tmux Isolation**: `TMUX_SERVER_NAME` env var를 create에서 설정한 경우, 동일 서버에서 동작합니다. 자세한 격리 패턴은 [tmux-agent-orchestrate-create/SKILL.md](../tmux-agent-orchestrate-create/SKILL.md) 참조.
> **Single source of truth**: `./.hermes/agent-sessions.yaml`. > **Single source of truth**: `./.mam/agent-sessions.yaml`.
## What this skill does ## What this skill does
@@ -132,7 +132,7 @@ tmux list-panes -t "$SESSION_NAME" -F 'cmd=#{pane_current_command} cwd=#{pane_cu
# 2. agent-sessions.yaml updated # 2. agent-sessions.yaml updated
python3 -c " python3 -c "
import yaml import yaml
d = yaml.safe_load(open('.hermes/agent-sessions.yaml')) d = yaml.safe_load(open('.mam/agent-sessions.yaml'))
s = [s for s in d['tmux_sessions'] if s['name'] == '$SESSION_NAME'][0] s = [s for s in d['tmux_sessions'] if s['name'] == '$SESSION_NAME'][0]
print(f' status: {s[\"status\"]}') print(f' status: {s[\"status\"]}')
print(f' pane.cmd_full: {s[\"pane\"][\"cmd_full\"]}') print(f' pane.cmd_full: {s[\"pane\"][\"cmd_full\"]}')
@@ -17,7 +17,7 @@ metadata:
> **Companion skills**: `tmux-agent-orchestrate-create` (start), `tmux-agent-orchestrate-resume` (re-attach), `tmux-agent-orchestrate-stop` (terminate), `tmux-agent-orchestrate-monitor` (live polling). > **Companion skills**: `tmux-agent-orchestrate-create` (start), `tmux-agent-orchestrate-resume` (re-attach), `tmux-agent-orchestrate-stop` (terminate), `tmux-agent-orchestrate-monitor` (live polling).
> **Tmux Isolation**: `status` 명령은 YAML에 등록된 모든 세션의 격리 서버(`tmux_server` 필드)를 자동으로 조회하여 상태를 확인하므로, `TMUX_SERVER_NAME` 환경변수를 수동으로 지정하지 않아도 모든 격리 서버의 세션 상태를 통합 조회합니다. > **Tmux Isolation**: `status` 명령은 YAML에 등록된 모든 세션의 격리 서버(`tmux_server` 필드)를 자동으로 조회하여 상태를 확인하므로, `TMUX_SERVER_NAME` 환경변수를 수동으로 지정하지 않아도 모든 격리 서버의 세션 상태를 통합 조회합니다.
> **Single source of truth**: `./.hermes/agent-sessions.yaml`. > **Single source of truth**: `./.mam/agent-sessions.yaml`.
## What this skill does ## What this skill does
@@ -30,7 +30,7 @@ This is the "what's running right now?" answer — faster than dispatching `tmux
```bash ```bash
command -v tmux command -v tmux
command -v python3 command -v python3
test -f .hermes/agent-sessions.yaml test -f .mam/agent-sessions.yaml
``` ```
If `agent-sessions.yaml` doesn't exist or is malformed → print clear error, exit 1. **Do not create it.** (Use `tmux-agent-orchestrate-create` first.) If `agent-sessions.yaml` doesn't exist or is malformed → print clear error, exit 1. **Do not create it.** (Use `tmux-agent-orchestrate-create` first.)
@@ -24,7 +24,7 @@ if [ "$JSON" = "1" ]; then
exit 0 exit 0
fi fi
# Project root (parent of .agents/) holds the tmux-agent-orchestrate-delegate-job .hermes registry. # Project root (parent of .agents/) holds the tmux-agent-orchestrate-delegate-job .mam registry.
# Resolved relative to this script — no hardcoded absolute path (review item 6). # Resolved relative to this script — no hardcoded absolute path (review item 6).
PROJECT_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../../../../" && pwd)" PROJECT_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../../../../" && pwd)"
@@ -95,9 +95,9 @@ def get_job_status(s):
# Candidate locations (review item 6: project-root-relative, no hardcoded abs paths): # Candidate locations (review item 6: project-root-relative, no hardcoded abs paths):
# 1) cwd-relative registry 2) project-root registry 3) project-root audit log # 1) cwd-relative registry 2) project-root registry 3) project-root audit log
candidates = [ candidates = [
os.path.join('.hermes', 'jobs', f"{jid}.json"), os.path.join('.mam', 'jobs', f"{jid}.json"),
os.path.join(project_root, '.hermes', 'jobs', f"{jid}.json"), os.path.join(project_root, '.mam', 'jobs', f"{jid}.json"),
os.path.join(project_root, '.hermes', 'delegate_job_logs', jid, 'status.json'), os.path.join(project_root, '.mam', 'delegate_job_logs', jid, 'status.json'),
] ]
for path in candidates: for path in candidates:
if os.path.exists(path): if os.path.exists(path):
@@ -1,6 +1,6 @@
--- ---
name: tmux-agent-orchestrate-stop name: tmux-agent-orchestrate-stop
description: "Stop an agent tmux session (claude, antigravity/agy) and update .hermes/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." description: "Stop an agent tmux 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: 1.0.0 version: 1.0.0
author: godopu author: godopu
license: MIT license: MIT
@@ -17,7 +17,7 @@ metadata:
> **Companion skills**: `tmux-agent-orchestrate-create` (start), `tmux-agent-orchestrate-resume` (re-attach), `tmux-agent-orchestrate-monitor` (live status). > **Companion skills**: `tmux-agent-orchestrate-create` (start), `tmux-agent-orchestrate-resume` (re-attach), `tmux-agent-orchestrate-monitor` (live status).
> **Tmux Isolation**: `stop` 명령은 YAML의 `tmux_server` 필드를 자동으로 파싱하여 해당 격리 서버의 세션을 안전하게 종료(kill)하므로, `TMUX_SERVER_NAME` 환경변수를 수동으로 지정할 필요가 없습니다. > **Tmux Isolation**: `stop` 명령은 YAML의 `tmux_server` 필드를 자동으로 파싱하여 해당 격리 서버의 세션을 안전하게 종료(kill)하므로, `TMUX_SERVER_NAME` 환경변수를 수동으로 지정할 필요가 없습니다.
> **Single source of truth**: `./.hermes/agent-sessions.yaml`. > **Single source of truth**: `./.mam/agent-sessions.yaml`.
## What this skill does ## What this skill does
@@ -37,7 +37,7 @@ The stop command is always **graceful by default**:
```bash ```bash
SESSION_NAME=<workspace>-creator-<agent> # convention SESSION_NAME=<workspace>-creator-<agent> # convention
AGENT_SESSIONS_YAML=.hermes/agent-sessions.yaml AGENT_SESSIONS_YAML=.mam/agent-sessions.yaml
# 1) Session is registered? # 1) Session is registered?
python3 -c " python3 -c "
@@ -286,11 +286,11 @@ if purge and purge_uuid:
print(f"purged: {brain}", flush=True) print(f"purged: {brain}", flush=True)
target['agy_conversation_id_own'] = None target['agy_conversation_id_own'] = None
elif agent == 'hermes': elif agent == 'hermes':
json_file = f"{home}/.hermes/sessions/session_{purge_uuid}.json" json_file = f"{home}/.mam/sessions/session_{purge_uuid}.json"
if os.path.exists(json_file): if os.path.exists(json_file):
os.remove(json_file) os.remove(json_file)
print(f"purged: {json_file}", flush=True) print(f"purged: {json_file}", flush=True)
hdb = f"{home}/.hermes/state.db" hdb = f"{home}/.mam/state.db"
if os.path.exists(hdb): if os.path.exists(hdb):
try: try:
import sqlite3 import sqlite3
+5 -5
View File
@@ -20,8 +20,8 @@
# =========================================================================== # ===========================================================================
# Single source of truth for the agent session registry YAML. # Single source of truth for the agent session registry YAML.
#default: <workspace>/.hermes/agent-sessions.yaml #default: <workspace>/.mam/agent-sessions.yaml
# AGENT_SESSIONS_YAML=/path/to/workspace/.hermes/agent-sessions.yaml # AGENT_SESSIONS_YAML=/path/to/workspace/.mam/agent-sessions.yaml
# Where the monitor (reconcile.sh) keeps its drift-state cache. # Where the monitor (reconcile.sh) keeps its drift-state cache.
#default: <workspace>/.cache/tmux-agent-orchestrate-monitor #default: <workspace>/.cache/tmux-agent-orchestrate-monitor
@@ -72,6 +72,6 @@
#default: (unset → no client key) #default: (unset → no client key)
# MQTT_KEYFILE=/path/to/client.key # MQTT_KEYFILE=/path/to/client.key
# Directory for delegate-job audit logs (sits beside .hermes/jobs/). # Directory for delegate-job audit logs (sits beside .mam/jobs/).
#default: <cwd>/.hermes/delegate_job_logs #default: <cwd>/.mam/delegate_job_logs
# DELEGATE_JOB_LOGS_DIR=/path/to/workspace/.hermes/delegate_job_logs # DELEGATE_JOB_LOGS_DIR=/path/to/workspace/.mam/delegate_job_logs
+1 -1
View File
@@ -8,7 +8,7 @@ test-sessions*.yaml.bak
test-sessions*.yaml.lock test-sessions*.yaml.lock
# delegate-job 임시/런타임 산출물 # delegate-job 임시/런타임 산출물
.hermes/ .mam/
.venv/ .venv/
__pycache__/ __pycache__/
*.pyc *.pyc
+3 -3
View File
@@ -43,8 +43,8 @@
### 🗃️ 레지스트리 및 상태 관리 ### 🗃️ 레지스트리 및 상태 관리
- 본 아키텍처는 목적에 따라 두 가지 레지스트리를 분리하여 운영합니다: - 본 아키텍처는 목적에 따라 두 가지 레지스트리를 분리하여 운영합니다:
- **잡 레지스트리 (Job Registry)**: 각 비동기 잡의 메타데이터와 생명주기는 개별 JSON 파일(`.hermes/jobs/<id>.json`)로 기록되며, 다중 세션 간의 동시 청구(claiming) 경합은 파일 단위의 `fcntl` advisory lock(`registry_lock` via `registry.py`)을 통해 방어합니다. - **잡 레지스트리 (Job Registry)**: 각 비동기 잡의 메타데이터와 생명주기는 개별 JSON 파일(`.mam/jobs/<id>.json`)로 기록되며, 다중 세션 간의 동시 청구(claiming) 경합은 파일 단위의 `fcntl` advisory lock(`registry_lock` via `registry.py`)을 통해 방어합니다.
- **세션 레지스트리 (Session Registry)**: TMUX 모니터링 상태 및 에이전트 구동 정보는 SQLite WAL 데이터베이스(`.hermes/agent-sessions.db`)를 통해 단일 호스트 내에서 안정적인 동시 트랜잭션으로 일관되게 제어합니다. 단, SQLite WAL 모드는 NFS(네트워크 파일 시스템) 환경에서는 완전한 파일 락이 보장되지 않으므로 로컬 파일 시스템 사용을 권장합니다. - **세션 레지스트리 (Session Registry)**: TMUX 모니터링 상태 및 에이전트 구동 정보는 SQLite WAL 데이터베이스(`.mam/agent-sessions.db`)를 통해 단일 호스트 내에서 안정적인 동시 트랜잭션으로 일관되게 제어합니다. 단, SQLite WAL 모드는 NFS(네트워크 파일 시스템) 환경에서는 완전한 파일 락이 보장되지 않으므로 로컬 파일 시스템 사용을 권장합니다.
### 🛡️ 보안 프로토콜 (HMAC-SHA256) ### 🛡️ 보안 프로토콜 (HMAC-SHA256)
- **무인증 PoC 모드**: 잡 레지스트리 생성 시 `auth_token``null`로 지정된 경우(PoC 기본 모드), 별도의 서명 검증을 생략하고 모든 이벤트를 수용합니다 (`verify_hmac`이 항상 `True`를 반환). - **무인증 PoC 모드**: 잡 레지스트리 생성 시 `auth_token``null`로 지정된 경우(PoC 기본 모드), 별도의 서명 검증을 생략하고 모든 이벤트를 수용합니다 (`verify_hmac`이 항상 `True`를 반환).
@@ -116,7 +116,7 @@ TMUX 환경에서 실행되는 에이전트가 화면 스크롤 한계로 인해
- [ ] **가상환경 의존성**: `pyyaml`, `paho-mqtt` 등 필요한 Python 패키지가 `.venv` 또는 `requirements.txt`에 포함되었는가? - [ ] **가상환경 의존성**: `pyyaml`, `paho-mqtt` 등 필요한 Python 패키지가 `.venv` 또는 `requirements.txt`에 포함되었는가?
- [ ] **환경 설정 파일**: MQTT 브로커 주소 및 보안 Credential이 `.env` 파일에 안전하게 로드되고 공유되는가? - [ ] **환경 설정 파일**: MQTT 브로커 주소 및 보안 Credential이 `.env` 파일에 안전하게 로드되고 공유되는가?
- [ ] **디렉토리 규약**: 레지스트리 경로(`.hermes/jobs/`) 및 로깅 경로(`.hermes/delegate_job_logs/`)가 `.gitignore`에 등록되었는가? - [ ] **디렉토리 규약**: 레지스트리 경로(`.mam/jobs/`) 및 로깅 경로(`.mam/delegate_job_logs/`)가 `.gitignore`에 등록되었는가?
- [ ] **스크립트 구비**: `mqtt_common.py`, `publish_event.py`, `job_subscriber.py`, `registry.py` 등의 핵심 모듈이 배치되었는가? - [ ] **스크립트 구비**: `mqtt_common.py`, `publish_event.py`, `job_subscriber.py`, `registry.py` 등의 핵심 모듈이 배치되었는가?
- [ ] **HMAC 활성화**: 새로운 레지스트리 잡 발급 시 난수 기반의 `auth_token`이 정상적으로 주입되고, 서명 기반의 상호 인증이 활성화되는가? - [ ] **HMAC 활성화**: 새로운 레지스트리 잡 발급 시 난수 기반의 `auth_token`이 정상적으로 주입되고, 서명 기반의 상호 인증이 활성화되는가?
- [ ] **운영 헌장 배치**: 본 규약 파일(`AGENT.md`)이 새 프로젝트의 **최상위 루트(Root) 디렉터리**에 배치되었는가? (협업을 수행하는 에이전트들이 온보딩 시 규칙을 가장 먼저 인지할 수 있도록 루트 경로 배치가 필수적입니다.) - [ ] **운영 헌장 배치**: 본 규약 파일(`AGENT.md`)이 새 프로젝트의 **최상위 루트(Root) 디렉터리**에 배치되었는가? (협업을 수행하는 에이전트들이 온보딩 시 규칙을 가장 먼저 인지할 수 있도록 루트 경로 배치가 필수적입니다.)
+3 -3
View File
@@ -43,8 +43,8 @@ Asynchronous communication and state management between agents are controlled vi
### 🗃️ Registry & State Management ### 🗃️ Registry & State Management
- This architecture maintains two distinct registries based on their purpose: - This architecture maintains two distinct registries based on their purpose:
- **Job Registry**: The metadata and lifecycle of each asynchronous job are recorded in individual JSON files (`.hermes/jobs/<id>.json`). Concurrency conflicts (claiming races) across multiple sessions are prevented via file-based `fcntl` advisory locks (`registry_lock` via `registry.py`). - **Job Registry**: The metadata and lifecycle of each asynchronous job are recorded in individual JSON files (`.mam/jobs/<id>.json`). Concurrency conflicts (claiming races) across multiple sessions are prevented via file-based `fcntl` advisory locks (`registry_lock` via `registry.py`).
- **Session Registry**: TMUX monitoring states and running agent metadata are consistently controlled using a SQLite WAL database (`.hermes/agent-sessions.db`) to support reliable concurrent transactions on a single host. However, since SQLite WAL mode does not guarantee complete file locking in Network File System (NFS) environments, we recommend using a local file system. - **Session Registry**: TMUX monitoring states and running agent metadata are consistently controlled using a SQLite WAL database (`.mam/agent-sessions.db`) to support reliable concurrent transactions on a single host. However, since SQLite WAL mode does not guarantee complete file locking in Network File System (NFS) environments, we recommend using a local file system.
### 🛡️ Security Protocol (HMAC-SHA256) ### 🛡️ Security Protocol (HMAC-SHA256)
- **Unauthenticated PoC Mode**: If the `auth_token` in the job registry is set to `null` (the default PoC mode), signature verification is skipped and all events are accepted (`verify_hmac` always returns `True`). - **Unauthenticated PoC Mode**: If the `auth_token` in the job registry is set to `null` (the default PoC mode), signature verification is skipped and all events are accepted (`verify_hmac` always returns `True`).
@@ -116,7 +116,7 @@ Use this checklist when deploying this agent orchestration model to a new projec
- [ ] **Virtualenv Dependencies**: Are required Python packages like `pyyaml` and `paho-mqtt` included in `.venv` or `requirements.txt`? - [ ] **Virtualenv Dependencies**: Are required Python packages like `pyyaml` and `paho-mqtt` included in `.venv` or `requirements.txt`?
- [ ] **Configuration File**: Are the MQTT broker address and security credentials safely loaded and shared via the `.env` file? - [ ] **Configuration File**: Are the MQTT broker address and security credentials safely loaded and shared via the `.env` file?
- [ ] **Directory Convention**: Are the registry path (`.hermes/jobs/`) and logging path (`.hermes/delegate_job_logs/`) added to `.gitignore`? - [ ] **Directory Convention**: Are the registry path (`.mam/jobs/`) and logging path (`.mam/delegate_job_logs/`) added to `.gitignore`?
- [ ] **Core Scripts**: Are the core scripts (`mqtt_common.py`, `publish_event.py`, `job_subscriber.py`, and `registry.py`) in place? - [ ] **Core Scripts**: Are the core scripts (`mqtt_common.py`, `publish_event.py`, `job_subscriber.py`, and `registry.py`) in place?
- [ ] **HMAC Enablement**: When a new registry job is created, is a random `auth_token` correctly injected, and is signature-based mutual authentication active? - [ ] **HMAC Enablement**: When a new registry job is created, is a random `auth_token` correctly injected, and is signature-based mutual authentication active?
- [ ] **Charter Placement**: Is this protocol file (`AGENT.md`) placed in the **top-level root directory** of the new project? (Placing it at the root is essential so that onboarding agents can recognize the rules immediately.) - [ ] **Charter Placement**: Is this protocol file (`AGENT.md`) placed in the **top-level root directory** of the new project? (Placing it at the root is essential so that onboarding agents can recognize the rules immediately.)
+7 -7
View File
@@ -48,7 +48,7 @@
생성된 `.env` 파일을 열어 설정을 필요에 따라 구성합니다. 생성된 `.env` 파일을 열어 설정을 필요에 따라 구성합니다.
> [!NOTE] > [!NOTE]
> `generate-env.sh`로 생성된 기본 `.env` 파일은 모든 환경 변수 항목이 주석 처리되어 있습니다. 주석 처리된 상태로 둘 경우 로컬 프로젝트 루트를 기준으로 한 상대 경로(`.hermes/` 등) 및 기본 공개 브로커 주소가 자동 지정되므로 그대로 사용하셔도 무방합니다. > `generate-env.sh`로 생성된 기본 `.env` 파일은 모든 환경 변수 항목이 주석 처리되어 있습니다. 주석 처리된 상태로 둘 경우 로컬 프로젝트 루트를 기준으로 한 상대 경로(`.mam/` 등) 및 기본 공개 브로커 주소가 자동 지정되므로 그대로 사용하셔도 무방합니다.
1. **MQTT Broker 설정 (`MQTT_BROKER`)**: 1. **MQTT Broker 설정 (`MQTT_BROKER`)**:
* 기본값은 HiveMQ 공개 브로커(`broker.hivemq.com`)로 잡혀 있으나, 보안 및 프라이버시가 중요한 프로덕션 작업 시에는 개인/사설 브로커 주소로 변경할 것을 강력히 권장합니다. * 기본값은 HiveMQ 공개 브로커(`broker.hivemq.com`)로 잡혀 있으나, 보안 및 프라이버시가 중요한 프로덕션 작업 시에는 개인/사설 브로커 주소로 변경할 것을 강력히 권장합니다.
@@ -94,15 +94,15 @@ pip install -r .agents/skills/tmux-agent-orchestrate-delegate-job/requirements.t
에이전트 제어 상태 및 잡 기록을 위해 로컬 레지스트리 디렉터리가 정상적으로 생성되었는지 확인합니다. 에이전트 제어 상태 및 잡 기록을 위해 로컬 레지스트리 디렉터리가 정상적으로 생성되었는지 확인합니다.
1. **필수 로컬 디렉터리 구조**: 1. **필수 로컬 디렉터리 구조**:
* `.hermes/jobs/`: 등록된 비동기 잡의 세부 메타데이터가 파일 형태로 저장되는 디렉터리 * `.mam/jobs/`: 등록된 비동기 잡의 세부 메타데이터가 파일 형태로 저장되는 디렉터리
* `.hermes/delegate_job_logs/`: 에이전트가 발행하는 모든 백플레인 이벤트 흐름이 기록되는 audit log (`events.ndjson`) 보존 디렉터리 * `.mam/delegate_job_logs/`: 에이전트가 발행하는 모든 백플레인 이벤트 흐름이 기록되는 audit log (`events.ndjson`) 보존 디렉터리
2. **Git 커밋 제어 (.gitignore)**: 2. **Git 커밋 제어 (.gitignore)**:
* 새 프로젝트 초기화 시 아래 파일들이 절대 리포지토리에 커밋되지 않도록 `.gitignore` 상태를 점검합니다. `!.env.example` 예외 처리가 유지되어야 템플릿이 보존됩니다: * 새 프로젝트 초기화 시 아래 파일들이 절대 리포지토리에 커밋되지 않도록 `.gitignore` 상태를 점검합니다. `!.env.example` 예외 처리가 유지되어야 템플릿이 보존됩니다:
```text ```text
.env .env
.env.* .env.*
!.env.example !.env.example
.hermes/ .mam/
.venv/ .venv/
__pycache__/ __pycache__/
*.pyc *.pyc
@@ -115,7 +115,7 @@ pip install -r .agents/skills/tmux-agent-orchestrate-delegate-job/requirements.t
환경 구축이 오작동 없이 안전하게 완료되었는지 아래의 체크리스트를 실행해 검증합니다. 환경 구축이 오작동 없이 안전하게 완료되었는지 아래의 체크리스트를 실행해 검증합니다.
> [!IMPORTANT] > [!IMPORTANT]
> 아래의 모든 검증 명령은 반드시 **프로젝트 루트 디렉터리**(`.hermes/` 디렉터리가 직접 보이는 위치)에서 실행해야 합니다. 잡 레지스트리 디렉터리 기본 경로가 프로젝트 루트 하위의 `./.hermes/jobs` 상대 경로를 기준으로 탐색되기 때문입니다. > 아래의 모든 검증 명령은 반드시 **프로젝트 루트 디렉터리**(`.mam/` 디렉터리가 직접 보이는 위치)에서 실행해야 합니다. 잡 레지스트리 디렉터리 기본 경로가 프로젝트 루트 하위의 `./.mam/jobs` 상대 경로를 기준으로 탐색되기 때문입니다.
### 검증 테스트 1: 잡 레지스트리 정상 구동 여부 ### 검증 테스트 1: 잡 레지스트리 정상 구동 여부
Python 스크립트 및 venv 라이브러리가 올바르게 로드되는지 확인하기 위해 잡 목록을 조회합니다. Python 스크립트 및 venv 라이브러리가 올바르게 로드되는지 확인하기 위해 잡 목록을 조회합니다.
@@ -149,11 +149,11 @@ sleep 2
--event started \ --event started \
--detail "Bootstrap MQTT verification connection check" --detail "Bootstrap MQTT verification connection check"
# 5. 이벤트 수신이 터미널(stdout) 및 .hermes/delegate_job_logs/events.ndjson 로그 파일에 정상 기록되는지 확인 # 5. 이벤트 수신이 터미널(stdout) 및 .mam/delegate_job_logs/events.ndjson 로그 파일에 정상 기록되는지 확인
# 6. 검증 완료 후 백그라운드 프로세스 종료 및 테스트 잡 레코드 수동 정리 # 6. 검증 완료 후 백그라운드 프로세스 종료 및 테스트 잡 레코드 수동 정리
kill %1 kill %1
rm -f ".hermes/jobs/$JID.json" ".hermes/jobs/$JID.lock" rm -f ".mam/jobs/$JID.json" ".mam/jobs/$JID.lock"
``` ```
--- ---
+7 -7
View File
@@ -48,7 +48,7 @@ Run the environment template copy script provided in the project root:
Open the generated `.env` file to configure settings as needed. Open the generated `.env` file to configure settings as needed.
> [!NOTE] > [!NOTE]
> The default `.env` file generated by `generate-env.sh` has all environment variables commented out. If left commented out, the system defaults to using relative paths (`.hermes/`, etc.) relative to the local project root, and the public MQTT broker. You can use it as-is without uncommenting anything. > The default `.env` file generated by `generate-env.sh` has all environment variables commented out. If left commented out, the system defaults to using relative paths (`.mam/`, etc.) relative to the local project root, and the public MQTT broker. You can use it as-is without uncommenting anything.
1. **MQTT Broker Setup (`MQTT_BROKER`)**: 1. **MQTT Broker Setup (`MQTT_BROKER`)**:
* The default broker is HiveMQ's public sandbox broker (`broker.hivemq.com`). However, for production work where security and privacy are critical, we strongly recommend changing this to a private broker address. * The default broker is HiveMQ's public sandbox broker (`broker.hivemq.com`). However, for production work where security and privacy are critical, we strongly recommend changing this to a private broker address.
@@ -94,15 +94,15 @@ pip install -r .agents/skills/tmux-agent-orchestrate-delegate-job/requirements.t
Ensure that the local registry directories required to track agent states and jobs are successfully created: Ensure that the local registry directories required to track agent states and jobs are successfully created:
1. **Required Directory Structure**: 1. **Required Directory Structure**:
* `.hermes/jobs/`: Holds detailed metadata files for registered asynchronous jobs. * `.mam/jobs/`: Holds detailed metadata files for registered asynchronous jobs.
* `.hermes/delegate_job_logs/`: Holds the audit logs (`events.ndjson`) for all backplane events published by agents. * `.mam/delegate_job_logs/`: Holds the audit logs (`events.ndjson`) for all backplane events published by agents.
2. **Git Ignore Configuration (`.gitignore`)**: 2. **Git Ignore Configuration (`.gitignore`)**:
* When initializing a new project, verify that the following entries are configured in `.gitignore` to prevent committing local runtimes to the repository. The exception `!.env.example` must be kept to preserve the template: * When initializing a new project, verify that the following entries are configured in `.gitignore` to prevent committing local runtimes to the repository. The exception `!.env.example` must be kept to preserve the template:
```text ```text
.env .env
.env.* .env.*
!.env.example !.env.example
.hermes/ .mam/
.venv/ .venv/
__pycache__/ __pycache__/
*.pyc *.pyc
@@ -115,7 +115,7 @@ Ensure that the local registry directories required to track agent states and jo
To verify that the environment has been successfully built without runtime errors, run the following verification checklist. To verify that the environment has been successfully built without runtime errors, run the following verification checklist.
> [!IMPORTANT] > [!IMPORTANT]
> All verification commands below must be executed from the **project root directory** (where the `.hermes/` directory is directly visible). This is because the default job registry path resolved by scripts is relative to the current working directory under `./.hermes/jobs`. > All verification commands below must be executed from the **project root directory** (where the `.mam/` directory is directly visible). This is because the default job registry path resolved by scripts is relative to the current working directory under `./.mam/jobs`.
### Verification Test 1: Registry Script Load Check ### Verification Test 1: Registry Script Load Check
Verify that the Python scripts and virtual environment libraries load correctly by listing jobs: Verify that the Python scripts and virtual environment libraries load correctly by listing jobs:
@@ -150,11 +150,11 @@ sleep 2
--detail "Bootstrap MQTT verification connection check" --detail "Bootstrap MQTT verification connection check"
# 5. Verify that the event is printed to stdout and written to the audit log: # 5. Verify that the event is printed to stdout and written to the audit log:
# .hermes/delegate_job_logs/events.ndjson # .mam/delegate_job_logs/events.ndjson
# 6. Stop the background subscriber and clean up the test job records # 6. Stop the background subscriber and clean up the test job records
kill %1 kill %1
rm -f ".hermes/jobs/$JID.json" ".hermes/jobs/$JID.lock" rm -f ".mam/jobs/$JID.json" ".mam/jobs/$JID.lock"
``` ```
--- ---
+5 -5
View File
@@ -184,7 +184,7 @@ stateDiagram-v2
#### Phase 1: Registration (`register`) #### Phase 1: Registration (`register`)
* **Trigger**: A delegator triggers `registry.py register` (or the `tmux-agent-orchestrate-delegate-job submit` command). * **Trigger**: A delegator triggers `registry.py register` (or the `tmux-agent-orchestrate-delegate-job submit` command).
* **Registry State**: Flips from non-existent to `pending` inside `.hermes/jobs/<job_id>.json`. A `last_seq` counter is initialized to `0`. * **Registry State**: Flips from non-existent to `pending` inside `.mam/jobs/<job_id>.json`. A `last_seq` counter is initialized to `0`.
* **Locking**: Exclusive fcntl file lock acquired over `.lock` during write. * **Locking**: Exclusive fcntl file lock acquired over `.lock` during write.
* **Durable Audit Log**: Writes `<logs>/<job_id>/meta.json`, sets status to `pending` in `status.json`, and appends a `registered` event line to `events.ndjson`. * **Durable Audit Log**: Writes `<logs>/<job_id>/meta.json`, sets status to `pending` in `status.json`, and appends a `registered` event line to `events.ndjson`.
@@ -287,7 +287,7 @@ graph LR
### 5.1 Orchestration Wrappers (`tmux-agent-orchestrate-*`) ### 5.1 Orchestration Wrappers (`tmux-agent-orchestrate-*`)
1. **`tmux-agent-orchestrate-delegate-job (submit)`**: 1. **`tmux-agent-orchestrate-delegate-job (submit)`**:
* Registers a job, spawns `job_subscriber.py` to capture standard output streams to `.hermes/jobs/<job_id>.subscriber.out`, and sleeps for `1` second. * Registers a job, spawns `job_subscriber.py` to capture standard output streams to `.mam/jobs/<job_id>.subscriber.out`, and sleeps for `1` second.
* Boots the agent pane in tmux: * Boots the agent pane in tmux:
```bash ```bash
tmux new-session -d -s "$sess" -c "$WORKDIR" \ tmux new-session -d -s "$sess" -c "$WORKDIR" \
@@ -308,7 +308,7 @@ graph LR
### 6.1 Limitations ### 6.1 Limitations
1. **Single-Host File Locking Vulnerability**: 1. **Single-Host File Locking Vulnerability**:
The advisory locking system previously relied heavily on `fcntl.flock`. While `agent-sessions.yaml` has been migrated to SQLite WAL to solve concurrent writes, the job metadata in `.hermes/jobs/` still relies on `fcntl.flock` which may behave non-atomically on NFS. The advisory locking system previously relied heavily on `fcntl.flock`. While `agent-sessions.yaml` has been migrated to SQLite WAL to solve concurrent writes, the job metadata in `.mam/jobs/` still relies on `fcntl.flock` which may behave non-atomically on NFS.
2. **Bearer Token Leakage over Plaintext (Public Broker)**: 2. **Bearer Token Leakage over Plaintext (Public Broker)**:
The `auth_token` mechanism is a simple plaintext bearer comparison. If the transport layer is unencrypted (e.g., using `broker.hivemq.com` on port `1883`), any eavesdropper on the network can steal the token and spoof legitimate events. The `auth_token` mechanism is a simple plaintext bearer comparison. If the transport layer is unencrypted (e.g., using `broker.hivemq.com` on port `1883`), any eavesdropper on the network can steal the token and spoof legitimate events.
3. **Subscriber Network Drop Orphanage**: 3. **Subscriber Network Drop Orphanage**:
@@ -336,7 +336,7 @@ graph LR
This project manages **two distinct state domains** that are often confused: This project manages **two distinct state domains** that are often confused:
### Session States (YAML — `.hermes/agent-sessions.yaml`) ### Session States (YAML — `.mam/agent-sessions.yaml`)
Managed by `.agents/skills/lib.sh` and the 6 `tmux-agent-orchestrate-*` skills. Managed by `.agents/skills/lib.sh` and the 6 `tmux-agent-orchestrate-*` skills.
Valid values (see `lib.sh` valid-status set): Valid values (see `lib.sh` valid-status set):
@@ -347,7 +347,7 @@ Valid values (see `lib.sh` valid-status set):
| `terminated` | hard-killed via `--mode hard`; tmux session destroyed | `stop` (hard mode), `monitor` reconcile | | `terminated` | hard-killed via `--mode hard`; tmux session destroyed | `stop` (hard mode), `monitor` reconcile |
| `archived` | soft-stopped via `--mode soft`; tmux left alive, YAML-only update | `stop` (soft mode) | | `archived` | soft-stopped via `--mode soft`; tmux left alive, YAML-only update | `stop` (soft mode) |
### Job States (Registry — `.hermes/jobs/<id>.json`) ### Job States (Registry — `.mam/jobs/<id>.json`)
Managed by `.agents/skills/tmux-agent-orchestrate-delegate-job/scripts/registry.py`. Managed by `.agents/skills/tmux-agent-orchestrate-delegate-job/scripts/registry.py`.
Valid values: Valid values: