Terminal Entity

Terminal is the first-class persisted Entity for one PTY-backed terminal transport. It owns Terminal identity, owner linkage, launch metadata, live PTY snapshot hydration, input and resize delivery by Terminal id, and hydration of ordered raw recording entries used to replay a terminated terminal.

Terminal is owner-agnostic. System, Repository, Mission, Task, Artifact, or AgentExecution may request or reference a Terminal, but Terminal behavior stays Terminal-owned. A Terminal may have an optional owner-provided name; Terminal identity is always id.

Sources

Responsibilities

Area Terminal owns
Identity Canonical terminal:<id> Entity id created by the Terminal class.
Ownership Generic owner reference naming the Entity that requested or owns this Terminal transport.
Launch metadata Working directory, optional owner-provided name, command, args, and creation time.
Persistence Narrow terminal storage row containing identity, owner, and launch metadata.
Live PTY state Hydrated state from TerminalRegistry: pane id, connected/dead state, exit code, dimensions, screen, latest chunk, and truncation.
Input and resize Delivery of raw PTY input or resize data to the active terminal by canonical Terminal id.
Recording hydration Ordered TerminalRecordingEntry Entity rows hydrated onto TerminalSchema.recordingEntries.
Entity events Contract-declared data.changed and recordingEntry.appended events published through the generic Entity event capability.

Non-Responsibilities

Neighbor Boundary
AgentExecution Owns execution process lifecycle, structured messages, lifecycle, activity, and semantic journal. It may link to a Terminal id, but does not own Terminal persistence or recording.
TerminalRecordingEntry Owns append-only raw PTY rows, sequence, full-text data indexing, and replay entry payloads.
TerminalRegistry Owns live in-memory PTY handles, screen buffers, input delivery, resize, and exit observation. It does not own persistence.
WebSocket terminal transport Carries interactive terminal input/output transport only. It does not define or distribute general Entity events.
Mission/Repository/System/Task/Artifact May create and address Terminals as owning Entities, but do not define Terminal identity or recording shape.

Contract Methods

Method Kind Argument schema Result schema Behavior Likely callers Side effects
create mutation TerminalInputSchema TerminalSchema Creates a persisted Terminal row, opens a PTY in TerminalRegistry, appends the header recording entry, subscribes to registry updates, publishes Terminal events, and returns hydrated Terminal data. System, Repository, Mission, Task, Artifact, AgentExecution, daemon entity dispatcher. Persists terminal; appends terminal_recording_entry header; starts live PTY transport; publishes data.changed and recordingEntry.appended.
read query EntityIdSchema TerminalSchema Resolves persisted Terminal storage, hydrates ordered recording entries, and overlays live registry state when active. Owner Entities, operator panes, replay readers, tests. None; it may return a disconnected snapshot if the Terminal is no longer live.
sendInput mutation TerminalSendInputSchema TerminalSchema Sends keyboard input or resize data to the live PTY addressed by Terminal id, then returns the hydrated Terminal read. Owner Entities and Terminal-addressed command paths. TerminalRegistry emits recording updates; Terminal recording subscriber appends TerminalRecordingEntry rows and publishes Entity events.

Events

Terminal declares two public Entity events in TerminalContract.events. Both are published through publishEntityEvent, validated against the contract by the daemon event publisher, addressed as terminal:<id>.<eventName>, and distributed to browser surfaces through SSE subscriptions. Terminal WebSocket transport remains separate: WebSocket carries interactive PTY bytes and resize traffic, not the contract-declared Entity event family.

Event Payload schema Publisher trigger Subscribers/surfaces Meaning
data.changed TerminalSchema Terminal.publishDataChanged after create, registry terminal updates, registry recording updates that can be rehydrated, and live read overlays. Daemon IPC Entity event subscribers and browser SSE subscribers using channel patterns such as terminal:<id>.data.changed or terminal:*.*. The public hydrated Terminal Entity data changed for one Terminal.
recordingEntry.appended TerminalRecordingEntrySchema Terminal.publishRecordingEntry after the initial header entry and after TerminalRegistry input, output, resize, or exit recording callbacks are persisted through TerminalRecordingEntry.append. Daemon IPC Entity event subscribers, browser SSE subscribers, replay panes, and audit views using channel patterns such as terminal:<id>.recordingEntry.appended or terminal:*.*. A raw Terminal recording fact was appended for one Terminal.

data.changed

data.changed carries TerminalSchema, the same hydrated Entity shape returned by read. Its payload includes the persisted Terminal identity, owner reference, working directory, optional launch command and args, ordered recordingEntries, and live TerminalRegistry overlay fields such as connected, dead, exitCode, dimensions, screen, latest chunk, and truncated.

This event reflects the current public Terminal Entity view. Persisted identity, owner, launch metadata, and hydrated recording rows come from storage; live screen, connection, exit, dimension, and chunk fields come from TerminalRegistry. The event is a data-change notification, not a terminal byte stream and not an AgentExecution lifecycle event.

The code declares no replay, idempotency, or exactly-once guarantee for this event. Consumers should treat each envelope as a validated latest Terminal view for the addressed Entity channel.

recordingEntry.appended

recordingEntry.appended carries TerminalRecordingEntrySchema, a first-class child Entity payload for one persisted raw PTY recording row. Important payload fields are terminalId, which links the entry to the owning Terminal; sequence, which orders entries within that Terminal; type, which distinguishes header, input, output, resize, and exit; occurredAt; and type-specific evidence fields such as data, literal, cols, rows, or exitCode.

This event reflects an append to Terminal-owned raw transport evidence. The recording row is persisted by TerminalRecordingEntry.append before publication. It is separate from TerminalRecordingEntryContract.events['data.changed'], which is the child Entity’s own data-change event; Terminal publishes recordingEntry.appended so subscribers watching the parent Terminal channel can update replay or audit views without separately discovering every child Entity id.

Ordering is represented by the payload sequence field and the storage index on (terminalId, sequence). The code does not declare a delivery-level replay or exactly-once guarantee for the SSE/IPC event envelope; consumers that need authoritative replay should read Terminal recording entries ordered by sequence.

Property Groups

Group Property Schema role
Identity id Canonical Terminal Entity id generated by the Terminal class and stored on TerminalStorageSchema.
Display intent name Optional owner-provided label; never identity.
Ownership owner Generic owner reference with entityName, entityId, and optional attributes.
Launch workingDirectory, command, args, createdAt Persisted launch metadata used to describe how the PTY was opened.
Recording recordingEntries Hydrated array of TerminalRecordingEntry Entities; registered as a non-storage zod-surreal reference to terminal_recording_entry.
Live PTY connected, dead, exitCode, cols, rows, screen, chunk, truncated Runtime overlay from TerminalRegistry, not Terminal storage.

Schema Map

Schema Purpose
TerminalSendInputDataSchema Raw keyboard or resize input data without id.
TerminalSendInputSchema Named method argument for sendInput; combines Terminal id with keyboard or resize data.
TerminalOwnerReferenceSchema Owner reference for any Entity that owns or requested the Terminal.
TerminalLaunchOptionsSchema Optional launch name, command, and args.
TerminalInputSchema Class mutation argument for creating a new Terminal. The class creates the id.
TerminalStorageSchema Persisted terminal table record. Does not store live screen state or recording rows.
TerminalSchema Hydrated Entity data: storage fields, live PTY state, and hydrated recordingEntries.
TerminalRecordingEntrySchema Event payload and child Entity data for one persisted raw PTY recording row carried by recordingEntry.appended.

ERD

erDiagram
  ENTITY ||--|| TERMINAL : specializes
  TERMINAL ||--o{ TERMINAL_RECORDING_ENTRY : owns-recording-events

  TERMINAL {
    string id PK
    string name
    object owner
    string workingDirectory
    string command
    array args
    string createdAt
  }
  TERMINAL_RECORDING_ENTRY {
    string id PK
    string terminalId FK
    int sequence
    string type
    string occurredAt
    string data
  }

The terminal row stays narrow. Terminal recording rows are separate first-class Entities so raw PTY data can be appended, searched, and replayed without rewriting the Terminal record.

Recording Flow

flowchart TD
  A[Terminal.create] --> B[Persist terminal]
  B --> C[Open PTY in TerminalRegistry]
  C --> D[Append header TerminalRecordingEntry]
  D --> E[Publish recordingEntry.appended and data.changed]
  C --> F[Subscribe to registry updates]
  F --> G[input / output / resize / exit]
  G --> H[Append TerminalRecordingEntry]
  H --> I[Publish recordingEntry.appended and data.changed]
  J[Terminal.read] --> K[Read terminal]
  K --> L[Find events by terminalId ordered by sequence]
  L --> M[Return hydrated TerminalSchema]

Input Flow

flowchart TD
  A[Caller] --> B[Terminal.sendInput]
  B --> C[Parse TerminalSendInputSchema]
  C --> D[Resolve Terminal by id]
  D --> E{Input kind}
  E -->|data| F[TerminalRegistry.sendKeys by Terminal id]
  E -->|cols and rows| G[TerminalRegistry.resize by Terminal id]
  F --> H[Registry recording update]
  G --> H
  H --> I[TerminalRecordingEntry.append]
  I --> J[Publish Terminal recordingEntry.appended]
  J --> K[Publish Terminal data.changed]
  K --> L[Terminal.read returns hydrated TerminalSchema]

Event Publication Flow

flowchart TD
  A[Terminal.create or TerminalRegistry callback] --> B{Terminal boundary fact}
  B -->|Hydrated Terminal changed| C[Terminal.publishDataChanged]
  B -->|Recording row appended| D[TerminalRecordingEntry.append]
  D --> E[Terminal.publishRecordingEntry]
  C --> F[publishEntityEvent]
  E --> F
  F --> G[Daemon event publisher resolves TerminalContract]
  G --> H[Validate event name and payload schema]
  H --> I[Create EntityEventEnvelope]
  I --> J[Daemon IPC subscribers]
  I --> K[Browser SSE subscribers]

The publish path is contract-controlled. Terminal chooses only the event name and payload; the daemon validates the event against TerminalContract.events and derives the public channel grammar before broadcast.

Cross-Control Checklist

Boundary Status
Class Terminal.ts owns creation, reading, input delivery, recording persistence wiring, and hydration.
Schema TerminalSchema.ts owns storage, method argument schemas, and hydrated recording references.
Contract TerminalContract.ts declares create, read, sendInput, data.changed, and recordingEntry.appended.
Storage terminal stores identity, owner, and launch metadata; terminal_recording_entry stores replay rows.
Reference metadata TerminalSchema.recordingEntries is a hydrated non-storage reference to terminal_recording_entry; TerminalRecordingEntry.terminalId references terminal.
Event publication Terminal event publication is implemented through the generic Entity event capability; daemon IPC validates against the contract before broadcasting; browser clients receive matching channels through SSE.
Schema metadata finding Storage fields carry zod-surreal descriptions, but some non-storage Terminal schemas and live-state fields still need complete Zod .meta({ description }) coverage in a dedicated schema documentation pass.