Skip to content

AbstractGateway v0.13.0

Choose a tag to compare

@github-actions github-actions released this 05 Oct 01:41
· 1024 commits to main since this release

Requires AbstractRuntime 0.9.0, AbstractCore 2.25.0 and AbstractVoice 0.14.0 (installed automatically). The terminal console abstractgateway-console 0.15.0 matches this release; see console-tui/CHANGELOG.md.

Fixed

  • A Read aloud during a generating turn no longer hangs the gateway (the 2026-10-04 21:23 watchdog restart, exit 75). A write request without a Content-Length (the console's /apps/<id>/ proxy streams request bodies, so Code opened from the console sends its Read aloud chunked) is buffered by the security middleware and replayed to the app; after the body the replay answered "empty body" forever without waiting. A streaming response listens for the client leaving by looping on receive(), so the stream became a busy loop on the event loop: nothing else ran, health probes failed, and the watchdog restarted the gateway after 30 s. The replay now hands over to the connection's own receive after the body. Every POST streaming endpoint reached by a chunked client had the same hole.

  • The installer's pending apps upgrade (apps-upgrade.pending) no longer brings an app updated in place from the Apps page back down to the installer's version.

  • The Apps card's text buttons (Install, Open, Update, Stop, the terminal app's buttons) show the kit tooltip instead of the browser's native one.

  • Apps → Assistant opens the Assistant it reports. With the package installed and an old AbstractAssistant.app also present, the card showed the package's version but Open started the app bundle. The gateway now reports, opens and watches one Assistant: the installed package, else the bundle. When the other one runs, the Apps card and the tray say so in one sentence ("Another Assistant is running: /Applications/AbstractAssistant.app 0.5.0 — quit it to use 0.13.0") and Open still starts the installed one (desktop.other_running on the app row).

  • The tray's Workflows submenu said "No runs in the last 24 hours" while runs were active: automation occurrences (whose parent is their controller) were left out, the run page was cut before sub-runs were filtered out, and runs that started more than 24 hours ago were hidden while still running.

  • /v1/chat/completions checks response_format through AbstractRuntime's structured_facade instead of importing AbstractCore, so the gateway again reaches AbstractCore only through AbstractRuntime (the import-boundary test is green). Requests and answers are unchanged.

  • The agent knows its workspaces. A run that names no workspace_root writes a relative path such as out.txt to its conversation's private session folder (<data dir>/workspaces/session-…; a run without a conversation, its own <data dir>/workspaces/<run>). The agent's workspace context lists each allowed workspace with its mode ((read-only) / (read & write), the level's narrowing included), plus Everything else: (…) under "Allow everything, refuse listed workspaces". Needs AbstractRuntime 0.9.0. Docs: console, security, configuration, api.

  • Sound effects have a length. POST /api/gateway/runs/{run_id}/music/generate takes seconds (same value as duration_s; 400 when the two disagree, 422 outside 0–3600) and the console Sandbox shows a Length (seconds) field in SFX (default 5) and Music (default 30) mode. With AbstractMusic's matching fix the output.sound route runs its own model (stabilityai/stable-audio-3-small-sfx) for the requested length: "laser gunshot" with 3 seconds is a 3.0 s clip (it was a 30 s clip from the music checkpoint).

  • POST /runs/{run_id}/voice/tts/stream prepares the stream (run load, durable child run, engine call) off the event loop: the gateway keeps answering /api/health and every other request while speech is set up and synthesised.

  • GET /api/gateway/about (and every About that reads it) reports the AbstractFramework release the installer recorded (bootstrap.env FRAMEWORK_VERSION) before the abstractframework package metadata, so installer installs show the right AbstractFramework version.

  • Models page: the filters-in-use label no longer overlaps the "N of M models" count on a narrow screen.

Security

  • Commands are sandboxed by the operating system. Every process-spawning tool (execute_command, shell_exec, the local helpers) runs inside an OS sandbox built from the run's effective workspaces: macOS sandbox-exec, Linux bubblewrap or Landlock. These are the keys the file tools read: private workspace read & write, allowed rows with their modes, refused rows, built-in refusals, the posture's default. Before this, a run that refused ~/Desktop could still ls it through execute_command. With no sandbox on the host, commands are refused with one sentence. The new flag abstractgateway serve --unsandboxed-commands (also runner; off by default, no environment variable) re-enables them unsandboxed; it is audited at boot as command_sandbox_configured. The gateway sets AbstractCore's host policy once per process at boot (command_sandbox.configure_at_boot): every command starts from the gateway's scrubbed environment, the same scrub as the apps (no token, secret, password or key, nothing ABSTRACTGATEWAY_*/ABSTRACTCORE_*). GET /workspace/policy and GET /discovery/tools carry command_sandbox {state, kind, line, sentence, unsandboxed_commands_allowed, configured, flag}. In /discovery/tools, process-spawning rows carry sandboxed: true|false and sandbox ("Sandboxed to this run's workspaces"). The web console shows the line under the Accounts head with the kit tooltip ("Commands sandboxed: macOS sandbox-exec" / "Commands refused: no sandbox on this host" / "Unsandboxed commands allowed (flag)"); the terminal console shows it on its Workspaces page. Each command's enforced paths ride the run ledger (the runtime's _sandbox stamp) and the tool result (sandbox). Needs AbstractCore with abstractcore.tools.sandbox and AbstractRuntime with SANDBOXED_TOOL_NAMES (round 12); floor abstractagent>=0.3.18 (its Python execution runs in the sandbox). Docs: security (new "Command sandbox"), configuration, api, console.
  • Workspaces: the most specific row wins. Rows may nest in any combination, at every level (gateway, account, session, run). Refusing /Users/me while allowing /Users/me/projects (read & write) is valid: the child is reachable and the rest refused. A refused row inside an allowed one refuses that subtree. The round-11 refusal of a row nested under a refused row is gone. Caps still bind: a row below the gateway never exceeds the gateway's most specific row at its path. Built-in refusals are absolute: a read-only or read & write row inside one is refused with '<path>' is inside the built-in refused workspace '<built-in>'. at every level, at the dry run, and in in-process clamps. The one exception is the account's own conversation folders inside the data folder. The effective set, its line, the run-start keys (refused rows → workspace_ignored_paths, resolved by the runtime with the same longest-prefix rule), the server file routes and the run workspace browser all follow the rule.

Changed

  • Automations follow your default workspaces at each run. An automation saved with Use my default now stores target.input_data.workspace = {"configured": false}. Before, the gateway froze a snapshot of the default (workspace_allowed_paths …) into the definition, so a wider default never reached it, and a client echoing that snapshot narrowed it. Each occurrence now resolves session > account > gateway when it is admitted, through AbstractRuntime's Runtime.set_occurrence_input_resolver, wired for each plane's owner by the bundle host. The definition keeps only a fail-closed fallback: the built-in denies and workspace_only. Derived keys echoed with configured: false are ignored. A definition saved before this change (no payload, gateway-derived keys at the account level) is recognised and followed the same way. Security fix (since round 11): every automation, including one with an explicit choice, is now re-checked against the current policy when each occurrence is admitted. Before, an explicit choice kept the keys derived when it was saved, so a workspace the admin refused or capped read-only afterwards stayed reachable, sandbox included, until someone edited the automation. Save-time derived keys are never trusted. The stored choice is re-clamped to the current eligible set and caps, and each dropped or lowered row is recorded with the gateway's sentence under _gateway_workspace.clamped. Requires abstractruntime with set_occurrence_input_resolver; the host refuses to build without it.

  • Console islands and console themes re-vendored from ui-kit 0.8.5 (round 13). In the kit, the schedule dialog gained a Workspaces slot and lost its Advanced disclosure, and tool rows gained a .af-tool-row__state badge style. The islands bundle only picks up its version string and the new component CSS; the themes only their provenance hash. The round-11 islands test now accepts kit 0.8.3 or later.

  • Apps: the status badge is the start/stop control. On every Apps card (web console and terminal console) Running stops the app and Stopped (or a crash) starts it, in one click as Install already is (POST /apps/{id}/stop / /launch, the requests the old buttons sent); the separate Stop and Start buttons under Technical details are gone (Open stays). The Assistant's badge quits the Assistant this gateway opened (POST /apps/assistant/stop now quits it; any other running Assistant is refused with 409 started_outside_gateway and left alone) and opens a stopped one. An app started outside the gateway shows the badge disabled with "Started outside the gateway — stop it where it was started"; for a user who is not an admin every badge is disabled with "Only an admin can start or stop apps". Every apps row carries the badge (status_control {label, tone, busy, action, enabled, tip}), so both consoles say the gateway's words: the web console as a button with the kit tooltip (focusable, Enter or Space; "Stopping…"/"Starting…" while its request runs), the terminal console as a selectable status cell (→ onto it, ← back, Enter or Space; s anywhere; the hint line says the tooltip). An installed, stopped app's badge reads Stopped (was "Installed").

  • Workspaces: three levels, no shared workspace (supersedes the 0.13.0 "two dimensions", "shared workspace" and "accounts narrow" lines below). The gateway policy defines the eligible set: the posture (Deny everything, allow listed workspaces / Allow everything, refuse listed workspaces with a default mode) and rows whose mode (Read-only, Read & write, Refused) is a cap; built-in refusals stay; a fresh gateway allows everything, read & write. An account (person or entity) picks its own default subset with the same shape (GET/PUT /api/gateway/workspace/policy/{account|me} → {account, configured, posture, default_mode, folders}; {configured: false} follows the gateway): any path inside the eligible set, at most at its cap. A conversation picks its own subset (GET/PUT /api/gateway/sessions/{id}/workspaces, stored by the gateway in the owner's plane, before or after the first run; "Use my default" = {configured: false}), and a run may carry a one-off workspace (start body or input_data.workspace; an automation keeps it in target.input_data.workspace, an older workspace_allowed_paths list is converted). Every door resolves run > session > account > gateway, clamps to the eligible set and caps (a client never widens: refused at the HTTP doors; at the in-process doors dropped or lowered and recorded with its sentence as _gateway_workspace.clamped, shown by GET /runs/{id}/workspace) and records _gateway_workspace.{level, summary} on the run. GET /api/gateway/workspace/effective/{account}[?session=] answers {level, posture, default_mode, folders: [{path, mode, cap, source}], summary, gateway_summary}; POST on the same route is a dry run for an unsaved payload. Every refusal is 400 {"detail": {"reason": "workspace_refused", "message", "path"}}. shared_workspace is gone (a PUT carrying it: "list it as a workspace"), as are builtin_never_allowed (now builtin_refused) and the run key workspace_shared_path. Entity workspaces are set by admins and the entity's creator. Migration at serve start (_migrated.workspace_policy_v2, audited, idempotent): posture "Allow everything, refuse listed workspaces", the old shared workspace a listed read & write row, rows kept as caps, each round-9 account entry a configured account layer. Docs: api, security, configuration, architecture, maintenance.

  • Every entity has a gateway account. Entity homes created before entity accounts existed (on older gateways, for example castor and mnemosyne) get one at serve start, minted like a creation (roles entity, token discarded), audited (entity_account_created, reason migration), once. Every Accounts row that is not archived offers the workspace action; "This entity has no gateway account yet" is gone.

  • Console: Accounts → Workspaces on the three-level model (no shared workspace). Eligible workspaces (administrators; the button that was "Shared workspace & allowed workspaces") holds the gateway's set: the posture, Everything else, and rows whose mode (Read & write / Read-only / Refused) is the cap no account can exceed, with the gateway's own data directory and credentials shown as always refused. The workspace icon on every Accounts row (users, entities — legacy ones included — and your own; an entity's for its creator and administrators) opens that account's level: the line "Gateway: …", Follow the gateway policy as a switch, the account's own posture and rows, a mode above a workspace's cap greyed with the tooltip "The gateway allows this workspace read-only". Both dialogs are the kit WorkspaceChooser (ui-kit 0.8.2, islands re-synced), the same component and words as the apps; every change is one PUT, a refusal shows the gateway's sentence with "Not saved.", there is no Save button. The console no longer path-checks rows itself (the gateway's PUT answers the sentence).

  • Apps: every row shows an available update, and the Assistant updates in one click. The Assistant's card reads the newest abstractassistant from PyPI and offers Update to x.y.z beside Open, as the browser apps now do too (it was under Technical details): the install job with abstractassistant==<latest> and the gateway's own pins. An Assistant the gateway opened is quit and opened again on the new version, signed in; one started elsewhere is left alone with "Quit it and open it again to run x.y.z". An app started outside the gateway shows "Latest x.y.z · Started outside the gateway — update it where it was installed", with no action; an Assistant installed from a source checkout (editable, read from its direct_url.json) shows the version its checkout's pyproject.toml declares and "Latest x.y.z · Installed from a source checkout — update it there", with no action. An update whose requested version did not land fails ("Asked for 0.14.0, but 0.13.0 is still installed — not updated.") and restarts nothing. Label and tooltip come from the gateway (update_label, update_tip), the same in the web console (kit tooltip) and the terminal console (u, with the tooltip as its confirmation). The npm registry, PyPI and GitHub share one cache (10 minutes, 1 minute after a failure), so a version published while the gateway runs shows up without a restart.

  • Terminal console Workspaces page parked. abstractgateway-console's W Workspaces shows one sentence, "Workspaces are managed from Accounts in the web console; the terminal console follows in the next update.", and sends no request: it no longer calls the routes this release removed (/admin/user-workspace-policy, /workspace/policy/self, the runtime-config workspace_* keys). Its Runtimes knobs no longer list workspace settings.

  • Console top bar: a memory and compute line replaces the address. Memory used of total (%), GPU busy % and the number of loaded models — the desktop tray's glance — from the Resources data (GET /api/gateway/host/state), refreshed every 5 seconds while signed in and the browser tab is visible. Its tooltip lists the real values (RAM, accelerator heap, model weights, KV caches, GPU load); a click opens Resources. The address and its copy button left the top bar: every address, with Copy, is on the Network page.

  • Tray Workflows submenu: what runs now comes first. Running runs, then runs waiting for a person or an event, each with the time elapsed so far, then the runs finished in the last 24 hours, newest first; each row opens Open in Observer (signed in, at that run). The tally reads Now: 2 running · 1 waiting — last 24 hours: 3 done.

  • GET /api/gateway/host/runs lists turn roots in the runtime's sense — automation occurrences included, automation controllers and sub-runs not — with activity (running when the root or a run below it runs, waiting, done), observer_path and active_count. Active rows are always listed, whatever their age and whatever limit; finished rows are windowed on their last update.

  • Apps page has no settings disclosures. Advanced: apps settings and Advanced: backlog settings (Continuum) are gone: the gear in the Apps toolbar opens Apps settings, the gear beside Continuum's Open opens Continuum settings (backlog folder, exec runner and process manager as switches). Every row applies on its own (a field on blur or Enter, a switch at once) and says "Saved" beside itself; there is no Save button.

  • The backlog folder and the backlog exec runner read no environment variable. The folder is the --backlog-root launch flag, the saved setting, or the gateway's own folder; the runner is --exec-runner on|off, the saved setting (the switch in Continuum settings), or off. A value an older launcher still exports (ABSTRACTGATEWAY_TRIAGE_REPO_ROOT / ABSTRACT_TRIAGE_REPO_ROOT, ABSTRACTGATEWAY_BACKLOG_EXEC_RUNNER / ABSTRACT_BACKLOG_EXEC_RUNNER) is stored once as the saved setting (at the next start), then the variable is ignored.

  • Workflows for non-admins: "Used by" shows the apps' plain names (the interface id is the tooltip) from the new interfaces map in GET /api/gateway/bundles, and Default workflow per app (an admin setting) is hidden instead of ending the page with an error.

  • Workflows rows no longer expand. With Older versions on, each older version is its own row with its own actions. Export, Open in AbstractFlow and Archive are icon buttons with tooltips in one row that never wraps.

  • Skills & MCP: the shelf folder is one row under the Skills list (folder field and Refresh curated shelf), not a disclosure.

  • Email for everyone shows its three switches (Mailboxes for users, Agent email tools for users, Sign-in by email) directly in the card; the Advanced disclosure is gone.

  • The OpenAI-compatible endpoint moved from /core/v1 to /v1. /core/v1 answers 308 to /v1 (method and body kept) and is deprecated. API keys are gateway tokens; the 0.12.0 endpoint token is still accepted (deprecated). Its settings moved from the Network page to the OpenAI API page (Network keeps a pointer). Migration: point your apps at /v1 and use each caller's gateway token as the key. A 0.12.0 settings file without Who can connect reads as Devices on my network.

  • Models page is one list with compact cards. Downloaded models no catalog build accounts for are rows under Not in the catalog in the same list (Status All / Downloaded, the same Delete, included in the chip counts); the separate On this computer list is removed. Each card's header is one line (name, organisation, parameters, licence, capability tags, badges) and each build is one line (engine · id · quant · size · status chips · Use as default · Delete); the recommended build keeps a small accent dot. On narrow windows the facts move under the id.

  • Console About is the shared compact card (ui-kit 0.8.0 islands, API "2"): name and version, the AbstractFramework version on this host, the gateway version, six links and the licence line. The per-package list stays on GET /api/gateway/about.

  • Accounts table: columns Name · Email · Runtime · Active · Actions. Email is one column, the address and the mailbox state (alice@example.org · connected, or "No address"). Runtime links to the Runtimes page filtered to that account. The actions are icon buttons (Email, OpenAI API, Logs, Workspace, Manage, Rotate token, Archive, Unarchive), 44 px targets, with no "⋯" menu; Rotate token asks inline. The table never scrolls sideways; below about 900 px each account is a flat block. The Workspace policy disclosure and dialog are removed; the Runtimes page's Workspace column opens the folder dialogs.

  • Tooltips on every icon button in the console (Accounts, Workflows, Models, Apps, OpenAI API, Runtimes, dialogs, the top bar) are the kit tooltip (ui-kit 0.8.1 islands bindTooltips): one explicit sentence per button ("Email address and mailbox of alice", "OpenAI API access for alice", "Activity log of alice", "Workspaces alice's agents may use", "Manage castor (mind, voice, prompt…)", "Rotate alice's sign-in token", "Archive alice (kept, hidden)", "Export basic-agent as a .flow file", "Delete from this computer (files only)"…), themed for light and dark, after 150 ms, on keyboard focus too, hidden by Escape, kept inside the window. No native title on icon buttons.

  • Console islands and kit theme CSS re-synced with ui-kit 0.8.1 (bindTooltips, mountWorkspaceChooser).

  • X-Forwarded-For from a proxy on the gateway machine is always used for the client address; the switch, renamed Trust proxies on other machines, extends it to proxies elsewhere.

  • GET /api/gateway/runs?include_metrics=true returns each run's totals across its sub-runs (subworkflows, agent sub-runs) on every store, the default file store included: steps, llm_calls, tool_calls and tokens_total are read from the ledger. Terminal runs are cached by run id and update time.

  • Dependency floors: AbstractRuntime>=0.9.0 (base, apple, gpu), abstractcore>=2.25.0 (base, embeddings), abstractvoice>=0.14.0.

Added

  • Each app's documentation for its Docs assistant: GET /api/gateway/docs/corpus?app=<id> (code, flow, observer, continuum, entity) returns the llms.txt the running app serves from its own build (GET /llms.txt on the app's loopback port, text/plain only), so the corpus always matches the app version in use. Not running, unknown, or an app without one: a 404 that says which. Omitted or app=gateway: the gateway's own corpus, as before.

  • Session purpose kind (conversation | docs): POST /api/gateway/runs/start takes kind: "docs" (with a session_id) for a Docs assistant chat; GET /api/gateway/runs?root_only=true defaults to kind=conversation, so docs chats never appear in the conversation lists of Code, Observer, Entity, Continuum, Flow or the console, with no app change; kind=docs is the Docs assistant's history and kind=all both. Docs chats stay in the one pool (readable by session_id, ledgered, archivable); every row carries kind; archived_sessions counts the listed kind. Stored in the plane's session_kinds.json (no mark = conversation). A run of the shipped docs-qa workflow marks its session docs whichever client starts it (the terminal console too), and a one-time migration (docs_qa_bundle_v1) marks every existing session whose turn root ran docs-qa (matched on the bundle id), so older docs chats leave the conversation lists.

  • The console's Docs assistant is the shared kit drawer (panel-chat DocsAssistantDrawer via the islands bundle's mountDocsAssistant, ui-kit 0.8.0): the same chat as every app — your question on the right, the answer on the left with Markdown, code, JSON and links, copy, attachments, live streaming when Streamed replies is on, Stop, an icon-only New conversation and close, and one footer line naming the grounding (the gateway's llms.txt through docs-qa 0.1.1, the version the terminal console pins); a Past conversations icon lists, reopens and archives earlier docs chats. It opens from the top bar's book button. The hand-built drawer and its text New conversation button are gone.

  • OpenAI API at /v1 with the standard OpenAI layout (/v1/models, /v1/chat/completions, /v1/embeddings, ...) and its own console page, Models → OpenAI API: Running/Stopped status with the Endpoint switch, Restart (ends open requests) and Check setup; Connect your app (base URL, your gateway token as the API key, New key); Access (Authentication Protected or Open, Requests without a key run as, Who can connect: this machine, your network, your tailnet or anywhere), applied immediately; supported surface and curl/Python/JavaScript examples with the real base URL; recent requests. Status for every account at GET /api/gateway/openai-api; admin POST /api/gateway/admin/core-endpoint {enabled, access, reach, open_account}, /restart and /check. See docs/openai-api.md.

  • Per-account OpenAI API access. Each user account has an OpenAI API switch (Accounts → the row's OpenAI API button; admin PUT /api/gateway/admin/accounts/{id}/openai-api {enabled}; account rows carry openai_api). It is on by default for an active account and written explicitly (on for active, off for inactive accounts) the first time the endpoint starts. Off, the account's key answers 403 openai_api_off; a deactivated or archived account's key answers 403 account_inactive. Entities have no key.

  • Open mode runs requests without a key as the account the admin picks (open_account): by default the built-in Guest, which may use the models (chat, responses, embeddings, text-to-speech, image generation) with no tools, files, attachments or media inputs and no /api/gateway access (403 guest_not_allowed), or a user account, whose models, providers and log it uses. Never an admin.

  • OpenAI API page per role: an admin sees Status, Connect, Access, Docs and every request; any other account sees whether the API runs, the base URL, their own key, the docs and their own requests. The key card shows the signed-in person's gateway token masked, with an eye and Copy, filled in by the browser from the token it kept at sign-in (no route returns a stored token; the gateway sends only a SHA-256 fingerprint to check the copy). The examples embed it, masked on screen and copied in clear.

  • Request log with recorded requests and responses. Each row opens to the request and the response as they crossed /v1 (credential-named fields and the caller's key read [redacted], inline media becomes its type and size, 256 KB per side), with Open in Observer when the request belongs to a run. GET /api/gateway/openai-api/logs (rows carry request_id and recorded) and GET /api/gateway/openai-api/logs/{request_id}; a non-admin reads only their own requests. Stored in the audit log.

  • Structured outputs at /v1/chat/completions: response_format json_object and json_schema (and the SDK's chat.completions.parse) for every provider, through AbstractCore's structured-output support (constrained decoding where the provider has it, the schema in the prompt otherwise); the answer is validated against the schema with feedback retries. 500 structured_output_invalid when it never matches, 400 invalid_response_format for an unusable schema.

  • OpenAI conformance at /v1, checked with the official openai Python SDK: the standard error envelope {"error": {message, type, param, code}} (validation errors answer 400), GET /v1/models/{id}, structured tool_calls for every model, chat stream chunks with the role on the first delta and finish_reason on the last, stream_options.include_usage, max_completion_tokens, base64 embeddings (the SDK default), 404 model_not_found for an unknown provider or a model id that is not provider/model, and 400 unsupported_parameter for response_format with tools, n > 1, logprobs and logit_bias. Request fields that would re-route a provider or carry a credential (base_url, api_key, provider, provider_kwargs, headers, and the like) are refused with 400 unsupported_parameter in JSON bodies, form bodies and query strings. x-request-id on every response; browser SDKs that send a key may call from any origin.

  • Delete a downloaded model from the Models page: a trash-bin Delete on every downloaded build, an inline confirmation with the size from the gateway ("Deletes 351 MB from this computer. Files only — nothing in your runs is touched."), a busy state, then "Not downloaded" and what was freed. Admin route POST /api/gateway/models/delete-download (model_download_delete_v1, dry_run measures) removes the files with the engine's own mechanism (Ollama delete, Hugging Face / MLX cache folder, one GGUF quant's files) and refuses a model that is loaded or locked ("Unload it first"), still downloading, or managed by LM Studio. Audited as model.download_deleted / model.download_delete_refused.

  • Archive a conversation: POST /api/gateway/sessions/{session_id}/archive and /unarchive (the session's owner or an admin; runs, ledgers and artifacts are kept; audited as session.archived / session.unarchived). GET /api/gateway/runs?root_only=true leaves archived sessions out, archived_only=true lists only them, and every listing carries archived_sessions. An archived session stays readable by its id.

  • automation.unarchive brings an archived automation back paused with its history. GET /api/gateway/automations leaves archived automations out unless archived_only=true (or status names archived) and carries archived_automations.

  • Voice defaults: GET /api/gateway/voice/defaults returns the default voice routes for every app — tts = the output.voice route (provider, model, voice), stt = the input.voice route (provider, model), each with configured and, when unset, a note. A request that names no engine runs exactly this route. The voice catalog (GET /voice/voices) reports the same: active_tts_provider / active_stt_provider are the configured routes (absent when none is set) and it carries gateway_defaults.

  • POST /runs/{run_id}/audio/transcribe answers with the route that ran (provider, model) and the server time (duration_ms).

  • Event-loop watchdog (serve --watchdog-seconds N, default 30, 0 = off): when the event loop has not run for N seconds, the gateway writes the stack of the blocked event-loop thread and every thread's stack to its log and exits with code 75, so the LaunchAgent (KeepAlive/SuccessfulExit: false), the systemd unit (Restart=on-failure) or the local supervisor restarts it. A GIL-independent faulthandler timer exits (code 1) 15 s later when native code holds the interpreter lock. GET /api/health reports watchdog: {enabled, limit_s, last_tick_age_s}.

  • Network: the Tailscale name (when the Tailscale CLI is installed and Tailscale runs) is listed with the addresses, and every detected address (LAN, Bonjour, Tailscale) is an accepted browser origin without a manual entry or a restart. Reached through another address? explains reverse proxies and Tailscale in plain words; manual allowed origins and proxy trust sit under Advanced.

  • Workspaces in the console, on Accounts (no separate page): Shared workspace & allowed workspaces (administrators, first at the top of Accounts) opens the gateway policy — the posture as a segmented switch (Deny everything, allow listed workspaces with the Allowed workspaces rows / Allow everything, refuse listed workspaces with one Everything else permission and the Refused workspaces rows), the shared workspace (required, always Read & write), each row Read-only, Read & write or Refused — with the exact line on top ("Deny everything, allow listed workspaces · Shared workspace (rw) · /data/project (rw) · /archive (ro)"). The Workspace icon on every account row (users, entities, your own) opens that account's workspaces with the kit chooser shared with AbstractCode and the Assistant (narrowing only), the gateway's one-line summary and Follow the gateway policy. Every change applies at once; a row applies when you leave it, after a path check that says why a path cannot be used (such a row is not saved), with an inline "Saved". An entity's workspaces are an administrator's: its row offers Workspace to admins only. A #workspaces link opens Accounts. See docs/console.md.

  • POST /api/gateway/workspace/path-check {path} → {path, normalized, absolute, exists, is_dir, valid, sentence}: the same rules the policy writes enforce (an absolute path to an existing folder), with a plain sentence when it fails. Any signed-in principal.

  • GET /api/gateway/admin/runtimes?account=<id>[&tenant_id=<t>] lists only that account's runtimes and echoes filter. The console's Runtimes page reads #runtimes?account=<id>, shows the filter as a removable chip and opens the account's single runtime at once.

  • Terminal console: pages in the web console's sidebar order with the web pages' data, actions and words — Accounts, a Workspaces page, Workflows, a Skills & MCP page, Providers with the local engines, OpenAI API, Models, Multimodal, Resources, Sandbox, Network, Setup and About. See console-tui/CHANGELOG.md and docs/console.md.

  • Edit a workflow's description where it is listed: the owner (an admin for the gateway's workflows) clicks the pencil, types, and Enter or leaving the box saves it; Escape cancels. PATCH /api/gateway/bundles/{bundle_id} {description} ("" goes back to the file's own text); shipped workflows refuse (409), a user on a shared workflow gets 403; the .flow file is never rewritten; audited as workflow.description. GET /bundles items carry description_edited and actions.can_edit_description.

  • The default shared workspace is never a guess. The stored value wins. Without one, the gateway settles it once at its first start: the legacy ABSTRACTGATEWAY_WORKSPACE_ROOT/_DIR (imported once); otherwise an existing install's current value, frozen, so nothing moves; otherwise <data_dir>/workspace for a fresh data folder, created on first use and reachable despite the data-folder deny. The runtime's repo-root guess and the working folder are no longer used. The decision is recorded under _migrated.shared_workspace_v1 and audited.

  • Workspaces, two dimensions. What can be reached is the posture: Deny everything, allow listed workspaces or Allow everything, refuse listed workspaces (with one default mode for every unlisted directory). How is set per workspace: Read-only, Read & write, or refused. The Shared workspace is always in, read & write; each conversation keeps its private session folder. Gateway policy GET /api/gateway/workspace/policy (everyone) / PUT (admin; any subset of {shared_workspace, posture, default_mode, folders: [{path, mode: ro|rw|deny}]}). Accounts (people, entities, the admin's own) only narrow: GET/PUT /api/gateway/workspace/policy/{account} (me = the caller; an entity's by an admin) with {default_mode: "ro"|null, folders: [{path, mode: ro|deny}]}. GET /api/gateway/workspace/effective/{account} is the set the gateway enforces, with one line for every surface (Deny everything, allow listed workspaces · Shared workspace (rw) · /data/project (rw) · /archive (ro)). Read-only workspaces are enforced by the runtime (writes refused with a sentence, reads allowed); a read-only default relies on AbstractRuntime's new workspace_writable_paths. A workspace inside the data folder (another conversation of the same account) counts only for the account whose data plane holds it. Audited as workspace_policy_changed. See docs/security.md and docs/api.md.

Removed

  • The old workspace model: the Allow my list / Allow everything except access modes, per-user allow/deny lists, launch-folder trust (a launch folder is reachable exactly when the posture says so) and Any folder (old clients). Its stored settings are migrated once, at serve start or the first read, without widening anyone. An old "allow everything except" gateway becomes "Allow everything, refuse listed workspaces". Otherwise the gateway becomes "Deny everything, allow listed workspaces": the old extra workspaces and every account's allowed folders become read & write rows, and accounts that did not have a folder get a refused row for it. Refused folders become refused rows. Previously trusted launch folders are not added. Missing folders are listed under _migrated.workspace_policy_v1 with the old block. The runtime-config keys workspace_root, workspace_mounts, workspace_allowed_paths, workspace_blocked_paths, workspace_default_mode, trust_client_launch_folder, client_workspace_scope_overrides and user_workspace_policies are refused on write and no longer read. GET/PUT /api/gateway/workspace/policy/self (now 410) and /admin/user-workspace-policy are gone; ABSTRACTGATEWAY_ALLOW_CLIENT_WORKSPACE_SCOPE / ABSTRACTGATEWAY_TRUST_CLIENT_WORKSPACE_SCOPE are no longer read. A run start with workspace_access_mode: "all_except_ignored" from a client, or a workspace the posture does not reach, answers 400. Every run's tool sandbox, whatever door started it, is bound by the host to the account's effective set. The /files/* helpers follow the admin account's effective set and only narrow on client parameters.
  • The admin-only POST /api/gateway/admin/core-endpoint/token/reveal: no route returns a stored token.