Pipeline events: building a software assembly line out of panes
Lines starting with ##> bypass the PTY and become clean events other panes react to — agents triggering agents, with 50KB of context riding along.
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 shared (redacted) output stream. Its listener processes that stream line by line, 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 a contextual prompt built 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.
The 50KB that makes it intelligent
Here's the design detail that separates this from dumb triggers.
A webhook fires with a payload and a prayer — the receiver knows that something happened, not what led to it. Rysh's listener does something better: it maintains a rolling context buffer (capped at 50KB) of the source pane's recent non-event output. When an ai:softdev event fires, the triggered prompt includes that accumulated context.
So when the dev pane emits ##>event:ai:softdev:golang:linting, the lint agent doesn't wake up amnesiac. It has watched the preceding work — the files touched, the errors hit, the choices made — because the stream it rode in on was the work. The event says "your turn"; the context says "here's the story so far."
That's the difference between an assembly line of specialists and a hallway of strangers shouting task names at each other.
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 with its own context buffer filling, can be prompted on results.
Each pane does one job. Each handoff is an event. The whole line is observable — every stage is just a pane you can read, and every gated action along the way (file writes, commits, non-allowlisted commands) still parks at rysh's approval gates. Automation doesn't bypass governance; it flows through it.
And because listening happens over the session's message bus, the line keeps running when you detach. Attach later and read what the factory did.
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 same channel as the work itself, in order, interleaved with the context that explains them. No race between "the signal arrived" and "the logs caught up."
Redaction applies. Events ride the shared output path — the one that's already secret-redacted at the source. Your trigger infrastructure can't leak what the pane wouldn't share.
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, context rides along.
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.
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