sessionpipe
v0.7.0
Published
sessionpipe: an open protocol for what your coding agents are doing. One install hooks Claude Code, Codex, Gemini CLI, Antigravity and more; four privacy tiers; secrets removed on your machine; sent to your server or to none.
Maintainers
Readme
sessionpipe
An open protocol, and a client, for what your coding agents are doing. One install hooks Claude Code, Codex, Gemini CLI and Antigravity (Cursor, Copilot CLI, Droid, Kiro and OpenCode are next); every event is filtered to a privacy tier you choose per destination; secrets are removed on your machine before anything leaves it; it goes to your server, to a file, or nowhere at all.
npx sessionpipe install # writes the hooks, backfills 30 days locally
sessionpipe tail --tier 1 # watch events, no server needed
sessionpipe sink add https://your-receiver.example --tier 1 --token <t>
sessionpipe doctor # what is wired, hook timing, sink state
sessionpipe uninstall # every config file byte-identical again| Tier | What leaves | |-----:|-------------| | 0 | session state and timing, folder, repo, branch, model, title, the kind of attention needed | | 1 | + tool names, durations, ok/error, file paths, the attention message | | 2 | + your prompts and the assistant's text, redacted | | 3 | + tool input and output |
No telemetry. Secrets never leave (secrets@1 runs above tier 0, always). Your
server or none. Pre-release: the reference receiver is still being built; see the
roadmap.
Control: messages to your sessions (M6: Claude Code, Codex, Antigravity)
A receiver that lists control can carry a message from your phone to a session on
this machine. It is signed on your device and checked here, against a key you
enroll at this terminal; the receiver can queue a message but can never forge one
(spec).
sessionpipe control pair spacesheep.dev # one link: approve with your passkey; installs the daemon (this folder by default)
sessionpipe control status # receivers, parked waiters, sessions mid-turn, open permission prompts
sessionpipe control keys [remove <id>]
sessionpipe control off # tell the receiver, forget its token and keysPairing through npx first installs that version globally and runs pairing from
that persistent copy. On Homebrew, the daemon uses a matching stable Node link
so a Node upgrade does not leave it pointing at a removed Cellar version.
Services paired with older versions are not repaired by install or update.
After upgrading, run sessionpipe control pair <receiver> again (without
--no-service) to rewrite the service with persistent paths. On Linux, restart an
already-running daemon afterward with systemctl --user restart sessionpipe-control.service;
macOS pairing reloads the LaunchAgent.
One daemon per computer holds one long-poll per receiver and delivers each message the
cheapest way the session allows: to sessionpipe wait parked in the session's
background (in place, ~60 ms, no network), at the session's next Stop if it is
mid-turn, through the Agent SDK for a session it started (if the SDK is installed), or
as a headless claude -p --resume (a --fork-session copy when a live process holds
it). A session stays reachable in place with zero idle turns by running, as a
background command with the longest timeout it can (Claude Code: raise
BASH_MAX_TIMEOUT_MS, else a background command is stopped after 2 hours at most):
sessionpipe wait # Claude Code: the session id comes from CLAUDE_CODE_SESSION_IDPermission prompts can be answered from the receiver too: while the terminal shows
its prompt, the PermissionRequest hook waits for your signed answer, and whichever
comes first wins. Nothing is ever auto-allowed. Safe mode (the default) runs headless
turns with --permission-mode dontAsk, so nothing that needs approval runs while
you're away.
Codex and Antigravity sessions are reached headlessly too, when codex or agy is
installed (the daemon looks on PATH, beside its own Node, in ~/.local/bin, or where
SESSIONPIPE_CODEX / SESSIONPIPE_AGY point). A new session is
codex exec --json -C <folder> or agy -p … --output-format stream-json; the next
message is codex exec resume <thread> or agy --conversation <id>. Safe mode runs
Codex in its workspace-write sandbox and agy with no permission flag (it denies, and
the reply lists, what it wasn't allowed to do); auto mode passes --approve-for-me /
--mode accept-edits. The bypass flags are never passed. Both CLIs pick a new
session's id themselves, so the daemon keeps the one your start named: it records the
pair in ~/.local/state/sessionpipe/control/aliases.json, and your receiver keeps
seeing one session under one id. A message to a Codex session someone has open (its
rollout written in the last 90 seconds) is acked unsupported rather than resumed beside them.
Before a Codex run the daemon checks codex login status (or CODEX_API_KEY) and says
codex login --device-auth if it isn't signed in.
Updates
The control daemon keeps its own copy current. About a minute after it starts, and then
once a day (the last check is kept in ~/.local/state/sessionpipe/control/update.json,
so a restart or a machine that sleeps doesn't ask more often), it reads
https://registry.npmjs.org/sessionpipe/latest. When that is a newer release it waits
until nothing is in hand — no message being delivered, no headless run or new session,
no Agent SDK turn, no permission prompt waiting for your answer, no session of yours
mid-turn — stops taking new
messages (the receiver holds them meanwhile), runs
npm install --global --ignore-scripts sessionpipe@<version> into the prefix it was
installed in, re-copies the hook launcher's files, and restarts onto the new code. A
running turn is never interrupted.
How it restarts depends on how it runs:
- launchd (macOS) or systemd (Linux), as
control pair/connectset it up: the daemon exits with code 75 and the service starts it again. Every service file sessionpipe has written restarts on a non-zero exit, so older installs need nothing. - Anything else (a terminal, tmux, a container that starts
sessionpipe control runitself): the old process stays as the new daemon's parent, with the same environment and output, and restarts it the same way after each later update. Whatever waits on the daemon (a shell, a container's init) keeps waiting.
One daemon runs per machine: a second sessionpipe control run finds the first one
answering on the local socket, says so, and exits 0.
Only a global npm install that this user can write updates itself; a copy in the npx
cache, a git checkout or a dev build is left alone. sessionpipe status and
sessionpipe doctor say whether it's on, when it last checked and what it found; the
daemon's log (~/.local/state/sessionpipe/control/daemon.log under launchd,
journalctl --user -u sessionpipe-control.service under systemd) has one line per check
and per update.
sessionpipe update off # the daemon stops checking ("update_check": false in config.json)
sessionpipe update on
sessionpipe update # install the latest now; the daemon restarts onto it once idleSESSIONPIPE_NO_UPDATE_CHECK=1 turns it off too, but only where the daemon can see it:
a launchd or systemd service doesn't inherit your shell's variables, while
sessionpipe update off reaches it (it re-reads the config every ten minutes).
Docs and the protocol: sessionpipe.org · Source:
github.com/micmmakarov/sessionpipe · Apache-2.0.
Provided as-is; check what a sink receives with sessionpipe tail before you point it anywhere.
