agent-mesh
CLAUDE.md0 starsChanged 16 days agoMITView on GitHub
agent-mesh lets an agent working in repo A hand repo B's part of a change to an agent running inside B (with B's CLAUDE.md/AGENTS.md, skills and hooks loaded), in a visible herdr pane with two-way messaging. README.md is the user-facing pitch and docs/guide.md the full reference; docs/design.md records why each decision was made and the alternatives that lost: read its section before changing behaviour. Open work is tracked in TODO.md, as deliberately short summaries: the agent doing a task works…
In this repository7 other files it gives its agents.
- check-planSkill
- delegate-agySkill
- delegate-claudeSkill
- delegate-reviewSkill
- find-peersSkill
- harvest-peersSkill
- make-conciseSkill
The file
We split this CLAUDE.md into the rules it gives an agent
3 rules. About 0 are common, 1 is distinctive, and 2 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈1
Distinctive · estimate
This author's own choices. Worth a look.
2
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Distinctive: line 11
Keep it current in the same change: remove (or tick) a task when the work lands, reword it when scope changes, and add a line for any …
- Very specific: line 88
Keep README, docs/guide.md, ORCHESTRATE.md, the skill descriptions and docs/design.md in sync when defaults (models, efforts, exit codes) …
- Very specific: line 89
Keep this file at most 24,000 bytes: agy truncates a rule file past that, and AGENTS.md is a symlink to it (tests/test_docs.py checks both).
≈ “Common” is an estimate: at least 1 in 5 of the 26,980 CLAUDE.md files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
CLAUDE.md / AGENTS.md
This file provides guidance to Claude Code (claude.ai/code) and Antigravity (agy) when working with code in this repository.
agent-mesh lets an agent working in repo A hand repo B's part of a change to an agent running inside B (with B's CLAUDE.md/AGENTS.md, skills and hooks loaded), in a visible herdr pane with two-way messaging. README.md is the user-facing pitch and docs/guide.md the full reference; docs/design.md records why each decision was made and the alternatives that lost: read its section before changing behaviour.
Task tracking
Open work is tracked in TODO.md, as deliberately short summaries: the agent doing a task works out the details itself. Check it before starting something that might already be tracked.
Keep it current in the same change: remove (or tick) a task when the work lands, reword it when scope changes, and add a line for any follow-up you discover but don't do now.
Peer projects
If mesh peers lists them, the orchestrators group is this repo's peers: agent orchestration tools like agent-mesh, cloned under ~/research/orchestrators. They are standing read-only askers that keep their context between asks; mesh peers shows each one's about. Ask them before designing a mechanism they may have solved, and for any Research: task in TODO.md: all at once with mesh ask --peers orchestrators --file q.md (one reports/peers-<stamp>.md), or one with mesh ask <name>-research --body "…".
agents-can-communicate(askeragents-can-communica-research): cross-vendor messages between sessions the user opened, no lead agent. Claude's inbox socket, agy native delivery.ccswarm: headless one-shot agents driven by a YAML flow state machine, persona quorum votes as gates. Flow/stage design, quorum review, loop caps.cyclops: a daemon watching agents in tmux, fused state signals, durable mailboxes with one doorbell per message. Delivery into a human's draft.firstmate: a bash supervisor over herdr panes (watcher, turn-end hooks, leases, caps). Its failure modes: hung agents, stale herdr registrations, trust dialogs.gastown: patrol agents that respawn workers, a merge queue, convoys. Stop-hook guards, nudge queues, auto-restart, mostly as lessons in what broke.mcp-agent-mail: an MCP server with mailboxes, identities and file leases. Inbox hooks, leases, two-store drift.orca: tmux plus raw typing, roles from env vars. Guarding a user's draft, spawning panes on demand.repowire: a Go daemon meshing live sessions: asks with acks and reminders, Claude's inbox socket, identity across reconnects.rove: a daemon and PTY host owning every engine, flat dispatch with lineage. Delivery, matching reports to tasks.tmux-orchestrator: an archived orchestrator → manager → engineer tree oversend-keys. Mostly a contrast.
Commands
uvx pytest -q tests # full suite
uvx pytest -q tests -k approve # tests matching a name
uvx pytest -q tests/test_mesh.py::test_name # one test
./install.sh # symlink into place (--uninstall reverses it); safe to re-runTo try bin/mesh by hand (reviewing a change, say), point MESH_HOME, MESH_WORKTREES and MESH_PEERS_DIR at temp dirs, MESH_CONFIG_FILE at a temp file, MESH_HERDR at a missing binary and MESH_CLAUDE at one that does nothing (/bin/false: a missing one makes Claude count as not installed), as tests/conftest.py does: the defaults are the machine's real run state and herdr's real panes, which every agent uses. A worker that forgets is refused every command but its own (check_worker).
There is no build or lint step: bin/mesh is one Python 3.10+ script with no dependencies. Keep it stdlib-only.
The installed mesh, ~/.config/agent-mesh and the skills are symlinks to this checkout: every agent on the machine runs whatever branch is checked out.
Layout and how the pieces fit
bin/mesh: the whole CLI (argparse in the# ── CLIsection, onecmd_*per subcommand). Sections are marked with# ── name ──comments: paths, run state, identity, herdr topology, models, sessions, worktrees, messaging, commands, past workers.protocol/: Markdown the agents follow, installed as~/.config/agent-mesh:ORCHESTRATE.mdthe root/worker procedure (and the mode table),PROTOCOL.mdthe messaging rules every mesh agent reads,PLAN_TEMPLATE.mdthe plan format. Agents do the orchestration by following these files, not code, so a behaviour change usually needs both abin/meshchange and a protocol/doc change.skills/delegate-agy,skills/delegate-claude: the/delegate-*entry points, typed by the user or invoked by an agent (never a worker) when a request fits the description. They point the root atORCHESTRATE.md.skills/delegate-review: a PR review the user asks for, by any mix of Claude, agy and Copilot (orall), run as one review round (mesh ask --reviewers). It reads only ORCHESTRATE's "Questions and reviews".skills/find-peers: finds projects that share a repo's core mechanisms, ranks them by overlap, and on the user's yes registers them as peers (mesh peers add), unbriefed. Root only, any repo.skills/harvest-peers: a research round by the peers (first brief or follow-up fromHARVEST.md, with bug-hunt targets), every finding checked in our code, survivors filed in the tracker on the user's yes. Never run byfind-peers. Root only.skills/make-concise: rewrites a repo's rules files concisely without dropping a rule, run by that repo's own agent;validate.py(stdlib, tested intests/test_make_concise.py) fails on a lost fence, marked block, code span, URL, path or@import, or a rules file over 24,000 bytes.skills/check-plan: a worker's self-check of its own draft plan (ORCHESTRATE Worker step 4): verify its claims in the code, attack it, fix the plan. Edits only the plan file, so it runs under the planning deny list; usable by hand in any session..claude-plugin/: the repo as a Claude Code marketplace of one plugin (source: "./") that shipsskills/; noversion, so users track commits. Skills only: the CLI and~/.config/agent-meshstill needinstall.sh(it skips the~/.claude/skillslinks when the plugin is installed, or with--plugin).tests/test_plugin.pychecks it ships exactlySKILLS(design §16).rules/global-snippet.md: the blockinstall.shwrites between markers into~/.claude/CLAUDE.mdand~/.gemini/AGENTS.md.herdr/mesh-notify: a herdr plugin (install.shlinks it) that sends a desktop notification when a root or a plain session isdoneorblocked, never a worker, and a root'sdoneonly once no worker owes it a report (design §20). It readsrun.jsonandlog.jsonldirectly and mirrorsRECEIVED_TYPES/REPORT_TYPES(tests/test_notify.pychecks they match).
Core mechanics that span several files: each bullet is the gist and the names to start from. docs/design.md has the why (by section) and, in "Code map", each one's functions. Change a mechanism, update both.
- Run state lives in
$MESH_HOME/<run>/(default~/.local/state/agent-mesh):run.json(agents, models history, worktrees), per-agent mailboxes,log.jsonlevents, and theRUN_FOLDERSagents write into (plans/,reports/,tasks/). Mutate it only throughediting_run(run_id)(locked, atomic write);editing_run(run_id, folder)edits an archived run. - Mailbox plus doorbell (design §3):
mesh sendwrites the message file first, thendelivertypes a one-line📬 [mesh]doorbell into the recipient's pane viaherdr agent prompt(ring_stored), retrying until its fresh(ref <token>)shows above the input box, and never into a human's draft (human_draft, per-panering_lock). A lost doorbell loses nothing.mainrunscheck_workerfirst: a worker of a live run runs onlyWORKER_COMMANDSand sends only to its parent (design §10). - Native transport (design §11):
meshwrites a Claude recipient's doorbell straight into its inbox socket (write_inbox), whennative_routefinds one that accepts it: a worker mesh launched accepting cross-session messages (worker_settingspassescrossSessionInbound: accept), or a root that takes unattested frames (inbound_setting,root_class).send_message(a sender'sSendMessage) remains only for a Claude sender reaching a bypass-mode root or a background worker launched before that flag; anything else rings the pane. A Claude pane worker that something else relaunched (a herdr restore,relaunched_outside_mesh) is reopened with its deny list before any doorbell (reopen_relaunched). The mailbox stays the record either way. - Views and sizes (design §9, §11): a worker runs in a herdr pane or as a
claude --bgsession (view); a Copilot asker isheadless.SIZE_DEFAULTSmaps (kind, size) to model, effort and view; trivial and small tasks have no plan phase (DIRECT_PREFIX),largeis theautoflow. A--bgsession never sees the launch's environment, solaunch_bgpasses identity and config through--settingsenv (worker_settings), and everyclaudecall runs onclaude_env(). Read-only, peer and small workers getpromptCacheTtl: "5m"there (worker_cache_ttl). - Identity (design §4) comes from
MESH_RUN/MESH_SELF/MESH_PARENT/MESH_ROLE, injected at spawn.inherited_identitydrops one that isn't this agent's: a Claude worker is its pane's only while itsCLAUDE_PIDleads the pane's process group (pane_runs_this_claude), andpane_workerfinds one a herdr restore relaunched withoutMESH_*. Agents are addressed by herdr name (<repo>-lead,<repo>-<slug>), with pane id as fallback. - Topology: a root supervises; one worker per repo, its own included, in its own git worktree under
$MESH_WORKTREESon branchmesh/<run>/<slug>. Workers never spawn agents (1:N, not 1:N:N; see design §7). - Policy (design §21): models, efforts and views per kind and size, reviewer slots, re-review rules, rounds, the tiny-PR lines,
quota_stop,peers_dir,repos_dir,skip_permissions,worker_cpus,memory_pressure_max,worker_memory_high,min_free_mb,idle_retire,hang_after,held_wait,side_panes,compact.*androot_reset.*arePOLICYleaves, read withsetting()(env >~/.config/agent-mesh.jsonorMESH_CONFIG_FILE> built-in; never the old constants directly) and printed bymesh routing. Docs name the leaves, not their values.installed/require_clikeep mesh off a CLI that isn't on PATH;pick_reviewersfills the slots from the available ones. Themeshfixture pointsMESH_CONFIG_FILEattmp_pathand makes every CLI look installed (with_config,clisinconftest.pychange that). - Models per phase (design §9):
--model autoplans on the first model inPLAN_CHAINwith quota, thenmesh approvecallsswap_model, which reopens the same conversation onEXEC_MODELatEXEC_EFFORT.mesh watchexits 7 on a quota error; the root then runsmesh switch. The quota reserve (reserve_guard,quota_stop,claude_usage,reserve_alert) refuses new work on a vendor once a quota isMESH_QUOTA_STOP% used, untilmesh reserve off. - Questions and reviews (
mesh ask, design §10): an asker is an ordinary worker that got a read-only task (ASK_PREFIX/REVIEW_PREFIX), so follow-ups and resume need no special case;cmd_askcomposesinit_run,reach_worker,deliverandwatch_agent. A review is of one commit (review_heads), and a Claude reviewer runs/code-reviewon it at the review's size (REVIEW_LEVEL) (CODE_REVIEW,head_diff). A Claude worker that isread_onlyorplanningruns underclaude_deny's--disallowedTools;swap_model(..., lift=True)(on approve) reopens it without. - Review rounds (design §10):
ask --repo --review --reviewers auto|all|<kinds>(ask_reviewers,review_round) runs a PR's reviewers at once, picked by size and agy's quota (pick_reviewers,agy_has_quota), onask --peers's collect path (collector,watch_each,write_answers): onereports/review-<n>-<stamp>.md, one root wake. Run again after a fix, it re-asks the same reviewers minus Copilot;review.rounds.<size>is a ceiling, and its last line says which round it was (rounds_hint). - Copilot askers (design §10):
ask --kind copilotgoes toask_copilot.copilot_askrunscopilotheadless on one--session-id, holds the entry while it runs (claim_copilot,live_holder) and files the last message as itsdone;file_orphanfiles the answer a killedaskowed.refuse_headlesskeeps pane commands away from it;open_peekfollows it in a pane beside the root (beside_root,.panes.lock). - Peers (design §14):
$MESH_HOME/peers.json(load_peers,editing_peers,cmd_peers) registers standing read-only askers, one per cloned project, in detached worktrees no run'smesh stopremoves (ensure_peer_worktree). Every ask runsprepare_peer;ask --peers(ask_peers,fan_out) reaches them in threads, waits withwatch_eachand writes onereports/peers-<stamp>.md. - Resume onto a finished PR (design §8):
leave_finished_prmoves a change task's worktree to a newmesh/<run>/<slug>when no PR from its branch is open (finished_pr). - Compaction on resume (design §12):
maybe_compactruns/compact <focus>headless on the stopped conversation beforeresume_workerreopens it, whenshould_compactsays so (compact.claudewarm,compact.claude_coldoncecache_cold,compact.agy). - Root reset (design §15): root commands print
root_hintwhen the root's transcript is big and its cache cold (root_context,root_reset.at).mesh resethands off to a detachedreset_root, which waits for a quiet input box, types/clear(or/compact), confirms it (cleared_session) and ringsbootstrap_text;maybe_autoresetdoes it from the root's own watch (on by default at 200k, warm or cold, quiet 5 min after a mesh turn or 30 after a user's:last_turn_by_user).mesh briefrendersbrief(). - Worker hooks (design, "The Stop hook"): every Claude worker launch passes
worker_hooksin--settings.Stoprunsmesh hook stop(stop_hook: blocks once per task with no report,claim_nudge, and on unread mail,hook_mail; always exit 0);SessionStartrunsmesh whoami --reminder <source>. Both outputs go throughhook_text(underHOOK_OUTPUT_MAX, no wall-clock text). - Derived status (design §13):
mesh statusrecomputes what each worker owes or waits on every call (waiting_rows), needing-the-root first; nothing of it is stored.read_logskips a torn line andlog_eventseals one before appending. - Watchdog:
watch_agentspolls oneWatchper worker (mesh watch <name>, or--anywith aRoster). A worker idle with no report since its last task/answer (since_last_task) gets onenudge, then exit 3/4; 7 is a usage limit, quota or login (limited), 8 a hang (progress_filesunchanged forHANG_AFTER), 0 a stopped run. A declaredwait(declared_wait) holds idle counting off. A worker at a limit no switch fixes gets acontinue_due(schedule_continue); the watch or the retirer sendscontinueat the reset (continue_worker, design §9).mesh watch, or the run's detached retirer that a worker'sdone/failedstarts (ensure_retirer,run_retirer; it skipswatchedworkers), retires a worker idleidle_retireafter itsdone/failed(Watch.maybe_retire; not whileviewersees the user attached or its pane focused), andsend(task/answer/question; aninfois only stored, exit 2; a retired peer is refused) oraskto a retired one resume it for the stored message (resume_stored, design §18).agent_statuschecks herdr against the process table, and git reads run ongit_env()(never the worker's index lock). - Resource guards (design §17):
memory_guardrefuses a new agent (last check before the side effects ofstart_worker,reopen_worker,cmd_ask's Copilot branch) undermin_free_mb(MESH_MEMINFO) or overmemory_pressure_max(memory_pressure,MESH_PRESSURE), counting machine-widereservations.jsonentries of agents still starting (release_memoryon failure);sweep_worktrees(fromretire_workerandstop_run) TERMs then KILLs whatleftover_processesfinds in a worker's worktree: orphan trees holding no terminal or agent.confine_agent(fromstart_agent,launch_bg,copilot_ask) pins each launched agent's threads to the least-heldcpu_window(worker_cpus, holders incpus.json) and, withworker_memory_high, a systemdmemory_scope(the retirer a worker's report startsleave_scopes it); themeshfixture records pins inmesh.pins. - Spend (
mesh cost, design §19):run_costreads Claude session files (claude_calls, folded bymessage.id, line by line) and agy steps (agy_calls), places each call on an agent of a run by time (cost_segments,segment_at: a root counts only while its run is open) and prices it (PRICES,call_cost), per agent, kind and phase, with cold resumes;--since/--untilcovers every session in a window. - Conversation lookup (
find_session): Claude workers get a--session-idrecorded at spawn; agy conversations are found in agy'sconversation_summaries.db, thenhistory.jsonl, by the first doorbell text (You are <name> (worker) in agent-mesh run <run>), and recorded on the worker's firstmesh send. Changing that doorbell wording breaks resume.
Conventions the design depends on
- Model changes happen only by reopening the same conversation (approve / switch / resume), never by briefing a fresh worker, to keep the worker's research context. Reject designs that add delegation tiers or cold hand-offs.
- The mesh is process-agnostic: protocol files never name a tracker, branch convention or repo-specific process; workers follow their repo's own rules.
- Skills name model families (
argon,flash,opus,sonnet), resolved to the newest id fromagy models; don't hard-code model ids. mesh resume/mesh openrefuse a worker that is still running (one process per conversation);openattaches to a running background worker instead.mesh stopstops background workers (neverclaude rm), removes only clean worktrees and keeps every branch that has a commit or an upstream (an untouched one is deleted and recreated from its recordedbaseon resume).- Re-run
./install.shonce a change that needs it is on the checked-out branch (merged tomain): a change torules/global-snippet.md(it is copied into~/.claude/CLAUDE.mdand~/.gemini/AGENTS.md, not linked), a skill added, removed or renamed (updateSKILLS/RETIRED_SKILLSand the plugin's description in.claude-plugin/marketplace.json), the herdr plugin moved, orinstall.shitself changed. An edit inside a linked file (bin/mesh,protocol/, a skill) needs no re-run. - Keep README,
docs/guide.md,ORCHESTRATE.md, the skill descriptions anddocs/design.mdin sync when defaults (models, efforts, exit codes) change. - Keep this file at most 24,000 bytes: agy truncates a rule file past that, and
AGENTS.mdis a symlink to it (tests/test_docs.pychecks both). Put detail indocs/design.md(function names in its "Code map") and a gist here.
Tests
tests/conftest.py loads bin/mesh as a module (mesh fixture), points MESH_HOME/MESH_WORKTREES at tmp_path, and replaces mesh.herdr with FakeHerdr (fake fixture): register answers with fake.on("agent", "get", result=...) / error=..., assert with fake.called(...); fake.screen(target, text, …) is what herdr agent read prints (one text per read, the last repeated). The run fixture gives an initialised run mtest with root pane w1:p1 and a worker repo-fix-typo. Git worktree tests use real temp repos. Tests must never run real agy, claude or copilot: the mesh fixture replaces run_cli with FakeCli (the cli fixture: cli.on("--bg", stdout=…), cli.called("stop", id); each call records argv, cwd, env and input), points MESH_HERDR at a missing binary and makes subprocess.Popen refuse agy, claude, copilot and herdr (RefuseAgentClis), so a path that bypasses the fakes fails the test, and points MESH_CC_SOCKS at an empty folder (claude_root(mesh, run) gives the root a socket; the inbox fixture binds real ones). For quotas use with_quotas(mesh, monkeypatch), which seeds the agy models cache from the AGY_MODELS fixture (its ids, like gemini-3.10-flash, are made up to test version ordering) and stubs agy_usage.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

