Skip to content

Agent sessions you can watch and answer from the web

Opensyntaqx opened this #304

Rebuild how people see and drive agent sessions on the web. The goal is a first-class experience for knowing what every session is doing and for answering it while it runs. We adopt the best open-source building blocks rather than invent our own, and we may revisit that later.

Direction

The two screenshots below show the target feel:

  1. A "Needs attention" rail. Sessions that are waiting on a person sit at the top of the sidebar. Each opens a card with Review / Check / Dismiss, and you act from the card itself, not from a link that leads somewhere else.
  2. A session page that reads as an answer. It opens with a summary sentence, then a few key metrics, then Highlights with numbered citations pointing back into the transcript or evidence, then Deliverables (artifacts, pull requests, docs). The raw transcript is there but folded behind "Worked for 34s", not the main event.

Where we are today

  • Listing and reading (/:owner/-/work/sessions, /:id, the pull request Session tab, the work item panel):
    • The pages re-run their loaders when a WebSocket ping arrives from services/notify (useLive, debounced 1–5s).
    • The ping doesn't say which session changed, so any session activity in the repository reloads the page.
    • Nothing is streamed.
  • Web actions: you can only stop, end or rename a session (session.tsx:120-146).
  • No input after start:
    • Nothing sends a prompt, reply or approval to a running session.
    • Hosted sessions automatically decline tool approvals: "nobody was watching" (services/integrations/src/agent_sessions.rs:1348).
    • The only input ever sent is user.message at start (:213) and user.interrupt on stop.
  • Inbox and chat cards:
    • Inbox "Waiting on you" cards only link out (notify-store.ts:112).
    • Session chat cards already have a "Needs approval" chip (session-card.ts:9), but nothing behind it.
  • Transcript model: SessionEntry kinds are prompt | message | tool_call | tool_result | note | skill. There are no diffs, plans, permission requests or terminal output as structured data.

What to adopt (research, Oct 2026)

There is no single open-source project covering the whole stack for bring-your-own-agent sessions, so we put these layers together.

Event model: Agent Client Protocol (ACP). We use ACP's session-update shapes as our internal schema:

  • message and thought
  • tool_call/tool_call_update, each with kind, status, diff, terminal and locations
  • plan
  • session/request_permission with allow_once, allow_always, reject_once and reject_always

ACP adapters already exist for Claude Code, Codex, Goose, OpenCode and Gemini. Only stdio is normative; the HTTP/WS transport RFD is still a draft, so we carry ACP over our own socket. Links: https://agentclientprotocol.com/protocol/tool-calls · https://agentclientprotocol.com/rfds/streamable-http-websocket-transport

Two other options:

  • Rivet Sandbox Agent SDK (Apache-2.0): a daemon that normalizes Claude Code, Codex, OpenCode and Amp into one HTTP+SSE schema, with a Cloudflare target. A candidate ingest layer where we control the sandbox. https://github.com/rivet-dev/sandbox-agent
  • AG-UI (CopilotKit): only for interop. It has no diff, permission-option or terminal primitives.

Transcript and tool UI:

  • assistant-ui (MIT): headless React. ExternalStoreRuntime reads from any store, and tool parts have a requires_action status for approvals. https://www.assistant-ui.com
  • @pierre/diffs (Apache-2.0): Shiki-based and virtualized, split or stacked, multi-file, with annotations and accept/reject hooks. https://diffs.com
  • shadcn AI Elements patterns, copied in and restyled (Tool, Confirmation, Plan, Terminal, Subagent, Checkpoint).
    • We already depend on shadcn, streamdown and use-stick-to-bottom.
    • Don't depend on the elements.ai-sdk.dev registry: it is deprecated at the end of Nov 2026 and moving into shadcn/ui.

Live interaction: Cloudflare Agents SDK (agents, MIT).

  • One Durable Object per session; browsers subscribe with useAgent/AgentClient.
  • Prompts, cancels and permission replies flow back through the DO to the harness adapter (ACP, Codex app-server, or Managed Agents for hosted sessions).
  • This fits the DOs we already run in services/notify.
  • Use useAgentChat only for agents we host ourselves: it assumes the AI SDK message model.
  • Links: https://github.com/cloudflare/agents · https://github.com/cloudflare/agents-starter (reference only, Kumo design system).

Examples to learn from:

  • claude-code-viewer (start, resume and watch Claude Code from the browser)
  • agentsview (multi-harness, SSE)
  • the ACP web clients Codeg and Gonggong Space

Rejected as embeds (they are full apps): OpenHands, Goose desktop, LibreChat, Open WebUI. Langfuse and Phoenix suit after-the-fact tracing, not steering.

Rough slices

Each slice is its own pull request; this item tracks the whole.

  1. Event model. Extend SessionEntry/contracts to the ACP shapes: diff, terminal, plan, permission request and reply. Keep the old kinds readable.
  2. Live per-session channel. A session DO that streams entries to viewers (no more full-page revalidation), with change events that carry the session id.
  3. Session page rebuild. Summary, then highlights with citations, then deliverables, with the transcript folded underneath. Built on assistant-ui and @pierre/diffs.
  4. Two-way.
    • Send a message to a running session.
    • Answer permission requests from the web; stop auto-declining hosted approvals when someone can be told.
    • Cancel and steer.
  5. Needs attention. The sidebar rail and actionable inbox cards (Review / Check / Dismiss), on web and mobile (apps/mobile session screens).
  6. Harness adapters. Claude Code via its ACP adapter or plugin hooks, Codex, and hosted Managed Agents.

Billing: who pays for what

Since e6fa6230 (Oct 12), g1t sells no AI. Model tokens are always paid on the workspace's own provider key, and this work keeps it that way.

Model tokens: the customer's provider, never g1t.

  • Hosted sessions already run on the workspace's Anthropic connection (agent_sessions.rs:1-8). Codex goes on its OpenAI key the same way.
  • The Cloudflare Agents SDK is used only as transport: a Durable Object per session and the sockets to browsers. No Workers AI and no g1t-held model key. That means no AI credit to allot and no token meter to revive.
  • If we ever run a model on our own key, that is a separate decision. It would reintroduce metering through reserve/settle, and it is out of scope here.

Sandbox time we host: metered like runner minutes.

  • This covers a harness we run for the customer, for example Claude Code or Codex in a Sandbox SDK container (possibly via Rivet's daemon).
  • Each session reserves before it starts and settles via record_sandbox at the machine's rate: cost plus 20%, drawn from included usage, then the trial, then the OSS pool, then prepaid credit, then owed. It is bounded by the owner's spend limit and the trust ceiling.
  • A session the customer runs on their own machine (Claude Code locally, reported through the plugin) costs $0, just as self-hosted runners do.
  • Waiting costs money. A hosted sandbox paused on a permission request still burns minutes, so:
    • The "Needs attention" card shows what it is costing while it waits.
    • After an idle limit, the sandbox is snapshotted and stopped instead of left running. It resumes when someone answers.

Session Durable Objects and live sockets: platform spend, not metered per workspace.

  • This matches how services/notify is treated today.
  • Add them to platform.rs monitoring and margin.rs reconciliation. Only meter them if the margin shows they matter.

What the customer sees.

  • The session page separates the two kinds of cost:
    • "Billed by Anthropic/OpenAI to your account": the existing cost_usd estimate, or the provider cost report where an admin key is connected.
    • "Billed by g1t": sandbox minutes.
  • Neither is ever shown as the other.
  • Workspace usage lists hosted-session sandbox time on its own line, not under "Other usage".

Done when

  • Anyone in the workspace can open a running session and watch it stream live.
  • That person can reply to it and approve or deny its tool calls from the browser.
  • Every session waiting on a person shows up in "Needs attention", and it can be resolved there.
  • Model tokens never reach g1t's bill; hosted sandbox time reaches it exactly, within the spend limit; and the session page tells the two apart.

1.png 2.png

Work

It may also need

  • hello2 work items with like titles landed there.

On it

s

syntaqxClaude CodeReleased

since

s

syntaqxClaude CodeReleased

since

s

syntaqxClaude CodeReleased

since

Sessions

In Chat

Why the work is the way it is, and how sure that is

Activity

ssyntaqxvia Claude Codeclaimed #304·
syntaqxopened flagon-io/g1t#306 for this·
syntaqxclosed this by merging flagon-io/g1t#306·
ssyntaqxvia Claude Codeno longer holds #304: the work item closed·
syntaqxreopened this·
syntaqxcommented

Reopened: #306 shipped the first slice (the app bar in place of the rail, Needs attention in Work's sidebar, and session pages that read as answers). Still to come from the plan above: the ACP event model, a live per-session channel, answering sessions from the web (messages, approvals, cancel), Needs attention for sessions waiting on a person, and harness adapters.

syntaqxopened flagon-io/g1t#309 for this·
syntaqxclosed this by merging flagon-io/g1t#309·
syntaqxreopened this·
syntaqxopened flagon-io/g1t#316 for this·
syntaqxopened flagon-io/g1t#317 for this·
syntaqxclosed this by merging flagon-io/g1t#316·
syntaqxreopened this·
syntaqxopened flagon-io/g1t#321 for this·
syntaqxopened flagon-io/g1t#322 for this·
syntaqxclosed this by merging flagon-io/g1t#321·
syntaqxreopened this·
syntaqxclosed this by merging flagon-io/g1t#322·
syntaqxreopened this·
syntaqxopened flagon-io/g1t#323 for this·
syntaqxclosed this by merging flagon-io/g1t#323·
syntaqxreopened this·
syntaqxopened flagon-io/g1t#324 for this·
syntaqxclosed this by merging flagon-io/g1t#324·
syntaqxreopened this·
ssyntaqxvia Claude Codeclaimed #304·
syntaqxopened flagon-io/g1t#328 for this·
syntaqxopened flagon-io/g1t#329 for this·
syntaqxclosed this by merging flagon-io/g1t#329·
ssyntaqxvia Claude Codeno longer holds #304: the work item closed·
syntaqxreopened this·
ssyntaqxvia Claude Codeclaimed #304·
ssyntaqxvia Claude Codereleased #304: released·

Sign in to comment.