AgentOS 2026.8.27
A release about ceilings and about what a file hands back. Money gets a hard stop that survives a restart, a skill zip stops being a way to take the gateway down, a credential read out of a config file stops landing in the transcript, and three subsystems that shipped in the wheel while doing nothing are gone.
A budget you can actually hit
A new [budgets] config section sets money ceilings — per session, per UTC day, per agent, per channel:
[budgets]
session_limit_usd = 5.00
session_warn_usd = 4.00
daily_limit_usd = 50.00
daily_warn_usd = 40.00A turn that starts at or above a hard limit is refused before any provider call, with a budget_exceeded error naming the scope and the number — not a generic failure the operator has to go read logs to explain. A matching *_warn threshold raises a one-shot budget_warning and lets the turn run.
The part that matters is where the check happens. A ceiling that is only read at the top of a turn is not a ceiling: one turn with a long tool loop can spend all day inside it. Ceilings are re-checked between iterations within a turn, so a runaway loop stops mid-turn rather than at the start of the next one.
And spend is persisted to ~/.agentos/state/spend_ledger.db, in its own database rather than through the async session writer. A budget kept only in process is a budget you clear by restarting — a runaway overnight loop that crashes and respawns would come back with a clean slate. It comes back to the same number.
Nothing is enforced until an operator sets a number. Changing [budgets] requires a gateway restart; the config RPC reports restart_required rather than pretending the new number is live. UsageTracker.check_warning() is removed — it had no callers, and this replaces it with a configurable, enforced equivalent. (#363, #473)
Secrets stop riding out in file content
read_file, read_spreadsheet and grep_search now mask credentials in the content they hand back to the model, and so do the two channels that quote file content alongside them: edit_file's closest-match hint, and terminal output from a command that reads a credential file (cat ~/.aws/credentials).
The sensitive-path denylist used to be the only thing standing between a secrets file and the model — and that denylist is lifted entirely under elevated-full mode, which cron agent_turn jobs run in by default. So read_file ~/.aws/credentials on a scheduled turn returned the raw keys straight into the persisted transcript, where they stay.
Masking uses a non-reusable «redacted:…» sentinel, DSN and URL passwords included, so a value read out of a config file cannot be written back over the working one by an agent that edits what it just read.
How much of the pass runs depends on the file. Shape-matched credentials (sk-…, JWTs, PEM blocks) are masked everywhere. The name-driven pass — the only one that catches a shapeless secret like aws_secret_access_key — runs everywhere except source code, where it would mask ordinary identifiers and hand back code that no longer matches the file. (#355, #459)
Masking itself got stricter about what it is allowed to touch. It was rewriting ordinary source: header names matched as substrings ("requiresApiKey": False), a value ran past the closing bracket ({"xi-api-key": api_key} lost its }), a bare number was masked as a credential, a vendor prefix matched mid-base64 inside an embedded font blob, and a PEM span across two adjacent string literals swallowed the code between them. Header names now match on a segment boundary, values stop at the punctuation that closes them, prefixes need a left boundary, and a PEM span must have a base64 body. A PEM block inside a read_file window is masked line by line, so the line numbers the reader computes its next offset= from stay correct. Quoted keys ("client_secret": "…") are recognised too, so credentials in JSON and YAML config are masked as the docstring always claimed.
A skill zip can no longer take the gateway down
ClawHubSource.fetch read every entry of a downloaded skill archive into memory with no cap on entry count or uncompressed size. A few tens of KB of nested deflate — a classic zip bomb — could exhaust the gateway's memory and kill the process.
The download is now streamed against a size ceiling, because httpx gunzips a Content-Encoding body with no limit of its own: a buffered read could be filled before any zip-level cap got a say. The archive is then refused past an entry count, a per-entry size, and a total uncompressed size, with each entry decompressed in chunks against a running total — so an archive that understates ZipInfo.file_size is caught mid-read rather than believed.
A hostile archive also fails closed rather than raising through the installer. An entry flagged encrypted, an unsupported compression method, or a truncated deflate stream reaches the caller as "no bundle", not as an exception that aborts a whole lockfile sync. Entry paths are checked against Windows-style escapes (..\, C:\), which posixpath.normpath leaves intact and only the installer's resolve check used to catch. (#357, #471)
aero-stock-lp
aero-stock-lp joins the Bankr skill hub. It range-LPs Coinbase tokenized equities (NVDA, AAPL, GOOGL, META) and AERO/USDC on Aerodrome Slipstream (Base) — opening, recentering and exiting concentrated-liquidity positions, reporting pool status, NAV, yields and P&L, and routing each position to whichever side pays more at this epoch: staked for AERO emissions, or unstaked for trading fees.
It ships as a directory in BankrBot/skills, so it browses and installs through the existing repo half of the Bankr source with no new code path. The Bankr user-skill allowlist — the half carrying skills published from a wallet on bankr.bot — is now empty; stock-premium-lp-manager is retired in its favour. Copies already installed keep working; the slug is no longer offered for browse or install.
Bankr catalog cards also wear the Bankr brand mark again. aero-stock-lp is the one entry in BankrBot/skills whose catalog.json ships a logo, so it rendered that artwork while every other card in the partner tab showed the Bankr symbol. The source now ignores the payload's logo entirely — membership in the catalog is the brand, and a repository-side edit can no longer repaint a partner card's identity.
Fixed — the gateway
- The Control UI bootstrap endpoint no longer leaks host details to any website the operator visits.
{control_ui.base_path}/api/bootstrapsat inside the prefix exempt from the loopback Origin guard, so with the defaultcors.allowed_origins = ["*"]any page couldfetch()it cross-origin and read the absolute config file path — which reveals the OS username — along with the configuredauth_mode. The payload no longer carriesconfig_pathat all; the console reads it from the authenticateddoctor.statusRPC. The Origin guard's Control UI exemption now stops at{base_path}/api/, andAuthMiddlewareis narrowed the same way, with a single carve-out for/api/bootstrapitself, which the console must read before it holds a token. (#351, #452) auth.mode = "password"no longer admits the gateway unauthenticated. The mode was advertised and env-bound (AGENTOS_AUTH_PASSWORD) but had no branch inAuthMiddleware.dispatch, so it fell through to the unauthenticated pass and left the whole non-RPC surface —/api/system/status,/api/config,/api/v1/files/upload,/api/audio/transcribe— open. Any typo'd mode did the same.auth.modenow validates at load time against the modes the gateway implements (none,token,trusted-proxy) and refuses anything else with a message naming the fix; the middleware fails closed with401on any mode without an enforcement branch, reading the config object live so a runtime mutation cannot reopen the hole. The value is case- and whitespace-normalized (" TOKEN "loads astoken). (#352, #458)- Multipart uploads are capped before the body is buffered, rather than after. (#436, #445)
- Channel approval prompts resolve the channel kind correctly. (#435, #441)
Fixed — the CLI and the tools
agentos chat,agentos sessions,agentos skillsandagentos envsend the resolved gateway token when they open their own WebSocket, and honourAGENTOS_GATEWAY_URLconsistently.resolve_authgrants no loopback exemption in token mode, soauth.mode = "token"broke all four commands even on a purely local install; the token resolver existed and these call sites never used it.chatadditionally ignoredAGENTOS_GATEWAY_URLand always dialled the hardcodedws://localhost:18791/ws. (#461)edit_file's fuzzy matcher runs off the event loop, so a large file no longer stalls the gateway. (#439, #446)- The
downloadinstall kind is hardened, and its hint agrees with the executor whenbinsis absent. (#451, #464, #465) - The sandbox runs the command when the
resourcemodule is missing, instead of failing on Windows. (#437, #443) - Deleting a session deletes its agent tasks and memory receipts with it. (#440, #444)
- The router applies the cost-aware override after the routing decision, not before it. (#438, #442)
- Cron delivery honours
account_idwhen resolving its target. (#359, #382) - Turn, compaction and tool hooks are wired into the engine, and a failing hook is logged rather than swallowed. (#361, #386)
Removed
Three subsystems that shipped in the wheel while being dead or permanently-failing (#362, #472):
- The
onboard_agentwizard —wizard.start/wizard.next/wizard.cancel/wizard.statusand their state machine — had no caller in the frontend or the CLI and no side effect: its terminal step returned the collected answers and never created an agent, while its hardcoded model list had gone two generations stale. The Agents view already creates agents throughagents.create. - The
canvasandnodesbuilt-in tools validated theiractionargument and then raisedToolErrorunconditionally. No node runtime exists anywhere in the tree to configure; onlyexposed_by_default=Falsekept them from failing in front of a model. tools/visibility.pyno longer exportsfilter_by_profile(returned its input) orprofile_allows_tool(returnedTrue), and the dispatch chain no longer runs theProfilePolicythat only called them. Profile enforcement has one home intools/policy_config.py.ToolProfileandresolve_profilestay — they are the live seam.
The agentos dist install inventory also stops advertising built-in tools that are not in the wheel. bundled_tools listed nodes (deleted above) and agent (no such module for some time), so an inventory diff across releases showed capability that was not there. A new parity test asserts every name in BUNDLED_TOOLS resolves to a module under agentos.tools.builtin.
Docs
The scheduling guide drops a stale claim about the read-only tool surface. (#422)
Install
uv tool install --python 3.12 "use-agent-os[recommended]==2026.8.27"Full changelog: https://github.com/use-agent-os/agent-os/blob/v2026.8.27/CHANGELOG.md