fix: deliver long CLI passthrough prompts intact (#3894)

Co-authored-by: Carlos Florencio <carlosmflorencio@gmail.com>
This commit is contained in:
Dmitry Koryakov
2026-09-24 15:45:00 +02:00
committed by GitHub
parent d602745281
commit 5cb908e4ad
28 changed files with 1129 additions and 143 deletions
@@ -0,0 +1,134 @@
---
created: 2026-09-22
status: complete
requirements:
- REQ-CLI-PASSTHROUGH-PROMPT-DELIVERY-001
system_design:
- ../../specs/cli/system-design/passthrough-prompt-delivery.md
legacy_specs: []
---
# Implementation Plan: Passthrough Prompt Delivery Integrity
## Overview
A prompt delivered to a CLI passthrough session loses whole leading read-sized
blocks of its body. Measured against a live instance, every agent-to-agent
message longer than one read arrived at the agent missing exactly N x 1022
bytes from the front, with no error on any surface. The planner writes the whole
body as one unframed PTY write, so the receiving TUI has to absorb a burst many
times larger than one terminal read and silently drops the reads it cannot take.
Two work orders correct this in the one place that plans PTY writes. The first
restores bracketed-paste framing for TUIs that accept it, which a probe against
the real CLI showed carries 16 KB in a single write without loss. The second
paces the remaining unframed path so a body can never exceed one read per write.
## Scope
### In scope
- Frame passthrough prompt bodies with bracketed paste when the agent accepts
it, keeping the submit keystroke a separate delayed write.
- Neutralize framing terminators inside the body.
- Split unframed bodies into rune-safe writes within the host read budget, with
an inter-write delay.
- Update the agent capability records that currently disable framing, and the
tests that pin the superseded behavior.
### Out of scope
- The structured (non-passthrough) prompt path.
- Operator keystrokes typed into the mirrored terminal view.
- Message wrapping, queueing, delivery mode, and mid-turn steering.
- Reading the terminal back to acknowledge delivery.
- The local hotfix build and install on the operator's instance, which happens
outside the repository and is tracked in the session that requested it.
## Technical approach
`PlanPassthroughStdinChunks` stays the single planner and stays pure. It gains
three rules: neutralize framing terminators in the body; frame the body when the
capability record allows it and the body is multi-line or larger than the
raw-safe write size; otherwise emit the body as consecutive rune-safe writes no
larger than the raw-safe size, each continuation carrying an inter-write delay.
The submit keystroke keeps its own trailing chunk and its existing delay.
Callers already loop over planned chunks and honor `DelayBefore`, so no caller
changes. The raw-safe write size is set below the smallest supported host read
capacity (1022 bytes on macOS); 400 bytes with a 30 ms inter-write delay is the
combination verified against the real CLI.
`claude_acp.go` and `tui_agent.go` stop setting `DisableBracketedPaste`. The
field stays in the capability record as the lever for a TUI that genuinely
cannot accept framing, and work order 02 makes that path safe.
## Tests
- `AC-CLI-PASSTHROUGH-PROMPT-DELIVERY-001.1`: planner tests in
`apps/backend/internal/agent/agents/passthrough_payload_test.go` assert the
concatenated chunk bodies reproduce the prompt exactly, for a body far larger
than one read, on both the framed and the unframed path.
- `AC-CLI-PASSTHROUGH-PROMPT-DELIVERY-001.2`: a framed body is one chunk
carrying start and end markers; an unframed body is consecutive chunks in
order.
- `AC-CLI-PASSTHROUGH-PROMPT-DELIVERY-001.3`: the submit keystroke remains the
final, separate chunk with `DelayBefore` equal to the configured submit delay.
- `AC-CLI-PASSTHROUGH-PROMPT-DELIVERY-001.4`: with `DisableBracketedPaste` set,
no chunk exceeds the raw-safe size and each continuation carries the
inter-write delay.
- `AC-CLI-PASSTHROUGH-PROMPT-DELIVERY-001.5`: a body containing a framing
terminator is neutralized before framing, and the marker does not survive into
a delivered chunk.
- `AC-CLI-PASSTHROUGH-PROMPT-DELIVERY-001.6`: covered by the planner being the
single source for all callers; existing caller tests in
`manager_passthrough_autoinject_test.go` and
`executor_passthrough_prompt_test.go` keep that wiring honest.
- `AC-CLI-PASSTHROUGH-PROMPT-DELIVERY-001.7`: existing passthrough prompt error
handling in `executor_passthrough_prompt_test.go` already asserts a write
failure surfaces to the caller; work order 02 keeps it passing with several
writes in flight.
## E2E tests
No Playwright scenario is added. The defect lives between the Go planner and a
vendor TUI's stdin handling, which Playwright does not observe. The behavioral
check that matters is the manual TUI probe recorded in work order 01, plus the
post-deploy comparison of stored message text against the agent's own
transcript.
## Work orders
- [x] [Task 01: Frame passthrough prompt body](task-01-frame-passthrough-prompt-body.md) (done)
- [x] [Task 02: Pace unframed passthrough writes](task-02-pace-unframed-passthrough-writes.md) (done)
## Verification results
Both work orders completed. A probe against the real vendor CLI confirmed a
framed 8 KB body written in one PTY write is accepted whole and submitted by the
delayed submit keystroke. `go test ./internal/agent/agents/` and
`./internal/orchestrator/executor/` pass; `go vet` and `gofmt` are clean and the
backend builds. Failures in `./internal/agent/runtime/lifecycle/` (worktree
preparer, Kubernetes prepare-script, and SSH identity-agent tests) are
environment-dependent: the same twelve tests fail on a pristine checkout of the
base branch, with and without this change. `golangci-lint` was not run: it is
not installed on the development machine.
End-to-end on a live instance running the patched `v0.95.0` build, against a
Claude CLI passthrough session: the initial prompt injector wrote 10174 bytes
and the agent's own transcript recorded 10174 bytes, starting at the first byte,
with all 60 line markers present. A `message_task_kandev` message of 9213 bytes,
the path that exposed the defect, arrived byte-for-byte identical to the stored
message, and the agent reported every marker from first to last. Before the
correction, every stored agent message above 5 KB had lost between 1022 and
5110 bytes from its start.
## Risks
- A TUI that does not enable bracketed-paste mode would render the markers as
literal text. Work order 01 confirms the framed body is accepted and submitted
by the real CLI before the capability records change.
- Custom TUI agents inherit the same change as the built-in Claude record. They
share the same TUI family but were not individually probed.
- The unframed path becomes slower in proportion to body size, which is
intentional: correctness over latency for a path with no acknowledgement.
@@ -0,0 +1,123 @@
---
id: "01-frame-passthrough-prompt-body"
title: "Frame passthrough prompt body"
status: done
wave: 1
depends_on: []
plan: "plan.md"
requirements:
- REQ-CLI-PASSTHROUGH-PROMPT-DELIVERY-001
acceptance_criteria:
- AC-CLI-PASSTHROUGH-PROMPT-DELIVERY-001.1
- AC-CLI-PASSTHROUGH-PROMPT-DELIVERY-001.2
- AC-CLI-PASSTHROUGH-PROMPT-DELIVERY-001.3
- AC-CLI-PASSTHROUGH-PROMPT-DELIVERY-001.5
- AC-CLI-PASSTHROUGH-PROMPT-DELIVERY-001.6
system_design:
- ../../specs/cli/system-design/passthrough-prompt-delivery.md
---
# Task 01: Frame Passthrough Prompt Body
## Summary
Restore bracketed-paste framing for passthrough agents that accept it, so a
prompt body larger than one terminal read reaches the TUI as one paste instead
of a burst it drops reads from. Neutralize framing terminators inside the body
first, and keep the submit keystroke a separate delayed write.
## In scope
- Add failing planner tests for a body far larger than one read, for framing
markers, and for a body containing a framing terminator.
- Neutralize framing terminators in the body inside the planner.
- Frame the body when the capability record allows framing and the body is
multi-line or larger than the raw-safe write size.
- Stop setting `DisableBracketedPaste` in the built-in Claude capability record
and in the custom TUI agent constructor.
- Update the existing tests that pin the superseded unframed behavior.
## Out of scope
- The unframed delivery path, which work order 02 paces.
- Caller changes: the initial-prompt injector, workflow step handler, and
session prompt path already loop over planned chunks.
- Detecting bracketed-paste support from the terminal output stream.
## Acceptance
- A probe against the real vendor CLI confirms a framed body of at least 16 KB
written in one PTY write reaches the input intact and is submitted by the
delayed submit keystroke. If submission fails, stop and re-plan: the fallback
is the paced path from work order 02 for this agent too.
- Concatenating the planned body chunks reproduces the prompt exactly, with
framing markers only at the outer boundary and none surviving from the body.
- The submit keystroke remains the final separate chunk carrying the configured
submit delay, and small single-line bodies keep their current bytes.
## Verification
```bash
(cd apps/backend && go test ./internal/agent/agents/ -run 'TestPlanPassthroughStdin|TestBuildPassthroughPayload|TestNewTUIAgentDefaultsToInkSafePassthrough|TestClaudeACP' -count=1)
(cd apps/backend && go test ./internal/agent/agents/ ./internal/agent/runtime/lifecycle/ ./internal/orchestrator/executor/ -count=1)
(cd apps/backend && gofmt -l internal/agent/agents)
```
## Files likely touched
- `apps/backend/internal/agent/agents/passthrough_payload.go`
- `apps/backend/internal/agent/agents/passthrough_payload_test.go`
- `apps/backend/internal/agent/agents/claude_acp.go`
- `apps/backend/internal/agent/agents/tui_agent.go`
- `apps/backend/internal/agent/agents/tui_agent_test.go`
- `docs/plans/passthrough-prompt-delivery-integrity/plan.md`
- `docs/plans/passthrough-prompt-delivery-integrity/task-01-frame-passthrough-prompt-body.md`
## Dependencies
None.
## Risks
- `TestNewTUIAgentDefaultsToInkSafePassthrough` and the Claude body-verbatim
assertion in `passthrough_payload_test.go` encode the behavior being replaced.
Rewrite them to assert the new contract rather than deleting the coverage.
- The historical reason for disabling framing was a submit keystroke absorbed
into the paste. The separate delayed submit chunk already addresses it; the
probe in Acceptance is what proves it, not the commit history.
## Parallelism
`sequential`
## Inputs
- `REQ-CLI-PASSTHROUGH-PROMPT-DELIVERY-001` and its system design.
- Diagnosis evidence: delivered bodies lost exactly N x 1022 bytes from the
front; a framed 16 KB body written once arrived complete in a probe against
the real CLI; an unframed 8 KB body lost roughly half its lines.
- Existing planner, capability records, and caller tests.
## Results
PROBE: An 8 KB body framed as a bracketed paste and written to the real CLI in
one PTY write was accepted whole, and the submit keystroke written 150 ms later
started the turn: the input box cleared and the agent moved to its thinking
state. Framing therefore does not break submission, so the design holds and the
paced fallback stays reserved for agents that refuse framing.
RED: Five behavioral failures. The Claude body chunk arrived unframed for a
multi-line prompt, an 11 KB multi-line body was not bracketed, a single-line
body past the raw-safe size was not bracketed, prompt-embedded paste markers
survived into the delivered body, and the custom TUI agent still disabled
framing.
GREEN: The planner now strips paste markers from the body, frames it when the
agent accepts framing and the body is multi-line or larger than
`passthroughRawSafeWriteBytes`, and keeps the submit byte a separate delayed
chunk. `claude_acp.go` and `tui_agent.go` no longer set
`DisableBracketedPaste`. `go test ./internal/agent/agents/` and
`./internal/orchestrator/executor/` pass. Failures in
`./internal/agent/runtime/lifecycle/` (worktree preparer and SSH identity-agent
tests) reproduce identically on a pristine checkout of the base branch and are
unrelated to this work order.
@@ -0,0 +1,115 @@
---
id: "02-pace-unframed-passthrough-writes"
title: "Pace unframed passthrough writes"
status: done
wave: 2
depends_on:
- "01-frame-passthrough-prompt-body"
plan: "plan.md"
requirements:
- REQ-CLI-PASSTHROUGH-PROMPT-DELIVERY-001
acceptance_criteria:
- AC-CLI-PASSTHROUGH-PROMPT-DELIVERY-001.1
- AC-CLI-PASSTHROUGH-PROMPT-DELIVERY-001.4
- AC-CLI-PASSTHROUGH-PROMPT-DELIVERY-001.7
system_design:
- ../../specs/cli/system-design/passthrough-prompt-delivery.md
---
# Task 02: Pace Unframed Passthrough Writes
## Summary
Make the remaining unframed path safe for an agent whose capability record
disables framing. Split the body into rune-safe writes no larger than the
raw-safe size and delay each continuation, so the receiving TUI never has to
absorb more than one terminal read at a time.
## In scope
- Add failing planner tests for an unframed body larger than the raw-safe size,
including a multi-byte body whose split would otherwise fall mid-character.
- Split the unframed body into ordered chunks within the raw-safe size, on rune
boundaries, each continuation carrying the inter-write delay.
- Name the raw-safe size and inter-write delay as constants with the host read
capacity they derive from.
- Keep the submit keystroke the final chunk with its own delay, after the last
body chunk.
- Confirm a write failure mid-body still surfaces to the caller.
## Out of scope
- The framed path from work order 01.
- Changing the capability records again; this path exists for agents that
genuinely cannot accept framing.
- Adaptive sizing or feedback from the terminal output stream.
## Acceptance
- With framing disabled, no planned body chunk exceeds the raw-safe size, the
concatenated chunks reproduce the prompt exactly, and no chunk ends inside a
multi-byte character.
- Every continuation body chunk carries the inter-write delay; the first body
chunk carries none.
- A write error on any chunk aborts the remaining writes and returns to the
caller as a failed delivery.
## Verification
```bash
(cd apps/backend && go test ./internal/agent/agents/ -run 'TestPlanPassthroughStdin' -count=1)
(cd apps/backend && go test ./internal/agent/agents/ ./internal/orchestrator/executor/ -count=1)
(cd apps/backend && gofmt -l internal/agent/agents)
```
## Files likely touched
- `apps/backend/internal/agent/agents/passthrough_payload.go`
- `apps/backend/internal/agent/agents/passthrough_payload_test.go`
- `docs/plans/passthrough-prompt-delivery-integrity/plan.md`
- `docs/plans/passthrough-prompt-delivery-integrity/task-02-pace-unframed-passthrough-writes.md`
## Dependencies
- Work order 01 establishes the framed path and the shared raw-safe size
constant this work order reuses.
## Risks
- Delivery time grows with body size on this path. Keep the constants in one
place with a comment naming the host read capacity, so a future change is a
deliberate trade rather than a guess.
- Splitting by bytes instead of runes would corrupt non-ASCII prompts, which is
exactly the traffic that exposed the original defect.
## Parallelism
`sequential`
## Inputs
- `REQ-CLI-PASSTHROUGH-PROMPT-DELIVERY-001` and its system design.
- Probe evidence: unframed writes of 400 bytes spaced 30 ms apart delivered a
3 KB body with no loss, while one 8 KB unframed write lost about half of it.
- The planner and tests as left by work order 01.
## Results
RED: A 3.8 KB unframed body still planned as one 3800-byte write, and the
no-submit-delay path planned one 1201-byte write, both far past the raw-safe
size.
GREEN: `paceRawBody` splits an unframed body into writes of at most
`passthroughRawSafeWriteBytes`, cutting on rune boundaries, with
`passthroughRawWriteInterval` before each continuation. The submit sequence
stays its own delayed chunk when the agent sets a submit delay and otherwise
rides on the final body write. The rune-boundary test caught a real off-by-one:
a body whose length is an exact multiple of the write size indexed one byte past
the end. `go test ./internal/agent/agents/` and
`./internal/orchestrator/executor/` pass, `go vet ./internal/agent/agents/...`
is clean, `gofmt -l internal/agent/agents` is empty, and `go build ./...`
succeeds.
Not run: `golangci-lint`, which is not installed on this machine and was not
added. The changed functions stay well inside the documented limits (longest is
28 lines, nesting depth 4).
@@ -76,7 +76,7 @@ When a passthrough session starts for a task that has a description:
No per-agent pattern matchers. The existing idle window is the only readiness signal. If an agent's CLI is unusual enough that an idle window misfires (writes a banner, then waits 5 seconds, then prompts), we make the idle window per-agent-configurable in `PassthroughConfig` (already exists as `IdleTimeout`). No new detection machinery.
For the Claude case: `claude_acp.go` retains `AutoInjectPrompt: true` as compatibility metadata, sets `DisableBracketedPaste: true` (Claude Code already enables bracketed-paste *mode* in its Ink TUI — injecting `ESC[200~`…`ESC[201~` delimiters breaks the prompt), and `SubmitViaBackslashEnter: true` (PTY writes: prompt, then `\`, then `\r` per [Claude terminal docs](https://code.claude.com/docs/en/terminal-config)). Ink may still treat programmatic Enter as newline only ([anthropics/claude-code#15553](https://github.com/anthropics/claude-code/issues/15553)) — if auto-submit fails, the user confirms with Enter. Other passthrough-capable agents without a `PromptFlag` use the same idle-based stdin route.
For the Claude case: `claude_acp.go` retains `AutoInjectPrompt: true` as compatibility metadata and uses a 150 ms `SubmitDelay`. The prompt body uses bracketed-paste framing, then the submit carriage return is sent as a separate delayed write. This keeps the submit byte out of the Ink paste burst. Custom terminal agents can set `DisableBracketedPaste: true` when they do not accept bracketed-paste delimiters; Kandev then uses bounded, paced unframed writes. The pacing is an empirical fallback because PTY writes do not provide a receipt from the TUI. Other passthrough-capable agents without a `PromptFlag` use the same idle-based stdin route.
### Follow-up prompts via PTY stdin
@@ -0,0 +1,96 @@
---
status: draft
system: cli
created: 2026-09-22
owners:
- kandev
---
# Passthrough Prompt Delivery Requirements
## Overview
In CLI passthrough mode Kandev runs a vendor CLI inside a PTY and types prompts
into it, because that CLI exposes no structured prompt channel. Every other
delivery surface hands the agent a framed message; passthrough hands it
keystrokes, so the prompt is only as reliable as the terminal path that carries
it. A host terminal delivers input to the reader in fixed-size reads, and a TUI
that cannot absorb a burst that large drops whole reads without reporting an
error. The operator sees a complete message in Kandev, the agent answers a
truncated one, and nothing in either surface marks the difference.
The CLI system owns this contract because it owns how Kandev drives a vendor CLI
through a PTY and the compatibility behavior that path needs. What is delivered
and when it is delivered stay with their own owners: the tasks system owns the
message API and its delivery modes, and the platform system owns delivery into a
generating turn.
## Terminology
- **CLI passthrough session:** An agent session whose profile sets
`cli_passthrough`, where Kandev runs the vendor CLI as a PTY child and mirrors
that terminal to the operator.
- **Prompt body:** The prompt text Kandev delivers, excluding the submit
keystroke.
- **Submit keystroke:** The byte sequence that makes the receiving TUI start a
turn for the delivered body.
- **Bracketed paste:** The terminal convention in which `ESC[200~` and `ESC[201~`
delimit pasted text so a TUI treats the whole burst as one paste rather than a
stream of keystrokes.
- **Single-read capacity:** The number of bytes a host terminal hands its reader
per read. It is a host constant, not a Kandev setting, and is smaller than a
typical prompt.
## Requirements
### REQ-CLI-PASSTHROUGH-PROMPT-DELIVERY-001: Passthrough Prompt Delivery Integrity
**Intent:** A prompt that Kandev accepts for a passthrough session must reach
the agent as the same text the operator or calling agent sent. Silent partial
delivery is worse than a failed delivery: the agent acts on a fragment, the
answer looks coherent, and the loss is invisible in every Kandev surface.
**User story:** As an operator running an agent in CLI passthrough mode, I want
every prompt to arrive whole, so that the agent answers what was actually sent.
#### Acceptance criteria
- **AC-CLI-PASSTHROUGH-PROMPT-DELIVERY-001.1:** When Kandev delivers a prompt to
a CLI passthrough session, the receiving agent shall receive the prompt body
in full and unmodified, at any body length.
- **AC-CLI-PASSTHROUGH-PROMPT-DELIVERY-001.2:** When a prompt body exceeds the
host's single-read capacity, delivery shall preserve body order and shall
present the body to the receiving TUI as one continuous input rather than as
unrelated input events it has to rejoin.
- **AC-CLI-PASSTHROUGH-PROMPT-DELIVERY-001.3:** When the prompt body has been
delivered, the submit keystroke shall arrive as a discrete input event, so the
receiving TUI starts exactly one turn for that body.
- **AC-CLI-PASSTHROUGH-PROMPT-DELIVERY-001.4:** When the receiving agent is
configured as unable to accept bracketed-paste framing, delivery shall keep
AC-CLI-PASSTHROUGH-PROMPT-DELIVERY-001.1 true by pacing the body within the
host's single-read capacity instead of writing one oversized burst.
- **AC-CLI-PASSTHROUGH-PROMPT-DELIVERY-001.5:** When the prompt body contains
byte sequences that would end the delivery framing early, delivery shall
neutralize those sequences so no part of the body reaches the TUI as terminal
input outside the prompt.
- **AC-CLI-PASSTHROUGH-PROMPT-DELIVERY-001.6:** When a prompt originates from the
initial task description, an operator composer message, a workflow step, or an
agent-to-agent message, delivery integrity shall be identical, because all
passthrough prompt sources share one delivery contract.
- **AC-CLI-PASSTHROUGH-PROMPT-DELIVERY-001.7:** When delivery of a body cannot
complete, Kandev shall report the failure to the caller rather than leaving a
partially typed prompt as a successful delivery.
## Out of scope
- Prompt transport for non-passthrough sessions, which uses the structured agent
protocol and not a terminal.
- Operator keystrokes typed directly into the mirrored terminal view, which the
terminal input path owns.
- Which prompt is delivered, its wrapping, its queue position, and its delivery
mode, owned by the tasks system.
- Delivery into an already generating turn, owned by the platform system's
mid-turn steering contract.
- How the receiving agent interprets or answers the prompt once it has it.
- Message storage and rendering in Kandev surfaces, owned by the tasks and UI
systems.
@@ -0,0 +1,120 @@
---
status: draft
system: cli
requirements:
- REQ-CLI-PASSTHROUGH-PROMPT-DELIVERY-001
---
# Passthrough Prompt Delivery System Design
## Purpose and boundaries
This design owns how a prompt that is already selected for a CLI passthrough
session becomes bytes on that session's PTY. It owns the framing of the body,
the size and pacing of the writes, and the separation of the submit keystroke.
It does not own the prompt text, its system wrapper, its queue position, or its
delivery mode; those arrive from the tasks system. It does not own PTY process
startup, terminal mirroring, or operator keystrokes, which belong to the
interactive process runner. It consumes the per-agent passthrough capability
record but does not own the agent catalog.
## Requirement mapping
| Requirement | Design section |
| --- | --- |
| `REQ-CLI-PASSTHROUGH-PROMPT-DELIVERY-001` | [Components and responsibilities](#components-and-responsibilities), [Data and contracts](#data-and-contracts), [Control flow](#control-flow), [Failure and recovery](#failure-and-recovery), [Security](#security) |
## Components and responsibilities
- **Passthrough capability record.** Per-agent configuration that states the
submit sequence, the submit delay, and whether the agent accepts
bracketed-paste framing. It is the only input that varies delivery behavior
between agents; delivery code never branches on an agent identity.
- **Stdin chunk planner.** The single function that converts a prompt and a
capability record into an ordered list of PTY writes. It is pure: no PTY, no
clock, no I/O, so every framing and sizing rule is unit-testable. It is the
only place that knows about framing markers and the host read budget.
- **Delivery callers.** The initial-prompt injector, the workflow step prompt
handler, and the session prompt path each obtain the planned chunks and write
them in order, honoring each chunk's pre-write delay. They add no framing of
their own.
- **PTY writer.** The lifecycle manager's passthrough stdin call and the
interactive runner behind it write one chunk per call to the PTY master and
surface write errors to the caller.
## Data and contracts
- **Planned chunk.** An ordered pair of the bytes to write and the delay to
apply before writing them. The first chunk carries no delay. This type already
exists and is unchanged.
- **Framing markers.** The bracketed-paste start and end sequences. They are
applied to the body only, never to the submit keystroke.
- **Raw-safe write size.** A byte budget for one write on the unframed path,
chosen below the smallest host single-read capacity Kandev supports, so a
receiving TUI never has to absorb more than one read worth of unframed input
at a time. Splits fall on rune boundaries so no write ends mid-character.
- **Inter-chunk delay.** The pause applied before each continuation write on the
unframed path, sized so the receiving TUI drains one read before the next
arrives.
- **Submit delay.** The existing per-agent pause before the submit keystroke,
which keeps that keystroke a discrete input event rather than trailing bytes
of the body.
## Control flow
1. A caller resolves the session's capability record and asks the planner for
the chunks of a prompt.
2. If framing is selected, the planner neutralizes framing terminators inside the body.
3. When the agent accepts bracketed-paste framing and the body is multi-line or
larger than the raw-safe write size, the planner emits the framed body as one
chunk. Small single-line bodies stay unframed, preserving today's bytes.
4. When the agent does not accept bracketed-paste framing, the planner emits the
body as consecutive chunks no larger than the raw-safe write size, each
continuation chunk carrying the inter-chunk delay.
5. When `SubmitDelay > 0`, the planner appends the submit keystroke as its own
chunk carrying the submit delay. When `SubmitDelay == 0`, it appends the
submit sequence to the final body chunk.
6. The caller writes each chunk in order, waiting for the chunk's delay with the
caller context first.
Ordering is total: the PTY is a single stream, and the caller writes
sequentially, so the body writes and submit keystroke are sent in order. The
unframed pacing reduces burst loss but cannot confirm what the TUI consumed.
## Failure and recovery
A failed write aborts the remaining chunks for that prompt. The session prompt
path returns the failure to its caller, which reverts session state and surfaces
the error, so a half-typed prompt is never reported as delivered. The initial
prompt injector logs the failure and stops rather than retrying, because a retry
would duplicate the already-written prefix in the agent's input.
Delivery does not read the terminal back, so it cannot confirm what the TUI
absorbed. Correctness therefore rests on framing and sizing rather than on
acknowledgement. The 30 ms unframed interval is an empirical pacing policy, not
a universal lossless-delivery guarantee. Any change to these rules needs
evidence from a real TUI.
## Security
Prompt bodies are untrusted for terminal framing. A body that contains a framing
terminator would end the paste early and let the remainder reach the TUI as
terminal input outside the prompt, which in a CLI agent means unintended
commands. The planner therefore neutralizes framing terminators in the body
before wrapping it. It performs no other rewriting: the body is agent-visible
content and must otherwise arrive byte-for-byte.
## Observability
Delivery callers log the byte length of what they write: the initial prompt
injector logs the injected description length, and the session prompt path logs
the prompt length at debug level. The planner itself logs nothing, because it is
pure.
The authoritative delivery check is comparison, not logging: the stored message
text against the receiving agent's own transcript of what it was sent. That
comparison is what exposed the original defect, a loss of exactly N x 1022
bytes from the start of every long body. Per-delivery logging of chunk count and
framing is not implemented; it would let an operator distinguish a mis-planned
delivery from one the TUI mishandled without a transcript.