The owner web app

This page is for developers changing the owner-facing web app. It records the rules each feature follows, the endpoints behind it and what only a real browser can check. How to use the app is user documentation, which belongs to a distribution; FreeInference’s is the Cloud Agents guide.

Conversation view

The turn rail

The rail sits in the Chat column’s right gutter. It appears when the conversation has at least two visible turns, the column is at least 720px wide, the reading area is at least 200px high and the device reports a fine pointer; Trajectory has none. The current turn has the longest, darkest mark, and a hovered or focused mark widens slightly. Mark lengths show interaction state only, never message length, duration, tokens or progress. Marks keep their spacing in long conversations; the rail scrolls on its own, with faded ends, and scrolling it does not scroll the transcript.

A preview shows the question as sent, not the conversation title, and the attempt’s last visible reply once the turn is terminal, so a running answer does not change beneath the pointer. It excludes reasoning, tool arguments, tool output and runtime records, and caps the question at 50 characters and the reply at 120. Copied Fork history uses durable messages for both transcript and preview, even where those turns have no stored event log.

A jump places the turn’s first question about 24px below the top of the reading area, or a turn without a question at its first visible content. It changes neither the URL, the selected attempt nor the workspace state. A later jump, manual scrolling or leaving the conversation cancels an unfinished one; a target that cannot be found leaves the transcript in place and announces that the turn is unavailable. Hovering and jumping send no request and persist nothing. A historical turn link limits the rail to the turns that view rendered.

The rail is a named navigation region of real buttons with one mark in the tab order; arrow keys, Home and End reach the others. The current mark has aria-current, the preview is the mark’s description, and a requested jump is announced once, while streaming output announces nothing.

Reading position

The transcript either follows new output at its end or holds the reader’s text in place as output arrives. It opens following, and follows again after Back to bottom, scrolling to the live end, or acceptance of a follow-up the reader sent. Scrolling up, jumping through the rail or selecting an older attempt holds the position. While held, new content adds a dot to Back to bottom and updates its name, without pulling the reader back. Switching to Trajectory and back keeps the position; reloading opens at the end.

Tasks and subagents

The Tasks and subagents window shows the selected turn and attempt’s latest recorded task list and delegated agents. It appears only when one of them has meaningful recorded data. A task creation result links the assigned id to the task’s name, so later status updates change the same row; Status not recorded means the selected events hold no status for it.

A subagent card’s reply, under Returned to the main agent, comes from where each runtime records it:

  • Claude Code’s Agent or Task, and OpenCode’s and Kilo’s task, return the agent’s answer as the call’s own result; Claude records the agent’s status beside it.

  • Codex returns a finished agent’s answer, or its error, as the message of the agent state its wait call reports.

  • pi has no subagents.

A tool from an MCP server is never shown as a task list or a subagent, whatever its name; Codex, pi, OpenCode and Kilo sessions recorded before each call stored its MCP server can still show one as a subagent. A card says when a reply is still pending, was not recorded or was stored in part, and a background launch says it returned only a launch notice. Trajectory links a child step to its delegating step through runtime ownership records: Claude Code records the delegating call, and Codex the subagent’s own thread. Sessions recorded before those records keep a flat step list.

Workspace pane keyboard

Workspace, Git, file-list and terminal tabs select a view with Left/Right, Home and End while focus stays in the tab strip. Narrow Git panes use a changed-file selector so the diff keeps the width, and the diff and file preview are keyboard-scrollable regions. Opening a file in a narrow pane focuses its preview; Back or Escape returns to the list and focuses that file.

Checking it in a browser

Unit and DOM tests cover the navigation data, focus and an injected box model (useConversationScroll.dom.test.tsx). Markdown reflow, the sticky composer, resize timing and real pixel positions need a browser and a conversation with several turns:

  1. Click a middle mark. Its question lands about 24px below the reading area’s top and the mark becomes current.

  2. Scroll well into a long turn until its question is off-screen. That turn’s mark stays current.

  3. Scroll up while output streams. The text keeps its screen position within a few pixels and Back to bottom appears.

  4. Use Back to bottom, or scroll to the end. Following resumes; scroll up again and it stays paused.

  5. Try columns on both sides of 720px, reading areas on both sides of 200px, and touch input. Without the rail, the transcript still scrolls normally.

  6. Expand a tool row, resize the composer, and open and close the workspace while reading history. The text under the reader stays in place.

  7. With about 100 turns, check mark spacing, rail scrolling and keyboard access to every mark.

  8. Fork a conversation; the copied turns have marks and previews.

  9. Switch to Trajectory and back: no rail there, and Chat keeps its position.

  10. Repeat the keyboard checks in both languages, both themes and with reduced motion.

The design and its sizing measurements are in the turn navigation design. Sources: JobDetail.tsx, TurnNavigator.tsx, conversationTurnNavigation.ts and useConversationScroll.ts.

Tool approvals

The Approvals inbox lists pending MCP calls across the owner’s conversations, including ones a collapsed task list hides. Its badge counts the loaded calls, with a trailing + when more exist. Approve releases that one held request under its current authorization and attempt fence; Deny refuses it. The inbox never changes a conversation’s permission rules. The queue refreshes when the owner’s change stream reports a relevant change, with polling as a fallback. An expired call leaves the queue even before the worker records the expiry, and a decision that loses a race with expiry or another tab cannot revive it.

Always allow, in a conversation’s tools panel, records the approval and the future permission in one server-side transaction, so the owner can leave or refresh after submitting. The rule is saved once no call on that conversation’s connection is waiting for approval or dispatch, and covers only the named capability in that conversation. Before saving, the server checks the owner, connection and policy revisions and the capability fingerprint again: a reconnection, a disabled connection, a permission change or a changed capability definition discards the intent instead of restoring a revoked permission. The call history shows whether the save is pending, applied or discarded.

A held call waits up to ten minutes. Claude Code’s per-server HTTP request timeout and remote idle timeout are set to the same 26-minute window as its overall tool timeout; raising only the overall timer would leave the network timers able to abandon a request that Approvals still shows as pending. A client that accepts SSE gets an immediate response and keepalive comments during the wait, then the call’s single JSON-RPC result or error. Disconnecting cancels the wait and expires the approval; nothing keeps dispatching on behalf of an abandoned request. An expired approval was never dispatched, and a changed session authorization also stops a held call before dispatch. An error after dispatch can leave the outcome unknown.

GET /v1/agent/mcp/invocations?pending_only=true reads the authenticated owner’s pending inbox; job_id or thread_id narrows it. History reads need one of those scopes, so omitting both never exposes account-wide history. limit defaults to 50 and accepts 1 to 100. A response carries invocations, has_more and next_before_id; while has_more is true, pass next_before_id as before_id. Ids run newest first and the cursor is exclusive, so new calls do not shift older pages. Reads and decisions are scoped to the owner and omit deleted conversations, whose audit history is kept. Reading never changes an invocation or grants a permission. The relay side is in MCP servers.

Memory, avatars and home suggestions

Memory is the lasting rules agents follow for an owner. Avatars and home suggestions are derived from recent requests on each read; nothing about them is stored, given to agents or affected by memory settings. The memory redesign explains the split.

Memory

Each memory is one rule in one of two scopes: user memory applies to every project, and project memory to one repository, where it takes precedence.

  • Tools. Every task can call memory_save, memory_update and memory_delete on the platform’s MCP endpoint; the agent decides when, and writes each memory as one English sentence. No extra model call is made. Updating or deleting archives the old memory, owner-written ones included.

  • Injection. When a task starts, its repository’s active memories and the owner’s user memories are added before the request with the ids the tools take, up to 12,000 characters: repository memories first, and within a scope the most recently written or edited first. Only the memories added get a new last_used_at; nothing is added when there are none.

  • Fence. Every write checks, in its own transaction, that the task’s attempt is still the running one, so a stopped or replaced task cannot change memory even with a request under way.

  • Scope. An agent saves to its task’s repository unless told a rule applies to every project; a chat without a repository saves user memories. An agent sees and changes only its task’s repository memories and the owner’s user memories.

  • Limits. Each scope has its own switch; a scope that is off is neither injected nor written, and keeps its memories. A scope keeps 30 active memories of at most 240 characters. When it is full, the least recently used agent-saved memory is archived to make room; owner-written or edited memories never are, so a scope full of them refuses a new one. Archived memories can be restored and are deleted after 90 days, by an hourly purge in every control-plane process. Adding a memory identical to an active one in the same scope, ignoring case and final punctuation, returns the existing memory.

GET /v1/agent/memories returns every project’s memories, and ?repo=owner/repository one repository’s, as {repo, enabled, memories, archived}. POST with {content, repo} adds an owner-written memory (repo is null for every project). PATCH /v1/agent/memories/{id} with {content, repo, expected_version} rewrites or moves it, DELETE with {expected_version} deletes it, and POST /v1/agent/memories/{id}/restore with {expected_version} restores an archived one. DELETE /v1/agent/memories clears one scope, archived memories included. PUT /v1/agent/memories/settings with {repo, enabled} turns a scope on or off, and GET /v1/agent/memories/projects lists the repositories the owner worked in or keeps memories for. Refusals carry a typed reason: memory_changed (409, a stale version), memory_scope_full (409), memory_invalid (422: credentials, over 240 characters, or limited to the current task) and memory_not_found (404). No response echoes memory text. The tables and their migrations are in the upgrade notes.

Avatars and home suggestions

Without an uploaded profile photo, an account is shown as a generated Pokémon companion: one of seven, with an expression and pose fixed for the account. The account menu and Profile show its fixed form, which GET /v1/agent/profile returns as companion_data_url, derived from the account alone so showing it on every page reads no requests. On the homepage, the habitat colors and orbiting badges follow the work areas detected, with English keywords, in the owner’s latest 50 requests. Every shape is drawn locally as a standalone SVG; account identifiers and request text are never embedded, and no image service or model is used.

The homepage also lists up to three fixed suggestions, chosen by counting English keywords in the same 50 requests: change requests without success checks or verification, failure reports without reproduction steps, and reviews without a stated scope. Only the owner’s own lines count: quoted text, code blocks, assistant replies, forked copies, automatic goal turns and deleted chats are ignored. GET /v1/agent/insights returns {focus_areas, suggestions, avatar}, derived on each read.

Workspace files

The Files pane reads the retained sandbox’s current repository and is read-only. Paths use forward slashes and stay inside the repository: absolute paths, parent traversal and .git paths are refused, and symlinks are shown without being followed. A folder listing is bounded: entries past the limit, and names that are not UTF-8, are left out, and the folder says so instead of reading as complete. An entry left out for the limit can still be opened by path. Returning to the workspace root cancels an unfinished path read, so a late response cannot move the tree. A file the run deleted reports that rather than a failed read.

Copy and download offer only complete contents within the preview size limit; an oversized file shows the limit instead of a partial copy. Raster images get a preview, and HTML and SVG stay escaped text. Download workspace, on each turn’s result in a conversation without a repository, saves the workspace as one .tar.gz without .git directories and metadata files; other hidden files are included and .gitignore does not filter it. A workspace over the server’s download limit is refused rather than saved in part. See Temporary workspaces.

Smaller features

  • Notifications. My Settings → Notifications enables browser notifications for agent questions and finished turns, each toggled separately and stored per account. While the app is open they cover every chat, and clicking one opens its turn; nothing is delivered while the browser is closed.

  • Plugins. Chosen with + → Plugins before a chat’s first task. Claude Code and Codex accept name@marketplace with a GitHub repository or public HTTPS marketplace source; pi, OpenCode and Kilo accept npm package names, optionally pinned to an exact version such as @example/plugin@1.2.3. Up to eight per chat. The sandbox installs them before the first prompt, and a failed installation stops startup as a task error. Selections persist with the chat and are inherited by Fork.

  • Prompt rewriting. See Draft prompt rewriting.

  • Review. Starts an independent code-review subagent in a workspace tab, covering every change since the conversation’s original base, current workspace changes included, and instructed not to edit files. It needs an available native Fork, so it cannot start from an archived chat or one whose context was lost. Selecting it again reopens the chat’s existing review.

  • Watch PR and merge. Starts a follow-up in the same chat that watches checks and reviews, fixes failures and feedback, and merges once the current head meets the required checks and reviews; repository rules decide what may merge without a person. It keeps unsent composer text and attachments, and starts only while no chat sharing the workspace has active work or waiting messages. The agent needs source-control tools that read checks and reviews, mark drafts ready and merge: the GitHub preset with a token holding Contents and Pull requests read and write. A connection to /mcp/readonly must be reconnected with the preset. Watching ends with the turn’s timeout, and a turn that made fixes ends so the platform can publish them.