Venom
RFC-0012venom-spec

Explicit pane termination and recursive container removal

Field Value
Status Accepted
Accepted 2026-11-07, explicitly approved by the maintainer
Authors commixon (commixon), with Claude
Created 2026-11-07
Linear COM-245; implementation COM-246
Supersedes None
Superseded by None

History

  • 2026-11-10 (venom-org/venom-spec#144, venom-org/venomx#147; normative, narrow compatible change; accepted by venom-run (unattended, delegated by the maintainer) on 2026-11-10): §7’s “MUST NOT include argv” rule excepts RFC-0007 §5’s derived argv0 (the basename of argv[0], at most 255 bytes) in the pane entity and pane.created. Every other part of argv stays excluded.

Summary

Add one destructive verb, pane.kill, and an opt-in recursive mode to window.remove and session.remove in venomx. pane.kill terminates a live pane’s process group with a requested signal, escalates to SIGKILL on a monotonic deadline, reaps it and optionally removes the pane. Recursive removal authorizes the whole subtree, seals it against new children, terminates every live pane in it with one shared deadline and deletes the subtree in one commit once every pane has exited.

This is a compatible extension of the RFC-0007 foundation. Calls that do not opt in keep RFC-0007 §5 exactly: removing a running pane fails conflict.pane_running, removing a nonempty container fails conflict.not_empty, and nothing is signalled. COM-246 implements this RFC after COM-238. Grants follow RFC-0005, events RFC-0003, stream closure RFC-0006, transport and cancellation RFC-0002, schema RFC-0004.

Motivation

RFC-0007 deferred live-pane kill and recursive removal so the foundation could keep removal atomic, with no unbounded wait and no implicit cascade. Without them, a client that wants to close a window must find every pane, get each program to exit by sending input, poll for exits, remove every pane, then remove the window. An agent cannot stop a hung program at all short of stopping the daemon. A presentation client’s “close window” binding (COM-223) has no single mutation to call.

Doing this carelessly is dangerous: an authorized root does not imply authorized descendants (window:* does not cover panes), a PID or PGID can be recycled to an unrelated process, a signal-ignoring child can stall a synchronous handler, and a spawn racing a removal can leave a ghost pane or a leaked principal. This RFC fixes each of those boundaries before COM-246 writes code.

Design

1. Examples

Assume a fresh daemon as in RFC-0007 §1: session s0, window w0 with panes p0 and p1, and window w1 with pane p2.

$ VENOMX_TOKEN=@owner venomx pane kill --pane p1 --json
{"pane":"p1","outcome":"terminated","removed":false,"escalated":false,"signal":"SIGHUP"}
$ VENOMX_TOKEN=@owner venomx pane kill --pane p1 --json
{"pane":"p1","outcome":"already_exited","removed":false,"escalated":false,"signal":"SIGHUP"}
$ VENOMX_TOKEN=@owner venomx pane kill --pane p1 --remove=true --json
{"pane":"p1","outcome":"already_exited","removed":true,"escalated":false,"signal":"SIGHUP"}
$ VENOMX_TOKEN=@owner venomx window remove --window w0 --json
{"error":{"code":"conflict.not_empty","message":"window has panes","data":{}}}
$ echo $?
5
$ VENOMX_TOKEN=@owner venomx window remove --window w0 --recursive=true --json
{}
$ VENOMX_TOKEN=@owner venomx session remove --session s0 --recursive=true --signal SIGTERM --grace-ms 500 --json
{}

A program that ignores the first signal is killed at the deadline:

$ VENOMX_TOKEN=@owner venomx session create --name hung --json
{"session":"s1","name":"hung"}
$ VENOMX_TOKEN=@owner venomx window create --session s1 --argv '["/bin/sh","-c","trap \"\" HUP TERM INT; while :; do sleep 1; done"]' --json
{"window":"w2","session":"s1","pane":"p3","principal":"g4","pid":41022}
$ VENOMX_TOKEN=@owner venomx pane kill --pane p3 --grace-ms 300 --json
{"pane":"p3","outcome":"terminated","removed":false,"escalated":true,"signal":"SIGKILL"}

The same call on the wire (RFC-0002 NDJSON, after hello):

{"id":5,"verb":"pane.kill","params":{"pane":"p3","grace_ms":300}}
{"id":5,"ok":{"pane":"p3","outcome":"terminated","removed":false,"escalated":true,"signal":"SIGKILL"}}

A response arrives only after the pane’s leader has been reaped (§4). The first response above came back as soon as the shell exited on SIGHUP; the second came back about 300 ms after the request.

2. Schema

All additions go in the venomx RFC-0004 format-1 manifest. The CLI flags follow RFC-0004 §4 (grace_ms → --grace-ms, booleans as --remove=true). All verbs are nonstreaming with standard exit policy.

Verb New parameters Result Dispatch grant
pane.kill (new) Required pane; optional signal, grace_ms, remove {pane,outcome,removed,escalated} plus optional exit_code or signal write
window.remove Optional recursive, signal, grace_ms {} (unchanged) write (unchanged)
session.remove Optional recursive, signal, grace_ms {} (unchanged) write (unchanged)

pane.remove is unchanged; pane.kill --remove=true is the live-pane form. The shared parameter records are:

.{
    .{ .name = "signal", .summary = "First signal sent to each live pane's process group", .type = .{ .kind = .string, .choices = .{ "SIGHUP", "SIGTERM", "SIGINT" } }, .default = "SIGHUP" },
    .{ .name = "grace_ms", .summary = "Milliseconds to wait after the first signal before sending SIGKILL", .type = .{ .kind = .integer, .min = 0, .max = 30000 }, .default = 2000 },
}

pane.kill adds:

.{
    .{ .name = "pane", .summary = "Pane to terminate", .type = .{ .kind = .string }, .required = true },
    .{ .name = "remove", .summary = "Also remove the pane once it has exited", .type = .{ .kind = .boolean }, .default = false },
}

window.remove and session.remove add:

.{
    .{ .name = "recursive", .summary = "Terminate every live pane and remove all children (destructive); signal and grace_ms apply only when true", .type = .{ .kind = .boolean }, .default = false },
}

The pane.kill result is a closed object: required string pane; required string outcome with choices terminated and already_exited; required booleans removed and escalated; optional integer exit_code (0–255); optional string signal. exit_code and signal describe the leader’s wait status and follow RFC-0007 §3: normal exit gives exit_code, death by signal gives the symbolic signal, never both. escalated is true only when this termination sent SIGKILL because the grace deadline passed (§4). It is false for already_exited.

The default SIGHUP and 2000 ms match RFC-0007’s shutdown. Only the three listed signals are accepted; arbitrary signal delivery is not part of this RFC. With recursive=false (the default), signal and grace_ms are ignored and the verbs behave exactly as in RFC-0007. These are additive optional inputs, so RFC-0004 §7 treats them as compatible; the schema digest still changes.

Summaries for all three verbs MUST say that the operation is destructive and cannot be undone once the first signal is sent. RFC-0004 format 1 has no MCP annotation member, so generated MCP tools carry this only in their descriptions (see Open questions).

3. Authorization and sealing

Every check happens in the request’s first reactor turn, before any signal, seal or event. Its order:

  1. RFC-0005 dispatch gate: write on some scope.
  2. write on the named target (pane:<id>, window:<id> or session:<id>), before lookup, so an unauthorized caller gets denied.write whether or not the target exists. Then the usual not_found.<kind>.
  3. unavailable.shutdown if the daemon is stopping (RFC-0007 §2, RFC-0009 §4). Authorization still comes first.
  4. For a recursive removal, write on every descendant: each window in ascending ID, then each of its panes in ascending ID. Concrete container scopes normally cover descendants (RFC-0005 §5), but kind wildcards and self do not always: write window:* holds no right on panes, and a self grant does not cover a pane another principal spawned into a window the caller does not own. The first failing descendant ends the request with denied.write. Following RFC-0005 §7, the error data has right and principal but no resource, because the caller did not name the descendant. There is no partial cascade.
  5. Conflict checks: the target or an ancestor already sealed by another in-flight recursive removal gives conflict.removing; with recursive=false the RFC-0007 conflict.not_empty and conflict.pane_running rules apply unchanged.

When every check passes, a recursive removal seals the target and its whole subtree in the same reactor turn. While sealed:

  • window.create into a sealed session, pane.split of a sealed pane, pane.relaunch (RFC-0010) of a sealed archive, pane.remove of a sealed exited pane, ordinary window.remove of a sealed empty window, and pane.kill --remove=true of a sealed pane fail conflict.removing without effects.
  • pane.kill with remove=false of a sealed pane joins its termination (§4) and succeeds when that pane has exited.
  • Reads, pane.capture, events and RFC-0006 attach (watcher or writer) work as before.

A creation staged before the seal (RFC-0007 §5: principal, PTY and child prepared, not yet committed) re-checks the seal at commit. A sealed target makes it fail conflict.removing with RFC-0007’s rollback: kill and reap the staged child, close its PTY, revoke the staged principal, discard staged events. A creation that committed before the seal is part of the subtree, was authorized in step 4 and is terminated with it. No interleaving can add a pane the authorization did not cover.

Authorization is not repeated at later phases. A grant revoked after the first turn does not stop an operation that already passed (as for in-flight requests in RFC-0005 §6.4), but a retry re-authorizes from scratch.

pane.kill needs write on the pane, as RFC-0005 §4’s table already lists. A pane principal holding write self may kill its own pane; its response may then be lost with its connection, which the caller must treat as an unknown outcome. Killing a pane ends its bound root process, so RFC-0005 §6.4’s automatic revocation of that principal and its descendants applies unchanged. Panes spawned from it keep running with revoked principals, exactly as when the root exits on its own.

4. Termination

A pane is running, terminating or exited. terminating is new, can only be entered through this RFC’s verbs, and is never left except to exited. A terminating pane still counts as running for RFC-0007’s conflict.pane_running.

Target. The only signal target is the pane’s process group, created with the PTY: its ID is the leader’s PID, recorded by the daemon when it forked the leader. It is never derived from a PID read later, /proc, OSC data or request params. The daemon MUST NOT signal a process group ID ≤ 1, its own process group, or any group it did not create in this daemon instance (RFC-0010 §7 already forbids old ones).

Pinning. A group ID cannot be reused while its leader is an unreaped zombie. The daemon MUST observe every pane leader’s exit without reaping it (waitid with WNOWAIT, or a pidfd), finish any group signalling that exit requires, and only then reap, all in one reactor turn. After a leader has been reaped, the daemon MUST NOT signal its group again. An already exited pane is therefore never signalled. Leaders SHOULD be signalled through a pidfd where the platform has one.

Escalation. In the first turn, the daemon marks each live target terminating, emits pane.terminating (§6), sends the requested signal to the group and then SIGCONT, so a stopped job can act on it. It reads the monotonic clock once (CLOCK_MONOTONIC on Linux) and sets the deadline to that time plus grace_ms. Wall-clock changes MUST NOT move it. On Linux, time spent suspended does not count, which can only make the grace longer.

  • If the leader exits before the deadline, the daemon sends one SIGKILL to the still-pinned group to stop the members left behind (for example a background job of a non-interactive shell that ignored the signal), then reaps. escalated stays false.
  • At the deadline, it sends SIGKILL to the group of each target whose leader has not exited, sets escalated, and starts a fixed 1000 ms kill-wait deadline on the same clock. On the leader’s exit it reaps as above.
  • grace_ms=0 sends the requested signal, SIGCONT and SIGKILL in the first turn.

All waiting is reactor timers and child-exit notifications. No step blocks the reactor, other requests, event delivery or stream output. A request’s total time is at most grace_ms + 1000 ms plus scheduling delay.

Joining. A pane has at most one termination. A pane.kill or recursive removal that finds a target already terminating joins it: no second requested signal is sent and the deadline is not extended. A joiner with a deadline earlier than the current one shortens it to its own. A joiner arriving after escalation sends SIGKILL once more (safe while pinned) and starts its own 1000 ms kill-wait. All joined requests complete from the same exit and report the same escalated and status.

Input. On entering terminating the pane’s input queue is frozen: queued bytes not yet written to the PTY are discarded, later RFC-0006 INPUT frames are discarded without closing the stream, and pane.send fails conflict.pane_terminating. RFC-0007 §5 already says a successful pane.send means “queued”, not “delivered”. pane.split of a terminating pane fails conflict.pane_terminating. The authoritative Vt keeps answering terminal queries until exit, so a program waiting on a reply is not stalled by the freeze.

Exit. A killed pane’s exit is an ordinary exit: the RFC-0007 observation and pane.exited sequence, its RFC-0006 EXIT frame with the final offset, and RFC-0005 automatic revocation. The pane, its Vt and its history are retained until removed.

Not covered. Processes that left the group (setsid, setpgid, the jobs of an interactive shell with job control, double forks) are not signalled. They are reparented to the daemon as subreaper and reaped when they exit, and doctor counts them as escaped descendants (RFC-0005 §12). Closing the PTY master at removal hangs up the terminal, which most programs still attached to it obey, but that is best effort. No response claims they were stopped. OS-backed containment is deferred to COM-262.

5. Completion, removal and failure

A request responds with success only when its completion condition holds:

Request Completion condition
pane.kill, remove=false The leader has exited and been reaped after the group sweep; the pane is exited; its exit events are committed.
pane.kill, remove=true As above, then the pane is removed (below).
Recursive window.remove / session.remove Every pane in the subtree is exited and reaped, and the subtree is removed (below).

Removal commit. Removal happens in one reactor turn, so event snapshots, RFC-0010 checkpoints and other requests see the tree either whole or gone. For each removed pane the daemon closes its PTY master, revokes its principal if still live, queues RFC-0006 CLOSE with {"reason":"pane_removed"} on every stream after anything already queued for it, and releases its Vt, history and ring. It then deletes the topology and appends events in this order:

  1. pane.kill --remove=true: RFC-0007’s pane removal (observation cleanup, pane.removed, then window.layout_changed with the parent collapsed and the revision incremented).
  2. Recursive window.remove: for each pane in ascending ID, observation cleanup and pane.removed; then window.removed. No intermediate window.layout_changed is emitted for a window that is being removed.
  3. Recursive session.remove: step 2 for each window in ascending ID, then session.removed.

IDs are never reused (RFC-0007 §3). In on-demand mode, removing the last session latches stopping in this same turn (RFC-0009 §4). A recursive removal of a subtree with no live panes performs its first turn and its removal commit together, so it is as atomic as RFC-0007 removal.

Removal never waits for a client. A slow or stalled stream gets CLOSE after the frames already in its bounded backlog (RFC-0006 §6.2); bytes beyond that are dropped, and RESYNC is no longer possible because the pane is gone. A slow event subscriber recovers removal events by cursor replay (§6).

Timeout. If a kill-wait deadline passes with a leader still not exited (for example a process in uninterruptible sleep), the request fails timeout.terminate (exit 7). Its data lists the panes still terminating, all of which the caller was authorized for in §3:

{"code":"timeout.terminate","message":"pane did not exit after SIGKILL","data":{"panes":["p3"]}}

Nothing is removed. Panes that did exit stay as exited panes, the seal is lifted, and stuck panes stay terminating. The daemon keeps supervising them: when a stuck leader eventually exits, it is swept, reaped and becomes an ordinary exited pane, but it is not removed, because no request is waiting. Doctor reports stuck panes until then.

Cancellation. RFC-0002 cancel, closing the requesting connection, and daemon shutdown end the request with cancelled (exit 8; on shutdown the response is best effort). Signals already sent cannot be taken back, so cancellation does not stop termination: escalation continues to its deadline and exits are reaped as usual. Cancellation does stop removal: nothing is removed and the seal is lifted. Cancellation before the first turn commits has no effect at all. A shutdown that begins during termination takes over the remaining processes with RFC-0007 §2’s own SIGHUP/SIGKILL procedure and deadline.

Retries. Every request is safe to repeat:

  • pane.kill on a terminating pane joins; on an exited pane it returns already_exited without sending a signal; with remove=true it removes the exited pane.
  • A retry after a lost successful remove=true or recursive response gets not_found.<kind>, because IDs are not reused. Clients that lose a response inspect current state, as with RFC-0010 restore.
  • A recursive retry after timeout.terminate or cancelled starts again at §3: re-authorize, re-seal, join any pane still terminating, skip exited ones, then remove.

No automatic retry happens on reconnect, restore or startup.

6. Events and retained authorization

The product event catalog (RFC-0003 §6, RFC-0007 §7) gains one entry and one data field; existing field types do not change:

Type Resource Data Snapshot entity
pane.terminating pane:<pane> pane string, requested_signal string pane, with optional boolean terminating

pane.terminating is emitted once per termination, in the first turn, in ascending pane ID order, after the turn’s seal and before any exit. Joining does not emit it again. requested_signal is the signal from the request (SIGHUP, SIGTERM or SIGINT); it is named so it is never confused with the death signal of a wait status. The venomx pane snapshot entity gains an optional terminating field, present and true only while the pane is terminating. Escalation and stuck panes are not events; they are visible in results, errors and doctor.

An event is authorized against its own resource. After a removal that resource no longer exists, but subscribers can still replay its events from a cursor within RFC-0003 retention. The daemon MUST therefore keep, for each removed pane, window and session, a frozen authorization record: its resource ID, its owner principal ID and that principal’s ancestor chain, and its own containment ancestors, all as of the removal commit. read on retained events about it (pane.terminating, pane.exited, pane.removed, window.removed, session.removed and any older retained event for it) is evaluated with live grants against this record: a <kind>:<id> scope matches the resource or a frozen ancestor, self matches the frozen owner chain. A record is freed when the last retained event that refers to it leaves the log, so its memory is bounded by retention. Records hold IDs only, never tokens.

7. Errors

New product errors:

Code Exit When / data
conflict.pane_terminating 5 pane.send or pane.split on a terminating pane; empty data
conflict.removing 5 Mutation inside a subtree sealed by an in-flight recursive removal; empty data
timeout.terminate 7 A leader did not exit by its kill-wait deadline; {"panes":[string]}

Unchanged: denied.write (RFC-0005 data), not_found.pane, .window, .session, conflict.not_empty and conflict.pane_running for non-recursive calls, unavailable.shutdown, cancelled, and invalid_params.enum / .range / .type for bad signal, grace_ms, remove or recursive. Errors, events and doctor MUST NOT include argv, except RFC-0007 §5’s derived argv0 in the pane entity and pane.created, terminal content or credentials.

8. Conformance matrix

COM-246 MUST pass each case against a real headless daemon, with real processes and PTYs, through both the CLI and MCP where a verb is called. “No side effects” means: no signal, no seal, no event, no topology change, no principal change. Cases that need an unkillable or reused process use a daemon test seam that withholds exit notification or injects a recycled ID; the seam is compiled only into test builds.

# Setup Action Expected
1 Pane running /bin/sh pane kill terminated, escalated=false, signal=SIGHUP; response after reap; pane.terminating then pane.exited; EXIT frame on streams
2 Pane ignoring HUP/TERM/INT (§1) pane kill --grace-ms 300 escalated=true, signal=SIGKILL; response in [300, 1300] ms plus scheduling
3 sh -c '(trap "" HUP; sleep 1000) & wait' pane kill Leader exits on HUP; group sweep kills sleep; no process left in the group
4 Group stopped with SIGSTOP pane kill --signal SIGTERM SIGCONT delivered; exits without escalation
5 Case 2 in progress Other clients ping, send to and attach other panes, read events Each answers within normal latency; nothing waits on the grace
6 Exited pane pane kill, then with --remove=true already_exited, no signal (seam observes none); then removed with RFC-0007 events
7 Missing ID; caller with and without write pane kill, recursive removals not_found.* for authorized caller; denied.write identical for existing and missing targets
8 Session with two windows, nested splits, running, exited and archive panes session remove --recursive=true Event order of §5 exactly; tree, PTYs, principals, Vts gone; IDs not reused
9 Nonempty window, running pane window remove, pane remove (no opt-in) conflict.not_empty, conflict.pane_running; no side effects
10 Caller with write session:s0 Recursive session remove Succeeds
11 Caller with only write window:*; caller with write self and a pane owned by another principal Recursive window remove denied.write without resource; no side effects on any pane
12 Split or window.create staged in a target Recursive removal commits first, then the other way round Either the new pane is part of the subtree and removed, or creation fails conflict.removing with no leaked child, PTY or principal
13 Pane terminating pane send, INPUT frames, pane split, pane remove conflict.pane_terminating, discarded without stream close, conflict.pane_terminating, conflict.pane_running; queued bytes never reach the PTY
14 Sealed subtree Each mutation in §3’s list conflict.removing; pane kill without remove joins and succeeds
15 A stream that never reads Kill and remove its pane Response independent of the stream; stream gets CLOSE pane_removed after its backlog or is closed by overflow
16 Subscribers with read pane:p1, read session:s0, unrelated read pane:p9 Remove, then replay from an older cursor Scoped subscribers receive the removal events; the unrelated one never does
17 Case 2 in progress cancel, and separately close the connection cancelled; process still killed by the deadline; nothing removed; seal lifted; retry succeeds
18 Seam withholds one leader’s exit Recursive removal timeout.terminate with that pane; other panes exited, nothing removed, seal lifted; doctor reports it stuck; later exit leaves an exited, unremoved pane; retry then succeeds
19 Two concurrent pane kill, the second with a shorter grace Both One requested signal; deadline shortened; both get the same result
20 Repeat after success pane kill, --remove=true, recursive removal already_exited; not_found.pane; not_found.window / .session
21 Sentinel processes outside the daemon; seam recycles a reaped leader’s PID All kill cases Sentinels, the daemon’s own group and the recycled ID never receive a signal
22 Pane kills itself with write self pane kill --pane <own> Pane terminates; principal and descendants revoked; daemon unaffected
23 Shutdown during case 2 SIGTERM to serve In-flight request cancelled (best effort); RFC-0007 shutdown bound holds
24 On-demand daemon with one session Recursive session remove stopping latched in the removal turn; later mutation unavailable.shutdown
25 Recovery enabled; checkpoint during case 2 Restore that checkpoint Terminating panes restored as interrupted archives; never a half-removed tree
26 Any case above window get, event snapshots during it Every published tree valid; revisions only increase; no ghost or duplicate pane
27 After all cases Daemon inspection No zombies, no extra PTY fds, no live principals of removed panes, frozen records freed after retention

Agent-contract impact

  • Verbs/schema: one new verb, pane.kill, and optional parameters on window.remove and session.remove (§2), generated into CLI, MCP, docs and completions from the one manifest. No new grant names, no local dispatch, no format change.
  • JSON/exits: standard. New errors in §7; timeout.terminate uses the existing timeout class (exit 7).
  • Events: pane.terminating and the requested_signal field, the terminating snapshot field, and frozen authorization records for replay of removal events (§6).
  • Skill: the generated skill MUST say that pane kill and --recursive=true are destructive and irreversible once started, need write on every affected resource, are never implied by plain remove, and that clients should inspect with window get / session get first and treat a lost response as unknown.
  • Doctor: add panes.terminating (info: count), panes.stuck (warn: IDs of panes past their kill-wait deadline), counters for kills, escalations, timeout.terminate, discarded input bytes and escaped descendants reaped, and events.removed_records (count). Standard redaction applies.
  • Config: none. Signal and grace are per request and bounded by the schema. No setting changes RFC-0007’s shutdown deadline.
  • Grants: write on the target and, for recursive removal, on every descendant, checked before effects (§3). RFC-0005 revocation and audit are unchanged; each denied.write is audited as usual.
  • Headless/OSC: fully headless. No OSC change; a pane’s OSC events stop at exit as today.

Alternatives

  • Make plain remove kill: breaks RFC-0007 callers that rely on conflict.pane_running and conflict.not_empty as a safety check.
  • Fire-and-forget kill (respond after sending the signal): cannot tell the caller whether cleanup happened, and leaves “kill then remove” to a polling loop. A bounded wait is cheap because it is asynchronous.
  • Best-effort recursive removal (remove what you may): an authorized root with an unauthorized descendant would leave a partial cascade. All-or-nothing authorization is simpler to reason about.
  • Remove each pane as it exits: clients would see a series of shrinking trees and could act on a half-removed window. One commit at the end keeps topology atomic.
  • Roll back on timeout or cancel: a delivered signal cannot be undone; pretending otherwise would misreport state.
  • Signal the whole terminal session or walk /proc: needs process enumeration, races with PID reuse and is not a containment boundary. OS containment is the right fix and is deferred (COM-262).
  • Configurable defaults: a per-daemon grace would make the same call behave differently on different machines. Per-request values suffice.
  • Do nothing: clients keep scripting send/poll/remove loops, and a hung program can only be stopped by stopping the daemon.

Open questions

  1. Arbitrary signal delivery (SIGUSR1, SIGWINCH, …): resolved, out of scope. This RFC only terminates; a signal-sending verb needs its own proposal.
  2. Escaped descendants and OS-backed containment: deferred to COM-262. Until then only the pinned process group is signalled and nothing claims more (§4).
  3. MCP destructive annotations: deferred to COM-263. Until then the verb summaries carry the warning (§2).
  4. Configurable grace or kill-wait: resolved, no config (Agent-contract impact). The fixed 1000 ms kill-wait can be revisited by measurement.

The maintainer accepted this RFC and its resolved/deferred questions on 2026-11-07. RFC-0007’s History records that this RFC now covers the live-pane kill and recursive removal that RFC-0007 §5 deferred.