talking-stick
v0.7.0
Published
CLI coordination tool for path-scoped agent handoffs.
Readme
Talking Stick
A CLI coordination tool that lets multiple AI coding agents share a single workspace without stepping on each other. One agent holds the stick at a time; handoffs carry structured context so the next agent doesn't have to re-derive it.
Multi-process-safe (SQLite WAL), liveness-aware, no daemon. Supports Claude Code, Codex CLI, Antigravity CLI (agy), Grok Build, and OpenCode out of the box. Gemini CLI identity is retained for existing sessions, but Gemini skill installation is deprecated in favor of Antigravity and the shared agents skill directory. Harnesses stay responsive through one canonical listen/wait loop (tt wait --events), diagnose coordination and local process state with tt health, and can chat out-of-band — without passing the stick — via tt msg send/recv.
Quickstart
Three steps, then you're coordinating two agents in the same repo.
1. Install the tt binary
npm i -g talking-stick2. Install the skill in every harness
tt install --allRestart any harness that was already running so it loads the updated skill. The skill teaches agents to coordinate by running tt CLI commands from the workspace. To tune the default collaboration prompt without editing installed package files, run tt instructions edit.
3. Try it: two agents, one repo
Open two terminal panes side by side — tmux split, iTerm split, two windows, whatever you like. cd into the same repo in each, and launch a different harness in each pane:
| Pane A — Claude Code | Pane B — Codex |
|---|---|
| cd ~/myrepo && claude [--dangerously-skip-permissions] | cd ~/myrepo && codex |
Then give both panes the same prompt — a shared goal plus the skill trigger:
/goal Work together to implement OAuth login. Use the /talking-stick $talking-stick skill for coordination
/talking-stick $talking-stick triggers the skill in either harness, and the goal keeps each agent driving toward the shared objective. You don't script the turn-taking — the skill teaches each agent how to join, wait, hand off, and review. They negotiate turns automatically, carry structured handoffs (status, next action, artifacts) across transitions, and never edit the repo at the same time.
Install options
| Method | Command | Notes |
|---|---|---|
| From npm | npm i -g talking-stick | Latest published release. Requires Node ≥ 22. |
| From GitHub | npm i -g github:mostlydev/talking-stick | Tracks the master branch; builds on install via the prepare hook. |
| From source | git clone … && npm install && npm link | For contributors. |
All three produce a tt binary on your PATH. Everything else below works identically.
Published package installs also bootstrap the skiller binary used by tt install
for skill-directory writes. Set TALKING_STICK_DISABLE_SKILLER_BOOTSTRAP=1 to skip
that postinstall bootstrap, or TALKING_STICK_DISABLE_SKILLER=1 to force the built-in
TypeScript fallback.
Verify without installing
Want to see exactly what tt install would change before touching anything?
tt install --all --printInstall into a subset
tt install claude-code codexDuring normal execution, install commands skip harnesses that are not present instead of failing or creating new harness config roots.
Update
Uses the right npm/pnpm/yarn by default:
tt self-updatett self-update also removes stale Talking Stick MCP registrations left by older installs. The first normal tt invocation after a package-version change runs the same cleanup if the package manager skipped lifecycle scripts.
Remove
tt uninstall --allSingle-harness uninstalls for shared-reading harnesses leave ~/.agents/skills/talking-stick in place because Codex, Antigravity, Grok, and OpenCode share that one skill location. Use tt uninstall agents or tt uninstall --shared to remove only the shared skill target.
What it gives your agent
Once installed, each agent harness has a skill that tells it to coordinate through the tt CLI:
tt list — which rooms exist under a path
tt join — join the room for this workspace
tt leave — explicitly leave a room; deletes it when no active members remain
tt wait --events — canonical listen/wait loop for ownership and room events
tt wait --park --events — stay coordinated without auto-claiming idle rooms
tt release — normal handoff to the next fair waiter, with structured Handoff
tt assign — explicit handoff to a named agent
tt take — deliberate claim when the prior holder is gone/stuck
tt kick — evict an idle member whose process is gone
tt state — authoritative state projection
tt health/status — concise local safety/action check; --verbose shows diagnostics
tt events — audit/debug log and lower-level event stream
tt notes add/list — durable async observations for the room
tt msg send/recv — out-of-band chat into the room event log
tt instructions — editable collaboration prompt loaded by the skillA workspace maps to a room — usually the git root or nearest project marker — so two agents cd'd anywhere under the same repo join the same room automatically. Marker files directly in your home directory are ignored for descendant paths, so scratch directories under $HOME do not collapse into one broad home-scoped room unless you explicitly join home itself.
The global skill tells the model when to join, wait, take over, leave notes, send messages, and hand off.
Editable collaboration instructions
The bundled skill is the safety floor. It is intentionally small and package-managed. Local collaboration preferences live in editable Markdown files that tt instructions shows to agents after they join.
tt instructions show # effective prompt for the detected harness
tt instructions show --harness codex # view one harness's effective prompt
tt instructions edit # edit user defaults
tt instructions edit --project # edit this repo's overrides
tt instructions reset --project # remove this repo's overrideEffective instructions are layered in this order: bundled defaults, user defaults at ${TALKING_STICK_DATA_DIR}/instructions.md (normally ~/.local/share/talking-stick/instructions.md), then project overrides at .talking-stick/instructions.md in the workspace root. User and project files are created lazily on first edit, so installing tt does not litter repositories or harness config directories.
Non-owner notes
While you wait your turn you may still need to flag something to the current owner: a subtle invariant, a related bug, a pointer to a doc. Non-owner notes give you a durable channel without interrupting the turn.
- Any joined member (owner or not) can
tt notes adda short plain-text body (≤ 16 KB). An optional--turn Nscopes the note to a specific turn; omitted, the note is room-scoped and survives turn transitions. tt notes listreturns notes for the room; readers can paginate with--afterand see the full history (older and resolved entries) with--all.- Notes are for observations and pointers, not for coordinating shared edits. Shared workspace changes still require holding the stick.
Out-of-band messaging
The stick guarantees single-writer authority over shared workspace state. It is not a chat protocol. When two agents need to talk — design questions, "are you about to break X?", live coordination — use messages instead of churning the stick.
tt msg send <recipient|room> "<body>" [--interrupt] [--stdin]
tt wait --events --after <cursor_event_seq> --json<recipient>is a fullagent_id, an unambiguous active display name (codex,claude), or the literalroomfor broadcast.--interruptmarks the message time-sensitive; receivers decide whether to act on it now.tt join --jsonandtt state --jsonreturncursor_event_seq; use that as the initial--aftercursor, or use--after 0when you intentionally want to replay history.tt wait --events --after <cursor>returns ownership state,events[], an updatedcursor_event_seq,wake_reason, and anextreminder. It is a bounded long-poll, not durable background coverage; restart exactly one listener with the returned cursor.tt events --wait,tt events --follow, andtt msg recvremain available for audit/debug or legacy fallback consumers. They are not the recommended harness loop.- The wait-events loop is owner-safe for non-holders: it does not grant or renew authority. It refreshes the caller's presence/wait interest so active harnesses stay visible.
- Event receive does not grant the stick. Only a
tt wait --eventsresult withstatus: "your_turn"and a liveguardian_pidgrants authority to edit shared files. - Ordinary non-guardian
ttcommands refresh a detected harness member's presence. Lease renewal is carried by the local guardian spawned bytt wait/tt take; reads such astt healthdo not extend owner authority. - Default
tt state, non-streamingtt events, andtt notes listhide much-older ghost rows behind a structuredhidden.older_countsummary. Defaulttt healthis a concise action card; usett health --verboseor--allfor full member and receiver diagnostics.
When to message vs note vs handoff.
- Message — conversational, ephemeral, between live processes. Six round-trips of "what about line 84?" cost about as much as one structured handoff and zero stick churn.
- Note (
tt notes add) — durable, resolvable artifacts. Leave a note when the next holder should consider something at handoff, or when the observation should outlive the conversation. - Handoff (
tt release/tt pass) — transfer of work. Messages do not replace handoffs; they live alongside them.
to_agent_id is routing, not ACL. Any room member can read any message via tt events --target any. Messages are not private. They also do not grant the stick — a non-holder paging the holder gets attention, not write authority.
Post-turn closeout
After a handoff, an agent keeps the wait loop alive while work is pending, parks when it is only waiting on an external signal, or — when the shared task is genuinely complete — stops and sends a final closeout instead of churning the room. The exact completion evidence an agent must see before declaring done lives in the skill (skills/talking-stick/SKILL.md).
How installation works per harness
tt install installs or refreshes the bundled talking-stick skill. Skill directory writes are delegated to the skiller binary when available; package postinstall bootstraps skiller automatically from the published release and verifies checksums.txt before installation. If skiller is missing, disabled, or fails its version gate, tt uses the built-in TypeScript fallback. It does not add MCP servers. During install, uninstall, package update, and first run after an installed package version changes, tt removes stale MCP registrations written by older Talking Stick releases.
- Claude Code: copied or linked into
~/.claude/skills/talking-stickbecause Claude Code does not read~/.agents/skills - Codex, Antigravity (
agy), Grok Build, and OpenCode: copied or linked once into the shared~/.agents/skills/talking-stick - Grok Build: also installs a trusted global session hook at
~/.grok/hooks/talking-stick-session.json - Gemini CLI: deprecated for skill installation;
tt install geminiprints a deprecation notice and runs cleanup only
By default, tt install links the bundled skill so local updates are picked up immediately. Pass --copy if you want a standalone snapshot.
For harnesses that previously had proprietary skill copies, tt prunes duplicate talking-stick entries conservatively: it removes only symlinks that resolve to the bundled Talking Stick skill, and preserves copied directories, foreign symlinks, or hand-authored entries. OpenCode cleanup checks both ~/.config/opencode/skills/talking-stick (honoring XDG_CONFIG_HOME) and the older ~/.opencode/skills/talking-stick location.
Stale MCP cleanup is strict for OpenCode JSON entries: it removes only the canonical mcp.talking-stick value with ["tt", "mcp"] and leaves hand-edited entries alone. Claude Code, Codex, and Gemini cleanup uses their own mcp remove commands when the old server name exists. Grok Build and Antigravity have no Talking Stick MCP registration path; install is native skill plus hook/shared skill only. Every cleanup run appends JSONL audit entries to ${TALKING_STICK_DATA_DIR}/update-migrations.log.
Human CLI invocations also perform a silent best-effort sync for already-installed file-based skills in Claude Code and the shared ~/.agents/skills/talking-stick target. If the installed skill is a copy, it is refreshed from the bundled skill; if it is a stale symlink, it is relinked. Missing skill installs are skipped. Gemini skill sync is deprecated; use Antigravity/shared install instead.
Human CLI
The same tt binary also works as a human CLI, useful for watching or participating in a room from your terminal:
tt whoami [--explain] # show the resolved CLI identity
tt list [path] # list rooms
tt join [path] [--force-new] # join the room for path
tt leave [path] # leave the room for path
tt wait [path] [--timeout 110s] [--park] [--events --after N] # canonical listen/wait loop; --park disables idle auto-claim
tt try [path] [--park] [--events --after N] # non-blocking claim/event check
tt state [path] [--all] # compact room state; --all shows older rows
tt health [path] [--verbose|--all] # concise safety/action check; verbose shows diagnostics
tt status [path] [--verbose|--all] # alias for health
tt events [path] [--all] [--after N] [--limit N] [--wait|--follow] [--event TYPE[,TYPE]] [--target self|any|agent] # audit/debug event log; --wait/--follow lower-level streams
tt msg send <recipient|room> <body...> [--interrupt] [--stdin] [--path DIR] # send an OOB message
tt msg recv [--wait|--follow] [--from agent] [--after N] [--target self|any|agent] [--path DIR] # receive OOB messages
tt instructions show [path] [--harness claude|codex|antigravity|gemini|grok|opencode|all] [--scope effective|bundled|user|project] # show collaboration prompt
tt instructions edit [path] [--user|--project] # edit user or project prompt
tt instructions reset [path] (--user|--project) # delete a user or project prompt
tt release [path] --status TEXT --next-action TEXT # normal handoff
tt pass [path] --status TEXT --next-action TEXT # pass/end your turn
tt assign <target|next> [path] --status TEXT --next-action TEXT # explicit handoff
tt take [path] [--reason TEXT] # human-friendly take/override
tt takeover [path] [--reason TEXT] # alias for take
tt notes add <body> [--turn N] [--path DIR] [--stdin] # leave an async note
tt notes list [--all] [--after ID] [--limit N] [--path DIR] # read notes
tt install <harness...> | --all [--print] [--copy] [--link] # install skill and clean stale MCP entries
tt uninstall <harness...|agents> | --all | --shared [--print] # remove skill and stale MCP entries
tt self-update [--print] [--manager npm|pnpm|yarn|bun] # update to the latest published tt[path] defaults to the current working directory. Omit it for normal in-repo coordination; pass it only when you intentionally want a different or nested room.
Help flags are always read-only. --help and -h take precedence over command
execution, even for stateful commands such as wait, release, assign,
notes add, or msg send; they do not join rooms, claim turns, spawn
guardians, write events, or update local session state.
tt self-update detects how tt was installed (npm / pnpm / yarn / bun, including npm-via-Homebrew/mise/asdf/nvm), runs the right global-update command, then removes stale MCP registrations from older Talking Stick installs. Pass --print to see the inferred command without running it; pass --manager to override detection. Running tt self-update from a development checkout (where tt resolves outside node_modules/talking-stick) refuses and tells you to git pull && npm install && npm run build instead.
Human CLI commands use a stable identity like human:<username>. When tt wait, tt take, or tt takeover wins the turn, a small background guardian keeps the lease alive on your behalf until you release, pass, or assign it. If that guardian's captured harness process appears gone but the harness has recent tt activity, Talking Stick retains the lease and keeps heartbeating; a process-gone and silent owner is surrendered as harness_gone. Human CLI take intentionally works without a required reason so an operator can step into a stuck room quickly; harness-aware CLI takeovers still require --reason unless the command includes --operator-requested.
CLI identity
By default, tt behaves like a human CLI and resolves to human:<username> only when no harness environment is detected.
Harness-aware CLI identity is resolved before the human fallback:
- Known harness environment markers such as
CLAUDECODE=1,CODEX_THREAD_ID,ANTIGRAVITY_AGENT=1,ANTIGRAVITY_CONVERSATION_ID,ANTIGRAVITY_TRAJECTORY_ID,GEMINI_CLI=1,CMUX_AGENT_LAUNCH_KIND=grok, orOPENCODE=1makettderive a harness-style identity automatically. Antigravity usesANTIGRAVITY_CONVERSATION_IDas the preferred session anchor, falling back toANTIGRAVITY_TRAJECTORY_IDand thenagyprocess ancestry. The cmux Grok marker is optional; Grok Build also works without cmux by walking process ancestry for agrokroot process. - Grok Build's installed hook records hook-only
GROK_SESSION_IDcontext into${TALKING_STICK_DATA_DIR}/grok-sessions.jsonl, letting later Grok-launchedttcalls upgrade from process identity to the real Grok session id.GROK_SESSION_IDby itself is not treated as a normal shell marker, and the hook is not required for basic Grok detection. - Set
TT_HARNESS_AGENT_ID=<agent-id>if the harness wants to export the exact agent id directly. - Set
TT_HARNESS_EXPORT=1only when you need ancestry-based harness detection without a known harness environment marker.
If no harness signal is present, tt stays on the human CLI path. That keeps ordinary shell usage predictable while preventing harness-launched shells from silently joining rooms as human:<username>.
Use tt whoami --explain to see which identity path the CLI chose.
Design highlights
- Workspace-root room resolution. An agent at any depth under
/repo/joins the/repo/room automatically. Nested rooms require explicitforce_new. - Structured handoffs.
tt releaseandtt passcarry a typedHandoffwith requiredstatus/next_actionand optionalartifacts[]pointing at specific files and line ranges. - Fair handoff selection. Normal release prefers a recent waiter that is new or has gone longest without holding the stick; if the best-known candidate is between wait polls, a short grace window prevents immediate recycling to a less-fair claimant.
- No immediate take-backs. If release leaves a handoff idle, the prior owner waits through the short grace window before reclaiming while another member exists.
- Ephemeral rooms.
tt leaveremoves membership, rooms with no active members are physically deleted, and long-idle rooms with no recent activity or provably live member process are purged opportunistically on later invocations. The default idle retention is seven days. - Fencing tokens.
lease_id+turn_idmake stale writes impossible — an agent who lost their turn cannot commit anything under the room's name. - Liveness-aware recovery. Dead or crashed holders are detected with OS-level process checks; claim-timeout takeover skips the prior owner when another active member is waiting.
- Readable default projections. State, events, notes, and health anchor to the room's newest real activity and collapse much-older ghost rows, while
--alland explicit cursors preserve full audit history. - Multi-process safe. Shared SQLite with WAL mode,
BEGIN IMMEDIATEwrites, 250 ms polling for the wait loop. No daemon required. - Per-call identity derivation. Harness-launched CLI calls derive identity from harness environment or ancestry. Human CLI callers get a stable
human:<username>identity.
Storage
The coordination database lives at:
- Linux/macOS:
~/.local/share/talking-stick/rooms.sqlite(or$XDG_DATA_HOME/talking-stick/rooms.sqlite) - Windows:
%APPDATA%\talking-stick\rooms.sqlite
Override with TALKING_STICK_DATA_DIR if you want to keep per-project state.
Development
npm install
npm test
npm run typecheck
npm run buildChangelog
See CHANGELOG.md for a per-version summary; full release notes live in docs/releases/.
When cutting a release, add entries under CHANGELOG.md's Unreleased section,
then run npm version <new-version>. The version lifecycle script moves those
entries into the new version section, writes docs/releases/<version>.md, and
adds the GitHub release link before npm commits and tags the version.
Read next
docs/talking-stick-plan.md— full protocol, state transitions, persistence model, design rationale, and open questions.docs/ambient-presence.md— design sketch for shell-prompt awareness, event streaming, and agent skills that make room state ambient rather than appointment-only.docs/non-owner-notes.md— design backing the v1 notes feature; documents what shipped, what was deferred (resolve_note, wait_for_turn unread hints), and the rationale.skills/talking-stick/SKILL.md— the portable skill installed into global harness skill directories.
License
MIT. See LICENSE.
