AgentOS 2026.8.21
A release about the ways in came in unequal: a voice note now says what it says, a scheduled turn can finish what it starts, a fetched page is finally untrusted in the way the prompt already claimed, and a custom skill starts from something that passes the gate.
A voice note is a message now
Inbound Telegram voice messages, audio files, and round video notes are transcribed before the turn is built, and the transcript becomes the message text.
Before this, a voice note reached the agent as the placeholder [voice] with the audio stripped — the message arrived, admitted and delivered, carrying nothing. The only way to be understood from a phone was to type.
The ElevenLabs STT call moved out of audio_transcription.py into a shared helper and got wired into channel message ingestion, so voice, audio, and video_note all take one path. Limits are checked before the download: 120 seconds by default, configurable through max_voice_duration_s, and 30 MB. Over either limit, or when STT fails, the sender gets a reply saying so and the message still reaches the agent under its old placeholder rather than being dropped — a failed transcription degrades to the previous behaviour instead of silence.
The channel download limit is relaxed to 30 MB for audio/ and video/ types, while the attachment whitelist stays strict: the raw audio is stripped once it has been transcribed, so the larger limit buys transcription and nothing else. Group mention detection also admits replies that target the bot, by user id or by username. (#312, #317)
A scheduled agent turn is elevated by default
agent_run cron jobs now run in bypass mode without a per-job opt-in.
An unattended turn that stops to ask for an approval nobody is there to give is a turn that does nothing — it burns its slot and reports back that it was blocked. Elevation was available, but as an opt-in each job had to remember, which meant the failure mode was the default.
The new global cron_default_mode on PermissionsConfig holds the default, and the router resolves effective elevation at execution time from the handler_key now carried in the cron envelope. Every other unattended kind — reminders, system events, script runs — stays strictly unelevated, and an explicit --no-elevated on any of them is honoured rather than rejected. Elevated warnings log source="config" or source="job", because the log is the only place that can say how elevation was granted, and the effective value is shown on Web UI job cards and in the CLI cron list table. The wire-level elevated field still means "explicit override", so existing jobs read back unchanged. (#311, #323)
A fetched page is untrusted for real
Web content the agent reads is wrapped in the same <untrusted source='…'> envelope the system prompt teaches, through a new wrap_untrusted_boundary helper in safety/injection_guard.py.
web_fetch had its own <external-content> tag. The dispatch layer did not recognize it, so a tool-call marker planted in a fetched page got zero enforcement — the prompt described a contract the code did not implement. The unified envelope is recognized by is_untrusted_fragment and extract_tool_call_refusal_reason for free, so markers inside a fetched page now trip the refusal path like any other untrusted fragment.
Only nested <untrusted> markers are entity-escaped — close before open, since the close pattern contains the open token — so the page itself passes through verbatim and stays readable, which full XML escaping cannot offer for bulk content the model has to actually read. The escaping is idempotent, so truncation re-wrapping still works. http_request wraps its text body and body_preview with the fetched URL as the source, with the 10k text cap applying to the payload and the envelope riding on top; binary and base64 paths are unchanged. web_search and x_search snippets stay deliberately out of scope — provider-mediated and heavily truncated, a per-snippet envelope would outweigh the payload. (#339, #340)
A custom skill starts from something that passes
agentos skills init <name> scaffolds a local skill: a SKILL.md with clean YAML frontmatter and a body long enough to clear the 20-character publish validation, plus scripts/run.py and its documented entrypoint mapping under --with-script.
Names are validated against ^[a-zA-Z0-9][a-zA-Z0-9.-]{0,63}$, so a name cannot walk out of the target directory. An existing file is overwritten only with --force, and other files in the directory are left alone. The target resolves through the usual layer order — ~/.agentos/skills, then ~/.agents/skills, then <workspace>/.agents/skills, then <workspace>/skills. The template documents how to declare binaries and env vars in frontmatter, which is the part that was reliably wrong when written from memory. (#316, #321)
The system prompt, trimmed
## AgentOS CLI Quick Reference is gone — two hardcoded commands that drift from the real CLI, whose canonical references are the bundled agentos skill and docs/cli.md. ## Workspace folds into ## Runtime, since OS, shell, and working directory are one environment concern; the per-line gating is preserved, so OS and shell stay full-mode only and the working-directory line keeps its own condition. Reply Guidelines now open with "Lead with the answer or outcome; keep supporting detail after it". Net −124 characters, about 31 tokens, on a full-mode render. (#343, #344)
Fixed
- Section headings no longer render glued to the section above them. Any section whose last line was conditional closed with
{% endif -%}, and the right-trim dash swallowed the blank line before the next heading — every full-mode prompt shipped so far rendered# Agentstuck onto## Product Identity,## Image Generationonto## Memory Recall, and## Memory Recallonto## Memory Write Guidance. Dropping the dash is safe in all prompt modes, because each closing tag sits inside its section's outer{% if %}. (#343, #344)
Install
uv tool install --python 3.12 "use-agent-os[recommended]==2026.8.21"Full changelog: https://github.com/use-agent-os/agent-os/blob/v2026.8.21/CHANGELOG.md