Skip to content

AgentOS 2026.9.5

Latest

Choose a tag to compare

@github-actions github-actions released this 05 Sep 03:57
· 13 commits to main since this release
d9ebb22

A contributor-fix release. Fourteen community PRs, one new pricing surface —
this one is mostly about shared state: connections two coroutines write at once,
caches that never evicted, and a task that cancelled itself.

Direct providers stop being billed at the placeholder rate

Point AgentOS at api.deepseek.com or generativelanguage.googleapis.com and
every model on it was billed at _DEFAULT_PRICING — $3.00 in, $15.00 out per 1M
tokens. Bare ids without a vendor prefix (deepseek-chat, deepseek-reasoner,
gemini-2.0-flash) and date-stamped snapshots (claude-3-7-sonnet-20250219,
gpt-4o-2024-08-06) both failed the startswith prefix scan and fell through to
it, which on a cheap model is an overcharge of up to 21x in usage tracking and
spend rollup.

Native rates for DeepSeek, Anthropic, Google Gemini and OpenAI now land with
their prompt-cache read discounts (cached_input_per_m — Anthropic's 90% cache
read, DeepSeek's 90% cache hit, the Gemini and OpenAI 50–75% cached prompt
rates), snapshot suffixes are stripped before lookup, and candidate
normalisation is provider-aware. Resolution is scoped to direct endpoints, so
aggregator routing and the existing static table baseline are untouched, and
Opus keeps its tiered rates. (#842)

Shared state that was not actually shared safely

SessionStorage runs every session write over one SQLite connection, and
nothing serialized them. Two coroutines could interleave inside a single
multi-statement transaction — one committing the other's half-written state —
and a mutating method that raised or was cancelled left its transaction open for
whoever committed next to adopt. Every committing runtime method now holds write
ownership across the complete transaction, and an open transaction is rolled
back when the method exits with an exception, cancellation included. Migration
writes stay outside the lock deliberately: they run sequentially during connect,
before the storage instance is exposed. (#891)

The scheduler had the same shape. JobStore.transaction() batched through
save_no_commit() and committed on context exit, but an exception inside the
block skipped the commit without ever issuing a rollback — leaving uncommitted
writes in the shared connection's buffer for the next caller to commit as its
own. Meanwhile save, delete, save_execution, prune_runs and
_reserve_job_for_run committed the shared connection directly, so a concurrent
writer could commit incomplete batch state out from under an open transaction.
Rollback now fires on any BaseException, a task-bound reentrant write lock
serializes the writers, and intermediate commits are deferred while a
transaction is active. (#964)

SessionWriteLock._locks never removed anything. Every session key ever
acquired kept its asyncio.Lock for the life of the process — one small leak
per unique session, unbounded on a long-running gateway. release() now pops
the entry when no acquirer is queued behind it, bounding the dict by currently
active keys; entries with waiting acquirers are kept, so handoff is unchanged.
(#966)

And TaskRuntime was evicting a session's routing envelope too eagerly.
_mark_terminal() popped _last_envelope_by_session whenever any task
finished, regardless of what else was queued or running for that session, so a
follow-up or proactive send() landing between tasks found no envelope and fell
back to a generic SourceKind.SYSTEM one — losing channel, account, recipient,
thread id and reply target mid-workflow. The pop now sits inside the existing
atomic check that both the pending queue and the running slot are empty.
One-shot provenance overrides stay one-shot. (#930)

Runtime correctness

Auto Pilot no longer freezes on a dead endpoint. When a routed model timed out
or returned a pre-content error, the runtime retried the same model three times
— roughly six minutes of nothing — instead of trying another tier. Hard
transport timeouts are now classified apart from transient blips, the timeout
retry is capped at one, a provider_timeout_retry warning is emitted, a
prioritised fallback chain is derived from the active router tiers, and the
terminal error names the /c0, /c2 and /auto escapes rather than just
failing. (#860)

The Discord adapter cancelled itself mid-reconnect. When _heartbeat_loop()
detected a missed ACK and drove a reconnect, _do_reconnect() unconditionally
cancelled self._heartbeat_task — which was the task it was running inside — so
the coroutine died at the next await during socket cleanup, before a new
WebSocket or a replacement heartbeat task existed. The cancel is skipped when
the heartbeat task is asyncio.current_task(); externally initiated reconnects
and adapter shutdown still cancel it. (#882)

Session search worked for English only. The FTS query sanitizer used
[^a-zA-Z0-9\s], which stripped every accented Latin, CJK, Cyrillic, Vietnamese
and Arabic character before the query reached FTS5 — café déploiement searched
for "caf" "d" "ploiement", 中文 报告 searched for nothing at all — while the
transcripts themselves had been indexed correctly the whole time. The pattern is
now the Unicode-aware [^\w\s], which still strips FTS5 operators. (#903)

list_dir survives a dangling symlink: a broken link is not a directory, so the
size lookup fell through to entry.stat(), which follows the link and raised an
unhandled FileNotFoundError that took down the entire listing — it now falls
back to entry.lstat().st_size on OSError (#844). agentos cost --export reports/usage.json creates the missing parent instead of raising
FileNotFoundError, matching what render_savings_pdf already did, on both the
JSON and CSV branches (#846). The gateway debounce buffer caps at 50 coalesced
messages per session_key and flushes on reaching the cap rather than growing
without bound, retaining its delivery task and draining it on shutdown (#796).

Named artifact delivery stops handing send_file a directory. When an
artifact's metadata carried an empty, whitespace, root or dot-relative target,
Path(filename).name resolved to "" and the delivery target became the
temporary directory itself — the hardlink failed and the shutil.copy2 fallback
copied the source in under its internal storage hash name while yielding the
directory path. The leaf is now sanitized, falling back to the source name or
artifact. (#742)

robinhood-chain-stocks stops lending a proven fake the credibility of a real
price. When uiMultiplier() reverts, isStockToken is false and SKILL.md
is explicit — not a Stock Token, do not hand over the address. An impersonator
reusing a listed company's ticker still got the real company's live Chainlink
feed attached to it, with holding.valueUsd calculated from it. Price and USD
holding value are now withheld with a readErrors explanation. isStockToken: null still resolves a price: an unreachable RPC node is not proof of fakery.
(#866)

Security

code_exec checked for destructive calls with shallow regex patterns only, so
anything that named the function indirectly reached the host filesystem without
passing the approval gate: getattr(os, "rem" + "ove")(path),
__import__("os").remove(path), importlib.import_module("os").remove(path),
exec/eval of a destructive string, from os import *, from os import remove as r, import os as o.

An AST visitor now runs whenever the regex fast path does not match. It resolves
statically computable strings — constants, concatenations, f-string values —
tracks imports and aliases for os, shutil, pathlib, subprocess and
importlib, follows wildcard and aliased imports into the local scope, and
flags dynamic getattr and __import__ targets that resolve to a destructive
attribute. The layer is additive: existing pattern coverage is unchanged. (#848)

Install

curl -fsSL https://raw.githubusercontent.com/use-agent-os/agent-os/main/install.sh | bash

or uv tool install --python 3.12 "use-agent-os[recommended]==2026.9.5".

Contributors

Thanks to @bukeeastrey, @Preciousuche, @Carlys17, @BunnyTeddy, @Tiktokaiagent,
@tejajakarulloh and @seno21 — every fix in this release came from a community PR.

Full changelog: https://github.com/use-agent-os/agent-os/blob/v2026.9.5/CHANGELOG.md