Files
alktty/docs/architecture/README.md
T
glm-5.3-flash bc4a9b6024 docs: align remaining alloc-failure references with ADR-010 §2A (prepublish review for v0.3.0)
- README.md ADR index: ADR-010 status notes the §2A amendment
- ADR-009: amendment note records the §2A supersession (in-band path
  shrinks to nothing from registered producers)
- ADR-010 Consequences: dial_failed replaces the stale in-band
  allocate_failed retry-policy bullet
- AllocFailed doc comment + tty-backend.md: describe both failure
  surfaces (direct-path frame / channels-path dial_failed)
2026-09-07 10:04:44 +00:00

157 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# alktty — Architecture
Terminal session protocol for the `alk/tty` ALPN: a
producer/consumer protocol crate on top of alkcall channels. This
directory holds the architecture spec docs, the BAST (Binary Abstract
Syntax Tree) document for the wire format, and the ADRs.
## Documents
| Document | Status | Description |
|----------|--------|-------------|
| [overview.md](overview.md) | draft | Crate purpose, the two-carriage model in brief, dependencies, ALPN, backend location map, feature gates |
| [tty-wire.md](tty-wire.md) | draft | The wire format: negotiation frame (JSON carriage), raw chunk codec (`[stream_type: u8][length: u32 be][payload]`), control channel split into `STREAM_CTRL_IN` / `STREAM_CTRL_OUT` halves, sentinels |
| [tty-bast.md](tty-bast.md) | draft | The BAST (Binary Abstract Syntax Tree) document for the binary framing layer of the `alk/tty` wire format (5-byte chunk header + negotiation length prefix); a normative JSON spec conforming to the BAST meta-schema at `https://alk.dev/bast/v1/schema`. JSON payloads are specified in `tty-wire.md` and the Rust source, not in the BAST |
| [tty-backend.md](tty-backend.md) | draft | `TtyBackend` trait, `TtyParams`, `TtyHandle`, `TtyControl` — the inversion point between the wire-format adapter and the backends. Carries REQ-TTY-01 (backends need not be natively async) |
| [tty-adapter.md](tty-adapter.md) | draft | `TtyAdapter` (`ProtocolHandler` on `alk/tty`): session lifecycle, three-pump bidirectional driver, negotiation errors, exit-chunk ordering (ADR-004), access control, session-cancel cleanup (ADR-005) |
| [tty-local.md](tty-local.md) | draft | `LocalTtyBackend` (the `local` feature module): `portable_pty` (PTY) and `tokio::process::Command` (pipe/runner). Carries REQ-TTY-02 (signal forwarding to the process group) |
## Applicable ADRs
Ported from the alknet mono-repo and renumbered into alktty's ADR
range (001..009; ADR-009 and ADR-010 are alktty-native). The alknet
originals at
`/workspace/@alkdev/alknet/docs/architecture/decisions/` remain the
authoritative source for any ADR not yet ported, and for the alknet
ADRs referenced by alknet number in the docs below (which are not
tty-specific and therefore not ported into alktty's ADR range).
| ADR | Title | Origin | Status |
|-----|-------|--------|--------|
| [001](decisions/001-wire-format-and-two-carriage.md) | alktty Wire Format and Two-Carriage Model | alknet ADR-052 | Accepted (amended 2026-07-18 — Phase 7 control-channel split) |
| [002](decisions/002-ttybackend-trait-and-ttyhandle.md) | TtyBackend Trait and TtyHandle — the Backend Inversion Point | alknet ADR-053 | Accepted |
| [003](decisions/003-local-backend-placement.md) | Local TTY Backend Placement (Single Crate with `local` Feature) | alknet ADR-054 | Accepted (records both the alknet sibling-crate decision and the alktty single-crate consolidation) |
| [004](decisions/004-exit-code-on-control-chunk.md) | Exit Code on a Control Chunk (the Last Chunk Before Stream Close) | alknet ADR-055 | Accepted |
| [005](decisions/005-backend-cleanup-on-session-cancel.md) | Backend Cleanup on Session Cancel (Drop of `exit_code` Kills) | alknet ADR-056 | Accepted |
| [006](decisions/006-negotiation-framing-self-contained.md) | Self-Contained Negotiation Framing (No alkcall-Internal-Wire-Types Dependency) | alknet ADR-057 | Accepted |
| [007](decisions/007-tty-inside-channels.md) | TTY Inside Channels — Sub-Streams, Not Wire Format | alknet ADR-077 | Accepted (**reversed by ADR-008** — kept for historical context) |
| [008](decisions/008-channels-pure-channel-multiplexing.md) | Channels Pure Channel Multiplexing (8-Byte Header, No `stream_type`) | alknet ADR-093 | Accepted (amends alknet ADR-071/074; reverses ADR-007) |
| [009](decisions/009-channels-open-op-is-the-negotiation.md) | The Channels Open Op's `input` Is the Negotiation | alktty-native | Accepted (resolves review #001 L1; amended by review #002 R4 — parse failure is a client-visible error frame; amended by ADR-010 — semantic failures move into the establisher) |
| [010](decisions/010-channels-establisher-migration.md) | Channels-Path Establishment Failures Move into an Establisher | alktty-native | Accepted (adopts alkcall 0.5.0 ADR-049; amends ADR-009's R4 amendment; amended 2026-09-07 §2A for alkcall 0.6's plan payload — allocation moves into the establisher, no in-band frame from a registered producer) |
## Key Design Principles
1. **A terminal session is a terminal concern, not an SSH or Docker
concern.** SSH and Docker are two backends that can allocate a PTY.
alktty owns the terminal session lifecycle; the backends
(`DockerTtyBackend`, `SshTtyBackend`, `LocalTtyBackend`) implement a
`TtyBackend` trait. This dissolves the PTY hedge in the alknet-ssh
research (DP-5): PTY is not an SSH feature delegated to a separate
crate, it's a tty feature that SSH happens to be able to provide. See
[overview.md](overview.md) and [ADR-002](decisions/002-ttybackend-trait-and-ttyhandle.md).
2. **Two-carriage model: JSON negotiation, then raw chunks.** The bidi
stream opens with a single length-prefixed JSON negotiation frame
(terminal params, backend selector, command), then switches to a raw
chunk format (`[stream_type: u8][length: u32 be][payload]`) for the
life of the session. The call protocol's JSON-RPC shape handles the
structured request; raw bytes handle the body, which is what a
terminal actually is. No per-chunk `EventEnvelope` overhead, no
base64. See [tty-wire.md](tty-wire.md) and
[ADR-001](decisions/001-wire-format-and-two-carriage.md).
3. **Fixed channel set, not extensible.** Five stream types (0=stdin,
1=stdout, 2=stderr, 3=ctrl-in, 4=ctrl-out), no negotiation. A 6th
channel type is a wire-format change (one-way door); the ALPN model
handles extensibility at the protocol level (a new ALPN is cheap, a
wire-format change is not). The impoverishment vs SSH channels is
the feature: alktty multiplexes *one* service (a terminal session)
with a fixed channel structure, not *arbitrary* services. See
[tty-wire.md](tty-wire.md).
4. **The backend trait is the inversion point.** alktty defines
`TtyBackend`; the backend crates implement it. alktty depends on
alkcall; backends depend on alktty for the trait; alktty does not
depend on any backend. This preserves alknet ADR-003's
no-handler-depends-on-another-handler rule. alktty does not depend
on alkcall's internal wire types either (the negotiation framing is
self-contained — [ADR-006](decisions/006-negotiation-framing-self-contained.md)).
See [tty-backend.md](tty-backend.md) and
[ADR-002](decisions/002-ttybackend-trait-and-ttyhandle.md).
5. **Backends need not be natively async (REQ-TTY-01).** The trait's
adapter-facing types (`AsyncWrite`, `Stream<Item = Bytes>`,
`BoxFuture`, `TtyControl`) are the adapter's contract. A backend may
expose blocking handles internally and bridge them via std threads +
tokio mpsc/oneshot (the pattern `portable_pty` requires, and the
local-PTY POC validated). The bridging pattern is a documented,
supported implementation strategy. See
[tty-backend.md](tty-backend.md) and [tty-local.md](tty-local.md).
6. **Exit code on a control chunk, last before stream close
([ADR-004](decisions/004-exit-code-on-control-chunk.md)).**
`{"type":"exit","code":N}` rides on `STREAM_CTRL_OUT` (stream_type 4)
and is the last chunk before the server closes the write half. This
gives coordinators deterministic completion notification — no
polling, no plugin state. The adapter owns the ordering; backends
resolve `exit_code` and the adapter awaits, sends the chunk, closes.
See [tty-adapter.md](tty-adapter.md).
7. **Drop of `exit_code` future kills the session target
([ADR-005](decisions/005-backend-cleanup-on-session-cancel.md)).**
On session cancel (connection drop, stream reset), the adapter drops
the `TtyHandle`, which drops the `exit_code` future without driving
it to completion. The backend's `exit_code` future's `Drop`-on-cancel
MUST kill the child/container/SSH process. This is a behavioral
contract on the `TtyBackend` trait — the adapter has no separate kill
method; the cleanup is wired into the `exit_code` future's `Drop` by
the backend. See [tty-adapter.md](tty-adapter.md) and
[tty-local.md](tty-local.md).
8. **The runner pattern is preserved, not specialized.** The local
backend in pipe mode (`terminal: None`) is a process-streaming
endpoint — the same shape as GitHub/Gitea Actions runners, just over
alk's transport instead of HTTP polling. alktty provides the
*mechanism* (framed byte stream + exit code); runner *policy* (job
management, log persistence, task graph) is a downstream crate's
job. See [tty-local.md](tty-local.md) and
[ADR-003](decisions/003-local-backend-placement.md).
9. **TTY always uses its 5-byte format, including inside channels
([ADR-008](decisions/008-channels-pure-channel-multiplexing.md)).**
The same `wire.rs` code runs in both direct `alk/tty` and channels
`alk/channels` modes; only the `BiStream` source differs. The
channels layer strips its 8-byte header and hands TTY the payload
transparently. This reverses the earlier two-mode design
([ADR-007](decisions/007-tty-inside-channels.md), kept for
historical context). See [tty-adapter.md](tty-adapter.md).
## Relevant Open Questions
| OQ | Title | Status | Relevance |
|----|-------|--------|-----------|
| OQ-43 | `TtyControl` trait object `Clone` constraint | resolved | `control: Option<TtyControlHandle>` via a `#[derive(Clone)]` newtype wrapping `Arc<dyn TtyControl + Send + Sync>`; the trait is NOT `Clone` (not object-safe) — the newtype carries `Clone`-ability |
| OQ-44 | Terminal modes (TTY modes) | deferred(scope) | `TerminalParams.modes` reserved; default terminal modes suffice for current scope; blocked on a concrete mode-control use case |
| OQ-45 | Flow control for high-throughput stdout | resolved | QUIC per-stream flow control is the backpressure mechanism (chain complete by construction); no application-level windowing. Reversal is an additive `ControlMessage` variant, not a wire-format change |
| OQ-46 | Runner API surface | deferred(scope) | The runner mechanism (pipe mode) is in alktty; runner policy (job management, log persistence, task graph) is a downstream crate, not in scope here |
| OQ-47 | Stdin closure canonical signal | resolved | Either a zero-length stdin chunk or a `{"type":"eof"}` control chunk; both are accepted; the spec recommends `eof` for explicitness |
## References
- `docs/plans/project-setup.md` — the current plan (phases 05). All
five phases are landed: Phase 4 landed this directory (architecture
docs + BAST schema + renumbered ADRs); Phase 5 landed the tests,
including the integration tests in `tests/` at the crate root.
- alknet originals of the ported ADRs (alknet ADR-052, 053, 054, 055,
056, 057, 077, 093) at
`/workspace/@alkdev/alknet/docs/architecture/decisions/` — the
authoritative source for any ADR not yet ported, and for the alknet
ADRs referenced by alknet number in the docs above (which are not
tty-specific and therefore not ported into alktty's ADR range).
- [alktype BAST format spec](https://alk.dev/bast/v1/schema) — the
normative format spec for BAST documents (the meta-schema
`tty-bast.md` conforms to); see also
`/workspace/@alkdev/alktype/docs/architecture/bast-format.md`.
- alkcall architecture README at
`/workspace/@alkdev/alkcall/docs/architecture/README.md` — the
producer/consumer protocol-crate pattern alktty follows.