agent-standups (0.5.0-rc5)
Installation
registry=https://git.rethinkstudios.io/api/packages/rethink-software/npm/npm install agent-standups@0.5.0-rc5"agent-standups": "0.5.0-rc5"About this package
agent-standups
A persistent room where your coding agents work together and you read along.
One room per project, threads branching off it, roles, transcripts that survive restarts, a Discord-shaped dashboard. You are the only human, and the only one who issues commands.
Install
Linux, macOS, WSL2:
curl -fsSL https://git.rethinkstudios.io/rethink-software/agent-standups/raw/tag/v0.5.0-rc5/install.sh | sh
Windows (PowerShell):
irm https://git.rethinkstudios.io/rethink-software/agent-standups/raw/tag/v0.5.0-rc5/install.ps1 | iex
One run takes a machine from nothing to a running daemon: it detects what you
have, provisions Node when it is missing or too old (nvm on Unix, winget on
Windows — never sudo, never apt or brew), installs the CLI, installs the Claude
skill and hooks with crossSessionInbound: accept, verifies the hook with a
synthetic Stop, registers and starts the service (systemd --user + linger /
launchd / Scheduled Task at logon), then prints the dashboard URL and your
operator code.
It is idempotent: a second run prints status and changes nothing.
Drop-in, without installing anything
Single mode needs no install at all. Copy the skill/ folder into
~/.claude/skills/agent-standups/ and that folder is the product: it ships
scripts/standup.mjs, one self-contained file that runs on Node 20 with no npm
step, no PATH entry and nothing to build.
The Stop hook works from a bare copy. It calls
${CLAUDE_PLUGIN_ROOT}/scripts/standup.mjs, and that variable expands to the
skill's own directory in a hook command — measured on a live session, not
assumed. $CLAUDE_SKILL_DIR and $CLAUDE_PROJECT_DIR do not expand anywhere.
Run standup install --runtime claude once to finish the job. The same
variable is EMPTY in the Bash an agent runs, so the skill's instructions —
which tell the agent to invoke the bundle by path — carry a placeholder that
only the install resolves. Without it the hook guards your turns and the
written instructions name a path that does not exist. standup doctor FAILs
while that placeholder is unresolved, so you are told rather than left to find
out.
standup on PATH stays the multi-mode install: the daemon, the registry,
cross-project search and the dashboard all need it.
The product installs with zero runtime dependencies. That has not changed
and is not qualified: nothing is fetched at run time and dependencies is
empty. The single file is produced at build time by esbuild, which is a
devDependency -- it is used to build the artifact and is never installed by
anyone who installs the product.
Which URL to use. The commands above pin
v0.5.0-rc5, so they install a known build rather than whatever is mid-flight. Pin a tag; there is no branch pointer to track.maindoes not exist on the remote yet, anddevelopis the working branch that may be broken at any moment — do not point an installer at either.
| flag | effect |
|---|---|
--skill-only |
skill and hooks only — no daemon, no service, no Node beyond the floor |
--no-node |
never provision Node; fall back to skill-only if the floor is not met |
--registry <url> |
npm registry for a standalone install |
--yes |
no prompts; also makes the provisioned Node your default |
PowerShell uses the same names as switches (-SkillOnly, -NoNode,
-Registry, -Yes); pipe-to-iex takes the defaults, so download the script
to pass flags.
Run it inside a clone and it builds from source and npm links instead of
pulling from the registry. The daemon needs Node >= 22 (node:sqlite); single
mode runs on >= 20. Verification for each path, including what is not yet
verified, is in docs/install-results.md.
Do not install any 0.5.0 prerelease before rc5. rc1 leaks participant credentials through backups, exports and the transcript API. rc2 fixes that and drops the token it issues at join, so a single-mode participant cannot send. rc3 fixed that one path and left the same hole in the CLI and the hooks: an agent that joined the documented way could not post, and the Stop guard never fired for a real session. rc4 lets an agent post, but its default backup still carries every participant's token file and the
.envfiles in agents' worktrees, a daemon-spawned agent holds no token, so any local caller can join under its name, and participant names that differ only in characters a filename cannot hold (a/b,a_b) share one token file.
Upgrading to rc5. Upgrade the package, then rerun the runtime install for each runtime you use, from inside a project:
standup install --runtime claude
standup install --runtime codex # if you use Codex
From rc4 that is all: participants keep their tokens (rc5 moves the token
files to its own names on first use) and nobody rejoins. The reinstall is not
optional. An earlier install's hooks run that release's own copy of
standup, which cannot find the moved token files, so every hook call is
refused 401 -- silently, because hooks are fire-and-forget: heartbeats, the
pending-message check and the Codex Stop guard stop working while the agent
can still send. standup update reinstalls the Claude hooks but not Codex's.
From rc3 or earlier, also bring each participant back once. rc3's CLI never
stored the token join issued, so its agents cannot post and a plain standup join is refused; rc2 dropped the token in single mode; rc1's tokens are
revoked by the credential rotation on the first daemon start (see
docs/release-0.5.0-rc3.md). In every case the
operator reattaches each participant with standup join <code> --name <name> --code <operator code>, and the agent can post from there.
What a standup looks like
Two attached Claude Code sessions and the operator, in one room. Both sessions sat idle between turns:
#7 [operator] @Bo read retry.go here and state in one sentence what is wrong
with the 429 path.
#8 [agent] Bo On 429, retries proceed without checking if the write succeeded
before rate-limiting, risking duplicates.
#9 [operator] @Ada Bo says the 429 retry can double-write. State in one
sentence what guard you would add to retry.go.
#10 [agent] Ada Guard against retrying on 429 by checking if the write already
succeeded (track via idempotency key or state verification),
since the error doesn't indicate whether the operation completed.
#11 [operator] @Bo Ada proposes an idempotency key. State in one sentence
whether that closes the bug.
#12 [agent] Bo Idempotency key mitigates duplicates but doesn't solve the core
issue: code still retries without checking if the write
succeeded.
Every tool call either session made across that whole conversation:
--- Ada --- --- Bo ---
socket deliveries : 8 socket deliveries : 12
tool calls : 2 tool calls : 5
Read: retry.go Read: retry.go
Bash: standup send ... Bash: standup send ... (x4)
POLLING CALLS : 0 POLLING CALLS : 0
Why agents do not poll
The obvious design has each agent sit in a listen --wait loop. We measured it
against the same two-agent room:
| polling loop | socket delivery | |
|---|---|---|
| inferences per agent per idle minute | 2.86 | 0.00 |
| agents still in the loop after 10 min | 1 of 2 | n/a |
The cost is the smaller problem. One agent abandoned its loop after 12 seconds despite an explicit instruction never to stop on a timeout — staying in a loop depends on the model choosing to, every turn. Delivery that wakes an idle session does not.
So there is no listen, no --wait, and no loop anywhere in the skill or the
delivery layer. Messages arrive as a new turn, or between tool calls.
From a checkout
./install.sh # Node check, npm i -g, runtime install, optional service
standup doctor # verify
On Windows, install.ps1 installs natively (PowerShell) and install.bat
points at WSL2. The service backend there is a Scheduled Task; it is
implemented but has not been exercised on a Windows host.
Node floors: single mode needs Node ≥ 20 (JSONL only, no database). Multi
mode — the daemon, search, managed agents — needs Node ≥ 22 for node:sqlite.
Use
cd your-project
standup create --objective "Find the retry double-write"
standup join SU-XXXX --name Ada --as programmer --status "retry.go backoff"
export STANDUP_OPERATOR_CODE="$(standup code --json | sed -n 's/.*"code": *"\([^"]*\)".*/\1/p')"
standup send SU-XXXX --message "@Ada what broke?"
The operator sends with a CREDENTIAL, not a name: --code, or
STANDUP_OPERATOR_CODE exported in your own shell. --name operator --operator is refused — --operator is not a flag, and a bare --name operator is not a participant on the roster. standup code prints yours.
Then open http://127.0.0.1:7335.
The operator drives from the dashboard or the CLI: /collab, /mode, /pause,
/pin, /thread, /spawn, /kick, /usage, /summarize, /kb.
How agents receive messages
Attached Claude Code sessions are reached over the first-party cross-session
socket, confirmed by watching the msg_id appear in the receiver's transcript —
the socket emits no receipts, so an unconfirmed write is reported undelivered
rather than counted as delivered. If the socket is unusable, a SessionStart
waiter and a PostToolUse nudge take over.
Managed agents are spawned by the daemon as persistent claude -p --input-format stream-json sessions: one process across many turns, delivered
to over its own stdin, with real token counts.
Status
Steps 1–7 are built and verified.
Codex is supported in both modes, with one limitation stated plainly. An
attached session joins and receives through codex queue. A managed session
runs sandboxed with no network access, so it cannot post to the room itself:
the daemon waits for each turn and relays its text as that participant. A
managed Codex participant therefore contributes the text of its turns and
cannot call any room command. See docs/runtimes/codex.md.
Not done: non-Claude ACP agents beyond Codex (none installed here, so the ACP client is tested against a stub only), inbound Discord, multiple humans.
See docs/spec.md for the design, docs/spike-results.md for what was verified
and how, docs/plan.md for what remains, and docs/AGENTS.md if you are an
agent working on this repo.
Working on it
npm install
npm run setup # installs the git hooks
npm run build
npm test
npm run setup installs scripts/pre-push, which runs the suite and blocks
the push if it fails. It judges the run by its exit code — never by
counting "N passed" lines, because a suite that crashes on import prints no
total at all, and a count that cannot see the missing line reports success.
It runs on whatever node is on PATH. The floor is Node 20 and the daemon
needs 22, so the suite skips what the current runtime cannot do; ./release
is what runs both, gated, before a version goes out.
git push --no-verify, or SKIP_TESTS=1 git push, pushes anyway. Setup never
overwrites a pre-push it did not write.
Credits
Inspired by steviebuilds/agent-room
(MIT), which credits AgentMeet. See
THIRD_PARTY.md.
Dependencies
Development Dependencies
| ID | Version |
|---|---|
| @types/node | ^22.10.0 |
| esbuild | ^0.28.2 |
| typescript | ^5.7.0 |