diff --git a/CHANGELOG.md b/CHANGELOG.md
index dd8d8a5..c44d87c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -9,6 +9,16 @@ remain historical snapshots. Beta does not mean error-free operation.
## [Unreleased]
+## [0.20.1] — 2026-11-11
+
+### Changed
+
+- Make expressive Telegram activity show the current phase, code-styled known tool
+ identifiers, completed/failed counts and an explicit long-wait hint. The header
+ indicator uses the existing heartbeat and can be disabled with
+ `TALOS_STATUS_ANIMATION=0`. Approval waits stop cosmetic edits; fatal errors freeze
+ the existing card as Stopped. Reply receipts and pending worker semantics remain.
+
## [0.20.0] — 2026-11-11
### Stable release
diff --git a/CLAUDE.md b/CLAUDE.md
index e49d1ac..ca0095d 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -14,7 +14,7 @@ preferences live in `USER.md`. All three are operator-owned prompt state and rel
|---|---|
| Gate path | `policy.py`, **996 lines** — has to stay readable in one sitting |
| Tools | **37**, every one gated |
-| Suites | **3492** collected tests · **277** adversarial · 44 end-to-end |
+| Suites | **3499** collected tests · **277** adversarial · 44 end-to-end |
| Home |
-
- talos 0.20.0 · operator console · MIT talos 0.20.1 · operator console · MIT This hall unfurls one true session, recorded on the working machine
@@ -378,7 +378,7 @@ That one decision shapes the rest of this manual. It is why the
command list is short, and why a few obvious conveniences are missing on purpose. Upgrade to 0.20.0 to use these commands. Preview the fingerprinted plan before
+ Included in the signed 0.20.1 installer
+ Upgrade to 0.20.1 to use these commands. Preview the fingerprinted plan before
applying it; staged content grants no permissions. Preview first. Talos inventories only portable profile text, memory documents
@@ -436,7 +436,7 @@ Follow the installation guide: inspect the Bash installer, install the signed release, configure your identity and model, then start terminal chat. Talos requires Python 3.11 or newer. The installer verifies the release and runs its tests; it does not start the agent automatically. A separate Pinokio launcher is also available; check the release it currently pins. Yes. Talos runs on Linux, including Raspberry Pi, as well as macOS. The agent and the model are separate: a Pi can run Talos while a configured provider supplies inference. Running a large model locally needs enough memory and compute for that model. Self-hosting does not imply that every model fits on a Pi. See model setup. Yes. Configure your own Telegram bot and explicitly allow your identity. Talos fetches messages by long polling; the Telegram channel does not need a public webhook. Follow the Telegram setup instructions. Keep one service polling each bot token to avoid competing consumers. Talos 0.20.0 can preview a redacted, fingerprinted plan for portable profile text, memory documents and Agent Skills, then apply exactly that plan into inactive staging. It does not import passwords, permissions, schedules, plugins, sessions, databases or live connector access. This feature is included in 0.20.0; see the migration guide. Talos Connections is the public source interface for the existing isolated-worker registry and secret-free disabled connector drafts. A draft is inert: the operator must separately install the server and opt in through both agent and worker allowlists. It contains no private Atlas service, bundled account, endpoint or credential. Connections is included in the signed 0.20.0 installer. See the guide and commands. Talos 0.20.1 can preview a redacted, fingerprinted plan for portable profile text, memory documents and Agent Skills, then apply exactly that plan into inactive staging. It does not import passwords, permissions, schedules, plugins, sessions, databases or live connector access. This feature is included in 0.20.1; see the migration guide. Talos Connections is the public source interface for the existing isolated-worker registry and secret-free disabled connector drafts. A draft is inert: the operator must separately install the server and opt in through both agent and worker allowlists. It contains no private Atlas service, bundled account, endpoint or credential. Connections is included in the signed 0.20.1 installer. See the guide and commands. Compare the same task, model, host resources and permission scope. Measure completed outcomes, operator effort, latency and recovery, not just stars or test counts. Talos focuses on a readable deterministic capability kernel and scoped approvals; other projects may suit different integrations or workflows. We do not claim a universal winner. Read the documented comparison and check each project's current source for its own capabilities. Talos 0.20.0 is stable under its documented operator contract. Automated tests and signed releases are evidence for specific checks, not a guarantee of error-free operation. Review the frozen operator contract, validate your own workflows and keep a rollback path. Experimental Computer adapters have separate qualification requirements; a source implementation is not a ready-to-use VM. Talos 0.20.1 is stable under its documented operator contract. Automated tests and signed releases are evidence for specific checks, not a guarantee of error-free operation. Review the frozen operator contract, validate your own workflows and keep a rollback path. Experimental Computer adapters have separate qualification requirements; a source implementation is not a ready-to-use VM. Visit Talos on GitHub and star it if you want to keep track of the project. Fork the repository to experiment, follow the contribution guide, or open a reproducible issue. Helpful contributions include installation reports, clearer documentation and regression tests. Share only sanitised examples, never tokens, private logs or personal configuration. These answers describe Talos, not a hosted service. Check the release notes for the version you install. A GitHub star is a bookmark and a signal of interest, not a security certificate. Terminal, Telegram, mail over IMAP,
WhatsApp through an operator-controlled broker over SSH. Every way in fetches;
nothing opens a listening socket. Telegram can show the current phase,
+ code-styled tool names and completed or failed counts in one card. The optional
+ indicator pauses for approval; a waiting hint never invents progress or an ETA. new in 0.20.0 included in 0.20.1 The 0.20.0 installer can prepare a bounded move from Hermes or OpenClaw and can record
+ The 0.20.1 installer can prepare a bounded move from Hermes or OpenClaw and can record
disabled connector drafts. It never inherits an old agent's passwords, grants,
schedules or live connections. Included in 0.20.0. Review the migration fingerprint before applying; imported
+ Included in 0.20.1. Review the migration fingerprint before applying; imported
content stays inactive until you explicitly activate a validated skill. None of this asks for belief. The installer runs the unit and
adversarial suites in front of you; the real-model cases are a separate opt-in E2E run. Stable source and signed release. Counts describe the source test suite, not speed or real-world success rates; signed release contents follow their tag. raise the keep Stable 0.20.0. Tested on Python 3.11 · 3.13, Linux and macOS, including Raspberry Pi.
+ Stable 0.20.1. Tested on Python 3.11 · 3.13, Linux and macOS, including Raspberry Pi.
Bring your own model: a supported CLI, configured API provider or local Ollama.
The installer runs the unit and adversarial suites in front of you, then starts nothing.
+
@@ -54,7 +54,7 @@ tools — but only after a deterministic permission kernel has ruled on the acti
Choose **Allow once**, **Allow this task**, or an exact-action standing approval;
you do not have to approve every step individually. Sandboxed shell execution and an
append-only audit trail keep actions bounded and reviewable. Talos is MIT-licensed;
-model-provider and hosting costs depend on your setup. The **0.20.0 stable** release retains the frozen
+model-provider and hosting costs depend on your setup. The **0.20.1 stable** release retains the frozen
[operator contract](docs/beta-contract.md). Stable is not a claim of error-free
operation or a substitute for reviewing permissions and keeping backups.
@@ -70,12 +70,13 @@ a handover, save and read back a local note, and compare release notes without i
missing facts. Already tried it? [Share your first-run result](https://github.com/talos-kernel/Talos/issues/new?template=first_run.yml),
including installation blockers. No star or positive review required.
-**Latest signed release — [0.20.0](https://github.com/talos-kernel/Talos/releases/tag/v0.20.0):**
-this stable release carries forward the qualified RC.3 runtime: safe agent migration,
-Talos Connections and labelled scheduled results, including the pypdf 6.21.4
-security fix. The operator contract and permissions remain unchanged.
+**Latest signed release — [0.20.1](https://github.com/talos-kernel/Talos/releases/tag/v0.20.1):**
+this stable update adds clearer Telegram live activity: phase headings, code-styled
+tool identifiers, measured outcomes and a quiet optional indicator. It retains safe
+agent migration, Talos Connections and labelled scheduled results. The operator
+contract, permissions and dependency locks remain unchanged.
-**Included in 0.20.0:** migration from Hermes or
+**Included in 0.20.1:** migration from Hermes or
OpenClaw can now fingerprint and apply an exact redacted plan into inactive staging,
archive that staging for recovery, and activate one validated imported skill at a time.
[Talos Connections](docs/connections.md) adds secret-free disabled connector drafts and
@@ -94,7 +95,7 @@ See the
**Source versus release:** `main` can contain newer changes. Signed installers and
package managers follow their pinned release; older archives remain immutable.
-0.20.0 includes the Beta.8 pip 26.2.2 hash lock and isolates pip from per-user
+0.20.1 includes the Beta.8 pip 26.2.2 hash lock and isolates pip from per-user
configuration. When upgrading from Beta.7 or older with custom pip settings, use
`PIP_CONFIG_FILE=/dev/null talos update` once; those older updaters still inherit pip
configuration.
@@ -107,7 +108,7 @@ configuration.
- **Inspect the evidence:** review tool outcomes and the audit trail; do not mistake a confident answer for a successful action.
Start with the [model, cost and permission FAQ](https://talos-agent.ch/faq/).
-Talos is not Talos Linux or Cisco Talos. It is a self-hosted AI agent, with a stable 0.20.0 release.
+Talos is not Talos Linux or Cisco Talos. It is a self-hosted AI agent, with a stable 0.20.1 release.
If this approach is useful, **star the repository to bookmark it**, try a small task and
[report a reproducible result](https://github.com/talos-kernel/Talos/issues).
Want to improve it? [Fork Talos](https://github.com/talos-kernel/Talos/fork) and read
@@ -330,6 +331,12 @@ compact labelled rows. Approval prompts keep the exact command visible.
`/stop` interrupts the run and `/log` opens its receipts. A finished conversation
does not imply that a delegated background job has finished.
+Version 0.20.1 adds phase-specific headings, code-styled tool identifiers and
+separate completed/failed counts to the expressive card. A quiet activity indicator
+uses the existing five-second heartbeat; `TALOS_STATUS_ANIMATION=0` disables it.
+Approval waits stay still, and a long wait is labelled without inventing progress
+or an ETA. Tool arguments and full commands remain confined to approval prompts.
+
Operator-defined OpenAI-compatible endpoints can be listed in the JSON file named by
`TALOS_CUSTOM_PROVIDERS` (default: `data/custom-providers.json`). Each entry needs
`name`, `base_url` and `models`; optional `env_key` names that endpoint's own key.
@@ -423,7 +430,7 @@ come from `stdin` instead of `getUpdates`.
```
$ talos chat
- ◉ Talos / 0.20.0
+ ◉ Talos / 0.20.1
Your terminal. Your rules.
──────────────────────────────────────────────────────────────────────────
model claude-cli/claude-opus-4-1
diff --git a/assets/demo/README.md b/assets/demo/README.md
index 33bab7b..1e5dfd4 100644
--- a/assets/demo/README.md
+++ b/assets/demo/README.md
@@ -10,7 +10,7 @@ DEMO_PROVIDER=claude-cli DEMO_MODEL=… assets/demo/build_gif.sh
```
`../talos-telegram-demo.gif` shows the same control model in Telegram: the live
-work trail, an approval card, a reply-only receipt that names a pending worker,
+work trail with phase headings, code identifiers and wait feedback, an approval card, a reply-only receipt that names a pending worker,
the worker result arriving separately, and a scheduled run arriving as its own
labelled notification. Rebuild it with:
@@ -50,6 +50,6 @@ pauses over two seconds are capped, so model latency does not inflate the GIF.
**Provenance of the shipped GIFs:** the terminal recording was rebuilt 2026-11-04
from Talos 0.20.0-rc.2 with `ollama/qwen3.8:latest` (local, no account). The
-Telegram animation was generated the same day from the current source UI strings;
+Telegram animation was regenerated 2026-11-11 for the 0.20.1 activity update;
its transport is intentionally offline and synthetic. Rebuild either asset with
the commands above.
diff --git a/assets/demo/build_telegram_gif.py b/assets/demo/build_telegram_gif.py
index f7bf924..5fd54c8 100644
--- a/assets/demo/build_telegram_gif.py
+++ b/assets/demo/build_telegram_gif.py
@@ -89,7 +89,7 @@ def plain(text: str) -> str:
return html.unescape(re.sub(r"<[^>]+>", "", text))
-def talos_states() -> tuple[str, str, str, str, str]:
+def talos_states() -> tuple[str, ...]:
client = DemoClient()
activity = TelegramActivity(
client,
@@ -109,16 +109,22 @@ def talos_states() -> tuple[str, str, str, str, str]:
activity.progress(progress("read_file", "running", step=2, summary="read — notes.md"))
client.now[0] = 3
reading = plain(client.messages[client.sent_order[0]])
+ client.now[0] = 5
+ activity.tick()
+ heartbeat = plain(client.messages[client.sent_order[0]])
+ client.now[0] = 35
+ activity.tick()
+ waiting = plain(client.messages[client.sent_order[0]])
activity.progress(progress("read_file", "done", step=2))
activity.progress(
progress("write_file", "running", step=3, summary="write — summary.md")
)
- client.now[0] = 7
+ client.now[0] = 37
activity.progress(progress("write_file", "needs_human", step=3))
approval = plain(client.messages[client.sent_order[0]])
activity.progress(progress("write_file", "done", step=3))
activity.progress(progress("delegate_codex", "running", step=4))
- client.now[0] = 15
+ client.now[0] = 45
activity.progress(progress("delegate_codex", "done", step=4))
activity.succeed()
trail = plain(client.messages[client.sent_order[0]])
@@ -132,7 +138,7 @@ def talos_states() -> tuple[str, str, str, str, str]:
scheduled = ScheduledRun("41c226d2", "Daily project status").wrap(
"The project is healthy. The dependency scan found no known vulnerabilities."
)
- return reading, approval, trail + "\n\n" + receipt, worker, scheduled
+ return reading, heartbeat, waiting, approval, trail, receipt, worker, scheduled
def font(size: int) -> ImageFont.FreeTypeFont:
@@ -220,14 +226,16 @@ def frame(messages: list[tuple[str, bool]], *, approval: bool = False,
if approval:
y = buttons(draw, y, selected=selected)
draw.rectangle((0, HEIGHT - 72, WIDTH, HEIGHT), fill="#17212b")
- draw.text((26, HEIGHT - 55), caption, font=SMALL, fill="#9fb3c8")
- draw.text((WIDTH - 26, HEIGHT - 55), "simulated transport · real Talos text",
+ if y > HEIGHT - 84:
+ raise RuntimeError(f"Demo frame overflows: {caption}")
+ draw.text((26, HEIGHT - 65), caption, font=SMALL, fill="#9fb3c8")
+ draw.text((WIDTH - 26, HEIGHT - 32), "simulated transport · real Talos text",
font=SMALL, fill="#6e8296", anchor="ra")
return image
def main() -> None:
- reading, approval_text, finished, worker, scheduled = talos_states()
+ reading, heartbeat, waiting, approval_text, trail, receipt, worker, scheduled = talos_states()
user = "Summarise notes.md, save the result, then have a worker verify it."
approval_card = (
"Approval required\nwrite_file · workspace/summary.md\n"
@@ -236,18 +244,21 @@ def main() -> None:
frames = [
frame([(user, True)], caption="1 · request received"),
frame([(user, True), (reading, False)], caption="2 · live measured work trail"),
- frame([(user, True), (approval_text, False), (approval_card, False)],
- approval=True, caption="3 · protected action waits for the operator"),
- frame([(user, True), (approval_text, False), (approval_card, False)],
- approval=True, selected=True, caption="4 · task-scoped approval selected"),
+ frame([(user, True), (heartbeat, False)], caption="3 · one card, current phase"),
+ frame([(waiting, False)], caption="4 · elapsed time is not progress"),
+ frame([(approval_text, False), (approval_card, False)],
+ approval=True, caption="5 · approval pauses the indicator"),
+ frame([(approval_text, False), (approval_card, False)],
+ approval=True, selected=True, caption="6 · task-scoped approval selected"),
+ frame([(trail, False)], caption="7 · completed work remains visible"),
frame([("Saved the summary. The verification worker is still running.", False),
- (finished, False)], caption="5 · reply receipt separates pending work"),
- frame([(finished, False), (worker, False)],
- caption="6 · the background result arrives separately"),
+ (receipt, False)], caption="8 · reply and pending work are separate"),
+ frame([(receipt, False), (worker, False)],
+ caption="9 · the worker result follows separately"),
frame([(scheduled, False)],
- caption="7 · a scheduled result is one labelled notification"),
+ caption="10 · scheduled results keep their own label"),
]
- durations = [1400, 1800, 2200, 1100, 2600, 2800, 3200]
+ durations = [1400, 1800, 1800, 2400, 2200, 1100, 2200, 2600, 2800, 3200]
frames[0].save(
OUT,
save_all=True,
diff --git a/assets/talos-telegram-demo.gif b/assets/talos-telegram-demo.gif
index daee8a3..01f6d2b 100644
Binary files a/assets/talos-telegram-demo.gif and b/assets/talos-telegram-demo.gif differ
diff --git a/docs/stable-readiness.md b/docs/stable-readiness.md
index 5f9aa73..1612ccf 100644
--- a/docs/stable-readiness.md
+++ b/docs/stable-readiness.md
@@ -95,4 +95,14 @@ installation or restart its service.
| `0.20.0-rc.1` | Published, superseded candidate | Original signed assets remain available. Its observation days do not qualify RC.3. |
| `0.20.0-rc.2` | Published, superseded candidate | Original signed security release remains available. |
| `0.20.0-rc.3` | Published upgrade baseline | Signed candidate, with seven deployed daily records dated 5–11 November 2026. Transient channel/provider errors were reviewed; no unresolved security or data-loss regression was identified. |
-| `0.20.0` | Stable source | Release publication requires the final-artifact gates above; matching signed assets and hosted CI establish delivery. No new runtime feature is included. |
+| `0.20.0` | Published stable baseline | Signed artifacts and [main CI](https://github.com/talos-kernel/Talos/actions/runs/42986282432) qualify the first stable release. Seven RC.3 calendar dates and final-artifact install/upgrade/rollback checks passed. |
+
+
+## Subsequent stable update: 0.20.1
+
+Version 0.20.1 adds the separately qualified Telegram activity update after the first
+stable release. It retains the operator contract, permissions and dependency locks.
+Qualify its exact source and signed bytes with the source, platform, real-model,
+installation/recovery and delivery gates above. Use `--previous 0.20.0 --current
+0.20.1`; do not relabel the historical RC.3 observations as observations of this update.
+Publication is established by matching signed assets and CI, not this source label.
diff --git a/site/console.html b/site/console.html
index 81066b5..565c56d 100644
--- a/site/console.html
+++ b/site/console.html
@@ -365,7 +365,7 @@
Witness one deed from door to ledger.
Witness one deed from door to ledger.
This candidate, measured
Numbers this candidate must ship.
embedded below. Either way the digits come from a count, not from memory.
Talos AI agent: the field manual
Section 03Give it a mind
0.20.0Move from Hermes or OpenClaw
+ 0.20.1Move from Hermes or OpenClaw
Section 04Run it
Section 05The terminal session
$ talos chat
-Talos 0.20.0 · anthropic-api/claude-opus-4-8 · autonomy 3
+Talos 0.20.1 · anthropic-api/claude-opus-4-8 · autonomy 3
speaking as cli:1000 · approvals possible · you are at a terminal
/help for commands, `exit` to leave
@@ -688,7 +688,7 @@
Section 11Channels
- Channel Trust Notes
+ telegram FULL Buttons for approvals, live status, images in. telegram FULL Approval buttons, phase headings, tool outcomes and images. Set TALOS_STATUS_STYLE=expressive for the live work card; TALOS_STATUS_ANIMATION=0 disables its indicator. cli FULL chat and ask. Approvals only at a terminal.mail ASK Sender proven from the receiving server's Authentication-Results, fail-closed.
diff --git a/site/faq/index.html b/site/faq/index.html
index 481ed11..3ee6da9 100644
--- a/site/faq/index.html
+++ b/site/faq/index.html
@@ -47,8 +47,8 @@ whatsapp FULL · NOTIFY Broker over SSH pull (FULL) · or Cloud API, outbound only (NOTIFY). Getting started
How do I install Talos on macOS or Linux?
Can Talos run on a Raspberry Pi or a VPS?
Can I use Talos as a self-hosted Telegram AI agent?
Can I migrate from Hermes or OpenClaw?
What are Talos Connections?
Can I migrate from Hermes or OpenClaw?
What are Talos Connections?
Your models. Your costs.
@@ -68,7 +68,7 @@ The terms of the grant
Judge the work
How should I compare Talos with IronClaw, OpenClaw or Hermes?
Is Talos production-ready?
Is Talos production-ready?
How can I star, fork or contribute to Talos?
Give it work worthy of a sworn hand.
4 channelslive activityMove the work. Leave authority behind.
- Move the work. Leave authority behind.
current recordings in the GitHub README.
Their provenance states which parts are real and which transport is simulated.Measured, with the path stated
Talos AI agent: your questions
The command remains yours
- 277 ways to break an agent.
Section 06Attack it yourself
Roll of Arms
Weigh the Orders
Source
- MIT · REV 0.20.0
+ MIT · REV 0.20.1
The MCP registry
guide with the full protocol is docs/claude-worker.md.
A curated starting point ships as examples/mcp-servers.json.
This address moved.
Return to the keep
The others can do more.
{m[1]}" if m[1] in TOOL_LABELS else m[0], line)
# Quiet metadata under a clear heading; every dynamic value stays escaped.
lines[index] = f"{line}" if index == 1 else line
return "\n".join(lines)
diff --git a/tests/test_telegram_live_tracking.py b/tests/test_telegram_live_tracking.py
new file mode 100644
index 0000000..1dc6755
--- /dev/null
+++ b/tests/test_telegram_live_tracking.py
@@ -0,0 +1,95 @@
+"""Telegram activity communicates measured states without exposing tool arguments."""
+from talos.agent_loop import AgentProgress, ProgressStage
+from test_telegram_progress_updates import setup_activity
+from test_telegram_ux import _tool, _result
+
+
+def test_live_phase_indicator_changes_only_the_header_and_uses_existing_tick():
+ client, activity = setup_activity()
+ activity.progress(_tool('run_shell', summary='shell — pytest'))
+ initial = client.sent[0][1]
+ assert 'Running .' in initial
+ assert 'run_shell' in initial and 'Running pytest' in initial
+ client.now[0] = 5
+ activity.tick()
+ assert 'Running ..' in client.edited[-1][2]
+ assert len(client.sent) == 1
+ activity.progress(_result('run_shell', 'done'))
+ client.now[0] = 10
+ activity.progress(AgentProgress(ProgressStage.THINKING, step=2))
+ assert 'Thinking ...' in client.edited[-1][2]
+ assert '1 completed · 0 failed or refused' in client.edited[-1][2]
+ activity.succeed()
+
+
+def test_reduced_motion_disables_the_indicator_but_keeps_measured_time(monkeypatch):
+ monkeypatch.setenv('TALOS_STATUS_ANIMATION', '0')
+ client, activity = setup_activity()
+ activity.progress(_tool('read_file', summary='read — notes.md'))
+ before = client.sent[0][1].splitlines()[0]
+ client.now[0] = 15
+ activity.tick()
+ assert client.edited[-1][2].splitlines()[0] == before
+ assert '15s' in client.edited[-1][2]
+ activity.succeed()
+
+
+def test_approval_parks_live_edits_and_resumes_on_a_real_result():
+ client, activity = setup_activity()
+ activity.progress(_tool('write_file', summary='write — notes.md'))
+ activity.progress(_result('write_file', 'needs_human'))
+ before = len(client.edited), len(client.sent)
+ client.now[0] = 90
+ activity.tick()
+ assert (len(client.edited), len(client.sent)) == before
+ assert 'Needs your approval' in client.edited[-1][2]
+ activity.progress(_result('write_file', 'done'))
+ assert '1 completed' in client.edited[-1][2]
+ activity.succeed()
+
+
+def test_waiting_hint_is_based_on_events_and_clears_when_work_returns():
+ client, activity = setup_activity()
+ activity.progress(_tool('run_shell', summary='shell — pytest'))
+ client.now[0] = 30
+ activity.tick()
+ assert 'Waiting for the next result' in client.edited[-1][2]
+ assert '0 completed' in client.edited[-1][2]
+ activity.progress(_result('run_shell', 'done'))
+ assert 'Waiting for the next result' not in client.edited[-1][2]
+ assert '1 completed' in client.edited[-1][2]
+ activity.succeed()
+
+
+def test_code_labels_never_reveal_commands_and_dynamic_html_is_escaped():
+ client, activity = setup_activity(name='Example')
+ activity.progress(_tool('run_shell', summary='curl --password PRIVATE_CANARY'))
+ activity.progress(_tool('read_file', summary='read — '))
+ rendered = client.edited[-1][2]
+ assert 'PRIVATE_CANARY' not in rendered and 'curl' not in rendered
+ assert '