VIEWPORT
Concepts

Sessions

One agent run on one machine. Runtime-only, owned by the daemon.

A session is one agent run. The daemon owns it. There is no server-side database where transcripts could leak from, because there is no server-side database for sessions.

When you load Sessions in the app, the page subscribes to every paired daemon over WebSocket and shows you the rows live. Take the daemons offline and the list goes empty. That's expected behavior, not a bug.

What's on the wire

The protocol between daemon and client lives in the open-source daemon at packages/daemon/src/server/ws-protocol.ts. Three frame types announce a session:

// daemon → client when an agent starts
{ "type": "session-started", "sessionId": "ses_…", "directoryId": "dir_…" }

// daemon → client on every update
{
  "type": "session-update",
  "sessionId": "ses_…",
  "seq": 142,
  "update": {
    "updateType": "tool-call",  // or agent-message, step-committed, …
    /* per-updateType payload */
  }
}

// daemon → client when the agent exits
{ "type": "session-ended", "sessionId": "ses_…", "reason": "completed" }

update.updateType values currently emitted:

agent-message, agent-message-chunk, agent-thought-chunk, user-message, tool-call, tool-call-update, permission-request, permission-resolved, state-change, step-committed, token-usage, system-status, attention, streaming-state.

What's not on the wire

There is no flat session.start / session.tool_call / session.end event stream. There is no server-side persistence of session content. Tool-call payloads, prompts, and agent transcripts only leave the machine on demand; the browser explicitly subscribes (subscribe) and the daemon streams from its local store under ~/.viewport/.

Privacy load-bearing

This is the load-bearing piece of the privacy claim. The daemon is open source so you can read every line that handles session content; the platform has no table to leak from.

Lifecycle

StateWhen
activeAgent is producing work. Update frames are flowing.
awaitingAgent is paused on a permission request or approval gate.
idleAgent is alive with nothing to do.
endedAgent exited. Updates stop.

If the daemon crashes or the machine goes offline, the session enters a disconnected substate. The platform shows its last-known status; it does not reconstruct the session server-side.

Starting a session

Two ways:

Programmatic (over the local WS):

// client → daemon
{
  "type": "launch",
  "directoryId": "dir_…",
  "prompt": "fix the failing auth tests",
  "model": "claude-3-5-sonnet",   // optional
  "configOverrides": {            // optional
    "agent": "claude-code",
    "costCapUsd": 5
  },
  "requestId": "req_…"
}

That's what vpd run sends.

Out-of-band: start claude or codex directly in a registered directory. The daemon picks up the session via its hook bridge and announces it to subscribers.

Resolution: what context the session sees

At session start, the daemon resolves the resource manifest for the working directory. Resolution is deterministic and driven by the repo-local .viewport/config.yaml (validated against the schema in the daemon repo). The resolved manifest names:

  • contexts to attach
  • workflows applicable in this scope
  • plans templates available
  • agentProfiles that apply

Inspect the resolution from the CLI:

vpd config resolve
vpd session manifest --session SID

Multi-repo sessions

If the agent runs above two repos (a meta-monorepo, a worktree), the daemon resolves both configs and merges them. Conflicts surface as conflicts in the resolved manifest. Nothing is silently chosen.

What's not a session

  • A workflow run is a coordinated set of one or more sessions and is persisted server-side. See Workflows.
  • A pairing is the trust relationship between a machine and a workspace. Sessions don't exist without one.
  • A plan is an artifact a session might produce. See Plans.

On this page