Repository navigation
AbstractGateway v0.13.0
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 onreceive(), 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.appalso 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_runningon 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/completionschecksresponse_formatthrough AbstractRuntime'sstructured_facadeinstead 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_rootwrites a relative path such asout.txtto 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), plusEverything 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/generatetakesseconds(same value asduration_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 theoutput.soundroute 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/streamprepares the stream (run load, durable child run, engine call) off the event loop: the gateway keeps answering/api/healthand 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.envFRAMEWORK_VERSION) before theabstractframeworkpackage 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: macOSsandbox-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~/Desktopcould stilllsit throughexecute_command. With no sandbox on the host, commands are refused with one sentence. The new flagabstractgateway serve --unsandboxed-commands(alsorunner; off by default, no environment variable) re-enables them unsandboxed; it is audited at boot ascommand_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, nothingABSTRACTGATEWAY_*/ABSTRACTCORE_*).GET /workspace/policyandGET /discovery/toolscarrycommand_sandbox{state, kind, line, sentence, unsandboxed_commands_allowed, configured, flag}. In/discovery/tools, process-spawning rows carrysandboxed: true|falseandsandbox("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_sandboxstamp) and the tool result (sandbox). Needs AbstractCore withabstractcore.tools.sandboxand AbstractRuntime withSANDBOXED_TOOL_NAMES(round 12); floorabstractagent>=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/mewhile 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'sRuntime.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 andworkspace_only. Derived keys echoed withconfigured: falseare 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 withset_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__statebadge 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/stopnow quits it; any other running Assistant is refused with 409started_outside_gatewayand 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;sanywhere; 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-offworkspace(start body orinput_data.workspace; an automation keeps it intarget.input_data.workspace, an olderworkspace_allowed_pathslist 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 byGET /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};POSTon the same route is a dry run for an unsaved payload. Every refusal is400 {"detail": {"reason": "workspace_refused", "message", "path"}}.shared_workspaceis gone (a PUT carrying it: "list it as a workspace"), as arebuiltin_never_allowed(nowbuiltin_refused) and the run keyworkspace_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, reasonmigration), 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 onePUT, 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'sPUTanswers the sentence). -
Apps: every row shows an available update, and the Assistant updates in one click. The Assistant's card reads the newest
abstractassistantfrom 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 withabstractassistant==<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 itsdirect_url.json) shows the version its checkout'spyproject.tomldeclares 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-configworkspace_*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/runslists turn roots in the runtime's sense — automation occurrences included, automation controllers and sub-runs not — withactivity(runningwhen the root or a run below it runs,waiting,done),observer_pathandactive_count. Active rows are always listed, whatever their age and whateverlimit; 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-rootlaunch 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
interfacesmap inGET /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/v1to/v1./core/v1answers308to/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/v1and 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 nativetitleon icon buttons. -
Console islands and kit theme CSS re-synced with ui-kit 0.8.1 (
bindTooltips,mountWorkspaceChooser). -
X-Forwarded-Forfrom 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=truereturns each run's totals across its sub-runs (subworkflows, agent sub-runs) on every store, the default file store included:steps,llm_calls,tool_callsandtokens_totalare 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.txton the app's loopback port,text/plainonly), so the corpus always matches the app version in use. Not running, unknown, or an app without one: a 404 that says which. Omitted orapp=gateway: the gateway's own corpus, as before. -
Session purpose
kind(conversation|docs):POST /api/gateway/runs/starttakeskind: "docs"(with asession_id) for a Docs assistant chat;GET /api/gateway/runs?root_only=truedefaults tokind=conversation, so docs chats never appear in the conversation lists of Code, Observer, Entity, Continuum, Flow or the console, with no app change;kind=docsis the Docs assistant's history andkind=allboth. Docs chats stay in the one pool (readable bysession_id, ledgered, archivable); every row carrieskind;archived_sessionscounts the listed kind. Stored in the plane'ssession_kinds.json(no mark = conversation). A run of the shippeddocs-qaworkflow marks its sessiondocswhichever client starts it (the terminal console too), and a one-time migration (docs_qa_bundle_v1) marks every existing session whose turn root randocs-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
DocsAssistantDrawervia the islands bundle'smountDocsAssistant, 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
/v1with 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 atGET /api/gateway/openai-api; adminPOST /api/gateway/admin/core-endpoint {enabled, access, reach, open_account},/restartand/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 carryopenai_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 answers403 openai_api_off; a deactivated or archived account's key answers403 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/gatewayaccess (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 carryrequest_idandrecorded) andGET /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_formatjson_objectandjson_schema(and the SDK'schat.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_invalidwhen it never matches,400 invalid_response_formatfor an unusable schema. -
OpenAI conformance at
/v1, checked with the officialopenaiPython SDK: the standard error envelope{"error": {message, type, param, code}}(validation errors answer 400),GET /v1/models/{id}, structuredtool_callsfor every model, chat stream chunks with the role on the first delta andfinish_reasonon the last,stream_options.include_usage,max_completion_tokens, base64 embeddings (the SDK default),404 model_not_foundfor an unknown provider or a model id that is notprovider/model, and400 unsupported_parameterforresponse_formatwith tools,n > 1,logprobsandlogit_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 with400 unsupported_parameterin JSON bodies, form bodies and query strings.x-request-idon 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_runmeasures) 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 asmodel.download_deleted/model.download_delete_refused. -
Archive a conversation:
POST /api/gateway/sessions/{session_id}/archiveand/unarchive(the session's owner or an admin; runs, ledgers and artifacts are kept; audited assession.archived/session.unarchived).GET /api/gateway/runs?root_only=trueleaves archived sessions out,archived_only=truelists only them, and every listing carriesarchived_sessions. An archived session stays readable by its id. -
automation.unarchivebrings an archived automation back paused with its history.GET /api/gateway/automationsleaves archived automations out unlessarchived_only=true(orstatusnamesarchived) and carriesarchived_automations. -
Voice defaults:
GET /api/gateway/voice/defaultsreturns the default voice routes for every app —tts= theoutput.voiceroute (provider,model,voice),stt= theinput.voiceroute (provider,model), each withconfiguredand, when unset, anote. A request that names no engine runs exactly this route. The voice catalog (GET /voice/voices) reports the same:active_tts_provider/active_stt_providerare the configured routes (absent when none is set) and it carriesgateway_defaults. -
POST /runs/{run_id}/audio/transcribeanswers 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/healthreportswatchdog: {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
#workspaceslink 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 echoesfilter. 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.flowfile is never rewritten; audited asworkflow.description.GET /bundlesitems carrydescription_editedandactions.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>/workspacefor 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_v1and 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 newworkspace_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 asworkspace_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_v1with the old block. The runtime-config keysworkspace_root,workspace_mounts,workspace_allowed_paths,workspace_blocked_paths,workspace_default_mode,trust_client_launch_folder,client_workspace_scope_overridesanduser_workspace_policiesare refused on write and no longer read.GET/PUT /api/gateway/workspace/policy/self(now 410) and/admin/user-workspace-policyare gone;ABSTRACTGATEWAY_ALLOW_CLIENT_WORKSPACE_SCOPE/ABSTRACTGATEWAY_TRUST_CLIENT_WORKSPACE_SCOPEare no longer read. A run start withworkspace_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.