Files
alktty/docs/architecture/README.md
T
glm-5.2 b3f50d1836 phase 4: architecture docs + BAST schema + renumbered ADRs
Port the alknet-tty architecture docs into alktty and add the BAST
document for the alk/tty wire format. Docs-only; no Rust source
changes.

Spec docs (docs/architecture/, flat layout — single-crate repo):
- overview.md — crate purpose, two-carriage model, deps, ALPN,
  backend location map, feature gates
- tty-wire.md — 5-byte chunk codec, control channel split
  (STREAM_CTRL_IN=3 / STREAM_CTRL_OUT=4), sentinels
- tty-backend.md — TtyBackend trait, TtyHandle, TtyControl,
  REQ-TTY-01 (backends need not be natively async)
- tty-adapter.md — TtyAdapter, three-pump driver, exit-chunk
  ordering (ADR-004), cancel cleanup (ADR-005), access control
- tty-local.md — LocalTtyBackend (local feature module), PTY +
  pipe modes, REQ-TTY-02 (signal forwarding to process group)
- README.md — architecture index

ADRs (docs/architecture/decisions/, renumbered 001..008 from
alknet 052,053,054,055,056,057,077,093 in order):
- 001 wire format + two-carriage model (incl. Phase 7 control-
  channel split amendment)
- 002 TtyBackend trait + TtyHandle
- 003 local backend placement (records both the alknet sibling-
  crate decision and the alktty single-crate consolidation behind
  a local feature)
- 004 exit code on a control chunk
- 005 backend cleanup on session cancel
- 006 self-contained negotiation framing
- 007 tty inside channels (reversed by 008; kept for historical
  context with reversal notice)
- 008 channels pure channel multiplexing (reverses 007; TTY
  always uses its 5-byte format)

BAST document (docs/architecture/tty-bast.md):
- Normative JSON spec for the alk/tty wire format, conforming to
  the BAST meta-schema at https://alk.dev/bast/v1/schema
- 5-byte chunk header (struct, big-endian: stream_type uint8,
  length uint32) + StreamType enum (Stdin=0..CtrlOut=4)
- ControlMessage union (field-name discriminator on type:
  resize/signal/eof/exit) with documented deviation that on-wire
  control payloads are UTF-8 JSON, not BAST's binary union
  encoding
- NegotiationFrame (4-byte BE length + UTF-8 JSON body) +
  NegotiateRequest / TerminalParams JSON shapes
- StreamType enum deviation noted: on-wire uint8, not BAST's
  standard u32 enum index (chunk header is 5 bytes, not 8)
- alktty does not depend on alktype; the hand-rolled wire.rs is
  the runtime codec, the BAST is the human-readable contract

AGENTS.md: fixed the ADR mapping table to match the plan's 8-to-8
mapping (the previous table substituted ADR-050 for 054, relabeled
056 as control-message split, dropped 077, and added a new
control-split ADR at 006 — inconsistent with both the plan and the
prose). ADR-050 (dynamic resource ownership) is an alkcall/alknet-
core ADR, not tty-specific, and is not ported; the Phase 7 control
split stays as an amendment inside ADR-001, mirroring alknet.

Verification (all pass, no Rust source changed):
- cargo test (80 passed)
- cargo test --all-features (103 passed)
- cargo clippy --all-targets -- -D warnings (clean)
- cargo fmt --check (clean)
- cargo check --target wasm32-unknown-unknown (clean)
- cargo clippy --target wasm32-unknown-unknown -- -D warnings
  (clean)
- cargo doc --no-deps: 9 pre-existing intra-doc-link warnings in
  src/session.rs and src/channels.rs (untouched by this commit;
  not introduced here)
- BAST JSON parses; StreamType indices match wire.rs constants
  (0=Stdin..4=CtrlOut)
- all markdown cross-reference links resolve
2026-08-17 10:52:26 +00:00

11 KiB
Raw Blame History

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 draft Crate purpose, the two-carriage model in brief, dependencies, ALPN, backend location map, feature gates
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 draft The BAST (Binary Abstract Syntax Tree) document for the alk/tty wire format; a normative JSON spec conforming to the BAST meta-schema at https://alk.dev/bast/v1/schema
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 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 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..008). 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 alktty Wire Format and Two-Carriage Model alknet ADR-052 Accepted (amended 2026-07-18 — Phase 7 control-channel split)
002 TtyBackend Trait and TtyHandle — the Backend Inversion Point alknet ADR-053 Accepted
003 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 Exit Code on a Control Chunk (the Last Chunk Before Stream Close) alknet ADR-055 Accepted
005 Backend Cleanup on Session Cancel (Drop of exit_code Kills) alknet ADR-056 Accepted
006 Self-Contained Negotiation Framing (No alkcall-Internal-Wire-Types Dependency) alknet ADR-057 Accepted
007 TTY Inside Channels — Sub-Streams, Not Wire Format alknet ADR-077 Accepted (reversed by ADR-008 — kept for historical context)
008 Channels Pure Channel Multiplexing (8-Byte Header, No stream_type) alknet ADR-093 Accepted (amends alknet ADR-071/074; reverses ADR-007)

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 and ADR-002.

  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 and ADR-001.

  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.

  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). See tty-backend.md and ADR-002.

  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 and tty-local.md.

  6. Exit code on a control chunk, last before stream close (ADR-004). {"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.

  7. Drop of exit_code future kills the session target (ADR-005). 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 and 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 and ADR-003.

  9. TTY always uses its 5-byte format, including inside channels (ADR-008). 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, kept for historical context). See 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). Phases 03 are landed; Phase 4 (this directory: architecture docs + BAST schema + renumbered ADRs) is landed by this commit; Phase 5 (tests, including integration tests in tests/ at the crate root) is not yet done.
  • 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 — 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.