AgentOS 2026.8.29
A release about reaching the agent by email, and about the places where output left the process without being counted — bytes into memory, secrets into the transcript.
Email is a channel
A mailbox is now a first-class channel (type = "email"). Inbound over IMAP polling, outbound over SMTP with In-Reply-To/References so a reply stays in the mail thread it answers. There is no platform app to register — IMAP/SMTP credentials are the whole setup.
One mail thread is one session. Quoted history is stripped before the text reaches the model, HTML-only mail is flattened to text, and inbound attachments plus generated artifacts ride the shared attachment pipeline under the usual size limits.
Access is fail-closed: allowed_senders is required and lists exact addresses or *@domain patterns. Mail from the agent's own address, and anything marked auto-generated (Auto-Submitted, X-Autoreply, List-Id, Precedence: bulk), is dropped — an autoresponder on the other end cannot start a mail loop. (#369)
Alongside it, a DM-shaped channel whose surface is itself threaded can opt into one session per thread with metadata['dm_thread_scoped']. Adapters that do not set it keep one session per peer, so Slack, Discord and Telegram DM keys are unchanged.
A memory view for what the agent knows
The Web UI console gets a browsable /memory view — sidebar entry, g m chord — with a curated-memory editor (entry count, size, live save), a knowledge-base document table with per-file status and manual ingest, a raw source-file explorer, and a semantic search explorer.
Behind it, memory.ingest grows multi-format text extraction and directory ingestion — PDF, DOCX, PPTX, Markdown, text, CSV, JSON/YAML and code files — scanning <workspace>/knowledge_base/. It is exposed as memory.curated.* and memory.knowledge_base.* JSON-RPC methods and as agentos memory ingest / agentos memory curated on the CLI. (#368)
Output that left without being counted
Three redaction and download-cap holes, all the same shape: a check that ran after the damage.
execute_codeoutput is redacted.shell.pyalready ran every output surface throughredact_terminal_output;execute_codebypassed redaction entirely, so a script printingos.environor reading a credential file leaked every secret verbatim into the transcript. Redaction now happens at_execution_result_json, the one choke point for all eight return paths. (#490)- Credentials are masked in git tool output.
git_diffreturns working-tree and staged content verbatim, so a.envcommitted once kept reaching the model in cleartext on every diff whileread_fileandgrep_searchalready redacted. Masking happens at the single_run_gitchokepoint, on the sandboxed and subprocess paths, on success and failure alike. A companion fix teaches the assignment pass about diff markers:+MY_SECRET=…went unmasked whereMY_SECRET=…was masked. (#496) - The
imagetool and the GitHub skill hub stop reading at the cap._fetch_image_urlbuffered the whole response body and only then compared it against the 20 MB ceiling;GitHubSource.fetch()buffered every blob of a skill directory with no per-blob cap and no total budget. Both now stream and stop the read the moment the accumulated size passes the limit — 20 MB for an image, 8 MiB per blob against a 32 MiB total for the hub — and the hub fails closed rather than installing a truncated bundle. A timeout bounds time, not bytes. (#506, #510)
Fixes
- Slack and scheduler webhooks survive a blip. Both routed their HTTP calls straight at
httpxand failed on the first error; they now go through the sameretry_requesthelper Discord uses — exponential backoff with jitter on 429 (honouringRetry-After), 500/502/503/504, connect errors and read timeouts. Fatal statuses such as 400/401 still fail on the first attempt. Retrying a read timeout can duplicate a delivery the receiver already accepted, the same trade-off Discord has always made; the webhook payload'sjobIdis the receiver's dedupe key. (#469) - A backing-off cron no longer busy-spins SQLite.
next_due_atlooked only atnext_run_atwhileiter_duealso waits onbackoff_until, so after a few failures on a frequent schedule the timer woke early, yielded nothing, and hammered the database for the whole backoff window. It now reportsMIN(MAX(next_run_at, backoff_until)). (#537) - A one-shot
ATschedule in the past is refused. It used to be stored withnext_run_atbehind us anddelete_after_run=True, firing immediately on the next tick with a stale payload. (#486) - MCP stdio reads with
readexactly.read(n)can return a short buffer, which truncated chunked tool results into ajson.loadsfailure; EOF now raises a clear truncated-body error. (#537) - Telegram entity offsets are sliced on the UTF-16 grid. Offsets and lengths are UTF-16 code units but were applied to a Python
strby code point, so an emoji before/help@mybotin a group made the bot ignore a command aimed at it. (#537) - An exhausted provider failover chain says so.
next_fallback_after_failure()advanced the chain index unbounded and surfaced a bare out-of-rangeIndexErrorfrom_build_providerinstead ofNo more provider fallbacks available. (#488) - Discord keeps dispatching after a reconnect. Opcode 7/9 and a dropped socket reconnected and then returned from
_dispatch_loop— heartbeat resumed and health still read connected, but messages and slash commands were never read again. (#538) - Cancelling xAI sign-in stops the poll loop. Cancel only reset the visible card, so an expiry could paint an error after dismissal and a restarted sign-in could be wiped by the old loop completing. (#538)
- A real nested
workspace/folder is no longer stripped. Any absolute path containing aworkspacesegment was rewritten from the last such segment, so reads and writes landed on a sibling file. Paths already inside the root are left alone; sandbox/workspace/...still remaps. (#538) - The projects page header stacks on mobile instead of overflowing.
Thanks to @iamhaniofficial, @Carlys17, @tejajakarulloh and @bukeeastrey.