Shared presentation chrome
| Field | Value |
|---|---|
| Status | Accepted |
| Accepted | 2026-11-07, explicitly approved by the maintainer |
| Authors | commixon (commixon), with Claude |
| Created | 2026-11-07 |
| Linear | COM-291; implementation COM-293, COM-294 (venom-std), COM-296, COM-297, COM-298 (venomx), COM-299 (venom), COM-300 (venom-desk), COM-301 (venom-os) |
| Supersedes | None; compatible extension of RFC-0006, RFC-0007 and RFC-0011, and adds an optional namespace to RFC-0005 grant.issue |
| Superseded by | None |
History
- 2026-11-13 (venom-org/venom-spec#174, normative; explicitly accepted by the
maintainer on 2026-11-13): with
grants.isolationoff (RFC-0005 section 11.1, the venomx default), namespaces are names only. Any principal may use any namespace exceptvenomxfor segments, key tables and float presets, and may clear any non-venomxsegment (§5.1, §7.4, §8.2). Also, a segment set withttl_msand nobinddefaults tobind = daemon(§5.1), so a CLI call’s segment outlives the call. Implementation: venomx#219. - 2026-11-13 (venom-org/venom-spec#173, normative, compatible addition; requested by the maintainer
on 2026-11-13 in venom-org/venomx#215): §7.7’s copy mode gains
/and?search andn/N. Implementation: venomx#215. - 2026-11-13 (venom-org/venom-spec#172, normative, compatible addition; requested by the maintainer
on 2026-11-13 in venom-org/venomx#213): §7.7’s scroll group gains
v,copy-mode, a vi-style keyboard selection that copies with OSC 52. It is client state, like scrolling (section 3). Implementation: venomx#213. - 2026-11-12 (venom-org/venom-spec#162, normative, breaking; explicitly
accepted by the maintainer on 2026-11-12): RFC-0023
splits each status segment into a slot and a source (static, template,
rule, pushed, exec, group), adds ordered
.left/.center/.rightlists, groups (the tray) and click targets, and addsstatus.rule.set|clear|list. It replaces section 5.1’s built-in segment table and removes section 5.3’sstatus.hideandstatus.priority. The rest of section 5 stands. - 2026-11-11 (venom-org/venom-spec#158, normative, compatible addition;
requested in venom-org/venomx#191 and explicitly accepted by the maintainer
on 2026-11-11):
all-keys(leader ?) is defined. It opens a list of every active binding that typing filters by fuzzy match, and⏎types the selected keys. Before,?at the root only showed the popup that was already up. Sections 7.5, 7.6 and 7.7 are updated, and section 12 gains case 22. Implementation: venom-org/venomx#193. - 2026-11-11 (venom-org/venom-spec#149, normative; requested in venomx#170 and
explicitly accepted by the maintainer on 2026-11-11): the default leader is
ctrl+band the default unlock isctrl+alt+b, replacing GH-83’sctrl+aandctrl+alt+a. The tmux habit wins over Claude Code’s background-task key, which leader leader still delivers (ctrl+b ctrl+b), and the shell’sctrl+anow reaches the pane unchanged. Sections 7.2, 11 and 12 (cases 4 and 19), Alternatives, Open questions and the examples are updated. Implementation: venomx#170. - 2026-11-10 (venom-org/venom-spec#135, editorial; accepted by venom-run
(unattended, delegated by the maintainer) on 2026-11-10): section 10.1’s
hostexample is redrawn with RFC-0022 section 3.2’s pane titles on the line above each pane. §7.7’s minimums are unchanged: the title row and the command-mark gutter come out of the window area before the layout tree divides it. - 2026-11-10 (venom-org/venom-spec#133, normative, compatible addition;
explicitly accepted by the maintainer on 2026-11-10): §7.7’s scroll group gains
{and}, which jump between the command starts a pane reports with OSC 133 A. RFC-0022 section 3.8 draws them. Section 12 gains case 21. - 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): §7.7’s
last bullet follows RFC-0007 §4, which now clamps an ancestor’s
ratioon a split instead of resetting it. - 2026-11-08 (venomx#44, normative; explicitly accepted by the maintainer on
2026-11-09):
§7.7 defines what
pane.resizedoes: which divider moves, its units, its limits, its result and its grant. - 2026-11-07 (COM-360, maintainer decision; normative, accepted through the
COM-360 PR review): the presentation client’s status line defaults to the
top of the terminal (
status.position = .top, section 5.3). venom draws its own status bar on the bottom row of the area it draws, in bothownandhostmodes (section 10); a product may draw its own bar inside its own pane, never one that imitates or replaces the presentation client’s (section 2). venom publishes novenom.*segments in v1 (section 10.2). Section 1’s example, section 11 and conformance item 2 are updated to match. - 2026-11-07 (COM-363, editorial): enum-typed config values are written as
ZON enum literals, as products parse them:
status.position = .top,ui.chrome = .auto, and the floatanchorandbordervalues (sections 5.3, 8.2, 10, 11). Multi-wordanchorvalues are spelled with underscores (.top_left) so they are plain enum literals. A quoted string for an enum-typed key is rejected (invalid_params.config_zon). - 2026-11-07 (COM-404, editorial): sections 4 and 8.1 point to RFC-0010 §4.1 checkpoint format 2, which makes pane tags and floating panes durable and never checkpoints popups. Nothing in this RFC changes.
- 2026-11-08 (GH-67, normative, compatible addition; explicitly accepted by the
maintainer on 2026-11-09): a product’s host-mode
key table and presets are registered by the owner, from outside every
managed tree, and such a table has no expiry (section 7.4). No pane
holds
writeonserver:rootto register. The owner may now use any table or preset name outsidevenomx; every other caller’s rule is unchanged. Section 7.4 also records that the last registrar wins. Sections 8.2, 10 and 12 are updated to match. Implementation: venomx#40, venom#87. - 2026-11-08 (GH-83, normative; explicitly accepted by the maintainer on
2026-11-09): the default leader is
ctrl+aand the default unlock isctrl+alt+a, because macOS reservesctrl+space(section 7.2). Leader mode has no timeout by default, andescandbackspaceleave it. Sections 7.5 to 7.7 add a key grammar shared by every product, a leader popup and a new built-in table. Bindings and groups gain labels, groups can be sticky (section 7.3), andkeys.prefixeschanges a product’s sub-prefix without restating its bindings (section 7.4). venom’s sub-prefix becomesvand venom-os’s becomeso. A newpane.resizeverb changes split ratios. Sections 1, 5.1, 10.1, 11 and 12 and the open questions are updated to match. - 2026-11-08 (GH-93, normative; explicitly accepted by the maintainer on
2026-11-09): section 7.3 says when a client reads
whenin leader mode: at the leader and at each focus move, not while typing. A focus move ends leader mode if the typed keys no longer lead to more keys. Section 12 gains case 20. Implementation: venom-term#39, venomx#57, venom-desk#14 (venom#130). - 2026-11-13 (GH-170, normative, compatible addition; explicitly accepted by
the maintainer on 2026-11-13): the built-in table binds
etotoggle-pane-float, which floats the focused tiled pane or tiles the focused float back throughpane.place(sections 7.7 and 8.3). Whenpane.placefloats a tiled pane,pane.placednow comes beforewindow.layout_changed. Implementation: venomx#207 (venomx#208).
Summary
venomx is the backbone that venom, venom-desk and venom-os build on, so
several UI features are wanted by more than one product: the status bar,
key bindings, notifications, floating views and pane titles. This RFC
decides who owns each of them. One presentation client per terminal
draws all chrome (venomx attach or venom-desk). Products publish
content into state that the venomx daemon holds: status segments,
pane tags, attention, notifications, key tables and floating panes. Every
attached client renders that state itself. The RFC also lists, as a
normative table, which presentation state belongs to each client and which
is shared, and it defines venom’s chrome modes. venomx implements the
daemon state and attach rendering; venom, venom-desk and venom-os adopt
it.
Motivation
The ecosystem doc (section 3) gives venomx “panes, sessions, persistence” and venom-desk “palette, which-key, rail, notifications”, but leaves open what happens where both want the same surface:
- Status bars. venom wants “2 agents running, 1 waiting” on screen;
venomx already draws a status line (
attach_app.zig); venom-desk wants a richer one. Three bars stacked on one screen, or a bar that only one product can write to, are both bad outcomes. - Key bindings. venom runs two Claude Code agents side by side in venomx
panes. Claude Code uses
ctrl+c,esc,shift+tab,ctrl+r,ctrl+o,ctrl+tandctrl+b(background a task). venomx’s hard-coded prefix today isctrl+b. venom also wants its own keys (“approve”, “next thread”) without stealing keys from venomx or from the agents. - Notifications and focus. venom needs to tell the human an approval is waiting. RFC-0007 section 8 forbids broadcasting focus, so venom cannot just jump the human’s view to the pane.
- Floating views. venom wants a settings view or a diff over the agents; venom-os wants a launcher and system settings. Each product drawing its own overlay means three overlay systems with three sizing rules.
Without a decision, each product will grow its own version of these and they will collide on screen and on the keyboard. Waiting on this RFC: COM-281 (attach chrome theme, which must know what the status line holds), venom’s TUI (P2) and venom-desk (P3).
Design
1. Overview by example
venom creates an agent pane, tags it, publishes a segment and registers its key table. All of this is ordinary verbs under venom’s own principal. (The segment shows the mechanism, which any product may use; venom itself does not publish segments in v1 and shows its counts on its own bar instead, section 10.)
$ venomx pane split --pane p0 --axis cols --argv '["claude"]' \
--tags '{"role":"agent","group":"thread-7"}' --json
{"pane":"p4","window":"w0","session":"s0","principal":"g9","pid":41022}
$ venomx status set --id venom.agents --side right --priority 50 \
--text "2 running · 1 waiting" --style warn --json
{"id":"venom.agents","revision":1}
$ venomx keys register --name venom --prefix v --label venom \
--bindings @venom-keys.json --json
{"name":"venom","prefix":"v","layer":"product","shadowed":[]}
$ venomx pane attention --pane p4 --level urgent --reason "approval: rm -rf build/" --json
{"pane":"p4","level":"urgent"}
The human’s venomx attach (or venom-desk) shows, on its own status line
(the top row by default, section 5.3):
s0 work │ 0:main* 1:logs ▲ 2 running · 1 waiting │ 14:02
and marks p4’s border in the urgent colour. The human presses ctrl+b,
then v, and the leader popup lists venom’s keys. i opens venom’s inbox as
a popup on this client, where venom handles the answer under its own
authorization. Neither venom nor venomx moved the human’s focus; the human
chose to look.
A second human attached to the same session sees the same segment and the same attention marker, but keeps their own window, focus and scroll (section 3).
2. Roles
| Role | Who | Owns |
|---|---|---|
| Daemon | venomx serve |
Shared state: topology, pane tags, status segments, attention, notifications, registered key tables, floating panes and popups (sections 4–9) |
| Presentation client | venomx attach, venom-desk |
Everything drawn on its terminal: status line, borders, overlays, which-key, the leader key and key routing, per-client state (section 3) |
| Product | venom, venom-os, agents, scripts | Content only: it writes daemon state with verbs and never draws on a terminal it does not own |
Rules:
- A terminal MUST have at most one presentation client. venom-desk is a
presentation client of venomx in its own right (RFC-0006 streams plus
verbs). It replaces
venomx attachand MUST NOT run inside a venomx pane as its normal mode of operation. - A presentation client started inside a venomx pane (
VENOMX_PANEis set) is nested. It MUST say so in its status line and SHOULD warn on start. Nesting is allowed (for examplesshto another host) but the defaults of this RFC assume it does not happen. - A product MUST NOT draw chrome inside a pane to imitate a status bar or an overlay of the presentation client. A product’s own TUI running in a pane (venom’s control pane, section 10) is ordinary pane content.
- Exception: a product’s own status bar. A product MAY draw a status bar of its own inside its own pane, about its own state (venom’s bottom bar, section 10.1). It MUST stay inside that pane, MUST NOT show the presentation client’s content (session, windows, mode, clock, other namespaces’ segments) and MUST NOT present itself as the terminal’s status line. The presentation client remains the only drawer of terminal-level chrome.
3. Per-client and shared state
This table is normative for every presentation client.
| State | Owner | Notes |
|---|---|---|
| Window shown | Client | Unlike tmux, attaching twice to one session does not mirror the current window |
| Pane focus | Client | RFC-0007 section 8; never broadcast |
| Scroll position, selection, copy mode | Client | RFC-0006 section 7 |
| Zoom (one pane fills the window) | Client | A viewing choice, like scroll. Zoom never resizes other clients’ panes. |
| Leader, lock and key-table mode | Client | |
| Which floating panes are visible | Client | Section 8 |
| Popups | Client | Visible only on the client they were opened for (section 8) |
| Status line layout and truncation | Client | Content is shared (section 5) |
| Pane content, cursor and modes | Shared | One program, one screen |
| Topology: sessions, windows, panes, splits | Shared | RFC-0007 |
| Split ratios | Shared | Part of the window layout, so venom-desk’s tiling shows the same arrangement to everyone |
| Floating pane geometry and z-order | Shared | Resolved against each client’s viewport (section 8) |
| PTY size | Shared | RFC-0006 section 8 policy (latest by default); watchers never resize |
| Pane tags, status segments, attention, notifications, key tables | Shared | Sections 4–7 |
A verb MUST NOT change any Client row of another client. There is no verb that moves a client’s focus, window, scroll or zoom. A product that wants the human’s eyes uses attention (section 6).
4. Pane tags
Pane tags are small string maps that let products describe their panes without venomx knowing what an “agent” is. Tags drive tag-scoped segments, key tables and floating presets.
$ venomx pane tag --pane p4 --set '{"role":"agent","group":"thread-7"}' --unset '["stale"]' --json
{"pane":"p4","tags":{"owner":"venom","role":"agent","group":"thread-7"}}
- Keys match
[a-z][a-z0-9_.-]{0,31}; values are UTF-8, 1–128 bytes, with no C0/C1 control characters. A pane has at most 16 tags. owneris set by the daemon at pane creation to the namespace of the creating principal (section 5.1) and is read-only. A pane created by the human throughattachgetsowner = "venomx".roleandgroupare conventional, not interpreted by venomx:roleis what the pane is (agent,shell,control,log),groupties panes of one unit of work together (a venom thread).window.create,pane.split,pane.floatandpane.popupaccept an optionaltagsparameter.pane.tagrequireswriteon the pane.- Tags are in the pane’s snapshot entity and in
pane.list. Changes emitpane.tags_changed {pane, tags, revision}. - Tags,
ownerincluded, are durable: RFC-0010 checkpoint format 2 (RFC-0010 §4.1) saves them and restore brings them back as saved. They stay labels, never permissions.
5. Status segments
5.1 Model
A segment is a short piece of styled text that some principal wants shown in every client’s status line.
| Field | Type / default | Meaning |
|---|---|---|
id |
<namespace>.<name>, required |
Namespace is the setting principal’s tool namespace (venom, venom-os, a user script’s user) |
text |
UTF-8, ≤ 256 bytes, no control characters | Shown verbatim; clients truncate |
side |
left | right, default right |
|
priority |
0–100, default 50 | Higher stays longer when space is short |
style |
normal | muted | accent | ok | warn | error, default normal |
A theme role, never a raw colour (brand book, COM-281). Each style has a brand glyph (● ▲ ✕ ◆ ○) so state is never shown by colour alone. |
when |
Tag map, optional | Shown only while the client’s focused pane has all these tags |
scope |
server | a session ID, default server |
A session-scoped segment shows only on clients showing that session |
ttl_ms |
1000–86400000, optional | Removed when it expires |
bind |
connection | daemon, default connection, or daemon when ttl_ms is given |
connection: removed when the setting connection closes, so a crashed venom leaves no stale “2 running”. A segment with ttl_ms already says when it ends, and one set from the CLI would otherwise vanish when the call returns. |
The namespace of a principal is a grant attribute. A venomx token
always names venomx as its tool (vnt1.venomx.<principal>.<secret>,
RFC-0005 section 3), so the namespace cannot come from the token. Instead
grant.issue gains an optional namespace parameter (matching
[a-z][a-z0-9-]{0,31}):
$ VENOMX_TOKEN=@owner venomx grant issue --grants '[{"right":"write","scope":"session:s0"},{"right":"spawn","scope":"session:s0"}]' \
--namespace venom --label venom --json
{"principal":"g9","token":"vnt1.…","expires":"…"}
- The owner and
adminprincipals may issue any namespace exceptvenomx. Any other issuer may only pass its own namespace; a child principal inherits its issuer’s namespace when the parameter is omitted (the attenuation rule of RFC-0005 section 6). - The owner’s namespace is
venomx. Principals created by venomx itself (pane principals, RFC-0007) inherit the namespace of the principal that created the pane. - A principal with no namespace (issued before this RFC, or by the owner
without the parameter) has namespace
user. - A principal MAY only set ids in its own namespace.
venomx.*is reserved for built-ins; the owner sets user segments asuser.*. - Isolation off (RFC-0005 section 11.1, the venomx default): the
namespace is a name, not a check. Any principal may set, replace or clear
ids in any namespace except
venomx, which stays reserved.grant.issue --namespacestill records the namespace,ownertags still carry it, andwhenfilters still match on it. The same holds for key tables (section 7.4) and float presets (section 8.2). Grants still apply:status.setneedswriteon its scope.
Built-in segments, all overridable in config (section 5.3). Replaced by RFC-0023 section 8, which lists every built-in and its default source; the table below is kept for history:
| ID | Side | Priority | Content |
|---|---|---|---|
venomx.session |
left | 100 | Session ID and name |
venomx.windows |
left | 90 | Window list, current one marked |
venomx.mode |
left | 95 | LEADER, LOCK, NESTED, table or sticky group name |
venomx.attention |
right | 80 | Count of panes needing attention |
venomx.clock |
right | 10 | Local time |
5.2 Verbs
| Verb | Params | Result | Grant |
|---|---|---|---|
status.set |
id, text, optional side, priority, style, when, scope, ttl_ms, bind |
{id, revision} |
write on scope (server:root for server) |
status.clear |
id |
{} |
Same as set; only the namespace owner |
status.list |
optional scope |
{segments:[…]} |
read |
status.set replaces the whole segment. Events: status.changed {id, revision, segment} and status.cleared {id, reason} with reason clear,
ttl, disconnect or revoked.
Errors:
| Code | Exit | When |
|---|---|---|
invalid_params.segment |
2 | Bad id, text, priority or when |
denied.namespace |
4 | Id outside the caller’s namespace, or venomx.* |
conflict.segment_limit |
5 | More than 32 segments for one namespace, or 128 in total |
5.3 Rendering and config
Changed by RFC-0023: layout is now ordered
.left,.centerand.rightlists, plus.segments.status.hideandstatus.prioritybelow are removed, andstatus.positionis unchanged.
Each client lays out segments by side, places them in priority order and
drops the lowest-priority ones first when the line is too narrow. A
segment that does not fit whole is truncated with … only if it is the
last one placed. Order within a side is by priority, then id.
.status = .{
.position = .top, // .top (default) | .bottom | .off
.hide = .{ "venomx.clock" }, // ids never shown
.priority = .{ .@"venom.agents" = 70 }, // per-id override
},
Enum-typed keys take ZON enum literals, in the config file and in
config set --value alike: .bottom, never "bottom".
Config changes go through config set|reload as usual. status.position = .off hides the line; segments still exist and venom-desk can show them
elsewhere (its rail). Moving it to the bottom:
venomx config set --key status.position --value '.bottom' --json
The default is .top so that the presentation client’s line and a
product’s own bar at the bottom of its pane (section 10.1) never sit on
adjacent rows looking like one stacked bar. A user who moves the line to
.bottom gets it below venom’s bar; nothing else changes.
6. Attention and notifications
Attention is a per-pane flag, like a tmux bell made explicit:
$ venomx pane attention --pane p4 --level urgent --reason "approval waiting" --json
{"pane":"p4","level":"urgent"}
$ venomx pane attention --pane p4 --clear --json
{"pane":"p4","level":"none"}
- Levels:
none,info,warn,urgent.reasonis ≤ 256 bytes. Grant:writeon the pane. - Clients MUST mark the pane (border in the level’s style, plus a glyph)
and count it in
venomx.attention. Clients SHOULD offer a built-infocus-attentionaction that focuses the next pane with attention. - A client clears attention by sending
pane.attention --clearunder its own identity when its user focuses the pane, unlessattention.sticky = true. A product can clear it itself (for example when the approval is answered elsewhere). - An OSC 7501 report and a BEL from the pane’s program MAY set
infoattention (configattention.from_bell, defaulttrue). As in RFC-0003 section 7, these are observations: they never carry approval or identity. - Event:
pane.attention_changed {pane, level, reason, revision}.
Notifications are short messages not tied to a pane:
$ venomx notify post --text "thread-7 finished" --level info --ttl-ms 8000 --json
{"id":"n12"}
- The daemon keeps a bounded queue (64 entries, oldest dropped). Each
entry has
id,text(≤ 512 bytes),level, optionalpane,ttl_ms(default 8000) and the poster’s namespace. venomx attachshows the newest unexpired entry in its status line for its TTL. venom-desk MAY keep a history and an inbox; that is product code.- Verbs:
notify.post(writeonserver:root),notify.listandnotify.dismiss(read, and dismiss is local to the client unless--all, which needswrite). Event:notify.posted.
Products MUST NOT emulate attention by moving focus, and no verb exists to do so (section 3).
7. Key tables
7.1 Layers of keys
- The focused pane’s program, for example Claude Code.
- A product TUI in a pane, for example venom’s control pane. It is layer 1 from venomx’s point of view.
- The presentation client: the leader key and the tables under it.
The client intercepts only the leader key (and, in lock mode, the unlock key). Every other key goes to the focused pane unchanged, encoded for its terminal modes (COM-257). The default configuration has no unprefixed bindings.
7.2 Leader
- Default leader:
ctrl+b, on every platform, as in tmux. It is a plain control byte (0x02), so it needs neither the Kitty keyboard protocol nor anything special over ssh. Its cost is that Claude Code uses it to background a task, which then takes a double press (leader leader). keys.leaderchanges it to any chord.ctrl+spaceworks where the OS and terminal deliver it (legacy input sends NUL,ctrl+@, which the client also accepts).ctrl+a(GH-83’s default) suits screen users, at the cost of the shell’s start of line.- Leader then leader sends the leader key to the pane, which also reaches a nested venomx.
- After the leader the client shows the leader popup and waits for the next key (section 7.6).
esccancels andbackspacegoes up one level. An unbound key cancels and is swallowed (section 7.5).- Lock mode (
leaderthenL, built-inlock) passes every key, including the leader, to the pane until the unlock key (keys.unlock, defaultctrl+alt+b) is pressed.venomx.modeshowsLOCK.
7.3 Tables and bindings
A table is a named set of bindings. The built-in venomx table holds
frequent actions directly after the leader (section 7.7). Product tables MUST live under a sub-prefix: one key after the leader
that opens the table, so a product can never shadow a built-in.
.keys = .{
.leader = "ctrl+b",
.tables = .{
.{
.name = "venom",
.prefix = "v",
.label = "venom",
.groups = .{
.{ .key = "a", .label = "agents" },
},
.bind = .{
.{ .key = "a s", .label = "stop", .exec = .{ "venom", "agent", "stop", "--pane", "{pane}" } },
.{ .key = "s", .label = "settings", .verb = "pane.popup", .args = .{ .preset = "venom.settings", .argv = .{ "venom", "settings" } } },
},
},
},
},
-
Key syntax. Kitty-protocol names joined with
+(ctrl+space,alt+shift+left,%), and a space between keys of a sequence. -
when. A tag map; the table is active only while the focused pane has those tags. A table withoutwhenis always active. In leader mode the client reads this when the leader is pressed and again each time its focus moves to another pane, for example because a popup closed, the focused pane exited or a click landed. Typing never re-reads it, and neither does a change to a pane’s tags, which applies from the next leader or focus move. After a focus move the popup shows the new pane’s tables and any typed keys are looked up again. If they still lead to more keys, leader mode goes on; otherwise it ends and nothing runs. With no keys typed yet it always goes on, even with no table active. The timeout (section 7.6) is not restarted.focus p7 (popup), table venom inactive ctrl+b popup has no `v` p7 closes, focus back on p0 (owner=venom) popup gains `v` v s runs venom's settings focus p0 (owner=venom) ctrl+b v LEADER venom focus moves to p1 (no owner tag) leader mode ends s reaches p1 -
Labels.
.labelon a binding is what the popup shows, and.labelon a table names its sub-prefix: 1–40 bytes, with no control characters. Without a label the popup shows the local action, the verb orargv[0]. -
Groups.
.groupsnames the inner prefixes of a table:.keyis relative to the table’s prefix, and.labelis required. A group’s keys come from the bindings under it..sticky = truekeeps the client in the group after a binding runs, untilesc. -
Targets. Exactly one of:
.local: a typed local action of the client (detach,zoom,focus-attention,toggle-floats,lock, …), as RFC-0011 section 5 requires..verbwith.args: a venomx schema verb sent with the client’s own identity (RFC-0007 section 8)..exec: an argv run by the client with no shell, stdin from the null device and output discarded (a non-zero exit shows a notification). The child gets no venomx token (RFC-0009 section 2), so it is authorized by whatever tool it calls under that tool’s own rules.
-
Placeholders in
argsandexec:{pane},{window},{session}of the focused pane, and{popup_ticket}(section 8.3). They are substituted as whole argv elements, never spliced into strings.
7.4 Registration and precedence
Products register tables at run time instead of editing the user’s config. A product’s table for host mode is registered by the owner, from a process outside every managed tree:
$ VENOMX_TOKEN=@owner venomx keys register --name venom --prefix v --label venom \
--bindings '[{"key":"s","label":"settings","verb":"pane.popup","args":"{\"preset\":\"venom.settings\",\"argv\":[\"venom\",\"settings\"]}"}]' \
--bind daemon --json
{"name":"venom","prefix":"v","layer":"product","shadowed":[]}
$ venomx keys list --json
{"leader":"ctrl+b","tables":[{"name":"venomx","layer":"builtin"},{"name":"venom","prefix":"v","label":"venom","layer":"product","source":"owner"}]}
A table that should apply only to a product’s own panes uses when with a tag
the product sets itself. owner would not do here: it is the namespace of
whoever created the pane (section 4), which is venomx for a pane made with
the owner’s credentials. venom’s table has no when, so its keys work from any
pane (RFC-0018 section 13).
Changing a prefix. keys.prefixes moves a table to another sub-prefix
without restating its bindings, and it survives the product registering again:
.keys = .{
.prefixes = .{
.{ .name = "venom", .prefix = "a" },
},
},
It is read from the user and defaults layers, and the user’s entry wins for the
same name. The precedence rules below apply to the moved prefix, and keys.list
reports the effective one.
Registered tables are daemon state, so every client sees them.
Authority. keys.list needs read. keys.register and
keys.unregister depend on the caller:
| Caller | Table names it may use | Grant |
|---|---|---|
The owner principal (RFC-0005 section 3.7) |
Any except venomx |
Holds everything |
| Any other principal | Its own namespace only, else denied.namespace (4) |
write on server:root |
- The first row tests the principal, not the namespace. A pane the owner
created also has namespace
venomx(section 5.1) and gains nothing from it. - With isolation off (section 5.1) every caller is treated like the first
row for table names; the
writeonserver:rootgrant still applies. writeonserver:rootcovers every session, so a pane MUST NOT be given it in order to register a table. A control pane holds a pane’s default rights:readon*,writeandspawnonself.- The owner token is refused inside a managed tree (RFC-0005 section 3.4), so
registration by the owner can only happen outside venomx. In venom’s case,
the process that is about to become
venomx attachregisters first, as the presentation client it is becoming (RFC-0005 section 3.7). It keeps no token for later, and it issues no principal for this. - A product started only inside a pane cannot register. Its table is there only if an earlier outside start registered it, or if it ships in the defaults file (section 11.1).
Lifetime. bind is as in section 5.1, and the table has no ttl. A table
ends at the first of:
| Registered by | bind = daemon |
bind = connection |
|---|---|---|
The owner principal |
keys.unregister, or the daemon stops |
The same, or the connection closes |
| Any other principal | keys.unregister, the daemon stops, or the principal is revoked or expires |
The same, or the connection closes |
So an owner-registered daemon table has no expiry: the owner principal
is never revoked or expired while the daemon runs. A long-running session’s
product keys keep working for as long as its panes do. Tables held by another
principal are bounded by that principal, and an unbound token’s expiry is
capped at grants.max_token_ttl, 24 hours by default (RFC-0005 section 6.2).
- No expiry gives away nothing. A table holds no authority: a
.verbbinding runs with the pressing client’s identity and an.execchild gets no venomx token (section 7.3).keys.listshows bindings to every principal withread, so a binding MUST NOT contain a credential. - Tables are not checkpointed, and the owner token changes on every daemon start. After a daemon restart the table is gone until the next outside start registers it.
- Paths in a table’s bindings stay as registered, so they can go stale when a
product moves. A stale binding fails when pressed (section 7.3), and the
next registration replaces it.
VENOMX_TOKEN=@owner venomx keys unregister --name venom --jsonremoves the table. - A product that relies on its table SHOULD tell the user while it is missing, in text that fits an 80-column status bar and is not cleared by a key press.
Last registrar wins. keys.register with a name already in use replaces
the whole table in one step, for every client. There is no merge, no
version check, and no refusal of a narrower table. source, bind and the
lifetime above all become the new registrar’s.
- venom’s popups pass
--no-owner=true, on every start: a popup is a pane, which RFC-0005 section 3.8 refuses the owner file, and a binding can’t hold a credential. Sov iconnects asunknownand can’t answer inbox items, andv scan’t save. Both happen in the control pane or from outside venomx. - A later start can narrow what an earlier one set up, by the same replacement.
sourcestaysownerfor each start that uses venomx’s owner credentials. - A non-owner principal in the table’s namespace can replace an owner-registered table, and the table then ends with that principal. Only the owner path gives a table no expiry.
- A user who wants bindings no registrar can change puts them in
keys.tablesin their config, which wins for the same table name (below).
Layers, highest first:
- User: the
keysmember of the user’s venomx config. - Defaults: the venomx defaults file that venom-os installs (section 11.1).
- Product: registered tables.
- Built-in.
A higher layer wins for the same table name or the same sub-prefix. Within
one layer, two bindings for the same key sequence are an error:
config check fails with invalid_params.key_conflict, and
keys.register fails with conflict.key_prefix (5) when the prefix is
taken in the product layer. A binding shadowed by a higher layer is
reported in the shadowed result field and by doctor.
Events: keys.changed {layer, table, revision}. Clients reload tables on
it.
7.5 Shared key grammar
This grammar applies to venomx, venom-desk and every product with its own TUI, whether it runs alone or with the others.
- One leader per input layer. The presentation client has
keys.leader. A product’s TUI MAY have its own leader for its own area, active only where its keys are not text (RFC-0018 section 13). A product under venomx gets a sub-prefix, never a second terminal-wide leader. - Every leader is configurable. Popups, help and hints MUST print the configured keys, never a fixed name.
- Bare keys act here; the leader goes elsewhere. A bare key acts on what is under the cursor. The leader reaches another place or anything global.
- The tree follows the verbs. The root holds nouns and the next level
holds actions, so
agent.*lives undera. Groups reuse their letters:nnew,xremove,rrefresh. A multiplexer’s frequent actions stay at the root, because speed matters more there than grouping. - Double-tap runs a group’s default. A key cannot be both a binding and
a prefix (section 7.4), so
aopens the group anda ais its default. - Arrows follow directions. Wherever
h j k lmean a direction, the arrow keys MUST do the same. - Unbound keys cancel. An unbound key ends leader mode and is swallowed. It MUST NOT reach a pane or run a bare key.
- Every leaf has a name.
:runs a command by name and?lists every key (section 7.6, All keys). - Each product stands alone. A product’s tree MUST NOT need another product. Sub-prefixes appear when a product registers them, and the popup shows only what is active.
7.6 Leader popup
The client draws one popup, from the active tables, after the leader and after each prefix key:
╭ venomx › venom ───────────────╮
│ a + agents s settings │
│ w + workspaces u usage │
│ v + splits d diff │
│ i inbox │
╰───────────────────────────────╯
- Delay. The popup appears
keys.popup_delay_ms(default 200) after the key. A faster key skips it, so someone who knows the keys never sees it.0shows it at once. - Waiting. With
keys.timeout_ms = 0(the default), the client waits until the next key. A non-zero value ends leader mode after that long without a key. - Leaving.
esccancels, andbackspacegoes up one level. - Header. The path so far, by table and group label.
- Rows. The key, a
+for a group, and the label, in table order. Bindings with the same target and label share one row (h ← focus left). - Placement. An overlay at the bottom of the terminal. It takes no focus
and never changes pane content or size. A terminal too small for every row
shows what fits and ends with
…. - Sticky groups. The popup hides after the group’s first binding runs, so
it does not cover what is being resized or scrolled.
venomx.modeshows the group’s name, and?shows the popup again.
All keys. leader ? runs all-keys (section 7.7). Below the root, ?
is an ordinary key: a sticky group shows its popup again, and anywhere else
an unbound ? cancels (section 7.5). all-keys ends leader mode and opens a
list over the pane area. Like the popup, the list changes no pane.
- Rows. One row per binding of the active tables, after section 7.4
precedence and
when, at every level and in every table. They come in precedence order (higher layers first, then table order), withsend-leaderlast. A row shows the keys after the leader, then the group or table path and the label:r h resize › resize-left,v d venom › diff. Bindings that share a popup row keep separate rows here. - Filter. Typed text filters the list. A row matches when each
space-separated term is a case-insensitive subsequence of its keys or of its
path and label. Better matches come first: consecutive characters, word
starts and an early first match rank higher. Ties keep table order.
backspacedeletes a character andctrl+uclears the query. While the list is open, every printable key is text,j,kandqincluded. - Moving.
↑ ↓andctrl+p/ctrl+nmove the selection, which wraps and stays scrolled into view. The header shows how many rows match out of the total, and an empty result showsno match. - Running.
⏎closes the list and types the selected row’s keys after the leader, exactly as if they had been pressed. A sticky group’s binding therefore leaves the client in that group with its popup hidden.⏎with no match does nothing. - Leaving.
esccloses the list and sends nothing to the pane.
The widget and the key router live in venom-term, so venomx attach,
venom-desk and venom’s own TUI look and behave alike. A product’s popup is
ordinary content of its own pane (section 2), and its header names the
product, so it cannot pass for the client’s.
7.7 Built-in table and sub-prefixes
| Key | Command | Kind |
|---|---|---|
h j k l, ← ↓ ↑ → |
focus-left, focus-down, focus-up, focus-right |
local |
tab / shift+tab |
focus-next / focus-prev |
local |
\ / - |
split-right / split-down → pane.split (on_exit = remove) |
verb |
c |
new-window → window.create (on_exit = remove) |
verb |
n / p / 1–9 |
window-next / window-prev / window-<n> |
local |
w / s |
window-picker / session-picker |
local |
r, then h j k l or arrows |
resize, sticky → pane.resize |
verb |
[, then j k, ↑ ↓, page_up, page_down, { } |
scroll, sticky; { / } jump to the previous / next command |
local |
[, then v |
copy-mode: leaves the scroll group for copy mode |
local |
page_up / page_down / ] |
scroll-page-up / scroll-page-down / scroll-bottom |
local |
z / ! / d |
zoom / focus-attention / detach |
local |
X |
close-pane → pane.kill (remove = true) |
verb |
f / F / x |
toggle-floats / focus-float / dismiss-popup |
local |
e |
toggle-pane-float → pane.place (section 8.3) |
verb |
: / ? |
command-prompt / all-keys (section 7.6, All keys) |
local |
L |
lock |
local |
| leader | send-leader |
local |
esc cancels leader mode and clears this client’s selection.
In the scroll group, { and } move the view to the previous or next
command start the pane reported with OSC 133 A (RFC-0003 section 8), placing
that prompt row at the top. In a pane with no OSC 133 marks, or past the
first or last one, they do nothing and leader mode goes on. They are client
state like the rest of scroll mode (section 3).
[ v starts copy mode on the focused pane: a cursor over the pane’s rows,
from the oldest loaded history row to the live view’s last row, moved with vi
keys (h j k l, w b e, 0 ^ $, gg G, H M L, page keys, { }, counts)
and searched with / and ? (plain text, smart case, wrapping around; n
and N repeat it), the search line drawn on the status row.
v and V select by character or by line, and y or ⏎ copies the
selection with OSC 52 and leaves. esc drops the selection, then leaves; q
leaves. Until it leaves, copy mode takes every key the router passes, so the
leader still works and nothing reaches the pane; the venomx.mode segment
shows COPY. Like scrolling it is this client’s own (section 3) and ends
when the stream’s epoch changes or the client types into the pane.
pane.resize {pane, direction, cells} changes split ratios. It is a verb
because split ratios are shared state (section 3).
$ venomx pane resize --pane=p0 --direction=right --cells=3 --json
{"window":"w0","revision":7,"moved":3}
directionisleft,down,uporright. It moves the divider of the pane’s nearest ancestor split on that axis (colsfor left and right,rowsfor up and down), so the same key grows a pane on one side of it and shrinks a pane on the other. Between two panes this is what tmux’sresize-panedoes. It differs when splits nest on one axis: in(p0|p1)|p2,rightonp1moves the divider of its own split,p0|p1, where tmux’s flat cells would move the one to its right.cellsis 1–65535, measured on the panes’ current PTY sizes, because the daemon knows no client’s viewport. The split’s newratio(RFC-0007 section 4) then gives each client the same share of its own viewport.- The move stops at the child’s minimum: one cell for each pane and separator
the child holds along the axis, so 3 columns for a nested
p2|p3. The divider never moves against the direction, even when a smaller viewport has already left it past that minimum.movedis how far the divider really went, read back from the newratio. It is 0, with no revision change and no event, when the pane has no such split or the divider can’t move, so a held key at the edge is quiet. The result still carries the current revision. - It needs
writeon the pane’s window, likepane.raise: the change also resizes other panes. A pane’s default rights do not cover it. - A floating pane or popup has no split:
conflict.layer. A removal in progress:conflict.removing. - A change publishes
window.layout_changed, which is how every client follows. - The built-in keys move one PTY cell at a time, and key repeat moves it further.
This holds on PTY cells, not on the viewport of the client pressing: on a client
narrower than the panes’ PTY sizes, some presses draw no change while reporting
moved: 1. - A split that would leave the child of an ancestor holding the new pane below
its minimum clamps that ancestor’s
ratio, taking cells only from the other child, which keeps its own minimum. Theratiois removed only when the cells can’t hold both minimums (RFC-0007 section 4).
Sub-prefixes are reserved so a built-in never takes them:
| Prefix | Table | Set by |
|---|---|---|
v |
venom |
venom (RFC-0018 section 13) |
o |
venom-os |
The venom-os defaults file |
m |
The user’s own tables | The user’s config |
8. Floating panes and popups
venom wants a settings view or a diff on top of the agents; venom-os wants a launcher and system settings. venomx provides the one mechanism they all use, so sizing, borders and keys behave the same everywhere.
8.1 Two kinds
| Floating pane | Popup | |
|---|---|---|
| Lives in | A window’s float layer (shared topology) | One client only |
| Seen by | Every client showing the window that has floats visible | Only the client it was opened for |
| Survives | Detach, other clients, until removed | Closes when its process exits, on esc/dismiss, or when its client detaches |
| Typical use | A log or monitor kept at hand, a persistent venom control view | Settings, a picker, a diff, a confirmation, the launcher |
on_exit default |
keep |
remove (RFC-0017) |
Both are ordinary panes: a PTY, a pane ID, RFC-0006 streams, grants, tags, attention. Only placement differs.
Durability (RFC-0010 §4.1, §5): checkpoint format 2 saves a floating pane’s preset name, explicit geometry fields and z; restore puts it back in its window’s float layer, resolves the preset against the restoring daemon’s config and gives it a fresh z above the live ones. Popups are never checkpointed.
8.2 Geometry and presets
Geometry is resolved by each client against its own window area, so one spec works on every terminal size.
.floats = .{
.default = .{ .width = "60%", .height = "60%", .anchor = .center, .border = .rounded, .min_cols = 20, .min_rows = 5 },
.presets = .{
.@"venom.settings" = .{ .width = "80%", .height = "80%", .anchor = .center, .title = "venom settings" },
.@"venom-os.launcher" = .{ .width = "50%", .height = "40%", .anchor = .top, .y = 2 },
.@"venom.diff" = .{ .width = "100%", .height = "70%", .anchor = .bottom },
},
},
| Field | Type / default | Meaning |
|---|---|---|
width, height |
Cells (80) or percent of the window area ("60%") |
Size; clamped to the window area and to the minimums |
anchor |
Enum: .center, .top, .bottom, .left, .right, .top_left, .top_right, .bottom_left, .bottom_right |
Where the rectangle sits |
x, y |
Signed cells, default 0 | Offset from the anchor |
min_cols, min_rows |
Cells | Below these, the client shows the float full-window instead |
border |
Enum: .none, .single, .rounded, .heavy |
Drawn by the client in theme roles |
title |
≤ 128 bytes | Border title; the pane’s OSC 2 title is used when absent |
- Presets are the configuration point. Products refer to a preset by
name (
--preset venom.settings) and SHOULD NOT pass raw sizes. The user (or venom-os defaults) can then resize any product’s view by editing the preset, throughconfig set floats.presets.venom.settings.width '"90%"'. An unknown preset falls back tofloats.defaultand is reported bydoctor. Explicit geometry params override the preset field by field. - Preset names are namespaced like segment ids. A product MAY ship default
presets by registering them (
floats.register, same authority, lifetime, last-registrar and layer rules as key tables, section 7.4, except that the owner may use any name outsidevenomx.*); user config wins. A table’s presets SHOULD be registered by the same caller as the table, so they end together. - PTY size. A floating pane uses RFC-0006 section 8: each writer
reports its resolved rectangle as its viewport, and the policy (default
latest) picks the size. A popup has one viewer, so its size is that client’s resolved rectangle.
8.3 Verbs
| Verb | Params | Result | Grant |
|---|---|---|---|
pane.float |
window, argv, optional preset, geometry fields, tags, cwd, env (RFC-0013), on_exit |
{pane, window, principal, pid} |
spawn on the window |
pane.popup |
ticket, argv, optional preset, geometry fields, tags, cwd, env, on_exit |
{pane, principal, pid} |
spawn on the ticket’s session |
pane.place |
pane, one of float (geometry or preset) or tile (target, axis) |
{pane, layer} |
write on the pane’s window |
pane.raise |
pane |
{pane, z} |
write on the window |
Floating panes appear in the window’s snapshot with layer = "float",
their geometry spec and their z-order. Events: pane.created (with
layer), pane.placed, pane.raised.
Popup tickets. A popup covers a human’s screen, so only that human may
cause one. A client mints a ticket when a binding uses {popup_ticket}:
an opaque, single-use string valid for 10 seconds, bound to that client
and session. A product receives it in its exec argv and passes it to
pane.popup. Without a valid ticket pane.popup fails
denied.popup_ticket (4). A client’s own .verb = "pane.popup" binding
mints and uses a ticket implicitly. There is no other way to open a popup
on someone else’s client.
Focus: a floating pane never takes focus by being created. A popup takes
focus on its client, because the human asked for it. toggle-floats (a
local action, default leader f) shows or hides the float layer on that
client only; leader F cycles focus among visible floats.
toggle-pane-float (default leader e) moves the focused pane between
the layers with pane.place, under the client’s own identity:
- A tiled pane is floated with
float = {}, sofloats.defaultgives its geometry. It keeps the client’s focus, and a client that hid its float layer shows it again. - A float is tiled next to the largest tile of the window as that client
lays it out (ties: lowest pane ID), split along the tile’s longer side:
colswhen its width is at least twice its height, elserows. With no tiled pane left it becomes the window’s only tile (tile = {}). - A popup cannot be placed; the client says so and sends nothing.
When pane.place floats a tiled pane, the daemon emits pane.placed
before the window.layout_changed that removes it from the tree, so no
client sees the pane in neither layer and moves its focus away.
Errors: not_found.preset is not an error (fallback above);
invalid_params.geometry (2) for unparsable sizes; denied.popup_ticket
(4); not_found.client (3) when the ticket’s client has detached.
9. Placement intent
Products describe where a pane belongs, not its geometry. RFC-0007’s
pane.split stays the primitive. This RFC adds an optional near
parameter to window.create and pane.split: a tag map. With near,
venomx picks the largest pane in the session whose tags match (falling
back to the given target) as the split source, so venom can say “put this
next to thread-7’s other panes” and venom-desk’s tiling policy can still
rearrange the result. Layout policies (BSP, master-stack, scrolling) are
venom-desk code that drives pane.split, pane.place and future
window.layout verbs; venomx itself keeps manual splits.
10. venom chrome modes
venom’s TUI has a config key ui.chrome:
| Value | Behaviour |
|---|---|
.own |
Full-screen venom TUI with its own status line and keys. Agents render natively from ACP / app-server events. Used when venomx is absent. |
.host |
venom’s TUI runs as a control pane (role = control) in venomx; agents run in sibling venomx panes. venom draws its own status bar on the bottom row of the control pane (section 10.1) and sets attention. Its key table is registered from outside the pane, not by the control pane (section 7.4). It does not publish venom.* segments in v1 (section 10.2). |
.auto (default) |
host when a venomx daemon is reachable, own otherwise. venom MUST NOT start a venomx daemon to get there (RFC-0017). |
10.1 venom’s own status bar
venom always draws its own status bar on the bottom row of the area it
draws: the whole terminal in own mode, its control pane in host mode.
It holds venom’s items only: attention (▲ 3 need you), running agents,
the current workspace, venom’s mode and key hints. The bar is venom pane
content under the section 2 exception, so it moves and resizes with the
control pane and is never drawn on any other pane or row of the terminal.
own mode (no venomx, 80 columns): venom owns the terminal, so there is no
top line.
┌ Agents ──────────────────┬ venom-echo conformance ───────────────────────┐
│ Needs you │ ▲ approve: zig build test │
│ ▲ venom-echo conformance │ Bash zig build test │
│ Running │ [y] allow once [a] always [n] deny │
│ ◆ wire framing docs │ │
│ Settled │ │
│ ● hello caps bits │ │
└──────────────────────────┴───────────────────────────────────────────────┘
▲ 1 need you · ◆ 1 running │ ~/venom-org │ NORMAL i inbox ? keys
host mode (inside venomx attach, 80 columns): venomx’s line is the top
row of the terminal; venom’s bar is the bottom row of its control pane p1.
The agent pane p4 on the right has no venom bar; its attention reaches the
user through the pane marker and notify.post (section 6). venomx draws
each pane’s title on the line above it, with no box around the panes
(RFC-0022 section 3.2).
s0 work │ 0:main* 1:logs ▲ 1 │ 14:02
─ p1 venom (control) ──────────────┬─ p4 claude · venom-echo ▲ ─────────────────
Needs you │ ● Bash(zig build test)
▲ venom-echo conformance │ Do you want to proceed?
Running │ ❯ 1. Yes
◆ wire framing docs │ 2. No
│
│
▲ 1 need you · ◆ 1 running │
The ▲ 1 in venomx’s top line is the built-in venomx.attention count
(section 5.1), driven by pane.attention on p4, not a venom segment.
10.2 No venom segments in v1
The two bars are disjoint. venom’s items appear only on venom’s own bar;
venomx’s top line shows only venomx built-ins and segments of other
namespaces. venom MUST NOT publish venom.* segments in v1. Outside its
pane, venom reaches the user through pane.attention and notify.post
(section 6). The segment mechanism of section 5 stays available to every
product, and a later amendment MAY let venom publish segments again (for
example a compact count while the control pane is hidden).
venom-desk’s palette lists the verbs of every product schema it can reach (venomx, venom, venom-mem), so a product gets palette entries for free.
11. Config summary
| Key | Default | Section |
|---|---|---|
status.position |
.top |
5.3 |
status.left, status.center, status.right, status.unlisted, status.segments |
RFC-0023 section 8 | RFC-0023 |
attention.sticky, attention.from_bell |
false, true |
6 |
keys.leader, keys.unlock |
ctrl+b, ctrl+alt+b |
7.2 |
keys.popup_delay_ms, keys.timeout_ms |
200, 0 (no timeout) | 7.6 |
keys.tables, keys.prefixes |
none, none | 7.3, 7.4 |
floats.default, floats.presets |
Section 8.2 | 8 |
All are hot-reloadable and changed through config set|reload.
11.1 Defaults file
venom-os (or a distribution) ships curated defaults without touching the
user’s config. venomx reads, in order, the first venomx/defaults.zon
found in $XDG_CONFIG_DIRS (default /etc/xdg), then the user’s
$XDG_CONFIG_HOME/venomx/config.zon. A key in the user file replaces the
same key from the defaults file; for keys.tables and floats.presets
the merge is per table or preset name.
- The defaults file is read-only to venomx:
config setalways writes the user file.config get --jsonreports each value’ssource(builtin,defaults,user). config checkvalidates both files; an invalid defaults file fails like an invalid user file.- This is implemented once in venom-std
cfgas a read-only lower layer, so every product gets the samedefaults.zonmechanism, not only venomx.
12. Conformance
- Two clients on one session show different windows, focus, scroll and zoom; no verb changes another client’s view.
- A segment set by a product appears on both clients, is truncated by priority on an 80-column client, and disappears when the product’s connection closes. With default config the status line is the top row.
- A principal cannot set
venomx.*or another namespace’s segment. - With Claude Code in two panes, every key except the leader reaches the
focused agent unchanged, including
ctrl+a,escandshift+tab. Claude Code’sctrl+barrives through leader leader (case 19). - A tag-scoped table is active only while a matching pane is focused.
- Same-layer key conflicts fail
config check; cross-layer shadowing is reported bydoctor. - An
execbinding’s child has no venomx token in its environment and placeholders are whole argv elements. pane.popupwithout a ticket, with an expired ticket, and with a ticket of another client all fail; a valid ticket opens the popup on that client only.- A preset change through
config setresizes the next float and, after reload, the existing ones. - Attention set by a product marks the pane on every client and is cleared by the focusing client.
- The owner registers table
venomand presetvenom.settings; the owner is refused tablevenomxand presetvenomx.x. - A pane principal, and a principal with
writeonserver:rootin another namespace, getdeniederrors for tablevenom. A pane created by the owner gains nothing from the owner’s rule. - With
bind = daemon, a table registered by a principal with a shortttlgoes when that principal expires; an owner-registered table is still there afterwards. - A second
keys.registerfor a name replaces the whole table: the first table’s bindings are gone,sourceis the second caller, andkeys.changedfires once. - After the leader, a key pressed within
keys.popup_delay_msruns with no popup drawn. Otherwise the popup appears, stays until a key, and lists only active tables, with labels and groups. - In leader mode
esccancels,backspacegoes up one level, and an unbound key cancels and reaches neither the pane nor a binding. h j k land the arrow keys move focus the same way, and share one popup row. A sticky group stays active after a binding runs and ends onesc.keys.prefixesmoves a registered table; the move survives anotherkeys.register, andkeys.listshows the effective prefix.- With the defaults,
ctrl+b ctrl+bdeliversctrl+bto the focused pane, with and without the Kitty keyboard protocol. - When focus moves during leader mode, the popup and the next key follow the newly focused pane’s tables. Leader mode ends if the typed keys no longer lead to more keys, and nothing runs.
- In the scroll group,
}moves to the next OSC 133 A prompt row and{to the previous one; in a pane with no marks both do nothing and the group stays active. leader ?from the root popup ends leader mode and lists every active binding of every level and table,send-leaderincluded. Typing narrows the list by fuzzy match,⏎on a sticky group’s binding leaves the client in that group, andesccloses the list with nothing sent to the pane.- With
grants.isolationoff, a CLI caller outside venomx with no token runsstatus set --id user.build --text ok --ttl-ms 60000andnotify post; both succeed, and the segment is still listed after the call returns. A pane’s principal setsvenom.x, andvenomx.xis stilldenied.namespace. With isolation on, the same calls are denied as in cases 3 and 12.
Agent-contract impact
- Verbs. New:
pane.tag,pane.attention,pane.float,pane.popup,pane.place,pane.raise,status.set|clear|list,notify.post|list|dismiss,keys.register|unregister|list,floats.register,pane.resize. Changed: RFC-0005grant.issuegainsnamespace(section 5.1);window.createandpane.splitgaintags,near;keys.registergainslabelandgroups, and its bindings gainlabel; snapshots gaintags,layer, geometry, attention. All are schema verbs, so CLI, MCP, docs and completions are generated. - Key bindings keep RFC-0001’s parity rule: every binding target is a local action, a schema verb or an exec.
--jsonand exit codes. Errors listed per section, on RFC-0002’s mapping.- Events.
pane.tags_changed,pane.attention_changed,pane.placed,pane.raised,status.changed,status.cleared,notify.posted,keys.changed. skilldocuments tags, segments, attention and presets for agents.doctorreports key shadowing, unknown presets, nesting and a leader the terminal cannot deliver.config: section 11.- Grants. Per verb above. Popups additionally need a client-minted
ticket. The owner may register key tables and presets under any name
outside
venomx(section 7.4); no pane holdswriteonserver:rootfor this. Default remains read-only. - Headless. All state works with no client attached; agents can read
segments and attention with
--json. - OSC 7501 / 133. May set
infoattention only (section 6).
Alternatives
- Do nothing / each product draws its own chrome. Stacked bars, incompatible overlays, and key collisions between venom and venomx.
- Products draw into venomx’s status line directly (a raw string or a
format language like tmux’s
status-right). Needs a template language (ruled out: no scripting in v1) or lets one product overwrite another. - venom-desk runs inside
venomx attach. Two layers of chrome and two leaders; every venomx feature would need a pass-through. - Keep
ctrl+aas leader (GH-83’s default). It spares Claude Code’s background-task key, but shells, readline and editors usectrl+afar more often than an agent’s background key, and tmux users reach forctrl+b. ctrl+spaceas leader (this RFC’s first default). macOS reserves it for switching input sources, and it needs NUL handling on legacy input.- A different default leader per platform. Instructions, habits and shared configs would stop carrying across machines.
- A flat table with no tree. Fast, but it runs out of mnemonic keys, and nothing shows what a product added. The root stays flat for frequent actions.
- A key that is both a command and a prefix, split by a timeout (as Vim does). Every use of the shorter command would wait for the timeout.
- A status-line hint instead of a popup. A single row cannot show a group’s labels. It also gave the reader only the 2-second timeout to read it.
- Unprefixed product keys (zellij style). zellij’s need for a “locked” mode shows how often this collides with programs in panes.
- Per-client layouts over shared panes. More flexible, but every client would re-tile on its own and venom-desk’s policies would diverge between viewers. Deferred.
- Products may move focus. Breaks RFC-0007 section 8 and is hostile when a human is typing; attention covers the need.
- Popups opened by any principal with
spawn. Lets an agent cover a human’s screen at any time; tickets tie popups to a human action. - Key table held by a pane with
writeonserver:root(the original section 7.4). Lets that pane reach every session, which the review of venom’s host mode (COM-299) ruled out. - Key table held by a time-limited
venomprincipal, renewed at each outside start (venom’s first implementation, COM-299). The keys of a long-running session stop after 24 hours while its panes keep working, the control pane has no credentials to renew them, and each start leaves another principal withwriteonserver:rootalive until it expires. - A registered table that outlives its principal’s expiry. Keeps the keys working, but the table then has no live owner, and expiry would mean something different here from every other principal-held resource. It also still issues a daemon-wide token for a job the owner can do directly.
- A
venomprincipal with no expiry. RFC-0005 section 6.2 requires an unbound token to expire, and it would leave a long-lived daemon-wide token to protect. - Only the defaults file (section 11.1). Needs a distribution to install it
and cannot follow a standalone
venom’s paths. It stays the way to cover a product started only inside a pane. - Refuse a replacement that narrows a table. venomx would have to compare binding argv it treats as opaque, and a legitimate upgrade can look the same.
Open questions
All five were resolved by the maintainer on 2026-11-07 by accepting the proposals below. None remains open within this RFC’s scope.
- Namespace from token — resolved: a venomx token always names venomx
as its tool, so the namespace is an explicit, attenuated grant attribute
added to
grant.issue(section 5.1), not derived from the token. - Defaults layer — resolved:
venomx/defaults.zonin$XDG_CONFIG_DIRS, below the user file, implemented once as a read-only layer in venom-stdcfg(section 11.1). - Leader delivery — deferred:
ctrl+bis the default (venom-spec#149, replacing GH-83’sctrl+a). Whetherctrl+bandctrl+alt+breach venomx across terminals, ssh and nesting is measured in venom-spec#60;doctorreports a leader the terminal cannot deliver in the meantime. - Notification history — resolved: venomx keeps only the bounded queue of section 6; history and an inbox are venom-desk product code.
- Float visibility — resolved: visibility stays per client (section 3). A shared toggle is not part of v1.