Standalone venomx entry point
| Field | Value |
|---|---|
| Status | Accepted |
| Accepted | 2026-11-07, explicitly approved by the maintainer |
| Authors | commixon (commixon), with Claude |
| Created | 2026-11-07 |
| Linear | COM-292; implementation COM-295 (venomx), COM-301 (venom-os) |
| Supersedes | None; amends RFC-0009 sections 2 and “Alternatives”, and RFC-0007 section 5 for opted-in panes |
| Superseded by | None |
History
-
2026-11-08 (venom-spec#92, normative, compatible addition; explicitly accepted by the maintainer on 2026-11-09): §4 rule 2 refuses to start a daemon below a manager, as RFC-0009 section 2 now does.
-
2026-11-08 (COM-305, normative; accepted through the COM-305 PR review): §3 says how
newstarts the default shell as a login shell, using theprogramparameter RFC-0013 now defines. The behaviour §3 asked for does not change.
Summary
venomx should be as simple as tmux when used alone: type venomx, get a
shell, detach and reattach, and the server goes away when the last shell
exits. Under venom-os the same binary must instead run as an always-on
daemon. This RFC makes whoever starts the daemon decide its lifetime:
a human’s interactive venomx starts an on-demand daemon, a supervisor’s
venomx serve runs a persistent one, and agents and scripts never start
one. It adds the interactive local commands venomx / venomx new and a
selector-less venomx attach, which compose existing verbs on the client
with no new daemon mutation path, and a per-pane on_exit policy so that
exiting the last shell ends the session. venomx implements it; venom-os
adopts the supervised serve.
Motivation
Today a human who wants one shell in venomx types, from the README:
venomx serve
VENOMX_TOKEN=@owner venomx session create --name work --json
VENOMX_TOKEN=@owner venomx window create --session s0 --argv '["/bin/sh"]' --json
venomx attach --session s0
and typing exit in that shell leaves an exited pane, a window and a
session behind (RFC-0007 section 5), so nothing ever ends on its own.
tmux needs one word. venomx is meant to be “a tmux replacement that agents
can drive” (ecosystem doc, section 3); without this RFC it is only the
second half of that sentence.
RFC-0009 already supplies the hard part: an on-demand lifetime, socket arbitration, readiness and last-session shutdown, all opt-in. What it deliberately left out is a human entry point. It rejected “attach implicitly creates a session/shell” because that would mix presentation with mutations. This RFC keeps that concern and answers it differently: the entry point is a client-side sequence of ordinary verbs, each authorized by the daemon in the normal way.
Waiting on this RFC: daily-driving venomx (P1 exit criteria), venom-os’s
supervisor design (P5) and venom’s ui.chrome = .auto (RFC-0016 section
10).
Design
1. Examples
First run, no daemon:
$ venomx
The terminal shows a shell in session s0, window w0, with the status
line of RFC-0016. Behind the scenes the CLI started venomx serve --lifetime on-demand --origin interactive, then ran session.create,
window.create with the user’s shell, and attached.
(inside venomx) $ exit
[venomx: session s0 ended; server stopped]
$ venomx ping --json
{"error":{"code":"unavailable.not_running","message":"No daemon at the selected socket","data":{"socket":"/run/user/1000/venom/venomx.sock"}}}
Detach and reattach:
$ venomx new --name work # leader d detaches
[venomx: detached from s0 (work)]
$ venomx attach # most recent session
$ venomx new -A --name work # attach to "work" if it exists, else create it
Under venom-os the supervisor runs:
$ venomx serve --json # persistent, as today
A human typing venomx then simply gets a new session on that daemon.
Exiting the last shell removes the session but the daemon keeps running,
because it was started persistent.
2. Who decides the lifetime
A daemon fixes its lifetime when it starts (RFC-0009 section 4) and a running daemon always wins. This RFC states who may start one, and how:
| Starter | How it starts the daemon | Lifetime |
|---|---|---|
| A human at a terminal | venomx, venomx new (section 3) |
on-demand |
| A supervisor (venom-os, systemd, CI) | venomx serve |
persistent |
| An owner script opted in by config | session create with daemon.startup = "on-demand" (RFC-0009) |
on-demand |
| An agent, MCP, venom, any other product | Never | Uses the running daemon or gets unavailable.not_running (6) |
There is no global “mode” toggle. The config keys only choose whether the two automatic starters are allowed:
| Key | Default | Meaning |
|---|---|---|
daemon.startup |
"explicit" |
Unchanged from RFC-0009: whether owner-opted session create may start a daemon |
daemon.interactive_start |
true |
Whether venomx / venomx new may start a daemon. venom-os MAY set it to false so a stray start can never shadow its supervised daemon. |
Both are restart-only startup policy, read by the client only after a connect reports a missing daemon, exactly as in RFC-0009 section 1.
serve gains an optional origin parameter (serve default,
interactive, session_create) that the launcher sets. It is recorded for
doctor and server.get only and never grants anything.
venom MUST NOT start a venomx daemon, including through venomx new.
With no daemon it runs with ui.chrome = .own (RFC-0016 section 10).
3. Interactive local commands
Three RFC-0011 local commands, all interactive = true (they need a
terminal on stdin and stdout, fail unsupported.terminal (10) otherwise,
and are left out of MCP and the skill):
| Command | Params | Does |
|---|---|---|
new |
optional name, cwd, attach_existing (-A), detached (-d), and trailing argv |
Ensure a daemon (section 4), session.create, window.create with argv or the default shell, then attach unless -d |
| (no command) | none | Same as new with no params |
attach |
selector now optional | With no selector, attach to the most recently attached session, else the most recently created one. Never starts a daemon and never creates anything. |
- Default shell.
$SHELLif it is an absolute path to an executable, else the user’s login shell from the password database, else/bin/sh. It is started as a login shell (argv[0]prefixed with-) like tmux:window.createwithprogramset to the shell’s path andargvset to-plus its basename (RFC-0013 §2), so/bin/zshruns as-zsh. - Default name. Without
--name, the session is named after the basename of the working directory (cwd, else the caller’s), truncated to 128 UTF-8 bytes;/givesroot. Socd ~/src/venomx && venomx new -Areattaches that project’s session. -A. If a session with exactly that name exists, attach to it instead of creating. Names are not unique (RFC-0007): with more than one matchnew -Afailsconflict.ambiguous_name(5) and lists the IDs.- Panes and sessions created by
newuseon_exit = "remove"(section 5), and the session useson_empty = "remove". - No new daemon verb.
newsendssession.createandwindow.create, then performsattach, through the same generated client as the CLI. Ifwindow.createfails,newremoves the session it just created (best effort) and reports the original error. - The attach-or-create alternative that RFC-0009 rejected is replaced by
this composition; RFC-0009’s other rejections stand: wire requests,
attach, MCP and read verbs never start a daemon.
The last-used session is client-side state in
$XDG_STATE_HOME/venomx/last-session, written on attach, holding the
daemon’s epoch and the session ID. A stale epoch is ignored.
4. Starting the daemon from new
new is added to RFC-0009 section 2’s eligible commands with these rules:
- Owner intent is implicit.
newbehaves as ifVENOMX_TOKEN=@ownerwere set: after readiness it reads the owner file under RFC-0005 section 3.7. A human does not type a token. - Managed-context guard, unchanged. If
VENOMX_PRINCIPALorVENOMX_PANEis present, even empty,newMUST faildenied.startup_context(4) before connecting. This also refuses nesting (as tmux refuses inside$TMUX).--nestedskips the guard for nesting only when a daemon is already running and the caller presents its own token; it never starts a daemon or reads the owner file. When no daemon is running, a manager’s marker above the caller (RFC-0009 section 2) also failsdenied.startup_context, before launching. - Terminal requirement. Stdin and stdout must be terminals. Agent shells (Claude Code’s Bash tool, Codex exec) have no TTY. This is accident prevention, not an identity boundary, like rule 2 (RFC-0009 section 2).
- Launch, readiness, deadlines and lock arbitration are exactly
RFC-0009 section 3, with
--lifetime on-demand --origin interactive. A persistent daemon that wins the race is used as-is. daemon.interactive_start = falsemakesnewfailunavailable.not_running(6) when no daemon runs.
5. Exited-pane removal (on_exit)
window.create, pane.split, pane.float and pane.popup (RFC-0016)
gain an optional parameter:
| Param | Choices | Default |
|---|---|---|
on_exit |
keep, remove |
keep for the verbs (compatible with RFC-0007); remove for pane.popup |
session.create gains on_empty (keep default, remove).
Semantics with on_exit = "remove":
- When the pane’s process exits, the daemon records the exit as today
(
pane.exited), then removes the pane in a following commit (pane.removed, reasonon_exit). Streams close per RFC-0006 with the exit status, so attached clients still see why it ended. - If that leaves its window with no panes, the window is removed
(
window.removed, reasonempty). Windows always cascade; an empty window has nothing to show, and there is noon_emptyfor windows. - If that leaves a session with
on_empty = "remove"with no windows, the session is removed (session.removed, reasonempty). - Removal of the last session by this cascade counts as explicit removal for RFC-0009 section 4, so an on-demand daemon then stops. A persistent daemon stays.
Rules:
on_exitis fixed at creation and shown in the pane’s snapshot entity.- Panes started by RFC-0015
runkeep their own result-retention rules and ignoreon_exit; agents need results after exit. - A pane removed by
on_exitis not archived by RFC-0010 recovery. A pane restored from an archive keeps its recordedon_exit. - The built-in key table (RFC-0016 section 7) passes
on_exit = "remove"for itssplitandnew-windowbindings, so everything a human creates by hand behaves like tmux. Products choose explicitly; venom SHOULD usekeepfor agent panes andremovefor helper popups.
6. Leaving the client
When a session the client shows is removed, the client attaches to the
most recent other session if one exists (tmux’s detach-on-destroy off
behaviour is config attach.on_session_end = "next"), else it exits 0
after printing the one-line message in section 1. Default: "exit".
7. Errors
| Code | Exit | When |
|---|---|---|
unsupported.terminal |
10 | new or bare venomx without a terminal (RFC-0011) |
denied.startup_context |
4 | Managed-context markers present (section 4) |
conflict.ambiguous_name |
5 | new -A matches several sessions |
not_found.session |
3 | Selector-less attach with no session |
unavailable.not_running |
6 | No daemon and starting is not allowed |
8. Conformance
- With no daemon,
venomxin a terminal starts an on-demand daemon, opens a login shell and attaches;exitremoves pane, window and session and stops the daemon. - The same against a persistent daemon removes the session but leaves the daemon running.
venomx newwithVENOMX_PANEset failsdenied.startup_contextwithout connecting; with--nestedand a token against a running daemon it attaches.venomx newwith stdin not a TTY failsunsupported.terminal.venomx attach,session list,mcpand venom never start a daemon.- Two concurrent
venomxstarts produce one daemon (RFC-0009 section 3). - A pane with
on_exit = keepsurvives its exit and can be captured; arunpane ignoreson_exit. new -A --name workattaches when oneworkexists, creates when none, fails with two.doctorshows lifetimeon-demand, origininteractive.
Agent-contract impact
- Verbs. No new daemon verbs.
window.create,pane.split,pane.float,pane.popupgainon_exit;session.creategainson_empty;servegainsorigin. New local commandsnewand the bare command;attachselector becomes optional. All in the generated CLI, docs and completions; interactive commands stay out of MCP andskill(RFC-0011). --jsonand exit codes. Section 7, on RFC-0002’s mapping.- Events.
pane.removed,window.removedandsession.removedgain reasonson_exitandempty. doctorreports latched lifetime, origin, the startup keys and a stale last-session file.config:daemon.interactive_start,attach.on_session_end.- Grants. Unchanged.
newauthorizes as the owner through the owner file; every verb it sends is checked by the daemon. Agents keep no path to owner rights (section 4 rules 2–3). - Headless. Unchanged; the new entry points are for humans only.
- OSC 7501 / 133. No change.
Alternatives
- Do nothing. venomx stays agent-first and humans keep using tmux.
- A global mode toggle (
mode = standalone | system). Two daemons could disagree about the mode; tying lifetime to the starter needs no coordination and matches RFC-0009’s latching. - Default
daemon.startup = "on-demand"for every command. Lets scripts and agents start daemons and bootstrap owner rights by accident, which RFC-0009 rejected for good reason. - A daemon-side
session.new_with_shellverb. A second mutation path with its own authorization; the client-side composition reuses the existing verbs. - Remove exited panes always. Breaks agents that capture output after a command ends.
- Socket activation (systemd). Possible later for venom-os; it is a supervisor choice and does not need protocol changes.
Open questions
All three were resolved by the maintainer on 2026-11-07 by accepting the proposals below. None remains open within this RFC’s scope.
- Short aliases — resolved: no command aliases in v1. The generated
CLI keeps one name per command (RFC-0011), and the README suggests shell
aliases (
alias va='venomx attach'). - Default session names — resolved:
newnames the session after the working directory’s basename (section 3). IDs stays0,s1. - Empty windows — resolved: an
on_exitcascade always removes the emptied window (section 5); no product needs a placeholder window.