Pipeline events: building a software assembly line out of panes
Lines starting with ##> bypass the PTY and become clean events other panes react to — panes triggering panes, and agents triggering agents.
CI pipelines taught us something true: software moves through phases — plan, build, lint, test, deploy — and handoffs between phases are where structure pays off.
Then we put agents in terminals and threw the lesson away. The typical multi-agent "workflow" today is a human ferrying context between chat windows: copy the diff from agent one, paste it to agent two, explain what happened, repeat. You are the conveyor belt.
Rysh has a primitive that removes you from the belt: pipeline events.
##>: a line that isn't for the shell
In any rysh pane, a line beginning with ##> takes a different path. It never touches the PTY — no shell echo, no prompt prefix. It's published directly onto the pane's output stream as a clean event line.
Think of it as a pane emitting a signal instead of running a command:
##>event:print:build finished, artifacts in ./dist
##>event:sh:softdev:golang:unit_testing
##>event:ai:softdev:golang:linting
On its own, an event is inert — a structured line in a stream. It becomes powerful because of who's listening.
Listeners turn events into actions
Wire two panes together with ##pane listen: the listening pane subscribes to the source's output streams. Its listener checks what arrives, and event lines get special handling:
##>event:print:<payload>— the payload is forwarded into the listening pane, clean, no alias prefix. A cross-paneecho.##>event:sh:softdev:<language>:<phase>— the listening pane runs the shell command mapped to that phase. Forgolang:unit_testing, that'sgo test -v ./....##>event:ai:softdev:<language>:<phase>— the listening pane's agent is triggered with the prompt template for that phase.
The softdev event family ships with phase definitions for a real development lifecycle — for Go: planning, development, linting, unit_testing, integration_testing, deployment, monitoring — each phase keyed by language:phase to a prompt template or a shell command.
One pane finishes its work and emits one line. Another pane starts its work. No human conveyor belt.
What the triggered agent actually knows
Be precise about what an ai:softdev event hands over. The triggered prompt names the phase and the source pane, and it has a slot for the source's recent output. The listener keeps a 50 KB buffer meant to fill that slot, but as the code stands today, ordinary output from the source doesn't reach it. So don't count on the downstream agent having watched the preceding work.
What does reach it is anything the source puts after the phase. ##>event:ai:softdev:golang:linting touched store/cache.go, new mutex around Get arrives in the listening agent's prompt as User instructions. The event says "your turn"; the text after it says what changed. Have the emitting agent write that line, and the handoff carries the part that matters.
Building the line
A concrete three-pane setup, built in about a minute:
# pane "dev" — an agent doing implementation work
# pane "test" — run: ##pane listen dev
# pane "review" — run: ##pane listen test
Instruct the dev agent to end each change with:
##>event:sh:softdev:golang:unit_testing
Now: dev finishes an edit → emits the event → the test pane runs go test -v ./... → its output (pass or fail) flows on its stream → the review pane, listening downstream, sees those results stream in and can be prompted on them.
Each pane does one job. Each handoff is an event. The whole line is observable — every stage is just a pane you can read.
Know which steps are gated. An ai:softdev event runs as an ordinary prompt in the listening pane, so the agent's tool calls go through the same approval flow as any other prompt. An sh:softdev event does not ask: it runs its phase's fixed command (go test -v ./..., go vet ./..., go build ./...) directly. Wire up only the shell phases you'd be happy to run unattended.
The listeners are actors in the rysh daemon, not in your terminal UI, so detaching doesn't tear the line down. Attach later and read what happened.
Why in-band events beat out-of-band glue
You could rig something like this with scripts, named pipes, and a watcher daemon. We tried living that way. The reasons in-band events win:
The stream is the protocol. Events travel in the pane's own output, next to the work that produced them — not in a side channel you have to correlate afterwards.
Agents can emit them. This is the quiet unlock: ##> is just a line of output. Anything that can produce a line can drive the pipeline — you, a shell script, or an agent that's been told "emit the unit_testing event when you're done." Agents signaling agents, in a format a human can read scrolling by.
Debugging is reading. When a pipeline misbehaves, the evidence is the transcript. The event is right there in the stream, between the work before it and the reaction after it.
Phases are a vocabulary, not a cage
The softdev phases give teams a shared language for handoffs — the same role stage names play in CI. print events cover ad-hoc signaling. The pattern generalizes: source emits, listeners react.
CI made phases first-class for builds. Pipeline events make them first-class for agents — in the same panes where you work, watchable in real time, one ##> line at a time.
Correction (2026-09-28): an earlier version of this article said listeners receive the source's shared, secret-redacted output stream, and that a 50 KB context buffer carries the source's preceding work into every triggered prompt. Neither matches the code: the listener subscribes to the source's output directly, with no redaction on that path, and ordinary output doesn't reach the context buffer today. It also said every action along the line parks at approval gates; shell-phase events run their mapped command without an approval step. Those lines have been removed or corrected.
Rysh is early. If you want to run a real agent assembly line on your codebase — and shape how this evolves — become a design partner: rysh.ai/design-partner
Related: ##pane listen · Four output streams per pane