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)"
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)
HOME_DIR="${HOME_DIR:-$WORKSPACE_ROOT}"
@@ -439,7 +439,7 @@ def db_exists(uuid):
def hermes_exists(uuid):
hdb = f"{home}/.hermes/state.db"
hdb = f"{home}/.mam/state.db"
if not os.path.exists(hdb):
return False
try:
@@ -529,7 +529,7 @@ elif agent == 'agy':
if cand and db_exists(cand):
emit(cand)
elif agent == 'hermes':
hdb = f"{home}/.hermes/state.db"
hdb = f"{home}/.mam/state.db"
if os.path.exists(hdb):
cand = None
try:
@@ -724,7 +724,7 @@ start_watchdog() {
local job_id="$1"
local workdir="${2:-$PWD}"
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
echo "ERROR: watchdog not found or not executable: $watchdog_script" >&2
@@ -1,6 +1,6 @@
---
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
author: godopu
license: MIT
@@ -16,7 +16,7 @@ metadata:
# 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).
> **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
@@ -137,7 +137,7 @@ TMUX_EPOCH=$(tmux list-sessions -F '#{session_created}' -t "$SESSION_NAME" 2>/de
## 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
- 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
python3 -c "
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']]
assert '$SESSION_NAME' in names, 'session not registered'
print('OK:', names)
@@ -7,5 +7,5 @@
- 브로커 PoC→운영 전환: [`mqtt-broker-setup.md`](./mqtt-broker-setup.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)
- 영구 감사 로그: `.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" 참조)
@@ -27,7 +27,7 @@ canonical concrete instance.
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**
(`.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
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
@@ -83,7 +83,7 @@ tmux-agent-orchestrate-delegate-job submit \
# subscriber pid: …
# agent launched in tmux session: demo
# 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
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
```
.hermes/jobs/<id>.json # metadata record (single source of truth)
.hermes/jobs/<id>.events.log # append-only JSON-lines log (debug, optional)
.hermes/jobs/.lock # fcntl advisory lock for the registry
.mam/jobs/<id>.json # metadata record (single source of truth)
.mam/jobs/<id>.events.log # append-only JSON-lines log (debug, optional)
.mam/jobs/.lock # fcntl advisory lock for the registry
```
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
Every job's lifecycle is mirrored to a **persistent, append-only audit log**
under `.hermes/delegate_job_logs/` (override with `DELEGATE_JOB_LOGS_DIR`;
default `<cwd>/.hermes/delegate_job_logs`). Unlike the registry — live state
under `.mam/delegate_job_logs/` (override with `DELEGATE_JOB_LOGS_DIR`;
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
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, …
events.ndjson # append-only, one JSON event per line, in time order
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`
still works unchanged (regression).
- [ ] **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
`status.json.status == "completed"` matches the registry record. A
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
```
.hermes/jobs/
.mam/jobs/
<job_id>.json # job metadata record (schema below)
<job_id>.events.log # append-only JSON-lines event log (debug, optional)
.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`.
---
@@ -143,13 +143,13 @@ that session.
## 7. Persistent audit log
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
`DELEGATE_JOB_LOGS_DIR`, default `<cwd>/.hermes/delegate_job_logs`). The registry
audit log at `.mam/delegate_job_logs/<job_id>/` (override with
`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
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)
events.ndjson # append-only, one JSON event per line, time-ordered
status.json # current status only (fast point-query)
@@ -71,11 +71,11 @@ _load_dotenv()
# Constants
# --------------------------------------------------------------------------
SCHEMA_VERSION = 1
DEFAULT_REGISTRY_DIR = ".hermes/jobs"
DEFAULT_REGISTRY_DIR = ".mam/jobs"
DEFAULT_TOPIC_ROOT = "python/mqtt/jobs"
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
# record (mutated in place), the audit log is an append-only history that
# survives even if the registry dir is cleaned up.
@@ -86,15 +86,15 @@ STATUS_FILENAME = "status.json"
def _default_logs_dir() -> str:
"""Audit-log root. Overridable with ``DELEGATE_JOB_LOGS_DIR``; otherwise
``<cwd>/.hermes/delegate_job_logs`` — we keep audit logs next to the
live registry (``.hermes/jobs/``) so the two runtime artifacts sit
``<cwd>/.mam/delegate_job_logs`` — we keep audit logs next to the
live registry (``.mam/jobs/``) so the two runtime artifacts sit
under the same parent dir and follow the same ``.gitignore`` rule.
The cwd of whichever process emits events (the bash wrapper and
scripts) is used as the anchor."""
env = os.environ.get("DELEGATE_JOB_LOGS_DIR")
if env and env.strip():
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()
@@ -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
# 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")
p_logs.add_argument("--logs-dir", default=None,
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,
help="show only the last N events (0 = all)")
p_logs.add_argument("--json", action="store_true",
@@ -37,7 +37,7 @@ pick_python() {
echo "$py_bin"
}
REGISTRY_DIR_DEFAULT=".hermes/jobs"
REGISTRY_DIR_DEFAULT=".mam/jobs"
usage() {
cat <<'EOF'
@@ -1,6 +1,6 @@
---
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
author: godopu
license: MIT
@@ -16,7 +16,7 @@ metadata:
# 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**.
> **Single source of truth**: `./.hermes/agent-sessions.yaml`.
> **Single source of truth**: `./.mam/agent-sessions.yaml`.
## What this skill does
@@ -68,7 +68,7 @@ hermes kanban create \
--body "$(cat <<'EOF'
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}'`
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
@@ -12,7 +12,7 @@ fi
JOB_ID="$1"
WORKDIR="$2"
LOG_DIR="$WORKDIR/.hermes/jobs"
LOG_DIR="$WORKDIR/.mam/jobs"
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 \
--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"
) &
@@ -1,6 +1,6 @@
---
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
author: godopu
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).
> **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
@@ -132,7 +132,7 @@ tmux list-panes -t "$SESSION_NAME" -F 'cmd=#{pane_current_command} cwd=#{pane_cu
# 2. agent-sessions.yaml updated
python3 -c "
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]
print(f' status: {s[\"status\"]}')
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).
> **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
@@ -30,7 +30,7 @@ This is the "what's running right now?" answer — faster than dispatching `tmux
```bash
command -v tmux
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.)
@@ -24,7 +24,7 @@ if [ "$JSON" = "1" ]; then
exit 0
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).
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):
# 1) cwd-relative registry 2) project-root registry 3) project-root audit log
candidates = [
os.path.join('.hermes', 'jobs', f"{jid}.json"),
os.path.join(project_root, '.hermes', 'jobs', f"{jid}.json"),
os.path.join(project_root, '.hermes', 'delegate_job_logs', jid, 'status.json'),
os.path.join('.mam', 'jobs', f"{jid}.json"),
os.path.join(project_root, '.mam', 'jobs', f"{jid}.json"),
os.path.join(project_root, '.mam', 'delegate_job_logs', jid, 'status.json'),
]
for path in candidates:
if os.path.exists(path):
@@ -1,6 +1,6 @@
---
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
author: godopu
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).
> **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
@@ -37,7 +37,7 @@ The stop command is always **graceful by default**:
```bash
SESSION_NAME=<workspace>-creator-<agent> # convention
AGENT_SESSIONS_YAML=.hermes/agent-sessions.yaml
AGENT_SESSIONS_YAML=.mam/agent-sessions.yaml
# 1) Session is registered?
python3 -c "
@@ -286,11 +286,11 @@ if purge and purge_uuid:
print(f"purged: {brain}", flush=True)
target['agy_conversation_id_own'] = None
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):
os.remove(json_file)
print(f"purged: {json_file}", flush=True)
hdb = f"{home}/.hermes/state.db"
hdb = f"{home}/.mam/state.db"
if os.path.exists(hdb):
try:
import sqlite3
+5 -5
View File
@@ -20,8 +20,8 @@
# ===========================================================================
# Single source of truth for the agent session registry YAML.
#default: <workspace>/.hermes/agent-sessions.yaml
# AGENT_SESSIONS_YAML=/path/to/workspace/.hermes/agent-sessions.yaml
#default: <workspace>/.mam/agent-sessions.yaml
# AGENT_SESSIONS_YAML=/path/to/workspace/.mam/agent-sessions.yaml
# Where the monitor (reconcile.sh) keeps its drift-state cache.
#default: <workspace>/.cache/tmux-agent-orchestrate-monitor
@@ -72,6 +72,6 @@
#default: (unset → no client key)
# MQTT_KEYFILE=/path/to/client.key
# Directory for delegate-job audit logs (sits beside .hermes/jobs/).
#default: <cwd>/.hermes/delegate_job_logs
# DELEGATE_JOB_LOGS_DIR=/path/to/workspace/.hermes/delegate_job_logs
# Directory for delegate-job audit logs (sits beside .mam/jobs/).
#default: <cwd>/.mam/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
# delegate-job 임시/런타임 산출물
.hermes/
.mam/
.venv/
__pycache__/
*.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`)을 통해 방어합니다.
- **세션 레지스트리 (Session Registry)**: TMUX 모니터링 상태 및 에이전트 구동 정보는 SQLite WAL 데이터베이스(`.hermes/agent-sessions.db`)를 통해 단일 호스트 내에서 안정적인 동시 트랜잭션으로 일관되게 제어합니다. 단, SQLite WAL 모드는 NFS(네트워크 파일 시스템) 환경에서는 완전한 파일 락이 보장되지 않으므로 로컬 파일 시스템 사용을 권장합니다.
- **잡 레지스트리 (Job Registry)**: 각 비동기 잡의 메타데이터와 생명주기는 개별 JSON 파일(`.mam/jobs/<id>.json`)로 기록되며, 다중 세션 간의 동시 청구(claiming) 경합은 파일 단위의 `fcntl` advisory lock(`registry_lock` via `registry.py`)을 통해 방어합니다.
- **세션 레지스트리 (Session Registry)**: TMUX 모니터링 상태 및 에이전트 구동 정보는 SQLite WAL 데이터베이스(`.mam/agent-sessions.db`)를 통해 단일 호스트 내에서 안정적인 동시 트랜잭션으로 일관되게 제어합니다. 단, SQLite WAL 모드는 NFS(네트워크 파일 시스템) 환경에서는 완전한 파일 락이 보장되지 않으므로 로컬 파일 시스템 사용을 권장합니다.
### 🛡️ 보안 프로토콜 (HMAC-SHA256)
- **무인증 PoC 모드**: 잡 레지스트리 생성 시 `auth_token``null`로 지정된 경우(PoC 기본 모드), 별도의 서명 검증을 생략하고 모든 이벤트를 수용합니다 (`verify_hmac`이 항상 `True`를 반환).
@@ -116,7 +116,7 @@ TMUX 환경에서 실행되는 에이전트가 화면 스크롤 한계로 인해
- [ ] **가상환경 의존성**: `pyyaml`, `paho-mqtt` 등 필요한 Python 패키지가 `.venv` 또는 `requirements.txt`에 포함되었는가?
- [ ] **환경 설정 파일**: 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` 등의 핵심 모듈이 배치되었는가?
- [ ] **HMAC 활성화**: 새로운 레지스트리 잡 발급 시 난수 기반의 `auth_token`이 정상적으로 주입되고, 서명 기반의 상호 인증이 활성화되는가?
- [ ] **운영 헌장 배치**: 본 규약 파일(`AGENT.md`)이 새 프로젝트의 **최상위 루트(Root) 디렉터리**에 배치되었는가? (협업을 수행하는 에이전트들이 온보딩 시 규칙을 가장 먼저 인지할 수 있도록 루트 경로 배치가 필수적입니다.)
+3 -3
View File
@@ -43,8 +43,8 @@ Asynchronous communication and state management between agents are controlled vi
### 🗃️ Registry & State Management
- 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`).
- **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.
- **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 (`.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)
- **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`?
- [ ] **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?
- [ ] **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.)
+7 -7
View File
@@ -48,7 +48,7 @@
생성된 `.env` 파일을 열어 설정을 필요에 따라 구성합니다.
> [!NOTE]
> `generate-env.sh`로 생성된 기본 `.env` 파일은 모든 환경 변수 항목이 주석 처리되어 있습니다. 주석 처리된 상태로 둘 경우 로컬 프로젝트 루트를 기준으로 한 상대 경로(`.hermes/` 등) 및 기본 공개 브로커 주소가 자동 지정되므로 그대로 사용하셔도 무방합니다.
> `generate-env.sh`로 생성된 기본 `.env` 파일은 모든 환경 변수 항목이 주석 처리되어 있습니다. 주석 처리된 상태로 둘 경우 로컬 프로젝트 루트를 기준으로 한 상대 경로(`.mam/` 등) 및 기본 공개 브로커 주소가 자동 지정되므로 그대로 사용하셔도 무방합니다.
1. **MQTT Broker 설정 (`MQTT_BROKER`)**:
* 기본값은 HiveMQ 공개 브로커(`broker.hivemq.com`)로 잡혀 있으나, 보안 및 프라이버시가 중요한 프로덕션 작업 시에는 개인/사설 브로커 주소로 변경할 것을 강력히 권장합니다.
@@ -94,15 +94,15 @@ pip install -r .agents/skills/tmux-agent-orchestrate-delegate-job/requirements.t
에이전트 제어 상태 및 잡 기록을 위해 로컬 레지스트리 디렉터리가 정상적으로 생성되었는지 확인합니다.
1. **필수 로컬 디렉터리 구조**:
* `.hermes/jobs/`: 등록된 비동기 잡의 세부 메타데이터가 파일 형태로 저장되는 디렉터리
* `.hermes/delegate_job_logs/`: 에이전트가 발행하는 모든 백플레인 이벤트 흐름이 기록되는 audit log (`events.ndjson`) 보존 디렉터리
* `.mam/jobs/`: 등록된 비동기 잡의 세부 메타데이터가 파일 형태로 저장되는 디렉터리
* `.mam/delegate_job_logs/`: 에이전트가 발행하는 모든 백플레인 이벤트 흐름이 기록되는 audit log (`events.ndjson`) 보존 디렉터리
2. **Git 커밋 제어 (.gitignore)**:
* 새 프로젝트 초기화 시 아래 파일들이 절대 리포지토리에 커밋되지 않도록 `.gitignore` 상태를 점검합니다. `!.env.example` 예외 처리가 유지되어야 템플릿이 보존됩니다:
```text
.env
.env.*
!.env.example
.hermes/
.mam/
.venv/
__pycache__/
*.pyc
@@ -115,7 +115,7 @@ pip install -r .agents/skills/tmux-agent-orchestrate-delegate-job/requirements.t
환경 구축이 오작동 없이 안전하게 완료되었는지 아래의 체크리스트를 실행해 검증합니다.
> [!IMPORTANT]
> 아래의 모든 검증 명령은 반드시 **프로젝트 루트 디렉터리**(`.hermes/` 디렉터리가 직접 보이는 위치)에서 실행해야 합니다. 잡 레지스트리 디렉터리 기본 경로가 프로젝트 루트 하위의 `./.hermes/jobs` 상대 경로를 기준으로 탐색되기 때문입니다.
> 아래의 모든 검증 명령은 반드시 **프로젝트 루트 디렉터리**(`.mam/` 디렉터리가 직접 보이는 위치)에서 실행해야 합니다. 잡 레지스트리 디렉터리 기본 경로가 프로젝트 루트 하위의 `./.mam/jobs` 상대 경로를 기준으로 탐색되기 때문입니다.
### 검증 테스트 1: 잡 레지스트리 정상 구동 여부
Python 스크립트 및 venv 라이브러리가 올바르게 로드되는지 확인하기 위해 잡 목록을 조회합니다.
@@ -149,11 +149,11 @@ sleep 2
--event started \
--detail "Bootstrap MQTT verification connection check"
# 5. 이벤트 수신이 터미널(stdout) 및 .hermes/delegate_job_logs/events.ndjson 로그 파일에 정상 기록되는지 확인
# 5. 이벤트 수신이 터미널(stdout) 및 .mam/delegate_job_logs/events.ndjson 로그 파일에 정상 기록되는지 확인
# 6. 검증 완료 후 백그라운드 프로세스 종료 및 테스트 잡 레코드 수동 정리
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.
> [!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`)**:
* 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:
1. **Required Directory Structure**:
* `.hermes/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/jobs/`: Holds detailed metadata files for registered asynchronous jobs.
* `.mam/delegate_job_logs/`: Holds the audit logs (`events.ndjson`) for all backplane events published by agents.
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:
```text
.env
.env.*
!.env.example
.hermes/
.mam/
.venv/
__pycache__/
*.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.
> [!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
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"
# 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
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`)
* **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.
* **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-*`)
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:
```bash
tmux new-session -d -s "$sess" -c "$WORKDIR" \
@@ -308,7 +308,7 @@ graph LR
### 6.1 Limitations
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)**:
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**:
@@ -336,7 +336,7 @@ graph LR
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.
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 |
| `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`.
Valid values: