{"slug": "talos-ai-agent-super-secure", "title": "Talos AI Agent Super Secure", "summary": "Talos, an open-source autonomous agent from talos-agent.ch, runs on a user's own machine and executes tools only after a deterministic security kernel approves each action via a capability token valid once for 30 seconds, with 164 adversarial scenarios tested on every change. The agent refuses to run without a sandbox on Linux or macOS, and ships with zero identities that may command it, positioning itself as a secure alternative to conventional AI agents that request shell access.", "body_md": "*An autonomous agent you can hand a shell to,because it can prove what it will not do.*\n\n[ talos-agent.ch](https://talos-agent.ch) ·\n\n[Field manual](https://talos-agent.ch/docs/)·\n\n[Changelog](/talos-kernel/talos/blob/main/CHANGELOG.md)\n\nTalos runs on your own machine, takes instructions over a chat channel, thinks through a\nlanguage model, and executes tools — but only after a deterministic security kernel has\nruled on the action. **The model proposes. It never decides.**\n\n```\n   message ──▶ event log ──▶ reason ──▶ ╔══════════╗ ──▶ capability ──▶ execute\n                                        ║  KERNEL  ║        token          │\n                                        ╚══════════╝                       ▼\n                                             ▲                     verify ──▶ receipt\n                              the only place effects are authorised\n```\n\nAuthority |\na token bound to exact arguments — valid once, for 30 seconds |\nA tool without a target extractor |\n`DENY` by construction, not by a rule someone wrote |\nShips with |\nzero identities that may command it |\nThe shell |\nsandboxed, or it refuses to run at all |\n\n```\ncurl -fsSL https://talos-agent.ch/install.sh | less   # read it first\ncurl -fsSL https://talos-agent.ch/install.sh | sh     # then run it\n```\n\nThe installer verifies the signature and the checksum, runs the full suite — and then\n**stops**. Nothing starts listening until you say so.\n\n**Contents** — twenty sections, in the order they matter\n\n**Start here**\n[Why this exists](#why-this-exists) ·\n[What it does not do](#what-it-does-not-do) ·\n[Install](#install)\n\n**Using it**\n[A session in the terminal](#a-session-in-the-terminal) ·\n[Work on the side](#work-on-the-side) ·\n[What it remembers](#what-it-remembers) ·\n[Seeing what it did](#seeing-what-it-did) ·\n[Commands](#commands)\n\n**How it holds**\n[How the kernel decides](#how-the-kernel-decides) ·\n[The autonomy dial](#the-autonomy-dial) ·\n[Timed runs](#timed-runs) ·\n[Announced plans](#announced-plans) ·\n[The browser that only reads](#the-browser-that-only-reads) ·\n[Delegating](#delegating) ·\n[Identity](#identity)\n\n**Evidence**\n[What a run looks like](#what-a-run-looks-like) ·\n[Audit trail](#audit-trail) ·\n[Tools](#tools) ·\n[Architecture](#architecture) ·\n[Roadmap](#roadmap)\n\nEvery capable agent eventually asks for shell access. At that moment you are trusting a language model with your machine, and the usual answers are unsatisfying: either it asks you to confirm everything (and you stop reading the prompts by day three), or it runs free behind a regex blocklist and you hope.\n\nTalos takes a third position. **Authority is a token, not a list.** Every effect is\nauthorised individually, bound to its exact arguments and targets, valid once, for thirty\nseconds. Forgetting to call the gate does not produce an unchecked effect — it produces no\neffect at all, because the raw runners are unreachable without a token.\n\nThat design is testable, and it is tested: 164 adversarial scenarios run on every change and\ntry to get an effect past the kernel. They are in [ redteam.py](/talos-kernel/talos/blob/main/redteam.py). Read them\nbefore you trust anything written above.\n\nStated plainly, because a security claim without its limits is marketing:\n\nIt runs under bubblewrap on Linux and`run_shell`\n\nneeds a sandbox the platform can actually provide.`sandbox-exec`\n\non macOS. Where neither is available it*refuses*rather than running unprotected, so on such a host the shell tool is simply unavailable until the operator overrides it on purpose (`TALOS_SANDBOX_ALLOW_UNCONFINED=1`\n\n).**It is not a multi-tenant security boundary.** One operator, one machine. Anyone who can run code in the process can reach the token mint.**There is no gateway, no setup portal and no** Each was considered and left out for the same reason. A gateway terminates foreign identities and then`config.yaml`\n\n.*claims*`channel:id`\n\nto the kernel instead of proving it, so identity checking would hang on a component outside the kernel. A web portal is a network service with its own authentication — by definition a second source of permission beside the allowlist. And a second config file would mean two truths about who may command it, when the path floor protects exactly one. What weakens a guardian is not the number of commands; it is a second way in.**It does not defend against a malicious model.** It defends against a*mistaken*one, and against prompt injection arriving through tool output. Those are different threats.**Search needs no account.** Without`TALOS_BRAVE_API_KEY`\n\nit used to refuse; now it answers over DuckDuckGo (the`ddgs`\n\npackage, imported at call time). A key decides*which*provider answers, not*whether*one does.**It has two ways in, and both of them fetch**(Telegram long-polling, mail over IMAP). That is the rule, not an accident: an inbound webhook would need a port the world can reach, which turns \"outbound only\" into \"publicly reachable\". Mail sits at`Trust.ASK`\n\n— an address proves no account, so it may ask and receive answers but never approve anything. WhatsApp is delivery-only for the same reason.**It hears locally or not at all.**`hear`\n\ntranscribes a recording with faster-whisper on the machine it runs on — what was said is often the most private thing in a day, and sending it to somebody else's model to be understood is the one place where \"runs on your machine\" would be traded for convenience. It is an ordinary`READ`\n\nwith the file as its target, so a recording under`~/.secrets/`\n\nis refused without the module knowing anything about secrets. The model loads on first use, not at boot.**It can see, on a real file.** A photo you send is fetched into`workspace/inbox/`\n\nand the note carries the path, so`see_image`\n\nhas something to point at — and the kernel has a target to judge. The fetch asks the allowlist first: the channel parses updates before the kernel has ruled on identity, so an unconditional fetch would let anyone who finds the bot write files to your disk.`speak`\n\nwrites a WAV offline (piper).**It can take one still out of a video.**`grab_frame`\n\nruns ffmpeg once and puts a single picture in the inbox;`see_image`\n\nlooks at it afterwards. Both the video and the picture are targets the kernel judges, so a video under`~/.secrets/`\n\nis refused exactly like a recording — without that, calling a file a video would have been the way around the secret floor. Where the picture lands is derived by the kernel, not chosen by the model, and it is deliberately one frame rather than a series: fanning ffmpeg out over a whole film and encoding every frame is how a small machine ends up unreachable.**It does not generate pictures or video, on purpose.** Reading one is a`READ`\n\nwith a target; making one is a paid call to somebody else's model, and it was removed rather than kept around waiting for credit. Seeing stays.\n\nRequires Python 3.11+ and a working [Claude Code CLI](https://claude.com/product/claude-code)\n(the reasoner runs it headless via OAuth — no API key, no per-token billing).\n\n```\ngit clone https://github.com/talos-kernel/talos.git\ncd talos\npython3 -m venv .venv && . .venv/bin/activate\npip install -r requirements.txt\n\npython -m talos setup                    # asks three things, writes a file, stops\npython -m talos doctor                   # what is still missing\npython -m pytest tests/ -q               # 1596 tests\npython redteam.py                        # 164 adversarial cases\npython -m talos                          # run it\n```\n\n`setup`\n\nproves what it can rather than trusting what you type: the token goes to `getMe`\n\n,\nand your identity comes from a **real message you send your own bot** — not from a number\nyou copy. A wrong token announces itself on the first poll; a wrong identity does not, and\na stranger's identity never does. It writes the file and stops; starting is yours.\n\nLater, a single part can be redone without repeating the rest:\n\n```\npython -m talos setup model              # switch what it thinks with\npython -m talos setup mail               # add the second way in (IMAP)\npython -m talos config list              # every key, its kind, whether it is set\npython -m talos config set TALOS_MODEL claude-fable-5\npython -m talos models --refresh         # ask each provider what it offers now\npython -m talos status                   # what it did last\npython -m talos chat                     # a session here; approvals at a terminal\npython -m talos ask \"how many …?\"        # one turn from here, answer on stdout\npython -m talos events --tool run_shell  # what happened — read-only, filterable\npython -m talos why 4831                 # why that was allowed or refused\npython -m talos review                   # what this installation should change\npython -m talos report --out audit.txt   # what was done and what was refused\n```\n\nA full walkthrough — install, identity, the session, every command, and the ones that are\nmissing on purpose — is at ** talos-agent.ch/docs**.\n\n`ask`\n\nis not a second way in — it is a **channel like any other**, with the same protocol\nand no special right. Two consequences follow, and both are the point of it. Whoever types\nthere must be in the allowlist as `cli:<uid>`\n\n, exactly like a Telegram number; a shell next\nto the agent is not an argument for granting it anything. And the turn always runs under\nthe **unattended ceiling**: a one-liner waits for nothing, so `NEEDS_HUMAN`\n\nbecomes `DENY`\n\nand says so. Approve in the chat, where somebody is actually looking.\n\nIt also refuses to run inside the agent's own sandbox. Otherwise the agent could start\n`talos ask`\n\nfrom its shell and give itself orders — no channel, no foreign identity,\nnobody reading along.\n\n`ask`\n\nanswers once and leaves. For anything that takes more than one turn there is\n`talos chat`\n\n— the same channel name, the same identity, the same conductor, the same\nkernel. The loop is line for line the one the service runs for Telegram; only the messages\ncome from `stdin`\n\ninstead of `getUpdates`\n\n.\n\n``` bash\n$ talos chat\nTalos 0.9.2-alpha  ·  anthropic/claude-opus-4  ·  autonomy 3\nspeaking as cli:1000  ·  approvals possible — you are at a terminal\n/help for commands, `exit` to leave\n\n› what changed in the log today?\n```\n\nThe second line is the one to read: it says, before you ask for anything, whether an approval is even possible here — so a refusal later is explained in advance rather than guessed at.\n\n**The unattended ceiling hangs on the terminal, not on the command.** This was the\ncontested decision. Applying `ask`\n\n's rule to an interactive session would mean `chat`\n\ncould never write anything; dropping it would mean `talos chat < jobs.txt`\n\nin a cron job\nlooks exactly like a human. So it is measured rather than claimed: **both stdin and\nstdout must be a real tty**. A pipe, a redirect, a cron run has none, and there\n\n`NEEDS_HUMAN`\n\nbecomes `DENY`\n\nas before. Checking only `stdin`\n\nwould let\n`talos chat > out.log`\n\npass as attended — nobody reads a question written to a file.The in-session commands are the same ones the messenger has, through the same command centre. A second vocabulary that only worked here is exactly the duplication this project has paid for elsewhere.\n\nSome questions take two minutes, and the conversation should not stand still for them.\n\n```\n› /background go through /var/log and summarise today's errors\n\nBackground #1 started: go through /var/log and summarise today's…\n  id bg_a1b2c3d4e5f6 · runs unattended, so anything needing approval is refused\n```\n\n**A background task is a scheduled run, not a helper carrying your rights.** That is the\nwhole security placement, and it saves inventing a new concept. It is *not* a subagent:\nthat one is born from model text and may therefore only read. Here a human typed the task,\nso its origin is the same as any other message. But nobody is sitting in front of it — so\nthe ceiling from the timed run applies, and anything that would need approval is refused.\nPutting a question into a chat where a different conversation is running is the most\nreliable way to land a \"yes\" on the wrong thing.\n\nIts context is **empty**: the task and nothing else. Two runs sharing one history write\ninto each other, and afterwards nobody can say which of them said what. The result does not\nflow back into the history either — it arrives as its own message, marked as a report.\n\nIf no ceiling is wired, the task is **refused** rather than run uncapped. A forgotten\nparameter may only ever allow less.\n\nThe conversation is kept per channel, in memory only, bounded by turns and by characters.\nWhen the bound is reached the **middle is summarised** and both ends stay verbatim — the\nhead usually carries the actual task, the tail whatever \"and that too\" refers to.\n\n```\nYou:                  set up the backup for the NAS\nAgent:                done — see /etc/backup.d\nEarlier (summarised): discussed retention, settled on 30 days; the NFS\n                      mount needed the hard option\nYou:                  and add the offsite copy\n```\n\nThe summary is labelled as one and never disguised as a verbatim turn — a summary that\nlooks like something actually said is a claim about words nobody used, and the model could\nquote it back as if it were. It goes into the prompt as history, never into the standing\ninstructions, and the transcript reaches the summariser explicitly framed as *data*:\notherwise \"summarise this\" is the most convenient place for an injected line to become an\ninstruction, and a permanent one, because the summary stays.\n\n**The bound is not a comfort feature.** If summarising fails, the oldest turns are\ndropped as before. A history that kept growing after a failed summary would turn a cost\nquestion into a leak — what was said weeks ago would go out again.\n\nThe thin feeling with a guardian does not come from missing verbs. It comes from not being\nable to see what it did and why. Both of these are **read-only**:\n\n```\npython -m talos events --tool run_shell   # what happened, filterable\npython -m talos why 4831                  # why that was allowed or refused\n```\n\n`why`\n\nnames the verdict, the rule that produced it, and the targets it applied to — and\nstates that those targets were *derived from the real arguments*, never taken from what\nthe model wrote. Then it shows the rest of the same run, because \"refused, and then?\" is\nthe question that makes people stop reading logs.\n\nThere is deliberately no `talos undo`\n\nbeside them. `/undo`\n\nexists, and since `talos chat`\n\nit is reachable from the command line through the same path the messenger uses. A second\none would be convenience against the doctrine.\n\n** config set refuses two whole classes of key**, and that is the point of it. Secrets\nnever go on a command line — that lands them in the shell history and in\n\n`ps`\n\nfor every\nuser of the machine. And `TALOS_ALLOWED_PRINCIPALS`\n\n, `TALOS_SECRETS_ENV`\n\n,\n`TALOS_MAIL_AUTHSERV_ID`\n\n, every `TALOS_BASE_URL_<PROVIDER>`\n\nand\n`TALOS_WEB_ALLOWED_ADDRESSES`\n\nare not settings but *policy*: whoever writes them does not have to talk the kernel into anything, they reconfigure it. Not settable, not even with a confirmation — a confirmation is exactly what gets clicked away.\n\n`config get`\n\nanswers `[REDACTED]`\n\nfor a secret **whether it is set or not**, so the command cannot be used to find out which doors a machine has.\n\n**A key belongs to one provider.** There is no single `api_key`\n\nand no single base url;\n`credentials.py`\n\nkeeps a route per provider — key *and* address in one piece, resolved at\nthe moment of the call rather than frozen when the reasoner was built, because `/model`\n\nswitches providers while the process keeps running. If the selected provider has no key,\nthe switch is refused and the previous one stays active: no other provider's key stands\nin for it. Until 2026-08-05 one field held `ANTHROPIC_API_KEY or OPENAI_API_KEY`\n\nfor every\nprovider, so choosing `openai-api`\n\nsent an Anthropic key to OpenAI as a bearer token — a\ncredential disclosure that looks like a typo, because the wrong recipient answers `401`\n\n.\n\n**There is no default allowlist.** `TALOS_ALLOWED_PRINCIPALS`\n\nmust name your identity or\nthe agent refuses to start. A shipped default would be a backdoor printed in the source.\n\n`PolicyKernel.decide()`\n\nreturns exactly three verdicts — `ALLOW`\n\n, `NEEDS_HUMAN`\n\n, `DENY`\n\n—\nand the executor calls no runner before it has one.\n\n| Class | Examples | Verdict |\n|---|---|---|\n| Ordinary work | `ls` , `git status` , tests, builds, writes inside the workspace |\nruns |\n| Risky but recoverable | `curl | sh` , `git reset --hard` , `rm -rf <path>` |\nasks you |\n| Persistence & secrets | `.bashrc` , systemd units, `~/.ssh` , the agent's own source |\nasks you |\n| Catastrophic | `rm -rf /` , `mkfs` , `dd` to a block device, fork bomb, reboot |\nrefused |\n| Secret reads, system paths | `cat /etc/passwd` , anything under `/etc /boot /usr /bin` |\nrefused |\n\n`DENY`\n\nreturns **before** the approval check, so a \"yes\" cannot reach a hardline rule.\nApprovals are one-shot, five-minute TTL, bound to the exact request, and re-verified against\nfile hashes immediately before execution (TOCTOU).\n\nTargets are derived from real tool arguments, never from a field the model could omit. A\ntool without a target extractor is `DENY`\n\nby construction.\n\n`/autonomy 0..5`\n\nsits above the kernel and **can only tighten, never loosen**. Level 5 is\nexactly the unfiltered kernel — not \"anything goes\". A dial that granted rights would be a\nsecond source of permission next to the kernel, which is the thing this design removes.\n\n| Level | Reads | Writes | Shell |\n|---|---|---|---|\n| 0 | refused | refused | refused |\n| 1 | asks | refused | refused |\n| 2 | free | refused | refused |\n| 3 | free | asks | asks |\n| 4 | free | free in workspace | asks |\n| 5 | free | kernel decides | kernel decides |\n\nThe level survives restarts because it lives in the event log. An unreadable log drops to level 0, not to the last convenient value.\n\n`/every 90 <task>`\n\nrepeats on an interval; `/every 0 8 * * MON-FRI <task>`\n\ntakes a cron\nexpression, because an interval can say \"every 90 minutes\" but never \"weekdays at 08:00\".\nAn expression is a better clock, not an extra permission — what runs afterwards passes the\nsame kernel.\n\nAnd it passes one ceiling more. During an unattended run `NEEDS_HUMAN`\n\nbecomes `DENY`\n\n:\nwhat may run without asking runs, everything else is **reported rather than performed**.\nNot parked until morning either — an approval question whose occasion is six hours old is\nhow reflexive clicking starts. So a timed run may do strictly *less* than something you\ntyped, which is the opposite of how cron usually works.\n\nFor a task with several steps the agent may announce the sequence before it starts:\n\n```\n≡ 3 steps — collect the log, find the failing service, report\n```\n\nThe announcement changes nothing about permission. Every step still passes the kernel one\nat a time, exactly as if it had been asked for alone, and a step that needs approval still\nstops and asks. What the announcement does is **bind the run**:\n\n- the step budget shrinks from the house limit to what was announced — a three-step plan cannot become forty tool calls;\n- the first step that fails ends the run with a report of what ran, what stopped it, and what therefore did not happen — instead of the model improvising around the failure, which is how an agent turns a refusal into a bigger second attempt;\n- the plan is read\n**once**. A tool result is a stranger's text; if it could install a second, larger plan mid-run, prompt injection would be a way to buy budget.\n\nA step may carry a condition, and the condition is **not evaluated by the model**:\n\n```\nPLAN: {\"goal\": \"restart and confirm\", \"steps\": [\n  {\"intent\": \"restart the service\", \"check\": \"contains:rc=0\"},\n  {\"intent\": \"write the report\",     \"check\": \"wrote:/tmp/report.md\"}\n]}\n```\n\nThis closes a real hole. `run_shell`\n\nreturns `rc=1`\n\nand the executor still records\n`DONE`\n\n— because the *tool* ran. Whether the *work* succeeded lived only in the output\ntext, and the only reader of that text was the model, which has an interest in the\nanswer. A condition is checked in code, in the order announced, against the receipt of\nthe step that ran: `ok`\n\n(finished cleanly), `contains:<text>`\n\n(its output held that\ntext), `wrote:<path>`\n\n(it wrote exactly there, as the *kernel* derived the target — not\nas the model claimed it).\n\nA condition can only ever withhold a confirmation. If one never comes true, the answer is delivered in full and the system adds its own line beneath it:\n\n```\n✕ 1/2 announced checks met — NOT confirmed done. Still open: step 2 (it writes to /tmp/report.md).\n```\n\nThe limit, stated plainly: **the model writes the conditions too.** A met condition proves\nthat the run's own stated expectation came true — not that the job was done well. Someone\nwho sets themselves a trivial condition passes it. What the code guarantees is narrower and\nstill worth a lot: that the condition was really *evaluated* rather than asserted, that it\nis shown to you word for word so you can judge its worth, and that the real count also\nlands in the event log — which the model cannot write to, and where a forged verdict line\nin its prose is contradicted.\n\nConditions inspect only the receipt of their own step, never the state of the world. One\nthat could read arbitrary files would be an oracle around the kernel (*\"read /etc/shadow\nand tell me whether it contains root\"*), and an unknown condition is dropped rather than\ncounted, so inventing vocabulary gains nothing.\n\nSo planning here does the opposite of what it usually does: it makes a run more\npredictable and *less* powerful. That is deliberate, and it is the same inversion the\nunattended ceiling makes — a capability that arrives with more rights attached is not a\ncapability this design accepts. Under that ceiling the two compose without knowing about\neach other: an unattended plan stops at the first step that would need a human, and says\nso.\n\n`web_fetch`\n\nreturns source; half the web is an empty shell without JavaScript. `browse`\n\nrenders the page in real Chromium and hands back what a reader would see.\n\nWhat it deliberately cannot do is **operate** a page — no clicking, typing or submitting.\nA click has no derivable target: \"click the third element\" cannot be mapped to a resource\nyou could bind a permission to, and a tool without a derivable target is `DENY`\n\nhere by\nconstruction. That is the right answer rather than a shortfall — an agent that operates\nstrangers' pages has a path to effect that no kernel catches afterwards.\n\nIt is also fenced more tightly than a URL check alone can manage. Chromium is pinned to\nexactly the address `guard_url`\n\nverified, and nothing else resolves at all:\n\n```\n--host-resolver-rules=\"MAP * ~NOTFOUND, MAP <host> <ip>\"\n```\n\nA redirect to another name goes nowhere, a script pulled from an ad network likewise, and a DNS rebind between check and fetch — the classic hole in every filter that inspects only the first name — has no effect. The cost is honest: pages that load their content from a CDN under a different name come back incomplete. Incomplete beats uncontrolled.\n\nEvery call gets a throwaway profile: no cookies, no signed-in sessions, no history. A page Talos opens sees a factory-fresh browser.\n\n`delegate`\n\nhands one self-contained question to a second run — and that run **may only\nread**. No writing, no shell, nothing that needs approval. It uses the same executor, the\nsame kernel and the same identity as its caller; what changes is a fourth ceiling that\nturns everything except reading into `DENY`\n\nfor as long as it lasts.\n\nThe reason is the same one that shapes timed runs. A delegated run is born from *model\ntext*, not from someone typing. Giving it the rights of the run that started it would make\ndelegation the most convenient way to produce an effect nobody asked for — a second source\nof permission wearing a different name. So delegation buys reach, never power: it is for\nlooking things up without filling the main run's context with the search. What comes back\nis data, bounded like any tool result, never an instruction.\n\n`SOUL.md`\n\ncarries the agent's **name and character**. Its first heading is the name —\nchange `# TALOS`\n\nto `# ARGUS`\n\n, restart, and the agent is renamed everywhere, including the\nheader of the live display. There is no second place where the name lives.\n\nThe file also sets the language rule: the agent answers in the language you wrote in.\n\nA plain question gets a plain answer — no status chrome. The moment a tool runs, one message starts tracking it and stays afterwards as the receipt:\n\n```\n◉ Talos · 9s · step 2/100\n✓ shell command  2s\n▸ write note — disk.md\n```\n\n`◉`\n\nagent · `◈`\n\nthinking · `≡`\n\nplan announced · `▸`\n\ntool · `✓`\n\ndone · `✕`\n\nfailed ·\n`⏸`\n\nwaiting on you · `⛒`\n\nrefused · `↩`\n\nrolled back. One glyph, one meaning, never in the prose of an answer.\n\n`exec.intent`\n\nis written **before** anything happens:\n\n```\nexec.intent → approval.parked → approval.granted → exec.intent → exec.result\n```\n\nA crash mid-action still leaves a trace. Every run carries the id of the token that\npermitted it. `/log`\n\nshows the last effects, `/undo`\n\nrolls back the last file change.\n\nEighteen, and every one of them passes the same gate. There is no privileged tool and no\ntool that skips the kernel — a tool without a target extractor is `DENY`\n\nby construction.\n\n`run_shell` |\na command, sandboxed, or refused where no sandbox exists |\n`entity_status` |\na known name resolved to an operator-configured fixed read-only probe |\n`read_file` / `write_file` / `undo_last` |\nordinary work, with a snapshot behind the write |\n`browse` / `web_fetch` / `web_search` |\nrender-only, guarded URL, keyless search by default |\n`see_image` / `grab_frame` / `hear` / `speak` |\na picture, one still out of a video, a recording, a voice |\n`vault_search` / `vault_get` / `vault_write_note` |\na markdown knowledge base, if you point it at one |\n`session_search` |\nwhat was said in earlier turns |\n`delegate` |\na sub-run that can only read |\n`ask_operator` |\nthe one way it can ask you something on purpose |\n\nTalos ships with no real entity names, hosts or service units. To enable entity-aware status checks, copy the neutral example and replace every placeholder with infrastructure you control:\n\n```\nmkdir -p data\ncp examples/entities.json data/entities.json\n```\n\n`data/entities.json`\n\nis runtime state: Git ignores it, updates preserve it, and Talos reads\nit as context rather than authority. `entity_status`\n\naccepts only a configured entity name;\nthe fixed URL or systemd user unit comes from this file and cannot be supplied by the model.\n\nThere is no image generation. That is a decision, not a gap: an agent that can produce photographs is a different conversation than one that can only look at them.\n\n**In a session** — the same set in the terminal and in the messenger, through one\ncommand centre. A second vocabulary that only worked in one place is exactly the\nduplication this project has paid for elsewhere.\n\n`/stop`\n\n`/queue`\n\n`/status`\n\n`/new`\n\n`/retry`\n\n`/background`\n\n· `/pending`\n\n`/approve`\n\n`/deny`\n\n`/allowed`\n\n`/revoke`\n\n· `/log`\n\n`/undo`\n\n`/policy`\n\n`/autonomy`\n\n`/tools`\n\n`/whoami`\n\n`/version`\n\n·\n`/usage`\n\n`/model`\n\n`/reasoning`\n\n`/debug`\n\n**On the command line** — thirteen, each answering a question an operator actually asks:\n\n`chat` |\na session here; approvals at a terminal |\n`ask \"…\"` |\none turn, for scripts and cron |\n`setup` · `doctor` · `config` · `models` |\nset it up, and find out why something won't work |\n`status` · `events` · `why <id>` |\nwhat it did, and why that was allowed or refused |\n`report` · `review` |\na record someone else can read · what this installation should change |\n`update` · `version` |\nsignature checked, tests first |\n\n`/policy <path|command>`\n\nis a dry run: it calls `decide()`\n\nand shows the verdict without\nexecuting anything. It is the fastest way to understand the kernel.\n\nSmall modules on purpose. The gate path (`policy.py`\n\n, 528 lines) has to be readable in one\nsitting — a gate you cannot read is not a gate.\n\n| Module | Role |\n|---|---|\n`policy.py` |\nthe kernel: target derivation, three verdicts |\n`capability.py` |\ntokens: mint, redeem, invalidate |\n`command_floor.py` |\nhardline and dangerous command detection |\n`approval.py` / `standing.py` |\none-shot and standing approvals |\n`autonomy.py` / `trust.py` / `channel.py` |\nceilings above the kernel |\n`executor.py` / `verifier.py` / `snapshot.py` |\nwrite-ahead execution, TOCTOU, undo |\n`conductor.py` / `agent_loop.py` / `worker.py` |\norchestration |\n`plan.py` / `schedule.py` / `subagent.py` |\nannounced sequences, timed runs, delegation — all only tighten |\n`web.py` / `browser.py` |\nthe only door out: URL guard, then a resolver cage |\n`reasoner.py` / `provider.py` |\npluggable model backends |\n`intelligence.py` / `evaluation.py` |\nbounded entity context, working state, factual review, adaptive reasoning and trace regression metrics |\n`credentials.py` |\none route per provider — key and address, resolved at call time |\n`sandbox.py` |\nbubblewrap / `sandbox-exec` ; refuses rather than running unconfined |\n`identity.py` / `ux.py` |\nname and glyphs |\n`telegram.py` / `mail.py` / `whatsapp.py` |\nchannels — every way in fetches, none listens |\n`vision.py` / `hearing.py` / `speech.py` / `frames.py` |\nreading a picture, hearing a recording, speaking, one still out of a video — ordinary READ/WRITE with a target |\n`cli.py` / `doctor.py` / `configcli.py` / `schema.py` |\nthe nine subcommands: diagnose, read and change settings — the schema decides what may be written, and the allowlist and the network exceptions never are |\n`askcli.py` |\n`talos ask` — one turn from a script, as a channel with no special right |\n`models.py` |\nlive model lists, cached on disk, added to the curated catalogue and never replacing it |\n`updater.py` |\nupdate beside the old tree, both suites in the new one, switch only if green |\n`eventlog.py` / `memory.py` / `usage.py` |\ndurable log, conversation memory, metering |\n\n`openat2()`\n\nwith`RESOLVE_BENEATH`\n\ninstead of realpath checks.**An adapter seam for tools.** Adding a channel is already just implementing a protocol; adding a*tool*still touches three places, one of them inside the kernel (`TARGET_EXTRACTORS`\n\n). The seam should make a new effect routine without making the kernel optional — everything still passes the same gate, the same tokens, the same snapshots, the same receipts.**MCP**, over that seam and over the official SDK, so a server's tools are proposals like any other — never a second place where permission is decided. The hard part is not the transport: a foreign tool arrives without a target extractor, and a tool without one is`DENY`\n\nby construction. Either a target can be derived from its schema, or the tool is read-only and has none — anything else would hand the decision to the server.**Ownership as the boundary, not just the floor.** While the agent and its config file belong to the same writable user, the floor and the sandbox are code in the same process. A separate user for the model worker would make it a real separation. On the hardened deployment the config already belongs to root and the agent may only read it.\n\nLanded since this list was first written: the sandbox for `run_shell`\n\n(bubblewrap /\n`sandbox-exec`\n\n, see [What it does not do](#what-it-does-not-do)), streaming replies,\ntimed runs under a ceiling that makes them weaker than typed ones,\n[announced plans](#announced-plans) built so that planning tightens a run instead of\nwidening it, read-only delegation, a render-only browser, the media tools, hearing on the\nmachine itself, one still out of a video, keyless search, **mail as a second way in**, and\na command line that grants nothing by being one.\n\nChanges to the kernel (`policy.py`\n\n, `capability.py`\n\n, `command_floor.py`\n\n, `approval.py`\n\n,\n`standing.py`\n\n, `autonomy.py`\n\n, `trust.py`\n\n, `verifier.py`\n\n, `executor.py`\n\n) need:\n\n`pytest`\n\n,`redteam.py`\n\nand`e2e.py`\n\ngreen, in that order.**Any loosening must add a red-team case** proving the boundary next to it still holds.\n\nIf you find a way past the kernel, that is the most valuable thing you can contribute —\nand it belongs in the **private** channel, not in a public issue: see\n[SECURITY.md](/talos-kernel/talos/blob/main/SECURITY.md), which also states the scope and the limits no patch will\nremove. Everything else — a missing tool, a refusal you disagree with, a wrong answer —\nis a normal issue and welcome as one.\n\nMIT. See [LICENSE](/talos-kernel/talos/blob/main/LICENSE).", "url": "https://wpnews.pro/news/talos-ai-agent-super-secure", "canonical_source": "https://github.com/talos-kernel/talos", "published_at": "2026-08-14 23:18:48+00:00", "updated_at": "2026-08-14 23:41:22.877977+00:00", "lang": "en", "topics": ["ai-agents", "ai-safety", "ai-tools", "ai-infrastructure"], "entities": ["Talos", "talos-agent.ch", "bubblewrap", "sandbox-exec", "TALOS_SANDBOX_ALLOW_UNCONFINED"], "alternates": {"html": "https://wpnews.pro/news/talos-ai-agent-super-secure", "markdown": "https://wpnews.pro/news/talos-ai-agent-super-secure.md", "text": "https://wpnews.pro/news/talos-ai-agent-super-secure.txt", "jsonld": "https://wpnews.pro/news/talos-ai-agent-super-secure.jsonld"}}