npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

agl

v32.12.0

Published

Agent Gateway Layer for OpenCode, FlexHarness, and Codex sessions

Downloads

13,306

Readme

AGL

AGL is a self-hosted workspace for working with OpenCode, FlexHarness, and Codex from one secure browser interface. It runs the agent runtimes behind a local controller, keeps conversations organized by project, and adds durable resources, model management, permission handling, workspace history, and safe lifecycle management.

The default setup is local-first: AGL listens only on 127.0.0.1, opens no agent runtime directly to the network, and protects the browser with a passkey. A trusted TLS reverse proxy can provide remote access when needed.

Issue Reporting and Security

For reporting bugs, issues, or security vulnerabilities, please visit community.foss.global/. This is the central community hub for all issue reporting. Developers who sign and comply with our contribution agreement and go through identification can also get a code.foss.global/ account to submit Pull Requests directly.

What AGL Provides

  • One browser workspace for OpenCode, FlexHarness, and native Codex conversations.
  • Explicit projects backed by existing directories on disk.
  • Live text, reasoning, tool activity, todos, permissions, and questions.
  • OpenCode, Flex, and Codex model selection, including per-conversation choices.
  • Conversation groups, ordering, search, rename, archive, and deletion.
  • Synchronized drafts, file attachments, scratchpads, tasks, and goals.
  • Native slash commands with transcript and Git-aware undo/redo.
  • Optional detached Git worktrees owned and validated by the controller.
  • Durable terminal resources that survive browser reloads.
  • Claude chats: controller-owned Claude Code terminals that resume the same conversation after a restart or upgrade.
  • Durable browser resources for human use and permission-gated agent automation.
  • Read-only Session Intelligence for questions about a conversation and other managed sessions.
  • Passkey authentication, temporary CLI passwords, and bounded audit records.
  • Detached start, verified stop, health status, and coordinated in-place upgrades.

OpenCode owns OpenCode sessions, FlexHarness owns Flex sessions, Codex app-server owns Codex threads, and every mutation is sent to the runtime that owns the conversation.

How It Works

Browser
  -> AGL controller and authenticated TypedSocket API
     -> official OpenCode SDK
        -> private OpenCode server on 127.0.0.1
     -> private framed IPC
        -> isolated FlexHarness child
     -> native Codex app-server protocol over owned stdio or an explicit WebSocket endpoint
     -> controller-owned terminals, browsers, uploads, and Git state
     -> SmartData persistence and audit records

OpenCode and FlexHarness are supervised independently. Flex can restart without taking down OpenCode, and an unsupported Flex runtime does not prevent OpenCode sessions or controller-owned terminals from working.

OpenCode is automatically recovered only after AGL observes the exact owned child exit. AGL immediately fences the exited runtime generation, aborts its in-flight requests, completes generation cleanup, and retries a fresh health-checked runtime before reopening admission. Generic or otherwise uncertain OpenCode failure remains fail-closed and is not automatically restarted. Fatal Flex child failures close admission and use bounded status and resource cleanup; if graceful disposal stalls, a watchdog forces the isolated child to exit so exact-generation recovery can proceed.

The browser receives normalized AGL data. It never receives the OpenCode server password, provider credentials, private database descriptors, raw provider responses, or Flex private snapshots.

Live Transcript Delivery

Controller protocol v28 sends bounded live Assistant text and reasoning deltas when the browser already owns the exact preceding transcript revision. Every delta carries its stream epoch, revision, and UTF-8 coordinates; Flex deltas also carry the harness's authoritative transcript order. The browser applies only contiguous, correctly sized updates to the canonical transcript; a gap, stale owner, invalid coordinate, or queue overflow blocks further deltas from that epoch and requests authoritative session-detail hydration instead. Only adjacent compatible pending deltas may be coalesced, so interleaved updates retain their original order.

Streaming text is rendered without Markdown parsing. AGL refreshes the affected messages and enables normal Markdown rendering after the harness reports terminal state or authoritative hydration settles the transcript. This keeps long responses responsive without creating a second browser-side message graph.

Requirements

  • Node.js 24, 25, or 26.
  • pnpm 11.21 or newer.
  • Git 2.43 or newer for workspace history and managed worktrees.
  • A WebAuthn-capable browser.
  • Linux or macOS for detached start, stop, and managed upgrades.
  • A prebuilt node-pty platform package for Linux, macOS, or Windows on x64 or arm64.
  • For FlexHarness, a usable local TPM2, access to its resource-manager device (normally through the tss group), and /usr/bin/systemd-creds with its TPM2 runtime libraries.

FlexHarness currently runs on Linux x64 with Node.js 24 or 25 and fails closed when its TPM requirements are unavailable. Other supported controller platforms still provide OpenCode and terminal functionality.

AGL includes an embedded SmartDB engine, so a separate database server is optional. It also pins the official OpenCode SDK and platform binary packages to one tested version.

Install

Install the public package:

pnpm add --global agl

To install from a specific registry, use pnpm's normal registry option:

pnpm add --global agl \
  --registry=https://registry.npmjs.org

Confirm the installation:

agl --version
agl help

Quick Start

Start AGL without creating an implicit project:

agl start

The first start prints a local URL and a one-time setup code. Open the URL, enter the code, and create the controller's passkey.

To register one existing directory during startup:

agl start --directory /absolute/path/to/project

AGL never derives a project from the shell's current directory and never creates project directories. Additional projects can be registered from the browser.

Check controller and harness health:

agl status
agl status --json

The default ports are:

  • Browser URL and default WebAuthn origin: http://localhost:4097.
  • Controller bind endpoint: 127.0.0.1:4097.
  • Private OpenCode server: 127.0.0.1:4098.

Authswitch accounts

Settings uses a section menu for General, Accounts, Codex connections, and Flex connections. The modal keeps its size while each section scrolls independently of the heading and actions; navigation moves above the content on phones. General drafts and account login progress survive section changes.

Settings → Accounts opens with the accounts screen of the AGL accounts service: which accounts this host has, what each one is used for, what uses it, and which logins the host already holds from before the service existed. It is read-only in this version — every account action is shown, disabled, with the reason — and it says so: accounts are moving there, and Flex logins are still managed in Flex settings until the move. When the service is not installed or not running, the screen names that state and what it means for running work instead of showing an empty list; when it stopped answering, the last values it delivered stay on screen, dimmed and timestamped.

Below it on the same section, inspect this host's Codex, OpenCode and Claude Code logins, save the current account, switch saved accounts, or remove a saved copy. The table shows email identifiers, usage and time until reset, plus provider-reported renewal or cancellation dates when available. The Usage column shows the window the provider marks as its headline, such as Claude's Fable weekly limit, and otherwise the account's weekly window, always named by its label. Details show every usage window, including per-model limits, with the provider's warning, critical and headline marks, plus available resets and lookup failures. Status reads never switch credentials.

Authswitch owns provider sign-in and native credential changes. Available Sign in actions open its device flow and save the completed login without activating it. Terminal users can also run authswitch codex login or authswitch opencode login. Unsupported hosted login flows show the harness's native login command. AGL's own passkey authentication remains separate.

What a change does to running work is stated per harness and per action. Only replacing the active login — switching accounts, or saving one and clearing it — reaches running sessions, and there the harness's own authswitch adapter decides: Claude Code swaps live, so its running sessions use the new login from their next request and nothing is restarted; OpenCode is the runtime AGL manages, so AGL restarts it once its work is idle; every other harness keeps the login its running sessions loaded until they are restarted. Saving a copy of the active login, removing a saved copy, signing a new account in and reading the account list all leave the active login in place, and say so instead of naming a restart. The offer to save an unsaved active login first is unchanged.

When authswitch finds AGL, its owner-authenticated local bridge asks the controller to coordinate the OpenCode account changes that replace or clear a login. AGL seals new work, checks both queued controller operations and native session activity, stops its owned OpenCode process, applies the authswitch operation, and restarts OpenCode. Saving the current account without replacing it is read-only and runs directly instead: OpenCode keeps running, active work is neither awaited nor interrupted, and no consent is asked for. Idle operations proceed automatically. Active work requires an explicit Wait for work to finish choice; no active session is killed to change accounts. A failed credential operation still attempts to restart the runtime. If restart fails, AGL keeps new work blocked and schedules recovery without repeating the credential change. Closing the browser or CLI does not abandon an admitted transaction.

Authswitch preserves and verifies an outgoing native login before replacing it. The UI offers to save an unstashed current account before switching. An unmanaged OpenCode terminal must still be exited manually before a switch, because AGL cannot reconstruct another terminal's process; saving an account needs no exited process at all, including for Claude Code, whose Save works while AGL-managed Claude terminals run. Remote Codex connections keep their own account and are not changed by this host's authswitch view.

Existing Flex connections keep their stable IDs and encrypted credential storage. Their provider login flow also uses authswitch. Selecting a Flex account for OpenCode delegates native credential activation to authswitch; subsequent controller startup and crash recovery preserve OpenCode's refreshed native credential. AGL reconciles its selected Flex association with the native account identity and clears a mismatched association.

The private agl authswitch --request <json> command is the installed authswitch bridge, not an agent MCP tool. Hosted clients use controller.authswitch.request and poll the returned operation ID; mutation requests must use maxRetries: 0. Account and operation responses contain no provider credentials.

Native Codex Harness

Choose New → Chat session and pick the Codex harness to use Codex inside AGL. Native threads support text and reasoning streaming, tool activity, the Codex model and reasoning-effort catalog, command and file approvals, ordinary user-input questions, interruption, history pagination, rename, archive, and deletion. AGL Ask/Yolo controls apply to the exact pending native approval request; approvals never outlive its server request.

Install Codex CLI 0.153.3 or newer and sign in using Codex before starting AGL. The local connection uses the existing Codex app-server control socket when present. This is the WebSocket-over-Unix transport described by the Codex app-server protocol, at CODEX_HOME/app-server-control/app-server-control.sock (the default home is ~/.codex). AGL owns only its connection to that shared server. If the socket is absent, AGL starts its own codex app-server --listen stdio:// child. An inaccessible or stale existing socket is reported as a connection failure. Set HARNESS_CONTROLLER_CODEX_EXECUTABLE to an absolute executable path when Codex is not on the controller's PATH; an explicit absolute CODEX_HOME is forwarded to the owned child.

Open Settings → Codex to save named remote connections, test them, inspect the account and rate-limit resets, or reconnect one profile. Use ws:// for a loopback endpoint or wss:// for remote TLS. Enter a bearer token separately; credentials are sealed through the controller host's TPM and persisted encrypted through SmartData. URLs remain immutable so an existing conversation cannot be redirected to another server. Credential replacement disconnects the old connection before activating the new revision. Retiring a profile prevents new conversations from using it while preserving existing origins.

For each project, select its connection and server-native directory. AGL checks that directory through Codex before saving it. Existing conversations retain their original server and directory when the project mapping changes. Models, reasoning efforts, availability and local-attachment support follow that same conversation or draft context. Remote connections disable local file attachments because the remote host cannot access AGL's upload directory. Existing HARNESS_CONTROLLER_CODEX_SERVER_URL and HARNESS_CONTROLLER_CODEX_SERVER_TOKEN configuration is imported once during the versioned migration; subsequent changes use Settings.

The sidebar automatically discovers non-archived CLI and Desktop conversations from the mapped project directory. Manual enrollment remains available in Settings for an explicit lookup. Opening the same thread on the same app-server shares its native history and live state with other clients, including a Desktop remote connection; sharing an account alone does not select the same thread. AGL never starts or stops an externally owned server. The open conversation's context menu offers Stop following in AGL while AGL is following the conversation, which unsubscribes AGL after its own active and queued prompts have settled, and Resume in AGL on a released conversation to join again; while AGL owns a running turn, or prompts of its own are still queued, there is nothing to hand over and neither action is offered. Between the transcript and composer, Codex reports another client's turn, a released conversation, a model reroute, a paused prompt queue, and — while AGL runs the turn — which model Steer and Queue next will use. A completed diff is summarized as added and removed line counts; View edits opens that turn's captured patch. Binary, incomplete, and unparseable patches are labelled instead of receiving invented counts.

When another Codex client owns the active turn, its conversation is marked orange in the sidebar and the whole composer stays read-only and copyable. Attachments, model, mode, effort, send, steer, and queue controls remain unavailable until ownership returns. Stop is independent: it is enabled only when the controller can interrupt the exact observed turn. Local unsent text and attachments survive ownership changes, refreshes, and rejected steering. While another client runs a turn, AGL follows its live updates and accepts the next prompt after that turn finishes. Neither client needs to release the conversation first.

While AGL owns a running Codex turn, Steer now sends input to that exact turn and Queue next retains a separate follow-up with its selected model. Queued prompts survive an isolated Codex restart or reconnect within the running controller. They are held in memory, so a controller crash or full controller restart does not retain them. Stop cancels queued work. An undispatched failure preserves the queued prompt and offers Resume queue; an uncertain dispatched request is never replayed. Interrupted tools show Stopped, and native plans, diffs and observed model reroutes remain attached to their turn.

AGL records a durable creation intent and selected connection before asking Codex for a server-assigned thread ID. It admits that exact ID into managed conversation state and finalizes the requested title and model before sending any turn. New public IDs include their profile identity; shipped legacy IDs remain unchanged. The durable origin outlives creation-intent retention. If initial settings cannot be finalized, the composer keeps that conversation and its unsent draft; the next send finishes setup on the same thread. Local shutdown verifies the owned process group before releasing its uploads; losing a shared or remote connection is not proof that its server stopped. An unavailable generation stays fenced until explicit reconnect succeeds. An unresolved remote creation requires the original server's thread identity to be resolved before recovery can safely proceed. Codex migration or connection failure leaves OpenCode and Flex available.

Native Codex root conversations in the mapped folder appear in the workspace regardless of which client created them. Discovery preserves each conversation's server origin when the project mapping changes. Native Codex uses its selected server's account; AGL's Flex account selector does not change it. The supported Codex commands are /model, /plan [prompt], /fork, /review [instructions], /rename <title>, /new, /clear, /resume, /copy, /status, /pwd, /usage, /agent (also /subagents), /archive, and /delete. They are discovered and authorized by the controller before AGL opens the corresponding picker, conversation/sidebar view, confirmation dialog, or native operation. /permissions, /mention, and /compact remain visible with their unavailable reason; Codex 0.155.1 does not return the compaction turn ID needed to correlate /compact safely. A mode mutation blocks subsequent mode-dependent dispatch until an exact native settings notification confirms the applied mode. A transport-unknown outcome stays explicitly unconfirmed and is never treated as success from a cached session read. Unknown command-shaped input is rejected without becoming a paid chat prompt, while absolute paths remain ordinary prompt text. Secret-input and unsupported custom host requests are rejected explicitly. Codex has no Session Intelligence adapter. Browser resources can attach to Codex conversations. A directly launched Codex task can control its attached browser through agl mcp; trusted Flex run channels remain a separate integration.

MCP Server

The installed agl command includes a unified stdio MCP server that combines 52 tools on one connection:

  • Nine CrossHarness tools for explicit OpenCode and Codex harness-server connections.
  • Six system inspection and reclaim-planning tools.
  • Thirteen durable tstask tools.
  • Twenty-four AGL controller tools for projects, task discovery and control, models, scratchpads, and resource lifecycle and attachment.

Configure an MCP host to execute:

agl mcp --port 4097

--port takes precedence over AGL_CONTROLLER_PORT; otherwise the controller port defaults to 4097. agl mcp starts and exposes all 52 tools without a running AGL controller. Only the AGL controller tools require a ready controller on the configured port; the CrossHarness, system, and durable-task tools remain available independently. agl mcp requires Linux x64 with Node.js >=24.12.0 <27.

On supported systems, a ready controller publishes a process- and generation-fenced private loopback endpoint under $AGL_HOME/runtime/mcp. The descriptor and bearer token remain owner-only runtime state. They are discovered and verified automatically; do not copy them into MCP configuration. The endpoint accepts only the explicitly registered, bounded AGL methods and is removed before controller shutdown proceeds.

Caller identity

Every MCP call carries the identity of the chat it serves, and the controller authorizes on that identity instead of on a task id supplied in the request. When AGL starts a chat it mints a per-start caller credential and writes it into that process's environment as AGL_MCP_CALLER_CREDENTIAL; the agl mcp server the chat launches reads it from the environment and presents it on the private endpoint. The credential is never an argument, never inheritable, never persisted, never logged, and never echoed in a response. It dies with the chat: a terminal exit, a task losing its exact managed identity, an OpenCode restart, or controller shutdown all revoke it, and the next start mints a new one.

A caller is one of three subjects:

  • Terminal — a chat running as a terminal resource, such as a Claude chat. The terminal conversation itself is the subject: resources are attached to the terminal, and the caller sees and acts on exactly what is attached to that conversation. The fence is the coding agent's own conversation id, so an attachment survives a terminal restart that resumes the same conversation and is refused once the conversation changes. A terminal subject manages an attached browser — create, attach, rename, start, stop, retire — and drives it as its own conversation: BrowserRuntime issues driving authority to a conversation, and the conversation that owns the terminal is one. A plain shell terminal owns no conversation and therefore drives nothing.
  • Runtime — a shared harness server. OpenCode runs one server per controller and addresses MCP servers per directory rather than per task, so an OpenCode-launched caller is identified as that runtime, not as one chat: it may act for its own tasks in the project named in each request. This is deliberately coarser than a terminal subject, and narrowing it requires per-task MCP servers upstream in OpenCode.
  • Task — a chat AGL manages as a task, acting only for that task in that task's project. The subject exists in the model and is enforced everywhere, but no task credential is issued yet: AGL cannot stamp a per-task credential into a harness that shares one server across tasks. Until OpenCode can carry a task id into the MCP server it launches, OpenCode chats act through the runtime subject.

Reading is open to every caller that holds the descriptor token, identified or not: controller_status, projects_list, sessions_list, context_resolve, models_list, session_read and session_scratchpad_read behave exactly as before. That keeps the cross-harness workflow intact, where a chat running outside any AGL terminal — and therefore without an AGL identity — inspects AGL-managed tasks. resources_list shows the unattached project resources to such a caller.

Writing is not open. Task mutations (session_send, session_scratchpad_update, session_model_set, session_rename, session_archive, session_stop) require the calling chat to be the task itself, or its shared harness runtime; a terminal caller owns no task and an unidentified host owns nothing. Resource mutations, project_add, session_create and browser_action require an AGL identity as well. Every caller must still run as the controller's local OS user; a remote MCP process cannot access this local endpoint without a separate authenticated transport.

| Tools | Behavior | | --- | --- | | controller_status, projects_list, project_add | Inspect the controller and register existing project directories. | | context_resolve, sessions_list | Resolve an exact canonical project folder and discover its nonarchived native root tasks from the configured OpenCode, Flex, and Codex connections. | | session_read, session_send | Read a task, or submit text through the existing harness connection. Reading is open to any caller; sending requires the calling chat to own the task. | | models_list, session_model_set | Inspect available models and reasoning variants; save the model used by subsequent AGL submissions. Pass project and task IDs to use a particular Codex task's connection. Saving requires the calling chat to own the task. | | session_create, session_rename, session_archive, session_stop | Create or rename a task, archive idle work, or interrupt a running task. Creation does not send a prompt and requires an AGL identity; the rest require the calling chat to own the task. | | session_scratchpad_read, session_scratchpad_update | Read or revise shared task notes. Reading is open to any caller; revising requires the calling chat to own the task. | | resources_list, resource_create, resource_rename | Inspect and create project browser or terminal resources. The listing shows the resources attached to the calling chat plus the unattached ones. | | resource_attach, resource_detach | Attach a resource to the calling chat, or detach a resource already attached to it, using its current attachment revision. A shared runtime caller names which of its own tasks to attach to; attaching a resource to another chat is a workspace-UI operation. | | resource_start, resource_stop, resource_retire | Control the lifecycle of a resource attached to the calling chat or of an unattached one. Retirement removes it from the active resource list. | | browser_action | Inspect, navigate, click, fill, press keys, or capture an image in a browser attached to the calling chat, alongside human viewers. The caller acts as its own conversation — for a terminal subject, the conversation that owns the terminal. |

To work with resources for an externally launched task, call context_resolve with its absolute directory and optional { harnessId, nativeId } task ID. A raw Codex thread ID is accepted only if exactly one configured connection matches; an ambiguous ID requires selecting the qualified ID returned by sessions_list. Discovery records the verified native task in AGL without resuming it or acquiring its Codex writer. Subsequent operations reuse the normal UI validation, audit, admission, and ownership checks. Targets must belong to the exact project and configured native connection. Adding a project does not connect an arbitrary external harness server.

Use the returned project and task IDs with resources_list or resource_create, then resource_attach with the resource's attachment.revision as expectedAttachmentRevision. Then call browser_action with the project and resource IDs, the current expectedAttachmentRevision, and an action such as { "action": "snapshot" } or { "action": "navigate", "url": "https://example.com/" }. The same workflow works for OpenCode, Flex, and Codex attachments. Several chats may be attached to one browser and all of them can drive it; each acts as itself, another chat attaching or detaching does not end your authority, and your own actions are still ordered against theirs by the attachment revision you observed. A terminal subject drives the browsers attached to its terminal as the conversation that owns that terminal, so an agent running in an AGL terminal drives its own browser; borrowing another chat's conversation is still refused rather than allowed, and a plain shell terminal, which owns no conversation, drives nothing. Browser actions have a 20-second deadline. Screenshots return MCP image content from that action's own lease and remove the temporary artifact after reading it; images are limited to 512 KiB, so use JPEG with lower quality if necessary. Live terminal input continues to use its resource transport.

Private controller requests are limited to 256 KiB and responses to 1 MiB. AGL text tool results are limited to 512 KiB; screenshot image bytes are separately limited to 512 KiB before base64 encoding, and session_send accepts at most 64 KiB of UTF-8 text.

session_send submits its supplied text directly through normal AGL session admission, model validation, queueing, audit, and harness acknowledgement. It never reads, clears, or emits browser composer draft state. If a mutation reports OUTCOME_UNKNOWN, inspect current state before acting again: the operation may already have succeeded. The MCP client does not replay mutations automatically.

session_scratchpad_read returns the shared durable scratchpad and its current revision. session_scratchpad_update accepts at most 32,768 characters and 128 KiB of UTF-8 text, requires the revision returned by the read, and reports CONCURRENT_CHANGE instead of overwriting a newer browser, Session Intelligence, or agent edit. Successful MCP updates are attributed to the agent.

Task mutations are limited to the calling chat's own task. Every task operation revalidates the recorded project, origin, generation, and deletion/archive boundaries, and can discover an externally created native root before use. Browser-only OpenCode direct-child scopes, child attention, child transcript reads, and child replies are never exposed through the private MCP endpoint.

The optional @modelprofile.com/mcp-tstask dependency is required when starting agl mcp. If the package manager omitted it, the server exits with a clear startup error instead of exposing a partial tool manifest.

Everyday Workflows

The footer's Stats button shows current CPU, memory, aggregate network rates, and the main disk and each monitored volume with capacity and IO busy figures. Metric cells reserve their width and use tabular digits, so changing readings do not move adjacent cells; the strip scrolls horizontally on a narrow screen. Stats stays pinned with a reserved ! or ? cue for stale or unavailable live samples; its tooltip and accessible name give the sample age. One fixed rail control shows the scroll direction and the number of strained cells with clipped or offscreen values. Values at a clipped edge disappear as a whole until scrolling reveals them, so a partial digit is never mistaken for a complete reading. Keyboard focus and horizontal scrolling reach every disk, the IO figures, both network rates, and the legal link at the end of the rail. Mount labels retain the path tail, while each cell's tooltip gives the full path. Open Stats for a live host-statistics panel with the sample age, memory used and total, the main disk, and every monitored volume. A sample older than six seconds is marked stale; its last observed values and filesystem rows are dated without reducing text contrast, while missing current values are identified as unavailable. Escape, Close and an outside press dismiss the panel; the panel updates without rerendering the workspace. The controller samples CPU, memory and network rates through a bounded smartsystem worker. Initial network discovery and later topology rescans run outside the controller event loop, keeping terminal echo and browser input responsive while metrics are collected. The controller owns and stops that worker.

Clicking one footer reading opens only that reading's one-hour graph in a compact anchored panel; network receive and transmit remain paired. Stats continues to open the complete dashboard. The account-limits cell beside Stats refreshes the proven active local account every five minutes. Its panel refreshes and shows all accounts, provider windows, reset deadlines, billing facts, renewal limitations, and provider-reported problems. A remote or ambiguous session identity is shown as unavailable instead of being matched to another local account.

The controller records a sample every ten seconds in SmartData even while the panel is closed. The panel displays the last hour of CPU, memory, network and per-mount disk capacity and busy readings using dees-catalog charts. The time axis remains one hour wide after startup or a collection outage: unsampled intervals and unavailable readings are gaps, not zero usage. The panel labels partial and stale history, names the end of recorded history separately from the live sample, and marks mounts shown only in history, and reports history retrieval failures. Distinct series colors identify paired readings, and the charts leave room for the graph and its axis labels. The history survives a controller restart and expires after its retention window.

The panel shows disk usage for the filesystem holding / and all monitored volumes, each with its mount path, used and total size and filesystem type. Volumes are enumerated through @modelprofile.com/mcp-system, which bounds every mount's statfs on its own, so a hung network mount shows -- with the reason in the panel instead of delaying the sample or the other volumes. Pseudo filesystems and volumes below 1 GiB are left out.

Every disk and volume row carries a second figure, IO, next to its capacity: how busy the disks behind that mount were over the last sampling interval. It is the share of the interval in which the busiest of those disks had I/O in flight — iostat's %util — so it answers "how much of the disk's time is already spoken for" the way CPU answers it for the processor. The figure takes the warning style from 80% and the error style from 95%. A mount's first sample is only a baseline and shows IO -- with the reason, as does its first sample after more than 30 seconds without one, so a stretch nobody watched is never passed off as current activity, and a mount whose disks cannot be attributed, for example a network share.

The panel adds the disks the figure comes from, read and write throughput and IOPS, and on the main disk the host's I/O pressure (/proc/pressure/io, the 10-second and 60-second some and full averages) where the kernel reports it. Volumes on one disk show that disk's figures; the panel names the disk. When a figure is shown, the panel also carries the caveat that busy is the share of time the disk had I/O in flight, and that SSDs and NVMe can read 100% before they are saturated.

The footer keeps the existing sustained-high-CPU alert. Memory flashes red when active use reaches 90% of total, and each disk flashes red when capacity use or its busy share reaches 90%; its tooltip names the cause. On Linux active memory excludes reclaimable page cache. Missing or stale readings never trigger a new alarm. Reduced-motion settings keep the alert red without flashing. The panel marks disk busy share from 80% as a warning and from 95% as critical. The panel always shows every rate and volume, and the legal link remains in the bar.

Errors

A failed workspace operation is not painted across the workspace. It goes into a journal in the browser tab, and the header shows an Errors button next to Settings carrying the number of failures not read yet. Its dialog lists them newest first with what the UI was doing, the time, the message, and the controller's reference for the failure. Errors that belong to one conversation — a scratchpad save, a session intelligence question — keep their banner next to that view as well, and are journaled too.

What the composer needs before it can send stays on a line below it and never enters the journal: the account and model a Flex message needs, an incomplete attachment, or composer text over the 64 KiB limit. The line goes as soon as the next keystroke or the next send answers it, or when the workspace opens something else. When Archive finds another change in progress, the guidance appears in the sidebar and clears when that change finishes; it is not journaled. A Codex conversation whose setup did not finish is both — the instruction to send again stands at the composer, and the failed controller call is journaled.

Copy on an entry, and Copy all, put a plain-text report on the clipboard: the AGL version, the time, the operation, the message, the reference, and the controller-side cause with its stack where the controller kept one. Request payloads are not recorded; exception messages and stacks are copied as recorded. Clear empties the journal. The last fifty entries are kept, in memory, for the life of the tab.

When the controller cannot classify a failure it answers The controller operation failed. — internal error text never travels to a client — with an opaque reference. It logs the cause under that reference and keeps the cause in a bounded in-memory journal of the last hundred failures, which only the authenticated owner can read, through controller.failure.detail.get. The Errors dialog resolves the references it collected through that request, so the dialog names the actual error behind the generic sentence. A private MCP request that fails the same way journals it under the method that failed and hands its caller the same reference.

Projects

A project is an explicitly registered existing directory. Every managed conversation, durable resource, and workspace operation is scoped to one project. Conversation model overrides are project/session scoped, while harness default models are controller-wide settings.

Projects are administered in Settings -> Projects, which lists every registered project with its directory and deregisters one. There is no project selector in the sidebar: the conversation list spans projects, and selecting a conversation moves the workspace to its project.

The same section edits the standard project directories: absolute existing directories AGL offers as project locations. Their immediate subdirectories are searched when you open a conversation and offered when you start one, but nothing is registered until a conversation is actually created or opened there. agl settings --add-standard-dir <path> and agl settings --remove-standard-dir <path> edit the same list from the terminal, and plain agl settings prints it.

The browser accepts absolute paths and paths relative to the configured projects root. It suggests matching directories while typing, rejects dot navigation, and reports unavailable paths instead of creating them. A directory typed into the new-conversation box is registered as a project when the conversation starts.

AGL records the complete filesystem ancestry for each project and revalidates it before path-sensitive work. Project removal is a durable deregistration, not a list-only action. Durable terminal and browser resources must be retired first. For a bound project, AGL then retires controller-managed state, deletes managed OpenCode and Codex conversations, cleans managed Flex roots, Git captures and managed worktrees, and removes project metadata through a resumable process. Removing metadata for an unbound project retains artifacts associated with the untrusted path. AGL never deletes files in the registered working tree.

The Git teardown works from what the reversion engine has records for — the remembered repository catalog and the owned worktree records — instead of walking the project directory, so a project with more subdirectories than the Git discovery limit still finishes its removal. Each record's repository binding is still re-verified against the live filesystem before anything is touched.

A removal that fails is resumed with backoff, and it resumes across restarts and upgrades because the intent is durable. A failure that repeating cannot fix — the Git discovery limit, fenced Git state, a dirty owned worktree, a permission error — stops the retry loop instead: nothing is deleted or abandoned, and the workspace shows a Removal blocked row naming the project and the reason, with Retry to resume it once the cause is resolved. Retry applies to a blocked removal only; a removal that is still being retried keeps its own schedule and is not interrupted. Starting AGL again also resumes a blocked removal once, so an upgrade that fixes the cause clears it without operator action. A pending removal, blocked or not, accepts no new work and is absent from the normal project list.

Conversations

AGL tracks conversations explicitly. A conversation appears in the sidebar because it was created through AGL or opened through the conversation search, never because it happened to exist in a project folder. The list spans every project, each row carries its project name, and the list header offers three actions.

New opens a menu: Chat session, then Terminal, Claude terminal, and Browser. The menu hangs under the button, is keyboard-operable, and returns focus to the button when dismissed.

New → Chat session opens the new-conversation box: the project (or a directory to register), the harness, and the model. The empty workspace shows exactly the same box, so the options never differ between the two. The harness is preselected from the one used last and is otherwise an explicit choice — AGL never starts a harness nobody picked. A new conversation remains a browser draft until its first message is submitted, so abandoning it does not leave an empty harness session.

The terminal and browser entries create in the current project. When no project is in context, one registered project is used without asking, several are picked from in a dialog, and with none registered the menu opens the flow that registers one.

Open searches harness conversations that AGL does not track yet, across every registered project and the immediate subdirectories of the standard project directories. Results show harness, project, and title, and mark what is already open. Picking one tracks it and registers its directory as a project when it is not one yet.

Last active uses the harness’s session-update time, shown in your local timezone.

Archive lists the conversations archived in AGL, across projects, and returns one to the active list. This archive is AGL's own: the harness conversation is untouched and keeps its own archive state.

Conversation features include:

  • Tip-first transcript loading with older-history pagination.
  • Live Assistant text, reasoning, tool state, todos, and subagent cards.
  • Inline permission requests and questions in transcript order.
  • Synchronized drafts and private temporary attachments.
  • Durable model choices and scratchpads.
  • Rename, archive, delete, abort, and session-specific Ask/Yolo controls.
  • Token and context metrics when the harness can prove them from complete history.

Archived conversations are hidden from the normal list and available in the Archive view, which serves them from a durable title cache and therefore starts no harness read. Archive is admitted only after active prompts, permissions, commands, and Session Intelligence work have settled.

AGL never automatically redispatches a Flex archive whose outcome is unknown. It performs bounded exact-session readback and finalizes a confirmed archive. Otherwise it stops the exact Flex generation before releasing archive admission; if that stop cannot yet be confirmed, AGL retains the fences and retries cleanup until the generation exits. An authoritative refresh that confirms the conversation is archived clears a stale browser error.

Managed Conversation Authority

Controller-created root conversations are automatically managed. On the first compatible startup, AGL seals one bounded migration cohort per project and harness and admits provider-present, non-deleted conversations that already have durable legacy AGL session state. Opening a project also discovers non-archived native root conversations in that exact folder, independently of which client created them. OpenCode uses exhaustive native cursor pagination, Codex uses the configured server and directory mapping, and Flex uses the project's registered native storage scope. Discovery creates durable managed membership before granting access; deletion fences and tombstones still take precedence. Archived conversations stay out of the active sidebar, and managed archives remain available in the archive dialog. Subagent conversations remain accessible through their parent. Sessions in other folders or unconfigured servers are not admitted. Unmanaged conversations remain excluded from ordinary reads, mutations, events, layouts, resources, Session Intelligence, and lifecycle operations.

Provider-declared parent/child relationships never grant ordinary managed-session authority. AGL does not infer or create child membership from a relationship, and independently managed children continue to use normal managed-session access.

For OpenCode, the browser can also receive a temporary scoped view of a provider-observed direct child of one active managed root. The child remains absent from generic session lists, reads, mutations, layouts, resources, Session Intelligence, lifecycle operations, and MCP. The scope permits bounded transcript reads and replies to exact pending child permissions or questions only. Permission replies are limited to one-time acceptance or rejection and cannot create a persistent provider grant.

Each direct-child scope is bound to the exact browser peer and credential, project, managed parent identity, parent and child provider generations, and OpenCode runtime generation. Active scopes expire after five idle minutes or 30 minutes total. A completed child receives a fixed ten-minute read-only grace period; if it resumes, the old scope is revoked and the browser must obtain a new generation. Capacity is limited to 16 scopes per peer and parent, 32 per peer, and 512 controller-wide.

Pending direct-child attention is attached only to the browser's managed-parent detail and is limited to 16 children, 32 requests, and 256 KiB. The browser reports when that transfer was truncated. Child invalidations are sequence-ordered, bounded, and delivered only to the exact authenticated peer; overflow or authority loss revokes the scope.

Conversation search accepts a non-empty printable title query of at most 2,048 UTF-8 bytes. A truncated response means more matches or more candidate directories exist: refine the title query, because the response and UI provide no continuation cursor.

Deleting a managed OpenCode conversation deletes the selected OpenCode conversation. Deleting a managed Flex root also deletes the exact-generation descendant subtree captured with that root. Flex session deletion and project removal require a complete snapshot and exact-generation cleanup cohort, each bounded to 2,048 entries; an oversized scope fails closed.

Groups And Ordering

Conversations and resources are peers in one ordered list per controller. The list holds the tracked conversations of every project and the resources of the project in context; drag either into a group, reorder it within or between groups, or use a group's context menu to rename or remove the group. Selecting a standalone terminal or browser deselects the conversation view; a resource selected beneath an attached conversation retains that parent context. Dragging into a group, between groups, or into the gap between rows is purely positional and never changes an attachment; only a drop on the centre half of a conversation row attaches a resource to that conversation, as described under Resources.

Removing a group does not remove its members. They return to the ungrouped section in their existing order. Confirmed layouts are revisioned, synchronized across browsers, and preserved if a later layout request fails.

Models And Provider Accounts

AGL stores an independent default model for OpenCode and FlexHarness. A conversation can override its harness default without changing other conversations.

Flex model identity consists of the provider, model, and optional variant, independently of any provider account. AGL presents each model once and reports variants and default status per connected account. An explicitly selected or persisted account is validated exactly and is never silently replaced. Without an account selection, AGL proceeds only when exactly one active capable account is available; otherwise the browser asks for an account selection or connection.

Flex provider accounts are connected from Settings through the provider's supported login flow. Credentials remain in a Flex-owned sealed store and never enter SmartData or the browser. The store's master key is sealed to the local TPM2 and survives normal host reboots, kernel updates, bootloader updates, and firmware updates. AGL never resets an unreadable credential store during ordinary startup. Clearing, replacing, or detaching the TPM requires restoring usable TPM access and explicitly invoking SmartSecretSealedFileStore.resetTpm2() with the exact service, store, and path identity while AGL is stopped. AGL currently provides no operator-facing reset command. That destructive reset discards the old ciphertext, and saved connections then require reauthentication through Settings. Account model catalogs, quota windows, and runtime availability are exposed as bounded normalized metadata.

An active Flex OpenAI account can also be selected for the controller-owned OpenCode runtime. AGL pauses the exact active OpenCode cohort, replaces the private OpenCode runtime, verifies health, and reopens admission only after the transition is settled.

Commands And Workspace History

The composer exposes one harness-aware slash-command catalog. OpenCode templates and built-ins remain OpenCode-owned; Flex commands are listed and executed through FlexHarness; Codex commands use the native 0.155.1 command surface plus AGL client actions that the controller explicitly authorizes. Commands that are available during an AGL-owned Codex turn still run as commands; they are never forwarded as Steer or Queue input. Idle-only and otherwise unavailable commands preserve the exact synchronized draft.

Flex /undo and /redo move transcript and workspace history together. For Git-backed projects, AGL captures tracked state and non-ignored untracked paths in controller-private storage without adding controller refs or generated objects to the source repository. Unsupported or ambiguous repository states fail closed or become explicit non-revertible barriers.

FlexHarness agent generation-lease cleanup retains its independent 30 second deadline, followed by a 35 second ordinary Flex IPC control deadline. Each controller-owned Git host operation is bounded to five minutes, Flex reversion maintenance has a separate 5 minute 35 second budget, and the enclosing Flex startup and destructive lifecycle operations remain bounded to 30 minutes.

Controller readiness never waits on Flex initialization for more than 30 seconds before stopping the Flex child and continuing. A Flex child that is still initializing when that boot budget expires is brought back through Flex recovery once the controller is ready, so agl start and agl upgrade observe readiness promptly while Flex keeps its full lifecycle deadline for maintenance-heavy startups.

A terminated Git child gets one second to exit after SIGTERM and then five seconds after SIGKILL to confirm close. A child that has exited but whose close event is late, for example because a descendant inherited its pipes, only fails its own command. Only a child that is still alive after SIGKILL seals the controller's Git reversion engine; AGL then closes that engine and constructs a replacement during the next Flex recovery attempt, or on its own backoff schedule while Flex stays operational, so reversion never stays disabled until a controller restart.

Flex also supports explicit managed worktrees:

/worktree create
/worktree list
/worktree remove <worktreeId>

Worktrees are never created automatically. Removal refuses a worktree with tracked, untracked, or ignored changes.

Tasks, Goals, Scratchpads, And Delegation

Flex conversations include session-local tools for structured tasks, the current goal, and durable scratchpad context. These records are generation-fenced so deleted and recreated sessions cannot inherit stale state.

Flex can delegate one level to a general subagent. Omitting taskId creates a child; an exact ID returned by an earlier completed delegation can resume that child in a later run. The child inherits the active parent's immutable model, variant, and provider account through generation- and run-scoped delegated authority that closes on terminal completion, cancellation, cleanup, or runtime loss. It never receives managed-session membership, and nested delegation is unavailable.

Pending child permissions remain visible in the managed parent conversation. Global Yolo and the exact active parent's session Yolo can reply once to that exact child, and a manual parent reply can accept once or reject. The relationship grants no other conversation authority: transcript preview and drill-in remain unavailable unless the child independently has managed membership.

Session Intelligence

Session Intelligence can answer a question about one managed conversation and, after reading it successfully, inspect only other managed sessions in the same project. Relationship metadata does not expand that access. It runs in an ephemeral, read-only FlexHarness session with no filesystem, shell, browser, mutation, or delegation tools.

The Session Intelligence panel remains visible while model availability is being checked or is unavailable. Its question composer is enabled only after the selected active OpenAI connection, or one unambiguous active fallback connection, validates openai/gpt-5.6-luna; otherwise the panel provides safe availability guidance, including connect or reconnect instructions when applicable, without exposing credential details.

Answers and scratchpad suggestions are bounded and revision-aware. At most four analyses run controller-wide and one per source conversation.

Resources

Resources are durable project-scoped capabilities separate from conversations.

A resource may be attached to several subjects at once. Every attached conversation may use it: a second chat attaching does not take the resource away from the first, both drive it, and each action is fenced by the attachment revision that caller observed. Another chat's attachment grants a caller nothing — every gate asks whether the set holds that caller's own subject.

Every conversation attachment stores the exact immutable managed-session identity, not only the reusable runtime session ID. A stale or legacy identity-less attachment is detached or rejected during reconciliation, and recreating a conversation with the same runtime ID never inherits its resources. Deleting a conversation removes only that conversation's attachment; the resource stays attached to the others.

Only a browser may be attached to a terminal, and a terminal carries conversation attachments only, so an attachment cycle is impossible by construction rather than by cycle detection. A terminal attachment also puts the conversation that owns the terminal into the browser's driving set, so that agent can act on the browser it attached; the terminal itself is never a member, because it is a process rather than a conversation.

In the sidebar a resource row shows one marker per attachment — two by name, the rest as a +N count. Dropping a resource on the centre of a conversation row attaches it there; the edges of that row and the gaps between rows stay positional, so reordering never attaches by accident. A drag attaches an unattached resource and moves a singly attached one, while a resource attached to several conversations is only repositioned by drag, because only an explicit choice can say which attachment was meant.

The resource context menu offers Attach to … when nothing is attached, and Additionally attach to …, Move to … and Detach when something is. Each opens a searchable conversation picker instead of listing every conversation, and never offers a conversation the resource is already attached to. Move and Detach ask which attachment they mean only when there are several. A Move is a single request, so the resource is never momentarily unattached, and Detach removes exactly the membership it named rather than clearing the set.

Terminals

New → Terminal creates a terminal resource, which runs a real shell in the project directory through the controller's shell-free node-pty integration.

  • Closing or reloading the browser does not stop the terminal.
  • Reopening a terminal shows its current screen, not its history: the controller sends the reconstructed state with up to a thousand rows of scrollback instead of replaying every byte the root produced.
  • Terminal title, attachment, stopped state, and last exit code are durable.
  • Terminal input, output, and scrollback are never persisted.
  • A terminal can be renamed, stopped, restarted, or retired from its context menu.
  • Selecting, opening or creating a terminal puts the caret on it, so the next keystroke reaches the pty. A reattach after a dropped connection never does, so a reconnecting terminal cannot take focus back from wherever you moved on to.

The controller parses each terminal's output as it arrives, so it always knows what that terminal shows, including output produced before anyone attached. An attachment opens with that state at an exact position in the output stream and continues with live output from exactly there, and a viewer that falls behind the in-flight window is handed the current state again rather than a gap. A repeated hand-over carries the current screen without its reconstructed history, so a viewer on a slow connection can catch up with a terminal that keeps producing; one that still cannot is dropped and re-attaches rather than having the controller serialize for it forever. Alternate-screen applications, mouse reporting with SGR coordinates, cursor visibility and style and the scroll region are restored with it; the window title, hyperlinks and underline style are not. A root that produces output faster than the controller can parse it is paused at the pty, as it would be behind a slow physical terminal.

AGL allows at most eight running terminals per project and 32 controller-wide.

Claude chats

New → Claude terminal creates a terminal resource whose root process is Claude Code rather than a shell. AGL mints the conversation's session id, persists it, and reuses it forever: a controller restart or agl upgrade brings the chat back with claude --resume <id> in the same project directory, and the conversation continues.

  • The restart intent is explicit. A chat you stop stays stopped; a chat the controller took down comes back. Controller shutdown never records a stopped intent.
  • Agent roots receive SIGTERM before SIGKILL so Claude Code can flush its transcript. The last in-flight assistant turn may still be lost; every completed turn is kept.
  • Before resuming, AGL proves the conversation is not already open elsewhere and refuses to start it if it is. Two processes resuming one conversation branch its transcript and silently discard a branch, so an unverifiable liveness listing is treated as unproven rather than free. Do not resume an AGL-owned chat by hand with claude -c, --resume, or the /resume picker.
  • A chat that fails to start three times in a row flips to stopped and shows why, instead of retrying on every controller start.
  • Claude Code must be installed and on the controller's PATH, or pointed at by HARNESS_CONTROLLER_CLAUDE_EXECUTABLE. It authenticates with its own credentials under HOME.

A Claude chat is a terminal, not an AGL session. It does not appear in the task sidebar or MCP session tools, and has no model picker, token metrics, permission projection, or upgrade pause prompt. The terminal is the interface.

Resources are scoped to the controller port. Starting AGL on a different port hides a project's Claude chats and does not restart them; their conversations remain on disk under Claude Code's own storage and are reachable again by starting AGL on the original port.

Browsers

New → Browser creates a browser resource, which provides tabs, navigation, shared viewport control, mouse, keyboard, and text input through a native video viewer. The controller opens one authenticated persistent event stream and one paired operation stream for each human view. These carry state, input, and video signaling. WebRTC carries video directly between the browser host and viewer, using LAN or WireGuard routes with no external STUN or TURN service by default. The endpoints must be able to reach each other over UDP.

The DevTools button toggles the official Chrome DevTools frontend inside the browser pane, with Elements, Console, Network, and Sources, and stays pressed while the inspector is open. The panel icon beside it — Show browser diagnostics, which becomes Hide browser diagnostics while the panel is open — toggles a panel on the right of the browser pane whose Screencast section carries the full diagnostics — codec, encoder, frame rate, bitrate, dropped frames, decode, frame age, capture-to-display, jitter buffer, round trip, input reply and queue, commands in flight, GPU compositing and video encoding — and stays open until you close it, on this browser. Show stats on video at the end of that section enables the one-line video overlay for this page lifetime; it is off by default and does not stop metrics collection or the sidebar readings. A reading the renderer has not reported is left out rather than shown as a zero. Website JavaScript errors appear as a compact Website error notice with a Console action; browser and transport failures retain their own diagnostics. The inspector is scoped to the selected tab and the authenticated human view. Closing or replacing that view, changing tabs, revoking authority, or recovering its transport closes the inspector. Document navigation and obsolete input preserve the inspector. Reopen DevTools after a signaling transport recovery.

Website alerts, confirmations, prompts, and before-unload dialogs appear in an origin-labelled modal. Prompt text preserves whitespace and empty responses. Only the exact pending dialog can be answered; switching views invalidates its UI. Held keys and buttons are retained while a dialog blocks input and released when it closes.

Inspection uses a separate pair of bounded authenticated streams, allowing debugger Resume commands while another CDP command is waiting. Small CDP events are batched into ordered writes of at most one MiB, with up to eight writes awaiting acknowledgement and one pending batch. Partial batches flush on the next event-loop turn. The native source charges retained payload and entry overhead against a byte budget, so large replays of small events do not hit a fixed event-count cutoff. Inspector failures retain their original reason and distinguish oversized messages from an exhausted receiver backlog. CDP access does not expose a public debugger socket, browser-wide targets, the private capture page, or another viewer's session. Static frontend assets and translations ship locally through SmartBrowser; no remote DevTools CDN is needed. This release bundles the Chromium 151 frontend and reports a version mismatch explicitly if the host browser uses another major version.

Every signaling pair is bound to the exact peer, credential, resource authority, view, and stream generation. BrowserRuntime owns each viewer's media peer; the client cannot supply another viewer's peer identifier. Offers and answers accept one video media section, with bounded payloads and negotiation identifiers. Ordinary websites retain the runtime's network confinement; the private capture extension owns media transport.

Chrome captures the active tab directly and uses native video encoding, decoding, congestion control, and pacing. GPU compositing and encoding are requested automatically where supported; the actual capabilities and encoder are reported rather than assumed. The detail quality policy preserves text resolution and lets Chromium adapt frame rate under pressure. Capture is capped at 2560 by 1600 pixels, 60 frames per second, and 16 Mbps; a second application downscaling loop is no longer used. The higher frame cadence reduces the wait between input and its visible result; congestion control and CPU adaptation still apply. The receiver requests a minimal jitter buffer where supported, and the browser applies its own minimum. Hidden viewers close their media peer and reconnect when visible; the final viewer leaving stops native capture. Human video viewers do not enable JPEG screencasting. Agent screenshots remain available independently.

While a view stays open, the controller renews its BrowserRuntime lease before expiry after rechecking the stored resource, authenticated viewer, and runtime authority. Renewal preserves the media connection and browser incarnation. Closing the view cancels renewal, closes the viewer's media peer while signaling is available, then revokes the lease and closes the transport. A stalled renderer has a bounded cleanup deadline; lease revocation still owns final m