feat: standalone coordinator and repository installer

This commit is contained in:
sid 2026-07-30 22:56:38 -06:00
commit 9c47ef5fde
11 changed files with 4794 additions and 0 deletions

5
.gitignore vendored Normal file
View file

@ -0,0 +1,5 @@
# Legacy pre-SQLite runtime residue. Current state lives in the private,
# host-local SQLite database outside the repository; never version either form.
state/
__pycache__/
*.pyc

237
DESIGN.md Normal file
View file

@ -0,0 +1,237 @@
# Agent coordination design
Status: implemented. Packaging and installation: [`README.md`](README.md).
## Boundary that actually isolates work
The shared Jujutsu working copy is also `@`. Every `jj` command snapshots all peer filesystem
edits into whichever change currently owns that checkout. File leases cannot prevent this because
the absorbing session did not write those files. This caused duplicate work, foreign features
inside unrelated commits, and one temporary loss/recovery incident.
Writable main agents therefore use independent colocated `jj git clone` instances. They have
independent operation logs, working-copy commits and Git backends, and ordinary Git-aware Nix
flake discovery works in them. The canonical checkout is for read-only inspection and one
explicit integration owner.
Native `jj workspace add` remains available behind `--native-workspace`. It is useful when sharing
one repository is intentional, but it shares the operation log/object store and is not the
default isolation boundary. In this repository its secondary directory also lacks an ordinary
`.git`, so `nix flake metadata .` does not discover the colocated Git flake normally.
Managed clones live under `/var/tmp/fleet-audit`, the large non-snapshotted scratch dataset.
`/tmp` and `/home` are on the nearly-full snapshotted dataset and must not receive agent clones.
## Identity
- A conversation ID is durable across resume.
- A runtime ID is a fresh UUID for one live attachment to that conversation.
- Human/LLM-facing runtime and workspace identities are exact five-word handles derived from the
full opaque ID with a domain-separated SHA-256 mapping over 128 common words (35 display bits).
Exact handle matches carry authority; prefixes never do, and an unlikely collision is refused
rather than guessed.
- Full UUIDs remain canonical in SQLite, tmux metadata, launcher environments and deterministic
audit exports. Handles are aliases, not secrets and not replacements for internal entropy.
- Managed launches export the exact workspace record as `COORD_WORK_ID`. It may default only
low-blast-radius workspace-local operations after proving that the live runtime owns the record
and `cwd` is inside its registered path. Resource scopes and destructive targets stay explicit.
- The launcher stores full IDs in tmux user options. Window names are presentation only:
`<short-task>-<sid4>`.
The distinction prevents a stale pre-resume process from renewing or releasing the resumed
process's resources.
## Transactional state
Runtime state is a private SQLite WAL database at
`$XDG_STATE_HOME/fleet-agent-coord/<project-id>.sqlite3` (override with `COORD_DB`). It is outside
every agent-writable clone, directory mode 0700 and database/WAL mode 0600.
This database is deliberately host-local. It coordinates Claude and Codex processes sharing one
machine; it does not serialize work performed from different fleet nodes. Cross-host deployment
still needs an explicit human/integration owner (or a future authenticated service).
All conflict-check-plus-claim operations use `BEGIN IMMEDIATE`. Resource names are exact,
repository-relative hierarchical names. Equal names and ancestor/descendant names overlap.
The compatibility matrix is:
| Existing | Requested append | Requested exclusive |
|---|---:|---:|
| append | allowed | refused |
| exclusive | refused | refused |
Leases cover files and global operations alike, for example:
- `tests/checks.nix`
- `integration/master`
- `push/origin/master`
- `activate/home/tpp15s`
- `deploy/system/nix-control`
- `external/fleet-dotfiles`
Heartbeats renew both runtime presence and held leases. Expired/dead runtime leases are reaped
transactionally. Partial release removes only the named resources.
Messages have monotonic integer IDs. A broadcast snapshots its recipient set at send time.
Delivery transitions `queued → delivered → acked`. Delivered context repeats at hook boundaries
until the receiving agent explicitly runs `coord ack <message-id>`. The first delivery carries the
body; later boundaries carry the message ID, sender handle, kind and body digest plus an explicit
`coord inbox --peek` recovery command. A rejected/lost harness response therefore cannot silently
consume it, while a long body is not reinjected on every tool call.
Audit events remain append-like in SQLite and can be exported deterministically as JSONL with
`coord audit`.
## Action composition
`coord batch ACTION : ACTION...` reduces agent/tool round trips without inventing a second command
language: every action is parsed by the ordinary CLI parser and executed by its ordinary handler.
It runs sequentially, stops on the first nonzero result, and can return compact human output or
one JSON envelope. The separator is a standalone token, so quoted message punctuation is not
special.
A batch is intentionally not atomic and performs no rollback. Each state-changing action retains
its own transaction, validation and audit event. The allowlist excludes clone creation/removal,
tmux process replacement and integration queueing. Generic composition must not become a way to
hide destructive scope or ambiguous external side effects.
Every nested action inherits the outer batch's already-resolved exact runtime. Identity flags are
rejected inside action token streams and the resolved nested runtime is asserted equal before
dispatch, preventing one batch from mixing or impersonating audit/lease authority.
Remote publication, integration, activation and deployment need purpose-built workflows with
explicit resources and expected object IDs. In particular, a future publish workflow must acquire
the exact push lease, authoritatively read the network remote, refuse an unexpected base, avoid
force, reread the new remote head, and retain the lease with recovery instructions after an
ambiguous mutation result.
## Enforcement and degradation
The shared hook recognizes Claude `Edit`/`Write` paths and every Codex `apply_patch` source and
`Move to:` destination. It canonicalizes against the hook payload's working directory and blocks
a peer's exclusive overlapping lease.
This is scoped enforcement, not a shell parser. Shell writers, formatters, generators and VCS
operations remain advisory. Isolation is what makes that honest limitation safe. A private clone
can continue working if the coordinator is unavailable.
The hook and lifecycle bridge fail open on load, parse or database failure. A positively
recognized conflicting write fails closed. Denial and `additionalContext` are separate branches
because Codex rejects a response containing both.
Claude subagents receive their own deterministic runtime UUID, derived from the full parent
conversation ID and hook `agent_id`. They therefore conflict, message, and lease independently
instead of inheriting the parent's authority. Writable subagents should still use their own
managed clone; the distinct identity makes accidental writes in the integration checkout fail
closed unless that subagent explicitly owns the resource.
## Workspace lifecycle and cleanup
Each record contains the full task/conversation/runtime identity, source and actual path, exact
base/current Jujutsu IDs, dependencies, planned/actual resources, gate/integration/lifecycle
states, immutable tmux IDs and an ownership token.
The default CLI view deliberately omits those machine fields. It prints handles, task/state, and
only the path needed to enter a newly created clone. `--verbose`, `--json`, and audit export are
explicit diagnostic/machine surfaces. Cleanup tokens remain private by default; normal removal
requires repeating the exact work handle and then compares the registry token with the on-disk
marker.
Lifecycle states support active, parked, resumed, archived, queued/integrating/lock-released/
integrated and removed work. Lock release is not integration evidence: a separate proof verifies
that the exact source commit is an ancestor of the exact target commit. Cleanup never guesses:
The current “queue” is an integration lock plus an auditable state machine, not a fair scheduler:
enqueue order and recorded dependencies are visible but do not automatically grant the next turn.
The integration owner adjudicates readiness and stale bases.
- path must remain below the recorded scratch root;
- private registry token and on-disk `.jj` marker must agree;
- working copy must be clean;
- an advanced change must be released/integrated;
- automatic cleanup requires a passed or explicitly waived gate.
Missing, dirty, ungated or identity-mismatched directories remain recovery candidates.
Known independent clones can be recovered after database loss with explicit `work adopt`. Adoption
requires both `.jj/` and `.git/`, a path beneath the configured scratch root, and a source distinct
from the candidate. It validates exact Jujutsu IDs before rotating the ownership marker. There is
deliberately no directory-name scan or automatic adoption.
If older shared-checkout work was lost, recover read-only with
`jj --at-op=<op> --ignore-working-copy file show <path>`, then merge the recovered content into a
fresh isolated clone. Never restore a whole stale file over newer work.
## Tmux rules
Managed sessions discover project ownership through tmux user options and mutate objects only by
immutable `$session`, `@window` and `%pane` IDs returned by tmux. Names are never lookup targets.
Automatic window renaming is disabled. Codex resumes with `codex-direct resume <conversation>`;
Claude resumes with `claude-direct --resume <conversation>`.
`work close` revalidates those IDs and kills the exact window. It refuses when called from any
pane in the target window, because killing that window would terminate the coordinator before its
registry update. If the operator is attached elsewhere in the target session's last window, it
first switches to another managed session and otherwise refuses, preventing the surprising drop
to a plain outer shell.
The ordinary `codex`/`claude` helper also persists interactive resume invocations and forwards
their arguments to the session runner. Automation (`codex exec`, `claude -p`, pipes and non-TTY
calls) remains direct and status-faithful.
## Proved failure modes
Behavioral checks cover:
- eight-way exclusive race: exactly one winner;
- six simultaneous messages: no loss and monotonic IDs;
- exclusive/append matrix, hierarchical conflicts, renewal/expiry and partial release;
- truncated identity refusal;
- `apply_patch` move-destination blocking;
- repeated delivery until explicit acknowledgement;
- independent clone metadata and explicit native-workspace limitations;
- dirty, ungated, advanced and ownership-mismatched cleanup refusal;
- tmux name collision handling and immutable-ID-only mutations;
- distinct Claude subagent authority, explicit orphan adoption and exact-window close;
- real disposable `jj git clone --colocate` create/inspect/remove.
## Implementation language and versioning
Python plus the standard-library SQLite driver remains the current best fit: hooks can run before
a Nix activation or build bootstrap, deployment has no compiled-artifact handoff, and the
transaction boundary lives in SQLite rather than process memory. A Go rewrite would improve
single-binary distribution, startup predictability and static typing, but would not improve the
isolation or locking model by itself. Those benefits do not currently justify a second
implementation and migration surface.
## Interface and context budget
Coordinator output crosses an unusually expensive boundary: lifecycle context and command output
are repeatedly injected into model context. The interface therefore follows these rules:
- managed environments infer caller identity; ordinary commands do not repeat it;
- required operands are positional (`claim integration/master`, `work create cache-review`);
- readable long options remain for genuinely optional semantics;
- default listings use exact word handles and suppress full IDs, conversations, paths and
workspace rows not requested by the caller;
- JSON is compact and explicit, and ownership secrets are redacted unless a private recovery
export opts in;
- full Jujutsu/Git IDs are stored as evidence but presentation output uses a sufficient display
prefix only where no later command consumes it as authority.
This is a larger saving than removing the two dash characters from every option. Cryptic flags
can increase correction turns and negate their tiny lexical saving.
The executable hook source is cooperative infrastructure, not a security boundary: a writable
clone contains a writable copy, and hook trust is hash-pinned by each harness. Before deploying
incompatible database schemas, add explicit versioned migrations with backup/rollback and package
one immutable coordinator runtime through Home Manager. That packaging can use Python or Go; it
does not require a language rewrite.
## Third-party adjudication
The current system is intentionally small and local. MCP Agent Mail may later add a searchable
mail UI, and `tmux-agent-status` may add an operator sidebar, but neither is authoritative for
leases. Beads adds a useful task DAG at the cost of Dolt; Gas Town and Agent of Empires are
Git-worktree-oriented and too invasive as the coordination substrate. Any pilot must compose with,
not replace, the transactional resource and isolated-clone boundary.

25
README.md Normal file
View file

@ -0,0 +1,25 @@
# coord
Harness-neutral coordination for Claude Code, Codex, and other repository agents.
The standalone project is the source of truth. Install a vendored copy into another repository:
```sh
./install /path/to/repository --project-id my-logical-project
```
An installation provides:
- `.coord/` — canonical CLI, hooks, runtime modules, installer, design, and immutable project ID.
- `.claude/coord/` — compatibility shims for existing sessions and old instructions.
- merged `.claude/settings.json` and `.codex/hooks.json` entries pointing at `.coord/`.
Upgrade an existing installation with the same command. Existing project IDs are preserved and a
conflicting requested ID is refused. Check drift without writing:
```sh
./install /path/to/repository --check
```
Runtime state remains outside repositories in the private host-local SQLite database described in
[`DESIGN.md`](DESIGN.md). Installing or upgrading never copies, deletes, or rewrites that state.

1274
coord Executable file

File diff suppressed because it is too large Load diff

203
hook.py Executable file
View file

@ -0,0 +1,203 @@
#!/usr/bin/env python3
"""Fail-open recognized-write guard and transactional peer-message delivery."""
from __future__ import annotations
import importlib.util
import importlib.machinery
import hashlib
import json
import os
import pathlib
import re
import socket
import sys
import uuid
HERE = pathlib.Path(__file__).resolve().parent
CLI_SPEC = importlib.util.spec_from_loader(
"coord_cli",
importlib.machinery.SourceFileLoader("coord_cli", str(HERE / "coord")),
)
try:
cli = importlib.util.module_from_spec(CLI_SPEC)
assert CLI_SPEC.loader is not None
CLI_SPEC.loader.exec_module(cli)
except Exception as exc: # the hook boundary must fail open, including import
print(f"coord-hook: cannot load coordinator; failing open: {exc}", file=sys.stderr)
raise SystemExit(0)
PATCH_PATH = re.compile(
r"^\*\*\* (?:Add|Update|Delete) File: (.+)$|^\*\*\* Move to: (.+)$",
re.MULTILINE,
)
MAX_MESSAGES = 5
def allow() -> None:
raise SystemExit(0)
def runtime_for(db: object, event: dict) -> str:
agent_id = str(event.get("agent_id") or "")
conversation = str(event.get("session_id") or "")
if agent_id and conversation:
sub_conversation = f"{conversation}:agent:{agent_id}"
exact = str(
uuid.uuid5(
uuid.NAMESPACE_URL, f"fleet-agent-coord:{sub_conversation}"
)
)
try:
return db.resolve_runtime(exact)
except KeyError:
return db.register_instance(
sub_conversation,
runtime_id=exact,
harness="claude-code-subagent",
host=socket.gethostname(),
cwd=str(project_root()),
label=f"subagent:{agent_id}",
)
exact = os.environ.get("COORD_INSTANCE_ID", "")
return db.resolve_runtime(exact or event.get("session_id", ""))
def project_root() -> pathlib.Path:
"""Return the checkout root; the override exists for packaged tests/tools."""
override = os.environ.get("COORD_PROJECT_ROOT", "")
return pathlib.Path(override).resolve() if override else cli.ROOT
def relative_path(raw: str, cwd: pathlib.Path, root: pathlib.Path) -> str | None:
candidate = pathlib.Path(raw)
if not candidate.is_absolute():
candidate = cwd / candidate
try:
return candidate.resolve(strict=False).relative_to(root).as_posix()
except ValueError:
return None
def paths_from(event: dict) -> list[str]:
tool_input = event.get("tool_input") or {}
cwd = pathlib.Path(event.get("cwd") or os.getcwd()).resolve()
root = project_root()
raw_paths: list[str] = []
direct = tool_input.get("file_path") or tool_input.get("path")
if isinstance(direct, str):
raw_paths.append(direct)
body = tool_input.get("command") or tool_input.get("input") or ""
if isinstance(body, str):
for match in PATCH_PATH.finditer(body):
raw_paths.append(match.group(1) or match.group(2))
normalized = (relative_path(path.strip(), cwd, root) for path in raw_paths)
return list(dict.fromkeys(path for path in normalized if path))
def output_context(messages: list[object]) -> None:
lines = []
for item in messages:
sender = cli.runtime_handle(item.sender_runtime_id)
if item.state == "delivered":
digest = hashlib.sha256(item.body.encode("utf-8")).hexdigest()[:10]
lines.append(
f"[{item.message_id}] {sender} pending again "
f"({item.kind}, sha256:{digest}); read with `coord inbox --peek`"
)
else:
lines.append(
f"[{item.message_id}] {sender} ({item.kind}): {item.body}"
)
print(
json.dumps(
{
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"additionalContext": (
"Messages from peer agent runtimes:\n"
+ "\n".join(lines)
+ "\nDelivery repeats until explicitly acknowledged with "
"`coord ack <message-id>`."
),
}
}
)
)
def main() -> None:
try:
event = json.loads(sys.stdin.read())
except Exception:
allow()
db = cli.store()
owner = runtime_for(db, event)
db.heartbeat(
owner,
ttl_seconds=cli.storelib.DEFAULT_INSTANCE_TTL,
renew_lease_ttl_seconds=cli.storelib.DEFAULT_LEASE_TTL,
)
managed_workspace = db.is_managed_workspace(project_root())
leases = db.active_leases()
for path in paths_from(event):
owner_has_lease = False
for lease in leases:
if (
lease["runtime_id"] == owner
and lease["mode"] == "exclusive"
and db.resources_overlap(path, lease["path"])
):
owner_has_lease = True
if lease["runtime_id"] == owner or lease["mode"] != "exclusive":
continue
if not db.resources_overlap(path, lease["path"]):
continue
print(
json.dumps(
{
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "deny",
"permissionDecisionReason": (
f"{path} is leased exclusively by "
f"{cli.runtime_handle(lease['runtime_id'])} for "
f"{lease['purpose'] or 'unstated'}. Coordinate before editing."
),
}
}
)
)
return
if not managed_workspace and not owner_has_lease:
print(
json.dumps(
{
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "deny",
"permissionDecisionReason": (
f"{path} is in the canonical integration checkout. "
"Writable work must use a managed isolated clone, or "
"the integration owner must claim this exact resource."
),
}
}
)
)
return
pending = db.inbox(owner, limit=MAX_MESSAGES, mark_delivered=True)
if pending:
output_context(pending)
if __name__ == "__main__":
try:
main()
except SystemExit:
raise
except Exception as exc: # fail open is load-bearing
print(f"coord-hook: failing open: {exc}", file=sys.stderr)
raise SystemExit(0)

303
install Executable file
View file

@ -0,0 +1,303 @@
#!/usr/bin/env python3
"""Install or verify a vendored, harness-neutral coordinator."""
from __future__ import annotations
import argparse
import json
import os
from pathlib import Path
import re
import secrets
import stat
import sys
from typing import Any
HERE = Path(__file__).resolve().parent
CORE_FILES = (
".gitignore",
"DESIGN.md",
"README.md",
"coord",
"hook.py",
"install",
"session.py",
"store.py",
"workspace.py",
)
EXECUTABLES = {"coord", "install"}
PROJECT_ID_RE = re.compile(r"[A-Za-z0-9][A-Za-z0-9._-]{0,127}")
CLAUDE_COMMANDS = {
"SessionStart": ('python3 "$CLAUDE_PROJECT_DIR/.coord/session.py"', 10),
"SessionEnd": ('python3 "$CLAUDE_PROJECT_DIR/.coord/session.py"', 5),
"PreToolUse": ('python3 "$CLAUDE_PROJECT_DIR/.coord/hook.py"', 10),
}
CODEX_DISCOVERY = (
'root="$PWD"; while [ "$root" != / ] && [ ! -f '
'"$root/.coord/{name}.py" ]; do root="${{root%/*}}"; '
'[ -n "$root" ] || root=/; done; '
'[ -f "$root/.coord/{name}.py" ] || exit 0; '
'python3 "$root/.coord/{name}.py"'
)
def legacy_wrapper(name: str) -> str:
if name == "coord":
return """#!/usr/bin/env python3
import os
from pathlib import Path
import sys
target = Path(__file__).resolve().parents[2] / ".coord" / "coord"
os.execv(str(target), [str(target), *sys.argv[1:]])
"""
if name in {"hook.py", "session.py"}:
return f"""#!/usr/bin/env python3
from pathlib import Path
import runpy
target = Path(__file__).resolve().parents[2] / ".coord" / "{name}"
runpy.run_path(str(target), run_name="__main__")
"""
if name in {"store.py", "workspace.py"}:
module = f"_coord_{name.removesuffix('.py')}_compat"
return f"""from importlib.util import module_from_spec, spec_from_file_location
from pathlib import Path
import sys
target = Path(__file__).resolve().parents[2] / ".coord" / "{name}"
spec = spec_from_file_location("{module}", target)
assert spec is not None and spec.loader is not None
implementation = module_from_spec(spec)
sys.modules[spec.name] = implementation
spec.loader.exec_module(implementation)
globals().update({{
key: value for key, value in vars(implementation).items()
if key not in {{"__name__", "__loader__", "__package__", "__spec__"}}
}})
"""
raise ValueError(f"unsupported compatibility wrapper: {name}")
def read_json(path: Path) -> dict[str, Any]:
if not path.exists():
return {}
try:
value = json.loads(path.read_text(encoding="utf-8"))
except (OSError, json.JSONDecodeError) as exc:
raise ValueError(f"refusing invalid JSON at {path}: {exc}") from exc
if not isinstance(value, dict):
raise ValueError(f"refusing non-object JSON at {path}")
return value
def is_coord_hook(item: object) -> bool:
return isinstance(item, dict) and isinstance(item.get("command"), str) and (
".coord/" in item["command"] or ".claude/coord/" in item["command"]
)
def merged_hooks(
current: dict[str, Any], desired: dict[str, tuple[str, int]]
) -> dict[str, Any]:
result = json.loads(json.dumps(current))
hooks = result.setdefault("hooks", {})
if not isinstance(hooks, dict):
raise ValueError("refusing hook configuration whose 'hooks' value is not an object")
for event, (command, timeout) in desired.items():
existing = hooks.get(event, [])
if not isinstance(existing, list):
raise ValueError(f"refusing hook event {event!r} whose value is not a list")
retained = []
for group in existing:
if not isinstance(group, dict):
retained.append(group)
continue
entries = group.get("hooks")
if not isinstance(entries, list):
retained.append(group)
continue
kept_entries = [entry for entry in entries if not is_coord_hook(entry)]
if kept_entries:
kept_group = dict(group)
kept_group["hooks"] = kept_entries
retained.append(kept_group)
installed: dict[str, Any] = {
"hooks": [{"type": "command", "command": command, "timeout": timeout}]
}
if event == "PreToolUse":
installed["matcher"] = ".*"
retained.append(installed)
hooks[event] = retained
return result
def codex_hooks(current: dict[str, Any]) -> dict[str, Any]:
desired = {
event: (
CODEX_DISCOVERY.format(
name="hook" if event == "PreToolUse" else "session"
),
timeout,
)
for event, (_command, timeout) in CLAUDE_COMMANDS.items()
}
return merged_hooks(current, desired)
def write_if_changed(
path: Path,
content: bytes,
*,
executable: bool = False,
check: bool,
drift: list[str],
) -> None:
observed = path.read_bytes() if path.is_file() else None
expected_mode = 0o755 if executable else 0o644
observed_executable = path.is_file() and bool(
stat.S_IMODE(path.stat().st_mode) & stat.S_IXUSR
)
mode_ok = path.is_file() and observed_executable == executable
if observed == content and mode_ok:
return
drift.append(str(path))
if check:
return
path.parent.mkdir(parents=True, exist_ok=True)
temporary = path.with_name(f".{path.name}.coord-new-{secrets.token_hex(6)}")
fd = os.open(temporary, os.O_WRONLY | os.O_CREAT | os.O_EXCL, expected_mode)
try:
os.write(fd, content)
os.fsync(fd)
finally:
os.close(fd)
os.replace(temporary, path)
os.chmod(path, expected_mode)
def project_id(target: Path, requested: str, *, check: bool) -> str:
canonical = target / ".coord" / "project-id"
legacy = target / ".claude" / "coord" / "project-id"
existing = []
for path in (canonical, legacy):
if path.is_file():
existing.append((path, path.read_text(encoding="utf-8").strip()))
values = {value for _path, value in existing}
if len(values) > 1:
detail = ", ".join(f"{path}={value!r}" for path, value in existing)
raise ValueError(f"refusing ambiguous existing project IDs: {detail}")
selected = next(iter(values), "")
if requested and selected and requested != selected:
raise ValueError(
f"refusing project ID change from {selected!r} to {requested!r}"
)
selected = selected or requested
if not selected:
if check:
raise ValueError("installation has no project ID")
raise ValueError("new installation requires --project-id")
if not PROJECT_ID_RE.fullmatch(selected) or ".." in selected:
raise ValueError("project ID must be one safe immutable slug")
return selected
def install(target: Path, *, requested_id: str, check: bool) -> list[str]:
target = target.expanduser().resolve()
if not target.is_dir():
raise ValueError(f"target is not a directory: {target}")
selected_id = project_id(target, requested_id, check=check)
drift: list[str] = []
destination = target / ".coord"
for name in CORE_FILES:
write_if_changed(
destination / name,
(HERE / name).read_bytes(),
executable=name in EXECUTABLES,
check=check,
drift=drift,
)
write_if_changed(
destination / "project-id",
f"{selected_id}\n".encode(),
check=check,
drift=drift,
)
legacy = target / ".claude" / "coord"
for name in ("coord", "hook.py", "session.py", "store.py", "workspace.py"):
write_if_changed(
legacy / name,
legacy_wrapper(name).encode(),
executable=name == "coord",
check=check,
drift=drift,
)
write_if_changed(
legacy / "project-id",
f"{selected_id}\n".encode(),
check=check,
drift=drift,
)
write_if_changed(
legacy / "DESIGN.md",
b"Canonical design: [`../../.coord/DESIGN.md`](../../.coord/DESIGN.md).\\n",
check=check,
drift=drift,
)
write_if_changed(
legacy / ".gitignore",
b"state/\\n__pycache__/\\n",
check=check,
drift=drift,
)
settings = target / ".claude" / "settings.json"
expected_claude = (
json.dumps(
merged_hooks(read_json(settings), CLAUDE_COMMANDS),
indent=2,
sort_keys=True,
)
+ "\n"
).encode()
write_if_changed(settings, expected_claude, check=check, drift=drift)
codex = target / ".codex" / "hooks.json"
expected_codex = (
json.dumps(codex_hooks(read_json(codex)), indent=2, sort_keys=True) + "\n"
).encode()
write_if_changed(codex, expected_codex, check=check, drift=drift)
return drift
def main() -> int:
parser = argparse.ArgumentParser(prog="coord-install")
parser.add_argument("target", nargs="?", default=".")
parser.add_argument("--project-id", default="")
parser.add_argument("--check", action="store_true")
args = parser.parse_args()
try:
drift = install(
Path(args.target), requested_id=args.project_id, check=args.check
)
except ValueError as exc:
print(f"coord-install: {exc}", file=sys.stderr)
return 2
if args.check:
if drift:
print(f"coord-install: drift ({len(drift)} files)")
for path in drift:
print(f" {path}")
return 1
print("coord-install: installation matches")
return 0
print(f"coord-install: {'updated' if drift else 'already current'}")
print(f"target {Path(args.target).expanduser().resolve()}")
return 0
if __name__ == "__main__":
raise SystemExit(main())

1
project-id Normal file
View file

@ -0,0 +1 @@
coord-tooling

172
session.py Executable file
View file

@ -0,0 +1,172 @@
#!/usr/bin/env python3
"""Best-effort session lifecycle bridge for Claude Code and Codex hooks."""
from __future__ import annotations
import importlib.util
import importlib.machinery
import hashlib
import json
import os
import pathlib
import socket
import sqlite3
import subprocess
import sys
import uuid
HERE = pathlib.Path(__file__).resolve().parent
SPEC = importlib.util.spec_from_loader(
"coord_cli",
importlib.machinery.SourceFileLoader("coord_cli", str(HERE / "coord")),
)
cli = importlib.util.module_from_spec(SPEC)
assert SPEC.loader is not None
SPEC.loader.exec_module(cli)
def annotate_tmux(runtime_id: str, conversation_id: str) -> None:
pane = os.environ.get("TMUX_PANE")
if not pane:
return
for key, value in (
("@coord_instance", runtime_id),
("@coord_conversation", conversation_id),
("@coord_task", os.environ.get("FLEET_AI_TASK", "")),
):
subprocess.run(
["tmux", "set-option", "-p", "-q", "-t", pane, key, value],
check=False,
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
)
def subagent_identity(conversation_id: str, agent_id: str) -> tuple[str, str]:
"""Derive the stable conversation/runtime pair shared by all hook processes."""
conversation = f"{conversation_id}:agent:{agent_id}"
runtime = str(uuid.uuid5(uuid.NAMESPACE_URL, f"fleet-agent-coord:{conversation}"))
return conversation, runtime
def main() -> None:
event = json.loads(sys.stdin.read())
conversation = event.get("session_id") or ""
if not conversation:
return
agent_id = str(event.get("agent_id") or "")
db = cli.store()
name = event.get("hook_event_name") or ""
requested = os.environ.get("COORD_INSTANCE_ID", "")
if agent_id:
conversation, requested = subagent_identity(conversation, agent_id)
harness = (
"codex"
if os.environ.get("CODEX_THREAD_ID")
else "claude-code"
)
if name == "SessionEnd":
# Without a launcher-provided runtime UUID, conversation identity is
# insufficient: a stale process could otherwise end a newer resume.
if not requested:
return
try:
db.end_instance(db.resolve_runtime(requested), reason="clean")
except KeyError:
pass
return
try:
# A direct, unwrapped harness cannot communicate a newly generated UUID
# to later hook processes. Reuse its one live conversation attachment;
# managed launchers always provide a fresh exact runtime.
if not requested:
runtime_id = db.resolve_runtime(conversation)
db.heartbeat(
runtime_id,
renew_lease_ttl_seconds=cli.storelib.DEFAULT_LEASE_TTL,
)
else:
runtime_id = db.register_instance(
conversation,
runtime_id=requested,
harness=harness,
host=socket.gethostname(),
cwd=str(pathlib.Path(event.get("cwd") or os.getcwd()).resolve()),
label=os.environ.get("FLEET_AI_TASK", ""),
)
except (KeyError, sqlite3.IntegrityError):
if not requested:
runtime_id = db.register_instance(
conversation,
harness=harness,
host=socket.gethostname(),
cwd=str(pathlib.Path(event.get("cwd") or os.getcwd()).resolve()),
label=os.environ.get("FLEET_AI_TASK", ""),
)
else:
runtime_id = db.resolve_runtime(requested)
db.heartbeat(
runtime_id,
renew_lease_ttl_seconds=cli.storelib.DEFAULT_LEASE_TTL,
)
annotate_tmux(runtime_id, conversation)
peers = [item for item in db.live_instances() if item["runtime_id"] != runtime_id]
leases = [item for item in db.active_leases() if item["runtime_id"] != runtime_id]
pending = db.inbox(runtime_id, limit=5, mark_delivered=True)
if not peers and not pending:
return
lines: list[str] = []
if peers:
lines.append(f"Live peer agents: {len(peers)}")
for peer in peers:
lines.append(
f" · {cli.runtime_handle(peer['runtime_id'])} "
f"{peer['harness']} task={cli.peer_label(peer)}"
)
if leases:
lines.append("Active peer resource leases:")
for lease in leases:
lines.append(
f" · {lease['path']} [{lease['mode']}] by "
f"{cli.runtime_handle(lease['runtime_id'])}{lease['purpose']}"
)
if pending:
lines.append("Pending durable peer messages:")
for message in pending:
sender = cli.runtime_handle(message.sender_runtime_id)
if message.state == "delivered":
digest = hashlib.sha256(message.body.encode("utf-8")).hexdigest()[:10]
lines.append(
f" · [{message.message_id}] {sender} pending again "
f"({message.kind}, sha256:{digest}); `coord inbox --peek`"
)
else:
lines.append(
f" · [{message.message_id}] {sender} "
f"({message.kind}): {message.body}"
)
lines.append(
"Use an isolated managed clone for writable work; claim shared integration, "
"push, activation, and deployment resources before those operations."
)
print(
json.dumps(
{
"hookSpecificOutput": {
"hookEventName": "SessionStart",
"additionalContext": "\n".join(lines),
}
}
)
)
if __name__ == "__main__":
try:
main()
except Exception as exc: # lifecycle hooks must fail open
print(f"coord-session: failing open: {exc}", file=sys.stderr)
raise SystemExit(0)

1139
store.py Executable file

File diff suppressed because it is too large Load diff

249
tests/test_install.py Executable file
View file

@ -0,0 +1,249 @@
#!/usr/bin/env python3
"""Behavioral tests for fresh install and legacy in-place migration."""
from __future__ import annotations
import importlib.util
import json
import os
from pathlib import Path
import subprocess
import sys
import tempfile
import unittest
ROOT = Path(__file__).resolve().parents[1]
INSTALL = ROOT / "install"
class InstallBehavior(unittest.TestCase):
def run_install(
self, target: Path, *args: str
) -> subprocess.CompletedProcess[str]:
return subprocess.run(
[sys.executable, str(INSTALL), str(target), *args],
cwd=ROOT,
text=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
)
def test_fresh_install_requires_explicit_immutable_project_id(self) -> None:
with tempfile.TemporaryDirectory() as temporary:
target = Path(temporary)
missing = self.run_install(target)
self.assertEqual(missing.returncode, 2)
self.assertIn("--project-id", missing.stderr)
installed = self.run_install(target, "--project-id", "fresh-project")
self.assertEqual(installed.returncode, 0, installed.stderr)
self.assertEqual(
(target / ".coord/project-id").read_text().strip(),
"fresh-project",
)
checked = self.run_install(target, "--check")
self.assertEqual(checked.returncode, 0, checked.stdout + checked.stderr)
def test_legacy_upgrade_preserves_hooks_identity_and_both_entrypoints(self) -> None:
with tempfile.TemporaryDirectory() as temporary:
target = Path(temporary)
legacy = target / ".claude/coord"
legacy.mkdir(parents=True)
(legacy / "project-id").write_text("legacy-project\n")
(target / ".claude/settings.json").write_text(
json.dumps(
{
"effortLevel": "high",
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "printf unrelated",
"timeout": 2,
},
{
"type": "command",
"command": "python3 .claude/coord/hook.py",
"timeout": 10,
},
],
}
]
},
}
)
)
(target / ".codex").mkdir()
(target / ".codex/hooks.json").write_text(
json.dumps(
{
"hooks": {
"SessionStart": [
{
"hooks": [
{
"type": "command",
"command": "python3 .claude/coord/session.py",
"timeout": 10,
}
]
}
]
}
}
)
)
installed = self.run_install(target)
self.assertEqual(installed.returncode, 0, installed.stderr)
self.assertEqual(
(target / ".coord/project-id").read_text().strip(),
"legacy-project",
)
self.assertEqual(
(target / ".claude/coord/project-id").read_text().strip(),
"legacy-project",
)
settings = json.loads((target / ".claude/settings.json").read_text())
self.assertEqual(settings["effortLevel"], "high")
commands = [
hook["command"]
for group in settings["hooks"]["PreToolUse"]
for hook in group["hooks"]
]
self.assertIn("printf unrelated", commands)
self.assertEqual(
sum("/.coord/hook.py" in command for command in commands), 1
)
self.assertFalse(any(".claude/coord/" in command for command in commands))
canonical = subprocess.run(
[str(target / ".coord/coord"), "--help"],
cwd="/",
text=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
)
compatibility = subprocess.run(
[str(target / ".claude/coord/coord"), "--help"],
cwd="/",
text=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
)
self.assertEqual(canonical.returncode, 0, canonical.stderr)
self.assertEqual(compatibility.returncode, canonical.returncode)
self.assertEqual(compatibility.stdout, canonical.stdout)
store_path = target / ".claude/coord/store.py"
spec = importlib.util.spec_from_file_location("legacy_store_test", store_path)
assert spec is not None and spec.loader is not None
module = importlib.util.module_from_spec(spec)
sys.modules[spec.name] = module
spec.loader.exec_module(module)
self.assertEqual(
module.friendly_handle("identity", "runtime").count("-"), 4
)
conflicting = self.run_install(
target, "--project-id", "different-project"
)
self.assertEqual(conflicting.returncode, 2)
self.assertIn("refusing project ID change", conflicting.stderr)
(target / ".coord/coord").write_text("# drift\n")
drift = self.run_install(target, "--check")
self.assertEqual(drift.returncode, 1)
self.assertIn("drift", drift.stdout)
repaired = self.run_install(target)
self.assertEqual(repaired.returncode, 0, repaired.stderr)
final = self.run_install(target, "--check")
self.assertEqual(final.returncode, 0, final.stdout + final.stderr)
def test_installed_cli_smokes_hello_claim_and_work_create(self) -> None:
with tempfile.TemporaryDirectory() as temporary:
root = Path(temporary)
target = root / "project"
subprocess.run(
["jj", "git", "init", "--colocate", str(target)],
text=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
check=True,
)
subprocess.run(
["jj", "-R", str(target), "bookmark", "create", "master", "-r", "@"],
text=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
check=True,
)
installed = self.run_install(
target, "--project-id", "smoke-project"
)
self.assertEqual(installed.returncode, 0, installed.stderr)
cli = target / ".coord/coord"
env = {
**os.environ,
"COORD_DB": str(root / "state/coord.sqlite3"),
"COORD_PROJECT_ID": "smoke-project",
}
hello = subprocess.run(
[
str(cli),
"hello",
"--conversation",
"smoke-conversation",
"--harness",
"test",
"--cwd",
str(target),
],
cwd=target,
env=env,
text=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
)
self.assertEqual(hello.returncode, 0, hello.stderr)
runtime = hello.stdout.strip()
claimed = subprocess.run(
[str(cli), "-i", runtime, "claim", "smoke/resource"],
cwd="/",
env=env,
text=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
)
self.assertEqual(claimed.returncode, 0, claimed.stderr)
created = subprocess.run(
[
str(target / ".claude/coord/coord"),
"-i",
runtime,
"work",
"create",
"smoke-work",
"--source",
str(target),
"--scratch-root",
str(root / "scratch"),
"--json",
],
cwd="/",
env=env,
text=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
)
self.assertEqual(created.returncode, 0, created.stderr)
record = json.loads(created.stdout)
self.assertEqual(record["task"], "smoke-work")
self.assertTrue(Path(record["actual_path"]).is_dir())
if __name__ == "__main__":
unittest.main(verbosity=2)

1186
workspace.py Executable file

File diff suppressed because it is too large Load diff