{
  "uid": "cogitave.learn.understand-massar.how-a-session-works",
  "kind": "moduleUnit",
  "href": "/modules/understand-massar/how-a-session-works/",
  "title": "How a session works: agent, broker, and the PTY bridge",
  "summary": "",
  "type": null,
  "products": [],
  "roles": [],
  "levels": [],
  "subjects": [],
  "headings": [
    "The three parties",
    "The wire protocol",
    "A real shell on a real PTY",
    "One session at a time, and nothing left running",
    "Try it without Diyar"
  ],
  "source": "A Massar session involves three parties and one simple rule for what crosses the wire: text frames carry control messages, binary frames carry terminal bytes. This unit walks through both.\n\n## The three parties\n\n| Party | Role |\n| --- | --- |\n| **massar-agent** (on the device) | Dials out to the broker, owns the PTY, interprets control frames |\n| **Diyar console broker** (in the cloud) | A dumb frame pipe between the agent and the operator - it does not interpret terminal traffic |\n| **Operator's browser terminal** | An xterm.js web terminal, connected to the broker's operator-facing endpoint |\n\nThe agent connects to the broker's agent endpoint (`/api/v1/console/agent`); the operator's browser connects to the broker's per-device endpoint (`/api/v1/devices/{id}/console`). The broker relays frames between the two sides of a device's session - the agent's own source describes the broker plainly as \"a dumb frame pipe; the agent interprets control and owns the PTY.\"\n\n## The wire protocol\n\nThe same rule applies on both hops through the broker (agent-to-broker and browser-to-broker):\n\n- **WS Text frames carry control messages**, as JSON: `open`, `resize`, `close`, and `exit`.\n- **WS Binary frames carry raw terminal bytes**: operator-to-agent is what the shell receives as stdin; agent-to-operator is what the shell writes to stdout.\n\n```rust\n// Control frame shape the agent deserializes:\nstruct Control {\n    t: String,          // \"open\" | \"resize\" | \"close\"\n    cols: Option<u16>,\n    rows: Option<u16>,\n}\n```\n\nWhen the broker relays an `open` control frame, the agent creates a fresh PTY sized to the given `cols`/`rows` and spawns a shell on it. A `resize` frame adjusts the PTY size for a terminal window that changed shape. A `close` frame - sent by the operator - kills the shell. If the shell exits on its own (the user typed `exit`, or it crashed), the agent sends a `{\"t\":\"exit\"}` control frame back so the operator's terminal knows the session ended without waiting on an operator-initiated close.\n\n## A real shell on a real PTY\n\nMassar does not run a restricted command interpreter - it opens an actual pseudo-terminal and spawns the operator's shell on it:\n\n- **Linux:** `openpty`, spawning `$SHELL` (falling back to `/bin/bash`).\n- **Windows:** ConPTY, spawning `%COMSPEC%` (falling back to `powershell.exe`).\n\nBoth are wired the same way once the PTY is open: a background thread does blocking reads from the PTY's output and forwards each chunk to the broker as a Binary frame; a second thread does blocking writes of whatever Binary frames arrive from the operator, feeding them to the PTY's stdin. A `resize` control frame is the only thing that changes the PTY's dimensions mid-session - the read and write threads never see it.\n\n> [!NOTE]\n> On Windows, ConPTY performs a cursor-position handshake (`ESC[6n`) that the terminal client must answer. The xterm.js web terminal does this natively, and Cogitave's own dev test client (`massar-op-test`) was updated to answer it too, so the automated cross-platform relay test passes.\n\n## One session at a time, and nothing left running\n\nThe agent tracks at most one open session per connection: a PTY handle, the channel feeding its stdin, and the child shell process. When the connection to the broker drops for any reason - the broker closes it, the network fails, the operator closes it - the agent kills the shell rather than leaving it running. There is no mechanism for a session to persist independent of its WebSocket connection.\n\n## Try it without Diyar\n\nThe repository ships a local, dev-only harness that proves this whole path end-to-end without a real Diyar deployment: a local broker, an agent, and a test operator client.\n\n```bash\ncargo build --features dev-tools\nmassar-broker-dev &                          # listens 127.0.0.1:8088\nMASSAR_BROKER_URL=ws://127.0.0.1:8088/agent MASSAR_DEVICE_ID=dev MASSAR_AGENT_TOKEN=dev massar-agent &\nMASSAR_DEVICE_ID=dev massar-op-test          # PASS = sentinel round-tripped through the PTY\n```\n\n> [!IMPORTANT]\n> `massar-broker-dev` and `massar-op-test` are dev-only binaries gated behind the `dev-tools` feature - they exist to prove the relay locally, not to stand in for Diyar. All end-to-end proof described in this module is from this local harness; there is no production deployment behind it yet.\n\nUnit 4 covers who is allowed to send that `open` frame in the first place, and what is recorded when they do.\n",
  "partOf": "cogitave.learn.understand-massar",
  "durationInMinutes": 7,
  "quiz": null
}