Bounded history-range capture from authoritative panes
| Field | Value |
|---|---|
| Status | Accepted |
| Accepted | 2026-11-07, explicitly approved by the maintainer |
| Authors | commixon (commixon) |
| Created | 2026-11-07 |
| Linear | COM-249; implementation COM-250 |
| Supersedes | None |
| Superseded by | None |
Summary
Add one read-only venomx verb, pane.capture_rows, that returns a range of
physical rows (scrollback and screen) from a pane’s authoritative terminal
as plain UTF-8 text. Rows are addressed by the (epoch, absolute row) pairs
of RFC-0006 §5.3. Every reply is bounded by row,
byte, cell and frame limits. A range that does not fit one reply is
returned in pages through an explicit, stateless next row; nothing is
ever silently truncated. Evicted rows, rows that don’t exist yet and stale
epochs fail with defined errors. The verb needs read on the pane under
RFC-0005, has no side effects, and never
touches a client’s viewport. RFC-0007 §5
pane.capture is unchanged. COM-250 implements it in venomx.
Motivation
RFC-0007 §5 gives agents pane.capture: the active visible screen only.
It says that history-range capture is deferred. Agents that drive builds,
test runs and REPLs in panes need the output that has scrolled off:
- venom watches agent panes and wants the last few hundred rows of a
failed
make test, not 24. - Agents that poll a pane need to read “everything since I last looked” without re-reading the whole scrollback each time.
- Scripts want a full transcript of a pane’s retained history.
The only way to get history today is an RFC-0006 emulating attach, which
means a binary-frame client, a ghostty decoder of the same pin and a
read attach that RFC-0006’s skill guidance tells agents not to use. A
plain-text verb gives the same rows to CLI and MCP callers.
Without a defined contract, each caller invents its own: wrapping
commands in tee, raising the screen size, or attaching. All of them
lose rows silently when output is fast, which is exactly when the output
matters. COM-250 is blocked on this RFC.
Design
1. Examples
A headless daemon is running (venomx serve). The human opts into owner
authority as in RFC-0007 §1. IDs assume a fresh daemon; the pane is
20×4 so the examples stay short.
VENOMX_TOKEN=@owner venomx session create --json
VENOMX_TOKEN=@owner venomx window create --session s0 --cols 20 --rows 4 \
--argv '["/bin/sh","-c","seq 1 1000; exec sleep 1000"]' --json
venomx pane capture-rows --pane p0 --tail 3 --json
seq prints 1 … 1000, one per row, so N is on absolute row N − 1
and the cursor sits on the empty row 1000. The last command returns the
three newest rows:
{"pane":"p0","screen":"primary","epoch":1,"offset":4893,"cols":20,"rows":4,"bounds":{"oldest":0,"active":997,"end":1001},"start":998,"end":1001,"done":true,"lines":["999 ","1000 "," "]}
Every line is one physical row of exactly 20 columns, trailing spaces
included, with no LF, styles or escape sequences. offset is the pane
byte offset the read is as of (4893 bytes: 2893 digits and 1000 CRLF
pairs).
Reading a fixed range page by page. The first call pins the range; each
later call passes the same epoch and end and starts at next:
$ venomx pane capture-rows --pane p0 --epoch 1 --start 0 --end 1001 --max-rows 2 --json
{"pane":"p0","screen":"primary","epoch":1,"offset":4893,"cols":20,"rows":4,"bounds":{"oldest":0,"active":997,"end":1001},"start":0,"end":1001,"next":2,"done":false,"lines":["1 ","2 "]}
$ venomx pane capture-rows --pane p0 --epoch 1 --start 2 --end 1001 --max-rows 2 --json
{"pane":"p0","screen":"primary","epoch":1,"offset":4893,"cols":20,"rows":4,"bounds":{"oldest":0,"active":997,"end":1001},"start":2,"end":1001,"next":4,"done":false,"lines":["3 ","4 "]}
A complete transcript as a shell loop (the default selection is all retained rows of the current epoch):
page=$(venomx pane capture-rows --pane p0 --json) || exit
epoch=$(jq -r .epoch <<<"$page"); end=$(jq -r .end <<<"$page")
while :; do
jq -r '.lines[]' <<<"$page"
[ "$(jq -r .done <<<"$page")" = true ] && break
page=$(venomx pane capture-rows --pane p0 --epoch "$epoch" \
--start "$(jq -r .next <<<"$page")" --end "$end" --json) || exit
done
Errors:
$ venomx pane capture-rows --pane p0 --epoch 0 --start 0 --end 10 --json
{"error":{"code":"conflict.epoch","message":"row epoch 0 is no longer current","data":{"screen":"primary","epoch":1,"oldest":0,"end":1001}}}
$ echo $?
5
$ venomx pane capture-rows --pane p0 --epoch 1 --start 990 --end 2000 --json
{"error":{"code":"not_found.rows","message":"rows 1001 to 1999 do not exist yet","data":{"reason":"future","screen":"primary","epoch":1,"oldest":0,"end":1001}}}
$ echo $?
3
$ venomx pane capture-rows --pane p0 --tail 3 --start 0 --json
{"error":{"code":"invalid_params.rows","message":"tail cannot be combined with epoch, start or end","data":{"param":"tail","reason":"exclusive"}}}
$ echo $?
2
The same verb on the wire (RFC-0002 NDJSON, after hello):
{"id":4,"verb":"pane.capture_rows","params":{"pane":"p0","tail":1}}
{"id":4,"ok":{"pane":"p0","screen":"primary","epoch":1,"offset":4893,"cols":20,"rows":4,"bounds":{"oldest":0,"active":997,"end":1001},"start":1000,"end":1001,"done":true,"lines":[" "]}}
2. Schema
One new verb in the venomx RFC-0004 format-1 manifest. Wire name
pane.capture_rows, CLI venomx pane capture-rows, MCP tool
pane_dcapture__rows. It is not streaming and declares the read right on
pane:<pane>.
Parameters:
.{
.{ .name = "pane", .summary = "Pane to read", .type = .{ .kind = .string }, .required = true },
.{ .name = "epoch", .summary = "Row epoch that start and end belong to; required with start or end", .type = .{ .kind = .integer, .min = 0, .max = 4294967295 } },
.{ .name = "start", .summary = "First absolute row (inclusive); default: oldest retained row", .type = .{ .kind = .integer, .min = 0, .max = 9007199254740991 } },
.{ .name = "end", .summary = "Absolute row after the last one (exclusive); default: current end", .type = .{ .kind = .integer, .min = 0, .max = 9007199254740991 } },
.{ .name = "tail", .summary = "Select at most this many newest rows of the current epoch; excludes epoch, start and end", .type = .{ .kind = .integer, .min = 1, .max = 9007199254740991 } },
.{ .name = "max_rows", .summary = "Most rows in this reply", .type = .{ .kind = .integer, .min = 1, .max = 10000 }, .default = 1000 },
.{ .name = "max_bytes", .summary = "Most UTF-8 bytes of line text in this reply", .type = .{ .kind = .integer, .min = 1, .max = 16777216 }, .default = 262144 },
}
Result fields, all required except next:
| Field | Type | Meaning |
|---|---|---|
pane |
string | The pane read |
screen |
"primary" | "alternate" |
The screen that was active at the read (§3) |
epoch |
integer | Row epoch of every row number in this result |
offset |
integer | Pane byte offset the read is as of: it reflects exactly PTY bytes [0, offset) (RFC-0006 §5.1) |
cols, rows |
integer | Pane size at offset |
bounds |
object {oldest, active, end} |
Retained rows at the read: [oldest, end), with the screen at [active, end) |
start |
integer | Absolute row of lines[0] (or where it would be, if lines is empty) |
end |
integer | End of the selection (exclusive), pinned by the first page |
next |
integer | Present only when done is false: the start of the next page, equal to start + len(lines) |
done |
boolean | True when this page reaches end |
lines |
array of strings | Rows [start, start + len(lines)), oldest first, in §5’s text form |
The manifest MUST give these as complete object descriptors (the closed
screen string uses choices). Format 1 has no unions or mutual
exclusion, so the handler enforces §3’s rules with invalid_params.rows.
No shared generator work is required.
3. Rows, epochs and selection
Row addresses are RFC-0006 §5.3’s: an absolute row is the index of a
physical row on the pane’s active screen, stable while older rows are
pruned and newer ones are written, and meaningful only within its epoch.
The epoch changes on any resize that changes the dimensions, on clearing
scrollback (CSI 3 J), on full reset (RIS), on every switch between the
primary and alternate screens, and when venom-term loses its row anchor.
The bounds are venom-term’s Vt.rows(): oldest is the oldest retained
row, end the row after the newest, active = end − rows.
The verb reads the screen that is active when it runs. While a full-screen
program holds the alternate screen, screen is "alternate", the epoch
is the alternate screen’s, and bounds.oldest = bounds.active: there is
no history to read. Primary-screen addresses taken earlier are stale, and
stay stale after the program exits, because both switches start a new
epoch. Reading the primary screen’s history during alternate-screen use
is deferred (Open question 1, COM-268).
Within one epoch the daemon MUST guarantee:
endnever decreases andoldestnever decreases.- A row below
active(history) never changes text. Only rows in[active, end)can change.
Both follow from how a terminal works: programs can only write to the
screen, and every operation that rewrites or removes history (reflow,
CSI 3 J, RIS, anchor loss) starts a new epoch. If venom-term ever
reports a change that would break either rule, the daemon MUST treat it
as an epoch change.
Selection. Exactly one of these forms, checked in this order:
| Params given | Selection [S, E) |
|---|---|
tail = n, with none of epoch, start, end |
[max(oldest, end − n), end) of the current epoch |
none of tail, epoch, start, end |
[oldest, end) of the current epoch |
epoch, optionally start and end |
[start ?? oldest, end ?? end) in epoch, which MUST be current |
tailtogether with any ofepoch,start,endfailsinvalid_params.rows(exclusive).startorendwithoutepochfailsinvalid_params.rows(epoch_required).end < startfailsinvalid_params.rows(order).- A given
epochthat is not the current one failsconflict.epoch. S < oldestfailsnot_found.rows(evicted).E > endfailsnot_found.rows(future). The daemon MUST NOT clamp either side: clamping would silently drop rows the caller asked for.tailis “at most n”: with fewer rows retained, the selection is shorter, and the result’sstartandbounds.oldestshow it. This is the meaning of the request, not truncation of a selected range.S = Eis valid and returnslines: [],done: true.
4. Read boundary, output and resize
The read runs on the pane’s own thread between two Vt.feed calls, as
RFC-0006 §5.1 requires for snapshots. One page is therefore a consistent
view of the terminal after exactly the PTY bytes [0, offset): output
arriving during the call is applied after it. Bounds, size, epoch,
screen and lines in one result all describe that same instant.
A resize is applied at a pane offset N (RFC-0006 §8). A read sees either
the size and epoch before N or the ones after it, never a mix, and
cols, rows and epoch say which. A pinned selection in the old epoch
then fails conflict.epoch; tail and default selections read the new
epoch.
Across pages. The daemon keeps no state between calls: each page is a
new read at its own offset. The guarantees of §3 make paging safe:
- History rows returned by one page are still valid in later pages of the same epoch, so pages never overlap, skip or reorder rows.
- Rows that were in
[active, end)when their page was read may change afterwards. A caller that needs the whole selection at one instant MUST fit it in one page (raisemax_rowsandmax_bytes, §6). - Fast output can prune rows between pages. The next call then fails
not_found.rows(evicted) with the currentoldest, so the caller learns exactly which rows,[next, oldest), are gone. It may continue fromoldestexplicitly. - Any epoch change between pages fails the next call with
conflict.epoch.
Rows written after the first page are outside the pinned end and are
not returned; read them with a new call starting at the old end.
5. Text
Each element of lines is one physical row, rendered exactly as
RFC-0007 §5 pane.capture renders a row:
- The cell text of every column, left to right: the full grapheme cluster of each cell, a space for each empty cell, trailing spaces kept.
- Width-zero continuation cells (the right half of a wide character) are
omitted, so a row’s display width is always
cols. - No LF, CR or other C0/C1 controls, no DEL, no escape sequences, styles, hyperlinks or images. The string is valid UTF-8.
A wrapped logical line is several rows; no row records whether it wrapped. Logical-line (unwrapped) addressing that survives reflow is COM-233 and is not part of this RFC.
For the same instant, lines for [active, end) on the active screen are
byte-identical to pane.capture’s text split on LF. The JSON encoder
escapes ", \ and nothing else beyond what JSON requires; it MUST NOT
escape non-ASCII characters (so byte counts in §6 match the wire closely).
6. Limits and pagination
A page holds rows [start, start + k) for the largest k such that all
of these hold:
| Limit | Value |
|---|---|
| Rows | k ≤ max_rows (default 1000, at most 10000) |
| Line bytes | Sum of the UTF-8 byte lengths of the k lines ≤ max_bytes (default 256 KiB, at most 16 MiB) |
| Cells | k × cols ≤ 4194304 (2²² cells, so at most 64 rows at the 65535-column maximum) |
| Frame | The complete encoded reply, envelope included, fits the connection’s negotiated max_frame (RFC-0002; default 1 MiB) |
If k reaches E − start, the page is the last one (done: true, no
next). Otherwise next = start + k and done: false. The daemon MUST
NOT shorten or drop any line to make it fit: a line is returned whole or
not at all.
If the selection is nonempty and its first row alone does not fit the
line-byte or frame limit, the call fails unsupported.result_size with
data.row and data.bytes (that row’s UTF-8 length). The caller can
retry with a larger max_bytes, or reconnect with a larger max_frame.
The cell limit always admits one row. A row’s bytes are bounded (cols
cells, each with a bounded grapheme), so this error only occurs with
small explicit limits or very wide panes of heavy graphemes.
The cell limit bounds the work done on the pane thread per call, which delays that pane’s output processing (never other panes). The daemon SHOULD export rows in bulk rather than cell by cell; a 10000-row, 250- column page SHOULD complete within 50 ms. There is no daemon-side cursor, cache or snapshot to expire.
7. Grants, viewports and side effects
readonpane:<pane>, through RFC-0005 containment (a concrete session or window scope includes its panes; a kind wildcard likewindow:*does not), exactly aspane.capture. The check runs before pane lookup and before §3’s parameter rules, so a caller without the right getsdenied.readwhether or not the pane exists and whatever the other parameters are. Unknown callers get RFC-0005’s configured default (readon*unless changed).- The verb reads only the authoritative
Vt. It MUST NOT move, scroll or resize any viewport, change the pane size or size-policy activity (RFC-0006 §8), affect any stream’s offset, backlog, backfill or resync, write to the PTY, or emit an event. Viewports, scroll positions and selections stay client-local (RFC-0007 §4). - Exited panes are readable: the Vt and history are retained (RFC-0007 §3). Panes restored as archives by RFC-0010 are readable like any other pane; their hydrated text starts in a new epoch.
- Like
pane.capture, the text can contain anything a program printed, including secrets. The daemon MUST NOT log, audit or count line text; the audit log records only RFC-0005’sdeniedrecords, which name the resource and never params.
8. Errors
New product errors declared on pane.capture_rows:
| Code | Exit | When | Data |
|---|---|---|---|
invalid_params.rows |
2 | §3 combination rules | {"param":string,"reason":"exclusive"|"epoch_required"|"order"} |
not_found.rows |
3 | Selection reaches outside [oldest, end) |
{"reason":"evicted"|"future","screen","epoch","oldest","end"}, current values |
conflict.epoch |
5 | Given epoch is not current |
{"screen","epoch","oldest","end"}, current values |
unsupported.result_size |
10 | First row does not fit (§6) | {"row":integer,"bytes":integer} |
Also declared, with their RFC-0007 meanings: not_found.pane (3) and
denied.read (4, RFC-0005 data). Schema validation errors
(invalid_params.range and the rest) are RFC-0004’s. A row address above
9007199254740991 cannot be represented in a valid result and fails as
RFC-0004 internal.result; at a million rows per second that takes 285
years. The error data reveals the pane’s bounds and screen, which is pane
state, only after the read check has passed.
9. Compatibility and venom-term
pane.capture(RFC-0007 §5), itsunsupported.result_sizeerror and its active-screen rule are unchanged. RFC-0010’s checkpoint text (always the primary screen) is unchanged.- RFC-0006 frames,
SCROLLBACKbackfill and size policy are unchanged. - This is a compatible addition under PROCESS.md “Changing an RFC”. On acceptance, RFC-0007’s History records that this RFC defines the history-range capture its §5 deferred.
venom-term. COM-250 needs no new library behavior. venom-term already provides everything this RFC uses:
| Need | venom-term API (docs/API.md) |
|---|---|
| Epoch and bounds | Vt.rows() → {epoch, oldest, end, active} |
| Row text, RFC-0007 rendering | Vt.exportRows(allocator, epoch, start, end), one LF-terminated line per row, StaleRows on a wrong epoch or pruned row |
Epoch changes on resize, CSI 3 J, RIS, screen switch, anchor loss |
Documented rows() semantics |
The daemon calls exportRows row by row (or in runs) to apply §6’s byte
limit, strips each LF, and maps StaleRows to §3’s errors after
re-reading rows(). Today exportRows reads every cell through
readCell, allocating per cell; a bulk row export would help meet §6’s
SHOULD but is an optimization, not a requirement, so no library issue is
opened. §3’s two guarantees are tested by COM-250’s conformance cases
(§10, cases 7 and 8) against the real library.
10. Conformance matrix
COM-250 MUST pass each case against a real headless daemon with real PTYs, through both the CLI and MCP. “Expected text” is computed independently by the test from the bytes it wrote, never by asking the daemon. “No side effects” means: no event, no PTY write, no size change, no change to any attached stream’s offset, backlog or viewport.
| # | Setup | Action | Expected |
|---|---|---|---|
| 1 | 20×4 pane, seq 1 1000 |
--tail 3; default selection paged with --max-rows 100 |
§1’s result; all 1001 rows in 11 pages, next chaining, last page done with no next, rows N−1 = N padded to 20 |
| 2 | Same | pane.capture, then capture-rows --epoch E --start active --end end at the same offset |
Lines equal text split on LF |
| 3 | Pane prints é (precomposed), e+U+0301, 漢字, ZWJ emoji, a tab, "\ |
--tail covering them |
Exact UTF-8 graphemes; wide chars once (no continuation cell); tab as spaces; every line’s display width = cols; JSON escapes only " and \; no byte < 0x20 or 0x7F in any line |
| 4 | Pane prints SGR colours, OSC 8 link, OSC 2 title, CSI 2J |
Default selection | No ESC or other control in any line; text only |
| 5 | Program writes continuously (e.g. yes) |
100 concurrent calls with --tail 50 |
Each result is consistent: end − active = rows, len(lines) = end − start, offset non-decreasing per connection; each line is a whole row |
| 6 | Same, with a small scrollback | Pinned range near oldest, paged slowly |
Either all pages succeed, or a call fails not_found.rows evicted with current oldest; never a silently shorter or shifted page |
| 7 | History of known rows | Read a history page, write more output, re-read the same rows in the same epoch | Identical text (history immutability, §3) |
| 8 | Writer attach with latest policy |
Resize the attach viewport during paging; also between two reads | Pinned call after the resize fails conflict.epoch; tail reads report new cols, rows, epoch; end and oldest never decrease within one epoch across 1000 reads |
| 9 | Pane runs less (alternate screen) over a long output |
Default selection; then quit less and use an old pinned range |
screen: "alternate", oldest = active, rows lines; after quitting, conflict.epoch, and a new default read shows screen: "primary" in a new epoch |
| 10 | printf '\033[3J'; printf '\033c' |
Pinned range from before | conflict.epoch each time |
| 11 | — | --tail 1 --epoch 1; --start 0 without epoch; --epoch E --start 5 --end 4 |
invalid_params.rows: exclusive, epoch_required, order; no side effects |
| 12 | — | --max-rows 0, --max-rows 10001, --max-bytes 0, --tail 0, --start -1 |
RFC-0004 invalid_params.range |
| 13 | — | --epoch E --end end+1; --epoch E --start oldest−1 (after pruning) |
not_found.rows future; evicted; data holds current bounds |
| 14 | — | --epoch E --start S --end S |
lines: [], done: true, start = S |
| 15 | Rows of 3-byte characters | --max-bytes equal to 2.5 rows |
Exactly 2 lines and next; with --max-bytes below one row, unsupported.result_size with row and bytes |
| 16 | 65535×4 pane with history | Default selection | At most 64 rows per page (cell limit), paging to completion |
| 17 | Connection negotiated max_frame 4096 |
Default selection over 80-column rows | Every reply ≤ 4096 bytes, still complete via paging; a row too large for the frame fails unsupported.result_size |
| 18 | Caller with read only on pane:p1 |
Read p0 (exists) and p9 (missing), each also with invalid selection params | denied.read for all four, identical apart from resource; no bounds leaked; one audit denied record each, with no params |
| 19 | Caller with read on session:s0; caller with read on window:* |
Read a pane in s0 | Allowed; denied.read |
| 20 | Two emulating attach clients on one pane, scrolled to different history rows, one with a selection | 50 calls of every selection form | Both clients’ scroll positions, selections and rendered state unchanged; no RESIZE, RESYNC or extra bytes on their streams; size-policy active writer unchanged |
| 21 | events --json subscriber from before the run |
Any successful or failed call | No event caused by the verb |
| 22 | Exited pane; RFC-0010 restored archive pane (where enabled) | Default selection | Readable; archive rows equal the saved plain text; new epoch |
| 23 | Canary string printed by the pane | All cases, plus doctor, daemon logs and audit log | Canary appears only in results |
| 24 | Existing RFC-0007 cases for pane.capture |
Rerun | Unchanged results, including unsupported.result_size |
Agent-contract impact
- Verbs/schema: one new verb,
pane.capture_rows(§2), in RFC-0004 format 1 with no generator change. CLI, MCP, docs and completions are generated from the manifest. Not streaming; no local dispatch. - JSON/exits: standard. New errors in §8; pagination is ordinary
result data (
next,done). - Events: none. Capture emits nothing and observes no events.
- Skill: the generated skill MUST say: use
--tail Nfor recent output; page with--epoch,--start <next>and the first page’s--enduntildone;conflict.epochmeans the screen was resized, cleared or switched and the caller should re-read with--tail;not_found.rowsevictedmeans output outran the reader, witholdestsaying where to continue; lines are physical rows padded to the pane width (trim trailing spaces for display); wrapped lines are split. It SHOULD tell agents to prefer this verb over attaching. - Doctor: add
capture_rowscounters: calls, pages withdonefalse, errors by code (includingnot_found.rowsby reason), and the maximum time a read held a pane thread, withwarnabove 50 ms. No text, ranges or pane contents. - Config: none. Limits are per request with fixed ceilings (§6), so there is nothing for a deployment to tune; scrollback depth stays the existing Vt setting. Revisit if a ceiling proves wrong in practice.
- Grants:
readon the pane, aspane.capture(§7). No new right. - Headless/OSC: fully headless; works with no client attached. OSC sequences are never returned as text (§5); their observations remain RFC-0003 events from the authoritative parser.
Alternatives
- Add range parameters to
pane.capture: one verb, but its result is a singletextstring of the visible screen with no epoch, bounds or pagination fields. Adding them changes the meaning of an accepted verb’s result for callers that pass nothing; a separate verb keepspane.captureexactly as specified. - Fail with
unsupported.result_sizeinstead of paging: simplest, but a 10000-row scrollback at 250 columns is about 2.5 MB, larger than the default frame, so full history would be unreachable without renegotiatingmax_frame. - Opaque server-side cursor: hides the addresses, but needs daemon
state with an expiry and a cap per caller.
(epoch, next, end)is already a complete, stateless cursor, and the caller can reason about eviction with it. - A streaming verb (RFC-0004 items): CLI output becomes NDJSON lines, but MCP buffers streams under RFC-0004’s 1024-item and 1 MiB caps, so a long range still needs a page contract, and one long stream holds the pane thread or loses consistency across items anyway.
- Clamp evicted or future ranges: convenient, but it is exactly the
silent truncation this RFC exists to prevent.
tailgives the convenient form explicitly. - Newest-first pages: matches RFC-0006 backfill, but readers that
print or diff text want chronological order, and
tailalready covers “the most recent rows”. - Return one
textstring with LF separators per page: likepane.capture, but it hides row identity and makesnextarithmetic depend on splitting. An array of rows maps one-to-one to addresses. - Logical (unwrapped) lines: what agents ultimately want, but it needs line identities that survive reflow, which is COM-233.
- Do nothing: agents keep attaching or wrapping commands in
tee, and lose rows silently under fast output.
Open questions
- Primary-screen history while the alternate screen is active. Deferred to COM-268. RFC-0010’s export reads a decoded copy of the primary screen, but those rows have no live epoch and the copy costs a snapshot per call. Until then the verb reads the active screen (§3).
- Logical-line addressing. Out of scope; COM-233. This RFC uses physical rows only and does not assume any logical-line identity.
- Default selection. Resolved: all retained rows of the current
epoch, oldest first (§3), so the no-argument call is the complete
transcript and
--tailis the explicit “recent” form. - Limits. Resolved: the numbers in §6 (1000 rows and 256 KiB by
default; ceilings 10000 rows, 16 MiB, 2²² cells; 50 ms pane-thread
target). They match RFC-0006’s
max_history_rowsdefault and keep a default page well inside a 1 MiB frame. - Library work. Resolved: none required (§9); a bulk
exportRowsis an optional optimization.
The maintainer accepted this RFC and its resolved/deferred questions on 2026-11-07. RFC-0007’s History records that this RFC now defines the history-range capture that RFC-0007 §5 deferred.