venomx daemon/session foundation
| Field | Value |
|---|---|
| Status | Accepted |
| Accepted | 2026-11-06, explicitly approved by the maintainer |
| Authors | commixon (commixon), with Codex |
| Created | 2026-11-06 |
| Linear | COM-237; implementation COM-238; attach COM-223 |
| Supersedes | None |
| Superseded by | None |
History
- 2026-11-11 (venom-org/venom-spec#151, venom-org/venomx#178; compatible
addition under RFC-0003 §6; explicitly accepted by the maintainer
commixonon 2026-11-11):pane.listentries (§5) and thepanesnapshot entity (§7) gain an optionalprocess: the name of the PTY’s foreground process group leader, sampled by the daemon (§5 says how and how often). They also gain an optional booleantitle_current, which says whether the OSC 2 title was set since the foreground last changed. §7 adds thepane.process_changedevent. Clients that name a pane prefer a current title, then RFC-0003 §8’scmdline, thenprocess, thenargv0. §9 adds verification cases. Requests and older readers are unchanged. - 2026-11-10 (venom-org/venom-spec#144, venom-org/venomx#147; compatible
addition under RFC-0003 §6; accepted by venom-run (unattended, delegated
by the maintainer) on 2026-11-10):
pane.listentries (§5),pane.createddata and thepanesnapshot entity (§7) gain an optionalargv0: the basename of the pane’s spawnargv[0], at most 255 bytes (§5 says how it is derived). RFC-0022 §3.2’s title line falls back to it when a pane has no OSC 2 title. It is never the directory part ofargv[0](anything up to its last/), the bytes past 255, or another argv element. It is also never RFC-0013program, RFC-0015 shell text or the cwd, which all stay redacted. RFC-0010 §6 and §8, RFC-0012 §7, RFC-0013 §7 and RFC-0015 §9 each gain a narrow exemption for it. Ashellrun’sargv0issh. Checkpoints don’t keep it, so a restored archive pane omits it, as it omitspidandstart. §9 adds its verification cases. Requests and older readers are unchanged. - 2026-11-09 (venom-org/venom-spec#131, venom-org/venomx#67; normative; accepted
by venom-run (unattended, delegated by the maintainer) on 2026-11-09): §4
pane.splitand tilingpane.placeclamp an ancestor split’sratiothat would leave the child holding the new pane below its raised minimum, instead of removing it. Cells come only from that ancestor’s other child, which keeps its own minimum; theratiois removed only when the cells can’t hold both minimums. Ancestors whose minimums the split did not raise keep theirratio. Removing it could squeeze the other side below its minimum and clip panes to zero while the viewport had room. - 2026-11-08 (venomx#44, normative; explicitly accepted by the maintainer on
2026-11-09):
§4 split nodes gain optional
ratio, the “arbitrary ratios” this section deferred, for RFC-0016 §7.7’spane.resize. Compatible: a layout nobody resized is unchanged on the wire, and a client that ignoresratiodraws equal shares. Such a client, while active on a pane, also pushes its equal-share PTY size on that pane, until venom-desk#15. A split that would leave a child below its minimum removes the ancestor’sratio. - 2026-11-06: The maintainer accepted RFC-0009, adding opt-in on-demand startup and session-count lifetime to §2. The explicit persistent default and COM-238 foundation scope remain unchanged. RFC-0009 governs the additional mode when implemented by COM-242.
- 2026-11-07: The maintainer accepted RFC-0012,
which adds
pane.killand opt-in recursivewindow.remove/session.removedeferred by §5. Calls that do not opt in keep §5’s exited-only and empty-only rules. COM-246 implements it. - 2026-11-07: The maintainer accepted RFC-0013,
which adds optional
cwd,envandenv_unsettowindow.createandpane.split, deferred by §5. It also makes every spawn setVENOMX_SOCKET, strip more inherited identity variables and follow stricterPATHsearch rules (RFC-0013 §9). COM-248 implements it. - 2026-11-07: The maintainer accepted RFC-0014,
which adds
pane.capture_rows, the bounded, paginated history-range capture deferred by §5.pane.captureand itsunsupported.result_sizerule are unchanged. COM-250 implements it. - 2026-11-07: The maintainer accepted RFC-0015,
which adds
run,run.wait,run.get,run.listandrun.cancel, the trusted run/wait execution deferred in Alternatives. A run is a new pane whose result comes only from the daemon’s wait status; existing verbs are unchanged. COM-252 implements it. - 2026-11-08 (COM-305): RFC-0013 adds an optional
programtowindow.createandpane.split, the file to execute when it differs fromargv[0]. Requests without it behave as before. - 2026-11-08 (venom-org/venomx#47, compatible addition):
pane.listentries of a live pane gainpidandstart, the(pid, starttime)the pane’s principal is bound to. Another daemon needs them to tell a pane’s process from a reused PID (RFC-0019 section 7.2). Both are absent once the process has exited and for a pane restored without one.
Summary
Define the smallest runnable venomx product around the RFC-0006 pane-stream server: one daemon/CLI executable, explicit foreground startup, in-memory sessions and windows, authoritative split topology, scoped discovery and schema-defined mutations. COM-238 implements this foundation; COM-223 adds the emulating presentation client. This RFC adds product semantics to RFC-0001–0006 without replacing their transport, grants or event contracts.
The governing shared contracts are agent behavior, transport, events, verb schemas, identity/grants and pane streams. Review and acceptance follow PROCESS.md.
Motivation
COM-222 supplies a server component with PTYs, terminal emulation, snapshots and raw-byte streams. It does not install an executable or define sessions, windows, product verbs or daemon startup. COM-223’s acceptance test needs two real clients to find the same session and keep its processes alive after both clients detach. A pane-only demonstration can exercise the protocol but cannot establish that product behavior.
The foundation belongs in a separate issue and PR. Implementing sessions, config, process ownership and discovery inside the renderer would obscure the daemon/client boundary and duplicate work needed by venom and venom-desk. The product MUST reuse venom-std’s shared facilities and conformance suite.
Design
1. A runnable headless path
Run the daemon in one terminal:
venomx serve
From an outside shell, the human explicitly opts into owner authority for mutations, as specified by RFC-0005. IDs in these examples assume a fresh daemon and successful preceding commands:
VENOMX_TOKEN=@owner venomx session create --name work --json
VENOMX_TOKEN=@owner venomx window create --session s0 --argv '["/bin/sh"]' --json
VENOMX_TOKEN=@owner venomx pane split --pane p0 --axis cols --argv '["/bin/sh"]' --json
venomx session get --session s0 --json
venomx window get --window w0 --json
After COM-223 lands, each of two host terminals can run:
venomx attach --session s0
attach --pane p0 remains the direct single-pane presentation entry point.
attach is not required to ship in COM-238. Every foundation operation MUST
be testable without a TTY, including with stdin closed. Commands MUST NOT
implicitly create a daemon, a session, a window or a shell.
2. Daemon startup and lifetime
serve MUST run in the foreground and use RFC-0002 socket selection,
locking, permissions, peer identity and negotiation. Concurrent starts for
one socket MUST yield one daemon and the standard lock-conflict failure.
The same executable MUST implement serve, generated ordinary CLI verbs,
mcp and, when implemented, presentation clients.
The initial mode is explicit persistent operation. A daemon MUST remain
alive with zero sessions, zero running children or zero clients. Detaching,
client crashes, socket disconnects and terminal closure MUST NOT terminate
panes. A CLI or presentation client that cannot reach a daemon MUST return
unavailable (exit 6), without auto-start or silently connecting elsewhere.
SIGINT or SIGTERM to serve initiates shutdown. The daemon MUST stop
accepting mutations, supervise termination of its pane process groups,
reap children and release transport, owner file and lock resources. It
MUST send SIGHUP to each live pane group, wait at most 2000 ms using a
monotonic deadline, then send SIGKILL to surviving groups and reap its
children. Socket draining MUST be bounded by that same grace deadline;
slow clients MUST NOT delay child termination. Already-exited panes need
no signal. Successful clean shutdown follows RFC-0001’s serve --json
behavior. An unkillable OS process is an operational shutdown failure,
not evidence of successful cleanup.
The daemon MUST act as a Linux child subreaper and bind process principals as RFC-0005 requires. A pane group is established by PTY creation; the daemon MUST NOT accidentally signal its own process group. Escaped descendants and platform limitations follow RFC-0005 and MUST be reported by diagnostics rather than claimed to be a sandbox.
State and PTYs survive client loss, not daemon restart. Each start MUST
generate a new venomx/<uuid> source shared by events and pane streams.
Sessions, windows, principals and panes are memory-only in this version.
On-demand/tmux-style startup, durable restore and crash recovery require
later work; this RFC does not define flags or config values for those modes.
3. Ownership and containment
The daemon owns this resource tree:
server:root
session:s0
window:w0
pane:p0
pane:p1
A session is a named container of windows. A window owns one split tree. A pane is a leaf referring to one COM-222 pane, PTY and authoritative Vt. A pane MUST belong to exactly one window and session; linking, moving and sharing a pane between windows are outside this version. An empty session or window is valid. Creating a session creates no processes; creating a window creates its initial pane and process atomically.
Session and window IDs MUST be allocated from separate increasing counters
as s0, s1, … and w0, w1, …. Pane IDs MUST continue using COM-222’s
increasing p0, p1, … counter. IDs MUST NOT be reused within a source,
including after deletion or failed creation. Counter exhaustion MUST fail
before allocation. IDs, rather than names or array positions, select targets.
Names are display strings, need not be unique, and MUST contain at most
128 UTF-8 bytes with no C0 or DEL controls. The default name is empty.
Each resource MUST record the resolved creating principal as owner.
parentOf MUST implement the tree above for RFC-0005 containment checks.
PTY processes get distinct pane principals with issuance, ancestry binding,
attenuation and environment sanitization from RFC-0005. In particular,
unknown clients remain read-only and a human’s owner token MUST NOT leak
into children. No TUI-provided principal, OSC field or JSON member can
replace the connection’s resolved identity.
Process exit MUST retain the pane, Vt, history and layout leaf. Exit status comes from wait status; signals MUST be represented by their symbolic name, with no invented successful exit code. Removing the final pane makes its window empty; neither the window nor session is removed automatically.
4. Shared topology, local presentation
window.get returns a flat tree so the closed RFC-0004 format-1 descriptor
can represent it without recursive types:
{"window":"w0","session":"s0","name":"","revision":2,"nodes":[{"kind":"split","axis":"cols","first":1,"second":2},{"kind":"pane","pane":"p0"},{"kind":"pane","pane":"p1"}]}
The root is array index 0; an empty window has nodes=[]. Nodes are in
preorder. A pane node has exactly kind and pane. A split node has
exactly kind, axis, first, second, and optionally ratio. Child indexes MUST be valid,
forward indexes; every non-root node has exactly one parent. Every pane in
the window occurs once. axis=cols divides left/right; axis=rows divides
top/bottom.
Of the cells a split’s one-cell separator leaves, avail, the first child gets
ceil(avail * ratio / 10000) and the second the rest. ratio is an integer
from 1 to 9999. Each child keeps at least one cell while avail is 2 or more.
With no ratio the split is equal, with the first child getting the extra cell
on odd dimensions. pane.resize (RFC-0016 section 7.7) sets it, and omits it
when the result is the equal split, so a layout nobody resized has no ratio.
pane.split and tiling with pane.place add a pane in the space the tree already
has. If that raises the minimum (RFC-0016 section 7.7) of the child of an ancestor
split that holds the new pane, and leaves that child below it, the ancestor’s
ratio is clamped: its divider moves just far enough to give that child its
new minimum, taking cells only from the other child, which keeps at least its own
minimum. Only when the cells the ancestor divides can’t hold both children’s
minimums is its ratio removed. An ancestor whose children’s minimums the split
did not raise keeps its ratio, even when one of its children is already below
its minimum, and so does an ancestor whose child that holds the new pane still
has room. Ancestors are worked out from the root down, on the panes’ current PTY
sizes as pane.resize measures them, so a clamp higher up gives the splits below
it their new cells. A clamp that lands on the equal split omits ratio. Clipping
otherwise stays as it was: a split without a ratio is left as it is, and a
viewport too small for an equal split still clips panes to zero.
For example, p0 | (pa | (pb|pc)) 161 columns wide, with root ratio 62 and inner
ratio 9807, draws p0=1 pa=155 pb=1 pc=1. Splitting p0 across needs three
columns on the root’s first side, so the root’s ratio is clamped to give it three,
and the other side keeps 157: p0=1 new=1 pa=153 pb=1 pc=1. Removing the ratio
instead would draw pa=78 pb=0 pc=0.
{"kind":"split","axis":"cols","first":1,"second":2,"ratio":6250}
pane.split replaces the source leaf with a split: existing pane first,
new pane second. pane.remove collapses its parent to the surviving
sibling, or leaves an empty tree if it removes the sole pane. The window
revision starts at 1 and increases on every topology change, without
exceeding RFC-0002’s safe JSON integer range. The daemon MUST validate all
invariants before publishing a topology.
The shared tree determines membership and arrangement. Active window,
focused pane, scroll position, selection and physical rectangles belong
to each presentation client. A small host terminal MUST clip or expose a
local overflow indicator; it MUST NOT delete shared leaves or send zero
dimensions. Positive viewport sizes and authoritative PTY sizing follow
RFC-0006’s latest, smallest and fixed policies unchanged.
5. Minimal product verbs
All entries below MUST be declared in one RFC-0004 manifest, with typed
parameters, results, errors and standard exit policy. Dotted names map to
spaced CLI paths. Requests and responses use RFC-0002; CLI --json emits
the unwrapped result. Every mutation MUST authorize before revealing target
existence or producing side effects.
| Verb | Parameters | Result | Required authorization |
|---|---|---|---|
session.create |
Optional name string, default empty |
{session,name} |
write on server:root |
session.list |
None | {sessions:[{session,name}]} |
read; return only directly readable sessions |
session.get |
Required session string |
{session,name,windows:[{window,name}]} |
read on session |
session.remove |
Required session |
{} |
write on session; must be empty |
window.create |
Required session, argv; optional name, cols, rows, grants |
{window,session,pane,principal,pid} |
spawn on session |
window.get |
Required window |
Section 4 object | read on window |
window.remove |
Required window |
{} |
write on window; must be empty |
pane.list |
Optional session string |
{panes:[{pane,window,session,title,exited}]}; a live pane also has pid and start, its process as RFC-0005 section 3.3 binds it; a pane may have argv0, process and title_current (below) |
read; filter each pane |
pane.split |
Required pane, axis, argv; optional cols, rows, grants |
{pane,window,session,principal,pid} |
spawn on source pane |
pane.send |
Required pane, text string |
{bytes} |
write on pane |
pane.capture |
Required pane |
{text,cols,rows,exited}, optional exit_code or signal |
read on pane |
pane.remove |
Required pane |
{} |
write on pane; must have exited |
IDs and text above are strings; dimensions and PIDs are integers, exited
is boolean, and bytes is a nonnegative byte count. Lists MUST be ordered
by their numeric ID counters, not lexical string order. The manifest MUST
include complete object descriptors; braces in this table are descriptive,
not a separate manifest language.
argv0. A pane that a spawn created (window.create, pane.split, and
the spawning verbs of later RFCs such as RFC-0015 run, RFC-0016
pane.float and pane.popup, and RFC-0010 pane.relaunch) carries an
optional string argv0 in pane.list, in pane.created data and in its
pane snapshot entity (§7). The daemon derives it once, when the spawn
succeeds, from the argv[0] it executes. It never uses RFC-0013 program or
the resolved executable path:
- Take the bytes after the last
/inargv[0], or all ofargv[0]when it has no/./usr/bin/nvimgivesnvim,./run.shgivesrun.sh, and-sh(RFC-0017 §3’s login shell) gives-sh. - If that is empty (
argv[0]is/or ends in/), the pane has noargv0. - If it is longer than 255 bytes, cut it to the longest prefix of at most 255 bytes that ends on a UTF-8 code-point boundary. Request strings are valid UTF-8 (RFC-0002), so the result is valid UTF-8 and not empty.
argv[0] here is the one the daemon actually executes. For an RFC-0015
shell run that is /bin/sh (the daemon runs ["/bin/sh","-c",<shell>]),
so its argv0 is sh. It is never derived from the shell text.
The daemon keeps only this derived string. argv0 is never the directory
part of argv[0] (anything up to its last /), the bytes past 255, or
another argv element. It doesn’t change for the life of the pane: it stays
after the pane exits, and an exec inside the child doesn’t change it. It
is absent when unknown, in particular on a restored archive pane: RFC-0010
§4 checkpoints exclude argv, so restore has nothing to derive it from. A
relaunch creates a pane with its own argv0. Readers MUST treat absence
as unknown and MUST NOT fill it in from title.
argv0 is a display label, not an identity: any spawn holder chooses
argv[0] freely (RFC-0013 §6), so clients MUST NOT use it to decide what
program runs or what it may do, and render it as untrusted text, like
title.
process. A live pane MAY carry an optional string process in
pane.list and in its pane snapshot entity (§7): the name of its PTY’s
foreground process group leader. The daemon reads the group with
tcgetpgrp on the PTY master. It reads the name again only when the group
ID changes: /proc/<pgid>/comm on Linux, proc_name on macOS, and on
other platforms the field is absent. The name is per job, not per process:
make stays make while it runs compilers, and a pipeline shows the
leader of its group. The daemon cuts the name like argv0 (rule 3) and
drops it when it is empty or not valid UTF-8. process is absent when
unknown, in particular when the pane has exited, on a restored archive
pane, or when the group can’t be read. Like argv0 it is a display label,
not an identity: a program sets its own comm (Linux cuts it to 15
bytes), so clients MUST NOT use it to decide what program runs or what it
may do, and render it as untrusted text.
Sampling. The daemon samples a live pane’s foreground group after it
reads PTY output, when it parses an OSC 133 marker or an OSC 2 title, and
on a slow timer, so that a silent command (sleep 100) is seen. It MUST
bound the rate to at most 4 samples per second per pane, and the timer to
at most 1 per second. It MAY run the timer only for panes that an attached
client focuses. Sampling MUST NOT delay PTY bytes to clients or the
parsers. When the daemon parses an OSC 2 title, it samples first, so a
pane.process_changed that the title’s program caused comes before that
pane.title_changed.
title_current. A live pane carries the boolean title_current in
pane.list and its pane entity. It is true when the pane’s title was
set by OSC 2 after its process and its RFC-0003 §8 active command last
changed, and false otherwise, including when the pane has no title. A
pane.title_changed sets it to true. A pane.process_changed, a
command.started or a command.finished sets it to false. A client that
follows events can therefore track it without a pane.list round trip.
Naming a pane. A client that shows what runs in a pane (RFC-0022 §2.8 and §3.2) SHOULD prefer, in order:
title, whentitle_currentis true;- the
cmdlineof the pane’s active RFC-0003 §8 command; process;argv0;title, when nothing else is known.
A title belongs to whoever set it, so a shell’s title doesn’t hide the program that runs after it, and a program’s own title (an agent’s task line) still wins over its process name.
argv is a nonempty array of strings, with a nonempty first entry and no
NUL bytes. It is executed directly, never implicitly through a shell.
cols and rows default to 80 and 24 and are in 1–65535. The daemon MUST
resolve the executable using its environment and inherit its working
directory. Per-request cwd and arbitrary environment overrides are deferred;
callers can explicitly execute a shell if shell behavior is intended.
grants uses the optional typed array from RFC-0005 section 6.2, including
its default interactive profile for owner-created panes. Spawn authorization
is sufficient for the topology insertion inherent in creation/splitting;
it MUST NOT be replaced by a weaker write-only check.
pane.send sends the exact UTF-8 bytes in text, with no implicit newline
or key notation. Its bounded queue and ordering MUST be the same ones used
by RFC-0006 INPUT. Successful {bytes} means the complete input was
accepted into that queue, not executed by the program. An exhausted queue
MUST reject the entire request before inserting any bytes. Binary and
keyboard-protocol input from presentation clients remains RFC-0006 INPUT.
pane.capture reads only the active visible screen from the authoritative
Vt. It MUST export text without terminal control sequences or styles,
separate physical rows with LF and omit width-zero continuation cells.
It MUST preserve cell text and spaces, with no final LF. It MUST NOT move
any client’s viewport. Large results that cannot fit the negotiated frame
MUST fail unsupported.result_size; history-range capture is deferred.
An explicit pane.list.session is authorized on that session before
lookup. With it absent, list every authorized pane, including when the
caller cannot read its parent session; returned ancestor IDs identify
containment, not permission to inspect sibling panes. session.list
does not expose names of unreadable sessions. window.get and
session.get require their container’s read right. These container results
expose membership IDs and display names as container metadata; they MUST
NOT expose descendant terminal contents or credentials. Concrete container
scopes include descendants under RFC-0005; a kind wildcard such as
window:* does not grant read on panes. Each pane attach/capture MUST
check its own right even after container discovery. A presentation client
MUST show an unavailable placeholder for an unreadable leaf, without
attempting owner elevation beyond RFC-0005. Direct attach --pane serves
pane-scoped callers.
Creating a window or splitting MUST prepare its child principal, PTY, process, layout allocation and events before commit. Before exec, the parent MUST establish the ancestry binding and release a child-start barrier. An exec-status pipe MUST distinguish exec failure from a program that starts successfully and later exits 127. On preparation/exec failure, the daemon MUST terminate/reap any child, close its PTY, revoke the prepared principal and discard staged resources/events. IDs may be consumed; ghost panes and partial trees MUST NOT be visible. Fast successful process exit still creates an exited pane and ordered create/exit events.
This version removes only exited panes. Removing a running pane MUST fail
conflict.pane_running; removing a nonempty container MUST fail
conflict.not_empty, without terminating or deleting anything. Explicit
live-pane kill and recursive removal are deferred. This keeps removal
atomic without an unbounded synchronous wait or an implicit destructive
cascade. Shutdown remains the separately defined process-group operation.
6. Errors
Shared transport, validation, authorization and cancellation errors retain their meanings and exits. Product declarations additionally include:
| Code | Exit | Meaning |
|---|---|---|
invalid_params.name |
2 | Invalid display name |
invalid_params.argv |
2 | Invalid argument vector |
invalid_params.size |
2 | Invalid dimensions |
not_found.session, .window, .pane |
3 | Authorized target absent |
denied.read, .write, .spawn |
4 | Required scoped right absent |
denied.delegation |
4 | Requested child grants exceed creator’s authority |
conflict.not_empty |
5 | Container still has children |
conflict.pane_running |
5 | Removal requires an exited pane |
conflict.pane_exited |
5 | Input or split requested on an exited pane |
unavailable.spawn |
6 | PTY/process preparation or exec failed |
unavailable.input |
6 | Entire input cannot be queued |
unavailable.shutdown |
6 | Mutation attempted during shutdown |
unsupported.result_size |
10 | Capture/list/layout result cannot fit the negotiated frame |
Name/argv/size errors use data.param naming the failing parameter.
Not-found, conflict, unavailable and result-size errors have empty data;
grant error data follows RFC-0005. Diagnostics MUST NOT include tokens or
raw environment contents. Allocation/ID/revision exhaustion MUST fail
without committing partial state, using standard internal (exit 11).
Protocol and hello errors remain RFC-0002. Stream errors remain RFC-0006.
7. Discovery and topology events
The product MUST extend its RFC-0003 event descriptor and catalog with:
| Type | Resource | Data | Snapshot entity |
|---|---|---|---|
session.created |
session:<session> |
session, name |
session: {session,name} |
session.removed |
session:<session> |
session |
Session disappears |
window.created |
window:<window> |
Complete section 4 object | window: complete section 4 object |
window.layout_changed |
window:<window> |
window, session, revision, nodes |
window: complete section 4 object |
window.removed |
window:<window> |
window, session |
Window disappears |
The product MUST preserve the fixed types of existing descriptor fields
and add these names with the same types in every event. nodes is an array
of the closed node descriptor from section 4, with per-kind field rules
validated by the publisher. Window/session events are authorized against
their own resource. Their snapshots include current state even when no
retained event exists. Empty containers remain snapshot entities. A
session snapshot contains only its own ID and name; window membership is
represented by window entities and queried by session.get. Event type
selection MUST select these current entities according to
RFC-0003’s catalog mapping, not reconstruct them from historical events.
Creation MUST commit state and append events in one reactor turn: session
creation emits session.created; window creation emits window.created,
then existing pane.created; splitting emits pane.created, then
window.layout_changed. window.created carries the initial tree in its
data, so no redundant initial layout event is required.
Pane removal emits RFC-0003’s observation cleanup and pane.removed, then
window.layout_changed. Container removal emits its removal event.
The relevant grant containment/ownership data MUST remain available until
removal events have been authorized for existing subscribers. Event
snapshot capture cannot observe half of a mutation. Root-process exit
emits the existing observation/pane.exited sequence; layout is unchanged.
venomx extends RFC-0003’s pane.created data and pane snapshot entity
with the optional string argv0 of §5. It is in pane.created data and in
the entity exactly when the pane has one, so a client that renders titles
from events needs no pane.list round trip. pane.title_changed and
pane.exited don’t carry it, and it never changes, so no event updates it.
The name is new in the shared descriptor, with the single type string.
venomx adds this event to its catalog, with the process and
title_current fields of §5 on the pane entity:
| Type | Resource | Data | Snapshot entity |
|---|---|---|---|
pane.process_changed |
pane:<pane> |
pane string, optional process string |
pane |
The daemon emits it when a sample (§5) gives a different process from
the last one, including a change to or from absent. A sample that gives
the same name, or a group change whose leader has the same name, emits
nothing. pane.exited implies that process is absent, so no
pane.process_changed follows it. process is new in the shared
descriptor with the type string, and title_current with the type
boolean.
For pane.exited, normal exit uses exit_code; signal death omits it.
The optional signal in product capture results and RFC-0006 EXIT does
not invent an exit code in RFC-0003’s existing pane event/entity.
A session presentation client SHOULD subscribe to window.* with empty
resources (the authorized projection), stage the initial RFC-0003 snapshot
through its checkpoint and select the windows for its target session.
New window creation is thereby visible without racing a list-then-listen
sequence. RFC-0003 resource filters are exact and do not include children;
resources=["session:s0"] is not a subscription to its windows or panes.
The client uses the same event cursor/recovery rules on gaps and reconnect.
It discovers current container names through ordinary scoped queries.
8. Attach boundary
COM-223 MUST support venomx attach --session <id> and
venomx attach --pane <id>; exactly one selector is required. Session
attachment initially shows the lowest-ID window, with local window choice
and pane focus. An empty session/window MUST have a visible empty state.
Creation or removal events update the shared topology; a removed focused
pane falls back locally to the lowest-ID surviving pane. No focus change
is broadcast as a mutation of another client’s view.
Each displayed pane MUST have one odd-ID stream on a negotiated binary
connection with venomx.pane_stream, as RFC-0006 specifies. Several panes
SHOULD share that connection. CONTROL or a separate ordinary connection
carries discovery, events and verbs. There is no new frame, raw PTY
subscription or alternate socket. A stream’s offset/decoder/resync state
is independent of other streams; losing the connection requires restoring
each affected stream. Emulating Vts MUST NOT send terminal query replies
or publish duplicate OSC events.
Presentation clients follow RFC-0005’s owner-file default and managed-tree restrictions. All product mutations invoked by bindings MUST use the schema-defined daemon verbs and the same authorization as CLI/MCP calls. Local viewport actions remain part of COM-223: they MUST use typed named actions shared by command invocation and bindings, without sending scroll or selection state to the daemon. This RFC adds no product-local dispatch field to RFC-0004 format 1, whose product verbs remain daemon requests. Any shared generator extension required for those local actions must be specified and reviewed before COM-223 ships.
9. Implementation boundary and verification
COM-238 MUST use the COM-222 component, venom-std proto/identity/dispatcher, shared config/events/artifact bundles and process supervision. It MUST pass the packaged agent-contract conformance suite from COM-216; build-only or library tests are insufficient. COM-216’s prerequisites include the unfinished shared config (COM-210), events (COM-208), artifacts (COM-215) and supervisor (COM-211). Complete-schema identity is COM-226. This RFC can be reviewed while those dependencies progress; the implementation issue MUST record them and MUST NOT replace them with product-local stubs.
Foundation acceptance tests MUST cover:
- A real headless daemon and two socket clients create/discover a session, window and split; CLI/MCP share result, error and grant behavior.
- All clients disconnect; the same PTYs and panes remain discoverable, continue recording output and attach with restored screen state.
- Nested cols/rows splits and removal preserve tree invariants; empty windows/sessions survive until explicitly removed.
- Unknown callers cannot mutate; scoped callers see only authorized state; child principals and environments do not inherit owner tokens.
- Spawn/exec failures and injected allocation failures leave no visible resources, leaked children, PTYs, principals or creation events.
- Exit status/observation ordering and retained exited-pane capture work; live-pane/nonempty removal is rejected without side effects.
- Topology changes during event snapshot/replay cause neither missing windows nor duplicate panes; removal visibility respects grants.
- Startup locking, no-daemon exit 6, bounded SIGINT/SIGTERM shutdown and new source identity after restart follow the lifecycle above.
- Build, allocator/leak tests, formatting and shared conformance pass.
argv0(§5):argv[0]/usr/bin/nvimgivesnvim,./run.shgivesrun.shand-shgives-sh;/anda/give noargv0. A 300-byteargv[0]whose multi-byte code point straddles byte 255 is cut before that code point. The same value appears inpane.list, inpane.createddata and in thepaneentity. A restored archive pane has noargv0. RFC-0015run --shellgivessh. Canaries placed in the directory part ofargv[0]or in later argv elements appear in nopane.listresult, event, entity, error or doctor output (terminal content the program itself prints is out of scope).process,pane.process_changedandtitle_current(§5, §7):sleep 1in a shell pane changesprocessfrom the shell’s name tosleepand back, with one event each way, even with no PTY output.makerunning children staysmake. A program that sets its title right after it starts gets itspane.process_changedbefore itspane.title_changed, and itstitle_currentis true. The shell’s earlier title becomes not current on the next process change orcommand.started. A burst of output samples at most 4 times a second. An exited or archive pane has noprocess. Acommwith invalid UTF-8 gives none.
COM-223’s separate end-to-end test adds two real presentation clients on one session: both see typing, their scroll/selection state is independent, one can be killed without affecting the other, and reattachment restores screen state. It additionally exercises incremental backfill, pending-row loading, offset mismatch, per-stream resync and topology changes.
Agent-contract impact
- Verbs/schema: section 5 defines the minimum product manifest, merged with existing shared declarations; CLI, MCP, docs and completions come from that one schema. Presentation attach is the RFC-0006 local entry.
- JSON/exits: RFC-0001/0002 unchanged; section 6 declares product errors.
- Events: section 7 adds session/window events and snapshots to the RFC-0003 product catalog. Existing pane/OSC events retain their semantics.
- Skill: generated text MUST describe explicit startup, in-memory lifetime, direct argv execution, named flags and read-only defaults. It MUST NOT expose the owner-token path or recommend agent attachment.
- Doctor: add checks for source, session/window/pane counts, tree invariants, process supervision and RFC-0006 ring/client/resync state; preserve standard redaction and result-status rules.
- Config: use shared ZON verbs and RFC-0005 grants plus RFC-0006
pane_streamfields. No new startup-mode or persistence config appears in this version. Reload MUST NOT destroy resources or change source. - Grants: section 5 supplies verb checks and section 3 the containment relation; RFC-0005 issuance, unknown defaults and audit rules are intact.
- Headless/OSC: the daemon owns PTYs with no display. Only its Vt answers queries and publishes validated OSC 7501/133 observations.
Alternatives
- Pane-only attach first: useful protocol validation, but cannot meet COM-223’s same-session criterion or provide normal product discovery.
- Put the entire foundation in COM-223: mixes lifecycle and grant changes with renderer behavior and makes independent review difficult.
- Build the entire MVP first: persistence, tmux compatibility, automatic startup, arbitrary layouts and trusted run/wait execution are useful later; they are not necessary to test session attachment.
- Client-owned session topology: would let clients disagree about membership and make detached/headless mutations depend on a TUI.
- One socket per pane: permissible but unnecessary; RFC-0006 already supplies independent streams and CONTROL traffic on one connection.
- Assume same-UID clients are writers: contradicts RFC-0005’s explicit read-only unknown principal and managed-tree restrictions.
- Do nothing: leaves COM-223 without a runnable product acceptance path.
Open questions
No unresolved decision is required for the scope proposed here. Maintainer review is required before acceptance, especially for the deliberately small initial surface: explicit persistent startup, equal splits, exited-only pane removal and no per-request cwd/environment overrides.
Local presentation-action schema support is deferred to COM-223 and MUST be specified and reviewed there before its client ships. Remaining future product extensions are tracked by COM-239; they MUST NOT be inferred from the simple create/send verbs or included in COM-238. Their implementing issues must specify any additional contracts before they ship.