2026-09-10 08:34:10 +00:00
2026-09-10 08:34:10 +00:00
2026-09-10 08:34:10 +00:00
2026-09-10 08:34:10 +00:00
2026-09-10 08:34:10 +00:00

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.
  • 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.

Native Codex Harness

Choose New → Codex chat 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. Stop following in AGL unsubscribes AGL after its own active and queued prompts have settled; Resume in AGL joins again. While another client has a running 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. Codex has no AGL slash-command or Session Intelligence adapter. Secret-input and unsupported custom host requests are rejected explicitly. 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.

An independently launched Codex, OpenCode, or other MCP host can call these tools. AGL does not need to launch or enroll the caller. The caller must 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.
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.
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.
session_scratchpad_read, session_scratchpad_update Read or revise shared task notes.
resources_list, resource_create, resource_rename Inspect and create project browser or terminal resources.
resource_attach, resource_detach Assign a resource to a task or detach it, using its current attachment revision.
resource_start, resource_stop, resource_retire Control a resource's lifecycle. Retirement removes it from the active resource list.
browser_action Inspect, navigate, click, fill, press keys, or capture an image in the browser attached to an explicit task, alongside human viewers.

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 returned project/resource/task 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. 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 operations can discover an externally created native root before use, then revalidate its recorded project, origin, generation, and deletion/archive boundaries. 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

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.

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.

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.

Conversations

Use New conversation for OpenCode or choose Flex chat or Codex chat from the New menu. A new conversation remains a browser draft until its first message is submitted, so abandoning it does not leave an empty harness session.

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 archived-conversations dialog. 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.

Enrollment search accepts a non-empty printable title query of at most 2,048 UTF-8 bytes and returns at most 512 OpenCode roots. A truncated response means more matches 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 can be grouped and reordered per project. Drag a conversation into a group, reorder it within or between groups, or use a group's context menu to rename or remove the group.

Removing a group does not remove its conversations. The conversations 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.

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.

SmartAgent 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.

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.

Terminals

A terminal resource 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.
  • Reattaching replays bounded in-memory scrollback.
  • 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, detached, or retired.

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

Browsers

A browser resource provides tabs, navigation, viewport control, mouse, keyboard, and text input through a human canvas. Protocol v24 opens one authenticated persistent event stream and one paired operation stream for each human view. Every pair is bound to the exact peer, credential, resource authority, attachment revision, view, and stream generation.

While a view stays open, the controller renews its BrowserRuntime lease before expiry after rechecking the stored resource, exact authenticated viewer and runtime authority. Renewal preserves the frame stream and browser incarnation. Closing the view cancels the renewal timer and aborts pending renewal, and revoked authority cannot be revived.

Frame metadata and image bytes enter the ordered stream together, eliminating the extra metadata acknowledgement round trip. Delivery remains bounded and completes only after both chunks are acknowledged. A full event queue reserves one bounded close announcement, and later protocol faults cannot interrupt that announcement.

State and operation results use bounded JSON packets. A frame is sent as one validated metadata packet immediately followed by its raw image bytes, rather than through a public URL or base64 JSON payload. Operation IDs, pending bytes, acknowledgements, event delivery, and recovery attempts are all bounded. Only adjacent compatible pointer-move or viewport operations can be coalesced, while cancellation retains a separately fenced path to live work.

The browser installs its renderer before activating a view. A recoverable stream failure suspends input, retires the old pair, opens and starts the next stream generation, and resumes the renderer's listeners before activating the replacement. Activation refreshes the runtime frame boundary, and input resumes only after that boundary arrives. Stale activation, recovery, cancellation, and close messages cannot act on another generation.

When attached to a Flex conversation, the resource can provide permission-gated navigate, snapshot, screenshot, click, fill, and press actions to that exact run. Every channel is bound to the project, resource revision, session, run, actor, and current Flex generation.

Agents and multiple human viewers share one browser incarnation and one ordered operation queue. The effective viewport takes the smallest width, height, and device scale requested by connected human viewers, and grows again when the smaller viewer leaves. Viewport acknowledgements include the accepted dimensions and revision, so a larger viewer does not wait for dimensions the shared browser will never use. Input remains fenced to the matching tab, frame generation, and viewport revision.

Changing the attached task revokes the old agent's authority while preserving human viewers and their streams. Independent MCP actions receive temporary capability bindings for the exact project, resource, attachment revision, and native task; cancellation and shutdown retain ownership of capability cleanup until it completes. Trusted Flex channels additionally require the active run and generation.

Frames for closed Flex channels or an exhausted frame backlog are dropped without stopping the Flex child or affecting other tasks. Resource stop and retirement announce view_closed before ending human streams. A resource authority replacement announces resource_changed; an unrecoverable view failure announces view_failed. Close announcements are delivered before stream teardown, and the client reports only unrecoverable failures. Authority replacement can reopen a selected active resource, bounded at three attempts within 30 seconds. Transport recovery allows six attempts within 45 seconds. Runtime attachment checks for high-frequency input, frame, and viewport operations reuse a read that is at most 250 milliseconds old.

Every live session streams JPEG frames at quality 70, scaled by Chrome to fit 2560 by 1600 device pixels, so a high-DPI viewer cannot produce frames near the runtime's 4 MiB frame cap; every produced frame is delivered and idle pages produce none. In the view, wheel input is accumulated and flushed once per animation frame, up to four input commands are in flight at a time, and coalescable input is merged or dropped under pressure instead of exhausting the input queue, so sustained scrolling neither stalls nor resets input. Frames are acknowledged on receipt and only the newest queued frame is decoded and presented once per animation frame. While a view is mounted it shows decoded frames per second, frames skipped by newest-only presentation, and the last input round trip. A pane with no view distinguishes an open in flight, which shows progress, from a closed view and an unavailable runtime, which say so.

Authentication

AGL creates exactly one passkey credential per controller database. Enrollment is first-wins and there is no weaker online passkey-reset path.

The one-time setup code expires after 30 minutes. Omitting --setup-code uses a random 32-byte code. A custom setup code is visible in shell history and weak codes are recoverable by anyone who can read the database, so prefer the generated value.

For temporary or scripted browser access, mint a CLI password:

agl temp-password --ttl-hours 2

Temporary passwords expire after at most 24 hours. Only their SHA-256 hashes are stored, and at most eight can be active.

Authentication belongs to one physical TypedSocket connection. Resume tokens are single-use, rotate on resume, remain only in browser session storage, and are invalidated by controller restart.

CLI

agl start [options]
agl status [--port 4097] [--json]
agl stop [--port 4097]
agl upgrade [--port 4097] [--registry <url>] [--grace-period-seconds 300] [--continue-sessions] [--json]
agl mcp [--port 4097]
agl foreground [options]
agl temp-password [--port 4097] [--ttl-hours 24]
agl help
agl --version

Important startup options:

--port <port>              Controller port; default 4097
--opencode-port <port>     Private OpenCode port; default 4098
--directory <path>         Register one existing project on this start
--projects-root <path>     Base for relative project paths and suggestions
--public-origin <origin>   Exact browser WebAuthn origin
--rp-id <hostname>         WebAuthn relying-party ID
--behind-tls-proxy         Declare trusted TLS termination upstream
--setup-code <code>        Supply an operator-chosen initial setup code

Runtime configuration is durable and immutable per controller port. Later management commands must use the same --port. The --directory option is transient and can register another project during a later stopped start.

stop verifies the exact package CLI, process, process group, command, port, and process-start fingerprint before signaling anything. AGL never kills processes by name.

Upgrades

Use the active global installation:

agl upgrade --port 4097

By default, upgrade package commands use pnpm's effective registry configuration. Override it for one durable upgrade transaction when needed:

agl upgrade --port 4097 --registry=https://registry.npmjs.org

Registry URLs must use HTTPS, except that HTTP is accepted for localhost, 127.0.0.1, and [::1]. They cannot contain credentials, query parameters, or fragments. Keep authentication in pnpm configuration rather than command arguments.

The upgrade worker:

  • Resolves the registry's latest version and never downgrades.
  • Pauses the exact active-session cohort before stopping a running controller.
  • Waits for admitted operations and data writers to drain.
  • Installs while respecting pnpm's configured dependency build policy; AGL uses prebuilt terminal binaries.
  • Restores and restarts the exact source version if installation fails before target startup and, for package transitions, before the commit begins.
  • If the controller was running, restarts the exact target and reopens prompt admission.
  • Preserves its transaction across worker or controller failure.

Use --continue-sessions to submit the continuation message to sessions paused for the upgrade. Without it, AGL reopens admission without sending another message.

If a forward-only upgrade worker is lost after target startup or package commit begins, a later fixed AGL installation can adopt exactly one stalled transaction during an explicit agl upgrade. Adoption requires a free port, no live worker, controller, Flex child, temporary-password writer, or token-bound metadata, and a compatible installed version. It preserves the original preparation timeline and paused cohort. Do not delete upgrade files, copy transactions between roots, downgrade the package, or bypass recovery with agl start.

Upgrade coordination lives under $AGL_HOME/upgrade. Private logs live under $AGL_HOME/logs.

Migrating From hcon

Starting from @modelprofile.com/harness-controller@20.0.2, run the active command twice:

hcon upgrade --port 4097
hcon upgrade --port 4097

The first invocation installs the same-package bridge. The second performs the exact package transition to AGL. Wait for each command to finish and use the controller's configured port. Afterward, use agl for all management commands.

The historical hcon transition uses pnpm's configured registry and does not support --registry. Configure pnpm before starting that migration when a specific registry is required.

AGL Home And Persistence

AGL owns one private root:

  • Global installations: $XDG_CONFIG_HOME/agl, normally ~/.config/agl.
  • Repository checkouts: <checkout>/.nogit/agl.
  • Explicit override: absolute normalized AGL_HOME whose parent already exists.

Important directories include:

  • database for embedded SmartDB data.
  • credentials for sealed Flex provider credentials.
  • git-reversion for private captures and managed worktrees.
  • logs for controller and upgrade diagnostics.
  • upgrade for durable upgrade coordination.
  • runtime for private browser, upload, socket, and OpenCode state.
  • migration for verified migration journals and retained source records.

Do not move these directories or edit migration and upgrade journals by hand.

SmartData stores runtime configuration, passkey metadata, projects, managed-session memberships, crash-recoverable session creation and deletion obligations, layouts, model choices, scratchpads, tasks, goals, resources, Flex public projections, provider metadata, and bounded audit events. Prompt text is not written to audit records.

Terminal I/O, active browser views and their paired transport streams, composer drafts and their pending attachment state, active capability leases, and running processes remain memory-only. Once a prompt is submitted, attachment files exist only in private operation directories for the lifetime of that exact harness operation.

Library consumers can resolve the same home contract:

import { bindAGLHomeEnvironment, resolveAGLHomePaths } from 'agl';

const paths = resolveAGLHomePaths();
const childEnvironment = bindAGLHomeEnvironment(process.env, paths);

Database Configuration

The embedded database requires no separate service. Override its directory with an absolute path:

export HARNESS_CONTROLLER_DB_DIR='/private/absolute/path'

To use an external MongoDB-compatible server:

export HARNESS_CONTROLLER_MONGO_URL='mongodb://user:password@127.0.0.1:27017'
export HARNESS_CONTROLLER_MONGO_DB='opencode_controller'

HARNESS_CONTROLLER_MONGO_DB defaults to opencode_controller.

Runtime Environment

OpenCode, FlexHarness, and terminal shells receive a minimal allowlisted environment. Controller and database variables, unrelated parent secrets, and generated OpenCode credentials are not inherited. AGL_HOME is the intentional exception: AGL forwards its canonical value so controller-owned child processes resolve the same private root.

Delegate additional non-controller variables explicitly when required:

export HARNESS_CONTROLLER_FORWARD_ENV='MY_TOOL_CONFIG,MY_CA_FILE'

Flex provider credentials do not use this mechanism. They stay in the sealed provider store. OPENCODE_AUTH_CONTENT is always rejected; AGL never reads or edits OpenCode's user-wide auth.json.

Credential-free HTTP_PROXY and HTTPS_PROXY values are forwarded. Proxy URLs containing credentials are not forwarded automatically.

Remote Browser Access

Remote WebAuthn requires HTTPS. Start AGL behind a trusted TLS reverse proxy:

export HARNESS_CONTROLLER_TRUSTED_PROXY_SECRET="$(node -e "console.log(require('node:crypto').randomBytes(32).toString('base64url'))")"

agl start \
  --public-origin https://controller.example.com \
  --rp-id controller.example.com \
  --behind-tls-proxy

The proxy must:

  • Remove client-supplied copies of AGL transport headers.
  • Inject the configured secret as X-Harness-Controller-Proxy-Secret.
  • Set X-Forwarded-Proto: https on WebSocket upgrades.
  • Preserve the exact public Host and Origin.
  • Forward WebSocket upgrades.
  • Restrict direct access to the backend listener.

Backend isolation is mandatory. Proxy mode binds the controller to all interfaces, while origin-less loopback CLI management remains intentionally available. Never expose the plain backend port directly.

Security Boundaries

  • Local mode binds the controller to loopback only.
  • The private OpenCode server always remains on loopback and uses a generated per-process password.
  • Application RPC starts only after the exact TypedSocket package-major handshake.
  • Client-managed connection tags are disabled; authentication and routing use server-owned peer state.
  • Setup attempts are globally rate limited before enrollment.
  • Permission auto-accept is an explicit server policy, never an assumption based on an open browser.
  • Questions are never auto-answered.
  • Unknown, malformed, concurrent, or unverifiable lifecycle state fails closed.

The global auto-accept permissions (yolo) setting replies once to pending permissions across registered projects and writes those replies to the audit log. Each conversation also has a run-local Ask/Yolo switch. For an active managed OpenCode root, that switch covers its currently active direct children under the same exact family authority. For an active managed Flex parent, it covers only the exact active delegated child bound to that parent run; a relationship alone grants nothing. Terminal children are never auto-accepted. Disabling either policy seals new automatic replies and drains already admitted replies before returning. Neither mode creates persistent broad provider grants.

Development

Install dependencies and run the project checks:

pnpm install
pnpm run build
pnpm run check:test
pnpm test

Run the CLI from source:

node cli.js help
node cli.js foreground --port 4097

The default test suite uses disposable embedded databases. Set HARNESS_CONTROLLER_TEST_MONGO_URL to include the external MongoDB integration test.

This repository's own source code is licensed under the MIT License. A copy of the license can be found in license.md.

The distributed browser bundle also includes SmartBrowser 4.0.2 under the MIT License and PDF.js 4.10.38 under the Apache License, Version 2.0. See third-party-notices.md for attribution, modification details, and license terms.

Please note: The MIT License does not grant permission to use the trade names, trademarks, service marks, or product names of the project, except as required for reasonable and customary use in describing the origin of the work and reproducing the content of the NOTICE file.

Trademarks

This project is owned and maintained by Task Venture Capital GmbH. The names and logos associated with Task Venture Capital GmbH and any related products or services are trademarks of Task Venture Capital GmbH or third parties, and are not included within the scope of the MIT license granted herein.

Use of these trademarks must comply with Task Venture Capital GmbH's Trademark Guidelines or the guidelines of the respective third-party owners, and any usage must be approved in writing. Third-party trademarks used herein are the property of their respective owners and used only in a descriptive manner, e.g. for an implementation of an API or similar.

Company Information

Task Venture Capital GmbH
Registered at District Court Bremen HRB 35230 HB, Germany

For any legal inquiries or further information, please contact us via email at hello@task.vc.

By using this repository, you acknowledge that you have read this section, agree to comply with its terms, and understand that the licensing of the code does not imply endorsement by Task Venture Capital GmbH of any derivative works.

S
Description
Agent Gateway Layer
Readme
54 MiB
Languages
TypeScript 100%