<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>Changelog releases</title>
    <link>https://amparo.ellmstack.dev/changelog</link>
    <description>Release notes for Changelog by EL AI Intelligence</description>
    <language>en</language>
    <item>
      <title>0.15.0</title>
      <link>https://amparo.ellmstack.dev/changelog</link>
      <guid isPermaLink="false">amparo-0.15.0</guid>
      <pubDate>Tue, 08 Sep 2026 00:00:00 +0000</pubDate>
      <description><![CDATA[<h3>Added</h3>
<ul>
<li>A provider catalog: <code>AMPARO_INFERENCE_PROVIDER</code> now accepts a catalog id or alias — <code>anthropic</code>, <code>openai</code>, <code>moonshot</code> (<code>kimi</code>), <code>deepseek</code>, <code>qwen</code>, <code>glm</code>, <code>mistral</code>, <code>groq</code>, <code>openrouter</code>, <code>gemini</code> (its OpenAI-compatible endpoint), <code>ollama</code>, <code>lmstudio</code>, or <code>custom</code> — each carrying its default endpoint and model; an unknown id errors with the catalog list instead of a bare parse failure.</li>
<li>The setup wizard's provider step is now a picker: a numbered friendly list (a number or a name, Enter keeps the default), the endpoint and model prefilled from the catalog and validated as entered, and a live probe after save ("reply with the single word: ok", short timeout) that confirms the model answers — failures print the reason and never block, and re-running the wizard picks up the saved profile as the defaults.</li>
<li>OpenAI-compatible endpoints get URL normalization: trailing slashes and a stray <code>/chat/completions</code> are stripped, a bare host gains <code>/v1</code>, and a URL without a scheme and host is rejected with a clear error.</li>
</ul>
<h3>Changed</h3>
<ul>
<li>Requests to OpenAI-compatible endpoints are body-pure: Amparo-only fields (<code>think</code>, <code>format</code>, <code>web_search</code>) no longer leak into the payload — strict endpoints (Moonshot, DeepSeek-class) stop rejecting requests with HTTP 400 — and JSON-schema requests map to the standard <code>response_format</code> field. The native Ollama path keeps <code>think</code>.</li>
<li>Reasoning models keep their chain of thought: <code>reasoning_content</code> round-trips into the next request for providers the catalog marks as reasoning (Kimi, DeepSeek, Qwen, GLM), so multi-turn tool calling keeps working; other providers never see the field.</li>
<li>The <code>[infer]</code> status line (boot banner and wizard summary) names the catalog label and model — e.g. <code>Moonshot AI (Kimi) · kimi-k3</code>.</li>
</ul>]]></description>
    </item>
    <item>
      <title>0.14.0</title>
      <link>https://amparo.ellmstack.dev/changelog</link>
      <guid isPermaLink="false">amparo-0.14.0</guid>
      <pubDate>Tue, 08 Sep 2026 00:00:00 +0000</pubDate>
      <description><![CDATA[<h3>Added</h3>
<ul>
<li>Windows interactive parity: <code>amparo tui</code> and <code>amparo code</code> run full-screen on Windows — Windows Terminal and the legacy console host — over the native console API: raw keystrokes as UTF-16 records, VT processing on stdout, quick-edit mode cleared for the session and restored on exit.</li>
<li>Bare <code>amparo</code> on a terminal opens the one-time setup wizard (first run), then the interactive TUI; piped stdin keeps the usage print and exit 2.</li>
</ul>
<h3>Changed</h3>
<ul>
<li>The unconfigured <code>amparo run</code> error now points at <code>amparo</code> — the setup wizard is one command away.</li>
</ul>]]></description>
    </item>
    <item>
      <title>0.13.0</title>
      <link>https://amparo.ellmstack.dev/changelog</link>
      <guid isPermaLink="false">amparo-0.13.0</guid>
      <pubDate>Sun, 06 Sep 2026 00:00:00 +0000</pubDate>
      <description><![CDATA[<h3>Added</h3>
<ul>
<li>Update notifications: <code>amparo update check</code> compares the running binary against <code>amparo.ellmstack.dev/version.json</code>; the site now serves a public changelog page (<code>/changelog</code>), a machine-readable version endpoint (<code>/version.json</code>), and an RSS feed (<code>/feed.xml</code>), all generated at deploy time; the TUI boot banner and the web operator surface show a dismissible notice when a newer build is published.</li>
</ul>
<h3>Changed</h3>
<ul>
<li><code>edit_file</code>'s output key <code>lines_changed</code> is renamed to <code>line_delta</code> — it always carried a delta, never a count, and now says so.</li>
</ul>
<h3>Fixed</h3>
<ul>
<li>Colored build output no longer hides errors: <code>parse_build_errors</code> strips ANSI CSI sequences before matching, so a build spawned under <code>CARGO_TERM_COLOR=always</code> (the CI gate's environment) counts and reports errors exactly like a plain one.</li>
</ul>]]></description>
    </item>
    <item>
      <title>0.12.0</title>
      <link>https://amparo.ellmstack.dev/changelog</link>
      <guid isPermaLink="false">amparo-0.12.0</guid>
      <pubDate>Sat, 05 Sep 2026 00:00:00 +0000</pubDate>
      <description><![CDATA[<p>M13, landed: the coding surface — <code>amparo code</code> opens a directory as an
interactive editor (file tree with git marks, diff-accept edits through
the gate chain, a streaming build/shell pane), and the reveal program's
platform verification and cross-surface handoff land alongside it: CI on
three OSes at the pinned MSRV, the fan-out approval hub, and the Telegram
receiver.</p>
<h3>Added</h3>
<ul>
<li><strong><code>amparo code [DIR]</code></strong> (the M13 coding surface): an alternate-screen editor — a file tree with the workspace's git marks, open-file viewing, diff-accept edits (with a file open, <code>e</code> submits one instruction as a one-shot agent turn behind the same gate chain as <code>amparo run</code>; the proposed diff renders and each hunk is accepted or rejected), and a run pane (<code>b</code> runs the detected build, <code>!</code> runs one shell command) that streams the build/shell output live, scrollable, with esc-abort. Piped, it degrades to a one-shot report.</li>
<li><strong>Streaming build/shell seams in <code>amparo-tools</code></strong>: <code>run_command_streaming</code> / <code>run_build_streaming</code> stream the child's stdout/stderr through a line callback (the first line is the command echo); both share the confined-spawn helpers with <code>execute()</code>, so the blocklist, env hygiene and timeouts cannot drift between the two paths.</li>
<li><strong>The fan-out approval gate (R3a)</strong>: with <code>--approval-endpoint</code>, the TUI renders the approval card locally *and* publishes it to the hub — the endpoint is now a publication target, not an exclusive web gate. Deny wins and is latched at the hub; a surface timeout is a deny.</li>
<li><strong>The Telegram receiver (R3b)</strong>: <code>amparo chat telegram --receiver URL</code> relays the hub's pending approvals to the operator chat with inline approve/deny buttons and posts the press back (<code>AMPARO_APPROVAL_TOKEN</code> is the hub credential). The live drill covered the phone-approve and Telegram-press paths against the deployed hub.</li>
<li><strong>CI on three platforms</strong>: a GitHub Actions matrix (Linux, macOS, Windows) runs both gates at the pinned MSRV (1.85) with warnings denied, and a tag-triggered release workflow builds all five installer targets natively and publishes a GitHub release with SHA256SUMS.</li>
</ul>
<h3>Changed</h3>
<ul>
<li><strong>Wall-clock tool timings</strong>: the <code>[exec]</code> line renders <code>duration_ms</code>, which every tool hardcoded to 0 — multi-second calls (<code>fetch_url</code>, builds) displayed as instant. <code>execute_call</code> (the CLI run / chat loop path) now stamps real elapsed time per attempt, matching what the MCP path's registry already did.</li>
<li><strong>Platform scratch dirs</strong>: the file/shell sandbox and the preflight blast-radius classifier now use platform-aware shared scratch roots — <code>/tmp</code> and <code>/dev/shm</code> on Unix, the system temp directory on Windows (which has no <code>/dev/shm</code> twin). The default-workspace fallback when <code>HOME</code> is unset is now the system temp dir instead of <code>/tmp</code>.</li>
<li><strong>MSRV-safe lockfile</strong>: the first pinned-1.85 CI run caught a lockfile that had drifted past the declared MSRV (resolved at rustc 1.98 — <code>idna_adapter</code> 1.2.2 and <code>icu</code> 2.3.x require 1.86/1.88). The lock now resolves MSRV-compatible versions via <code>resolver.incompatible-rust-versions = "fallback"</code> in <code>.cargo/config.toml</code>, and the CI gates run <code>--locked</code> so dependency drift fails loudly instead of re-resolving.</li>
</ul>
<h3>Fixed</h3>
<ul>
<li><strong><code>run_command</code> on Windows</strong>: the shell tool hardcoded <code>bash -c</code>, which on Windows resolves to the WSL shim and fails without a WSL distro — commands never ran and the working-directory echo was absent. The tool now runs <code>cmd /C</code> on Windows (the cwd echo is <code>cd</code> with no arguments, cmd's equivalent of <code>pwd</code>); the Unix <code>bash</code> path is unchanged.</li>
<li><strong>Chat e2e cwd assertion on Windows</strong>: the per-user-workspace round trip asserted the workspace path against the recorded LLM request bodies as a string needle, but the tool result is a JSON-encoded string inside the messages, and the request serializer escapes it a second time — a Windows path lands in the body with its separators quadrupled, so any escaped needle fails on Windows while Unix passes untouched (no backslashes to escape). Byte-level CI diagnostics pinned the levels; the assert now builds the needle by double-encoding the path (<code>serde_json::to_string</code> twice) — the identity on Unix, the measured body encoding on Windows. The tool itself ran fine (right cwd, exit 0) throughout.</li>
<li><strong>Ledger rotation on Windows</strong>: rotation renames the tmp ledger over the live append handle — POSIX allows renaming over an open file, Windows refuses it without delete sharing, so every rotation failed with an access-denied append warning on Windows. The append and reopen handles now open with <code>FILE_SHARE_DELETE</code> on Windows.</li>
<li><strong>cli_e2e harness</strong>: a <code>run_with</code> timeout now kills the child and carries its partial stdout/stderr in the panic (a stall and a slow runner look identical from outside; the bytes tell them apart), and the TUI <code>!</code> shell test accepts the platform <code>ExitStatus</code> wording — "exit status" on Unix, "exit code" on Windows.</li>
<li><strong>Integration tests at the pinned MSRV</strong>: cargo 1.85 builds the package binaries for <code>cargo test</code> but does not set <code>CARGO_BIN_EXE_&lt;name&gt;</code> (that arrived in a later cargo), so the real-process e2e suites (<code>cli_e2e</code>, <code>chat_e2e</code>, <code>bin_e2e</code>) now fall back to locating the binaries next to the test executables in the profile directory.</li>
<li><strong>Sandbox boundary on macOS and Windows</strong>: both sides of a boundary comparison now resolve through the same view — the deepest existing ancestor is canonicalized and the tail re-appended — instead of canonicalize-or-lexical. On macOS the temp dir and <code>/tmp</code> are symlinks into <code>/private/…</code>, and a not-yet-existing write target can never canonicalize, so every fresh workspace write was silently denied. The workspace check is also component-wise now: it appended <code>/</code> to the root string, which can never match a Windows <code>\</code>-separated path, so every relative file write on Windows was denied at resolution.</li>
<li><strong>Windows TUI dead code</strong>: the interactive reader (raw mode, keypress approvals, picker) is Unix-only by design — on Windows the surface runs piped. The Unix-only machinery is now allow-listed for dead code on non-unix instead of faking a Windows reader.</li>
<li><strong><code>read_file</code> not-found message is OS-independent</strong>: a missing file now reports <code>File not found: &lt;path&gt;</code> on every platform instead of the raw io error text, which differs per OS ("No such file" vs "The system cannot find the file specified") — the agent reasons over this string, and it must not shift underneath it. Other io errors keep the OS text.</li>
<li><strong>Windows config test fixture</strong>: the absolute-workspace rejection test used <code>/etc/passwd</code>, which is drive-relative (not absolute) on Windows and so sailed through validation. The fixture is now platform-aware — a Windows absolute path trips the rule on Windows. The wizard test had the mirror-image shape: its <code>/tmp/…</code> workspace answer is root-relative on Windows and the wizard (correctly) joins the runner's drive prefix on, so the assertion now checks the contract — an absolute path carrying the workspace name — instead of the Unix literal.</li>
<li><strong>CI wait budgets</strong>: the chat test harnesses polled with 5 s ceilings and the <code>chat_e2e</code> round trips with 30 s; loaded 2-core CI runners (especially Windows) blew both on scheduler contention alone, and the e2e children were killed mid-round-trip. Budgets are now 30 s and 120 s — a polling ceiling, not a correctness window: a stalled driver still fails, just not prematurely. The ledger tests' fetch targets also moved from refused-connection ports to accept-and-drop loopback listeners, a deterministic offline failure that closes in milliseconds on every OS.</li>
<li><strong>Chat approval press race</strong>: the gate registered its approval in the router only after the button message was sent; a press delivered in that window found no entry and was consumed as already-decided, so the gate auto-denied after its 60 s timeout (observed on Windows CI). The gate now registers first — a press cannot outrun the buttons it presses — and unregisters if the message send fails.</li>
<li><strong>Swarm+schedule test's two-second horizon</strong>: the cross-feature test committed its promise to <code>now + 2 s</code>, but the schedule call runs only after a spawn_agent turn AND a child sub-task — on loaded Windows runners the turn outran the instant, the schedule tool honestly refused the past instant (fail-closed), and the store was empty at the assert. The instant is now ten minutes out; the synthetic scan drives the fire without waiting.</li>
</ul>]]></description>
    </item>
    <item>
      <title>0.11.0</title>
      <link>https://amparo.ellmstack.dev/changelog</link>
      <guid isPermaLink="false">amparo-0.11.0</guid>
      <pubDate>Tue, 01 Sep 2026 00:00:00 +0000</pubDate>
      <description><![CDATA[<p>M12, landed: the ecosystem terminal — Amparo writes directly into both
siblings. The TUI stores memories in an Engram vault and manages the
Guardrail Console's org policy rules, with credential setup delegated to
the sibling CLIs.</p>
<h3>Added</h3>
<ul>
<li><strong>Org policy rules in the Guardrail Console</strong> (sibling change): a deny-only, harden-only rules API under <code>/api/orgs/{id}/policies</code> (list, create, toggle, remove) plus a key-scoped <code>GET /api/orgs/current</code>. The console proxy rewrites <code>/check</code> verdicts for rules matching the check's <code>tool_name</code> — the engine's verdict is preserved in <code>engine_verdict</code>, the deny is attributed (<code>org_rule_id</code>, reason <code>"org policy: …"</code>), and the rewrite applies in audit and enforce modes alike.</li>
<li><strong><code>OrgPolicyClient</code></strong> (<code>amparo-tools/src/org_policy.rs</code>): a thin reqwest client for the console's org-rules API, configured from <code>AMPARO_POLICY_KEY</code> + <code>AMPARO_CONSOLE_POLICY_URL</code> (default <code>https://guardrail.elai-intelligence.com</code>). Every failure degrades to a <code>[policy] not connected</code> line with a hint — never a panic, nothing written locally.</li>
<li><strong>The TUI writes into both siblings</strong> (<code>amparo tui</code>): <code>/memory add</code> stores into the resolved memory backend (Engram when wired — verbatim, skips surfaced honestly) and <code>/memory search</code> retrieves; `/policy list|deny|toggle|enforce|audit` drives the console's org rules and surfaces the console's error text verbatim (including the Pro-plan tier gate). A new <code>! &lt;command&gt;</code> escape runs a shell command from the prompt — the delegation path for <code>! guardrail link</code>.</li>
<li><strong>The wizard's 10-answer contract</strong> (<code>amparo wizard</code>): step 3 gains the console URL (<code>AMPARO_CONSOLE_POLICY_URL</code>), and steps 3/4 print delegation guidance — the sibling CLI found on PATH ("<code>guardrail link</code> pairs this machine") or its install one-liner.</li>
<li><strong>Real-shape verification</strong>: the Engram adapter's mocks are pinned to the live engramd v0.1.4 capture/search response shapes, and the full cross-repo path was drilled against a fresh console: gk_ key → check through the console proxy → deny rule → <code>verdict: deny</code> with the engine's verdict preserved → enforce flip reflected in <code>/api/orgs/current</code> → rule removal restores the engine verdict.</li>
</ul>]]></description>
    </item>
    <item>
      <title>0.10.0</title>
      <link>https://amparo.ellmstack.dev/changelog</link>
      <guid isPermaLink="false">amparo-0.10.0</guid>
      <pubDate>Mon, 31 Aug 2026 00:00:00 +0000</pubDate>
      <description><![CDATA[<p>M11, landed: adoption — Engram and Guardrail go native, and the web
surface goes live (see the README "Native integrations" section and
<code>docs/trial-bundle.md</code>).</p>
<h3>Added</h3>
<ul>
<li><strong>The Engram memory backend</strong> (<code>amparo-tools/src/engram_store.rs</code>): <code>EngramStore</code> implements the <code>Memory</code> trait over engramd's REST surface (<code>POST /memories/search</code> → <code>MemoryEntry</code>, <code>POST /memories</code>, <code>GET /health</code> probe). Env-gated behind <code>AMPARO_MEMORY_BACKEND=engram</code> with <code>AMPARO_ENGRAM_URL</code> (default <code>http://127.0.0.1:8787</code>) and an optional <code>AMPARO_ENGRAM_KEY</code>; wired at all four registry sites (CLI run, MCP serve, chat driver + dispatch). Engram stays behind the trait — recommended, never a dependency. A daemon that is down at startup or mid-run degrades to the built-in store with one <code>[memory]</code> warn; never fatal.</li>
<li><strong><code>amparo doctor</code> probes both companions</strong>: the Engram check (<code>--engram-url</code>, <code>AMPARO_ENGRAM_URL</code>, or the default when the engram backend is configured) does one real <code>GET /health</code>; an unreachable daemon is a problem that names the degradation. The Guardrail check's <code>--probe</code> does one real <code>/check</code> and reports audit mode ("policy: … in audit mode — verdicts are advisory") when the engine answers <code>enforced: false</code> — a visible mode, never a finding.</li>
<li><strong>The web surface went live</strong> at <code>amparo.ellmstack.dev</code> — the deployment promised in <code>docs/web-surface.md</code>: the thin MCP bridge on the site box against the v0.9.0 approval seam (<code>--approval-endpoint</code>, 60 s fail-closed), sandboxed systemd unit, Caddy vhost, grey-cloud DNS, Let's Encrypt. The app holds no policy keys — the spawned <code>amparo mcp-serve</code> process holds them. Deployment-only: no crate surface changed beyond the probes above.</li>
</ul>]]></description>
    </item>
    <item>
      <title>0.9.0</title>
      <link>https://amparo.ellmstack.dev/changelog</link>
      <guid isPermaLink="false">amparo-0.9.0</guid>
      <pubDate>Mon, 31 Aug 2026 00:00:00 +0000</pubDate>
      <description><![CDATA[<p>M10, landed: coordination &amp; surfaces — the blackboard, notifications,
rollback hints, the web-approval seam, and the CLI scheduler (see
<code>docs/m10-coordination-surfaces.md</code>).</p>
<h3>Added</h3>
<ul>
<li><strong>The blackboard</strong> (<code>amparo-tools/src/blackboard.rs</code>): <code>blackboard_read</code> (Observational) / <code>blackboard_write</code> (LocalMutating) over <code>&lt;workspace&gt;/.amparo/blackboard/board.jsonl</code> — append-only rows, last write per key wins on read, every row kept. Shared across the delegation chain via the registry clone; rows carry no writer identity (arguments are model-chosen) — the trusted writer (the loop's task id) rides in the <code>[bus]</code> event.</li>
<li><strong><code>send_notification</code></strong> (<code>amparo-tools/src/notification.rs</code>): an ExternalEffector tool behind a <code>NotificationTransport</code> seam — <code>StderrTransport</code> (default: `[notification] to &lt;destination&gt;: &lt;message&gt;<code>) and </code>WebhookTransport<code> (POSTs </code>{"destination", "message"}<code> JSON). </code>amparo run --webhook-url URL` wires the webhook; the chat hosts wire platform transports; the approval copy names the destination; a transport failure is a failed tool result, never a task crash.</li>
<li><strong>Rollback groups</strong> (W3): <code>ToolExecutor::rollback(&amp;call)</code> returns a display-only <code>RollbackSpec { undo, markers }</code> — computed pre-execution, rendered as a <code>[rollback]</code> line and in the destructive-call approval copy, never executed. Destructive file calls back up the previous contents to <code>&lt;path&gt;.amparo-bak</code> before writing (fail-closed: no backup, no write).</li>
<li><strong>WebApprovalGate + <code>--approval-endpoint</code></strong> (W4, <code>amparo-agent/src/web_approval.rs</code>): the web-approval seam per <code>docs/web-surface.md</code> (contract v3) — POST the full <code>ApprovalRequest</code> (arguments, reasons, blast_radius, session_label, rollback hint) and poll <code>{endpoint}/{call_id}</code> for the decision; 60 s timeout, 1 s poll interval, every failure mode fails closed. Flag on <code>amparo run</code> and <code>amparo mcp-serve</code> (http(s) validated; mutually exclusive with <code>--auto-approve</code>); the MCP server's <code>gate_and_dispatch</code> gained preflight classification + session label so web payloads over MCP carry them.</li>
<li><strong>MCP spawn + CLI scheduler</strong> (W5): `amparo mcp-serve --max-sub-agents N<code> registers </code>spawn_agent<code> (opt-in; absent or </code>0` = no spawn tool) under one shared budget across the spawn chain; <code>amparo run</code> always registers <code>schedule</code> (top-level tasks only), and <code>due_scan</code> fires due <code>cli</code> promises at run start — concurrently with the main task, through the same gate chain, sharing provider/policy/approval/flags; <code>--resume</code> is a run start too. Missed past the grace window = fail-closed. A fire is a fresh task with a reduced tool set (no <code>spawn_agent</code>, no <code>schedule</code>).</li>
<li><strong>M10 test sweep (W6)</strong>: the W4 append one-off closed as an environmental one-off (24 clean runs, no in-code mechanism); the four nanos-based test-root helpers gained per-process sequence counters; four new e2e tests (gate-chain fire, denied fire executes nothing, resume fire, MCP spawn denied without an approver) — 739 tests across the workspace at the W7 gate.</li>
</ul>
<h3>Changed</h3>
<ul>
<li><code>spawn_agent</code> is no longer absent from every non-chat surface: the MCP server exposes it as an explicit opt-in (<code>--max-sub-agents N</code>, shared budget, still gated, still denied without an approver). It remains absent from <code>amparo chat dispatch</code> (no session, no delegation chain, no audit).</li>
<li><code>schedule</code> is no longer chat-only: <code>amparo run</code> registers it with the process-scoped scheduler — best-effort by design (fires at run start only; the chat host keeps the 30 s ticker).</li>
</ul>]]></description>
    </item>
    <item>
      <title>0.8.0</title>
      <link>https://amparo.ellmstack.dev/changelog</link>
      <guid isPermaLink="false">amparo-0.8.0</guid>
      <pubDate>Sun, 30 Aug 2026 00:00:00 +0000</pubDate>
      <description><![CDATA[<p>M9, landed: verification &amp; QA — the QC council beside policy, the
operator's sweep, and the audit-mode notice (see
<code>docs/m9-verification-qa.md</code>).</p>
<h3>Added</h3>
<ul>
<li><strong>The QC council</strong> (<code>amparo-agent/src/qc.rs</code>): deterministic rule auditors that run beside policy — after the candidate final answer, before the verification prompt. Four rules over the run's own records: <code>unexecuted_tool_claim</code> (the answer cites a registry tool that never executed), <code>evidence</code> (executed calls exceed tool results still in context), <code>cost_honesty</code> (a <code>$</code> inference figure diverges from the <code>chars/4</code> accounting estimate; leaf runs only), and <code>pii_shape</code> (residual PII shapes as category counts — values never enter a finding, I6). Verdicts are advisory (<code>Approved</code> / <code>WithFindings</code>); findings append to the verification prompt as issues to check ("ignore any that are wrong"), and verification stays the model's call. <code>AgentEvent::QcAudit</code> (render + sink arms), a <code>[qc] audit #N …</code> tracing line, and in-memory stats counters (logged, never persisted).</li>
<li><strong><code>amparo doctor</code></strong> (<code>amparo-cli/src/doctor.rs</code>): one deterministic, read-only sweep — workspace existence + writability, ledger readability (unparseable lines flagged), session checkpoints (<code>Running</code> older than 7 days = stale), notebook/skills/schedule JSONL parseability, policy-engine reachability (TCP dial; <code>--probe</code> sends one real <code>/check</code> and consumes one engine check), and the <code>--chat-config</code> TOML. Exit 0 healthy / 1 problems / 2 usage; check lines on stdout, problems on stderr; cron-able. The sweep reports and never repairs.</li>
<li><strong>The audit-mode stderr notice</strong> (<code>amparo-policy</code>): <code>AuditNoticeEngine&lt;E&gt;</code> prints the exact promised line — `policy engine is in audit mode; verdicts are advisory` (<code>docs/trial-bundle.md</code> §"graceful degradation") — once per process, on the first verdict carrying <code>wire::AUDIT_ONLY_MARKER</code>. Display-only (I1): verdicts pass through untouched. Wired at all three surfaces (run, mcp-serve, chat — the driver shares one flag across its per-task engines).</li>
<li><strong>Session tagging</strong> (<code>WirePolicyEngine::with_session_id</code>): every <code>/check</code> request carries the session id (omitted when absent — never sent as null). <code>amparo run --session-id ID</code> (default: the task id; on <code>--resume</code>, the checkpoint's original id) and <code>amparo mcp-serve --session-id ID</code> (optional); the chat driver tags <code>platform:user_id</code>.</li>
<li><strong>M9 test sweep (W4)</strong>: process-level doctor exit-code matrix, notice-once + session-tagged checks against a new <code>MockPolicy</code> wire responder, default-session-id e2e, and wire-level session-id serialization tests — 669 tests across the workspace at the W5 gate.</li>
</ul>
<h3>Changed</h3>
<ul>
<li>None — no M6–M8 surface changed. The verification prompt gains an appended section only when findings exist; an empty pass leaves it byte-identical.</li>
</ul>]]></description>
    </item>
    <item>
      <title>0.7.0</title>
      <link>https://amparo.ellmstack.dev/changelog</link>
      <guid isPermaLink="false">amparo-0.7.0</guid>
      <pubDate>Sun, 30 Aug 2026 00:00:00 +0000</pubDate>
      <description><![CDATA[<p>M8, landed: sub-agents and scheduling behind the gate chain — no member
exits the gate chain, and a model is never the approver (see
<code>docs/m8-swarms.md</code>).</p>
<h3>Added</h3>
<ul>
<li><strong>Token accounting + the cost line</strong> (<code>amparo-agent</code>): <code>estimate_tokens</code> (chars/4, the standard approximation — deterministic and uniform across providers; an estimate, not provider billing), accumulated per turn into <code>AgentReport.tokens_estimated</code> and <code>tool_calls</code> (counted at dispatch). `AgentConfig. cost_per_million_tokens: Option&lt;f64&gt;<code> (default </code>Some(3.0)`, a stated mid-range model assumption; <code>None</code> omits the cost line, counts still shown). Rendered as `~$0.04 in inference (estimate, chars/4, $3/1M tokens)` — the method states its assumptions.</li>
<li><strong>Delegation identity</strong> — host-generated task ids (<code>Agent::with_task_id</code>); child ids chain (<code>{parent}.{n}</code>: <code>sess-123.1</code>, <code>sess-123.1.1</code>). `Checkpoint.parent_task_id: Option&lt;String&gt;<code> (additive, serde default); </code>LedgerRow.task_id` / <code>parent_task_id</code> (additive — pre-M8 rows parse with <code>None</code>); <code>LedgerSink::new</code> gains <code>(task_id, parent_task_id)</code>. <code>ApprovalRequest.session_label: Option&lt;String&gt;</code> (<code>None</code> at non-agent sites) renders `[session] sub-agent sess-123.1 of task sess-123 wants to run:` in CLI approvals and the same label in chat approval messages — display-only (I1), the gate is unchanged. <code>TaskStarted</code>/<code>TaskComplete</code> gain an optional <code>task_id</code> in their render lines.</li>
<li><strong><code>spawn_agent</code></strong> (<code>amparo-agent/src/spawn.rs</code>, registered by the hosts): param <code>task</code> (required), trust tier <code>ExternalEffector</code> — creating an acting entity always asks a human. The child is a fresh <code>Agent</code> sharing the parent's provider, policy engine, approval gate, event sink, registry, <code>PathPolicy</code>, checkpoint store, tenant, trust ceiling, and config (the rate knob inherits); <code>child.run()</code> executes inside the tool executor and the child's report returns as the tool result. <strong>The budget is shared across generations</strong> (<code>Arc&lt;Mutex&lt;usize&gt;&gt;</code>) and fails closed: past the limit the result is <code>swarm budget exhausted: N sub-agents max</code> and no agent exists. <code>SubAgentSpawned</code> event renders <code>[spawn] {child} under {parent}: …</code> (render + NotebookSink arms).</li>
<li><strong>Preflight <code>sub_agent</code></strong>: <code>BlastRadius::SubAgent</code> (severity 2; the classes renumber 0–5) with the note `spawns a sub-agent that acts under the same gate chain<code>; </code>classify<code> maps </code>spawn_agent` to it (static override). Display-only.</li>
<li><strong><code>--max-sub-agents N</code> on <code>amparo run</code></strong> — default 4; <code>0</code> means the tool is not registered (swarms off); garbage or negative → usage error, exit 2. Chat: <code>UserProfile.swarm: Option&lt;SwarmProfile&gt;</code> (<code>[users."…".swarm]</code> with <code>max_sub_agents</code>, default 4, and <code>schedule</code>, default false) — per-profile → per-tenant (I2), unknown keys rejected. <code>spawn_agent</code> is deliberately absent from the MCP and <code>amparo chat dispatch</code> surfaces.</li>
<li><strong><code>schedule</code></strong> (<code>amparo-chat</code>): a persisted promise, not an execution. `ScheduledTask { id, tenant, requester, task (PII-stripped at write — I6), at: RFC 3339, status: pending | fired | missed, result }<code> at </code>&lt;workspace&gt;/.amparo/schedule/&lt;id&gt;.json` (atomic tmp + rename); <code>ScheduleTool</code> params <code>at</code> + <code>task</code>, tier <code>ExternalEffector</code>, preflight <code>system_wide</code>; registered only in the chat driver, per-profile <code>schedule: true</code>, after <code>with_spawn_agent</code> (children never inherit it). The driver's 30 s ticker (Amparo's first background loop; <code>MissedTickBehavior::Delay</code>, startup scan) runs the pure due-scan (<code>due_scan(tasks, now, grace)</code>, 60 s grace) and fires each due promise back through the full gate chain as its original requester — fresh Agent, continuity off, ledger + checkpoints attached, wrapped in <code>TimeoutApprovalGate</code> (90 s) over the chat gate (60 s): firing while nobody is present auto-denies, never silently ahead of the gate. Past the grace window a promise is marked <strong>missed</strong> — fail-closed, never fired late — and the requester is notified; a fire notifies `[schedule] {id} fired — {answer}`. The fire path carries no skills/case-library/spawn/ schedule — a promise fires one task.</li>
<li><strong><code>amparo schedule list|cancel</code></strong> — the operator's window on the queue: <code>list</code> prints every promise; <code>cancel</code> moves a pending promise to <code>cancelled</code> (<code>cancelled by the operator</code>) — a status change, never a deletion. Exit codes follow the <code>amparo run</code> contract (2 usage / 1 runtime / 0 ok).</li>
<li><strong>The swarm report</strong> — `swarm: N sub-agent(s) (ids), M tool calls, ~$X.XX in inference (estimate, chars/4, $R/1M tokens)` (tool calls and tokens include the parent's): <code>[swarm]</code> in the CLI, appended to the final answer in chat.</li>
</ul>]]></description>
    </item>
    <item>
      <title>0.6.0</title>
      <link>https://amparo.ellmstack.dev/changelog</link>
      <guid isPermaLink="false">amparo-0.6.0</guid>
      <pubDate>Sat, 29 Aug 2026 00:00:00 +0000</pubDate>
      <description><![CDATA[<p>M7's two deliberate exclusions, landed: the WASM eval sandbox and the
ledger quota lever (see <code>docs/m7b-sandbox-quota.md</code>).</p>
<h3>Added</h3>
<ul>
<li><strong><code>amparo-sandbox</code></strong> (new crate): <code>SandboxRuntime</code> — fuel-metered, deterministic WASM execution (10M fuel, 4 MB module, 4 MB memory, 30 s wall clock, 4 concurrent; all <code>with_*</code> builders), with <code>SandboxError</code> (invalid wasm, base64, execution, fuel, memory, module-too-large, compile, timeout, import-rejection) and <code>SandboxResult</code> (output, elapsed, fuel consumed, memory used). <strong>No imports and no WASI</strong>: a module with any import is rejected at compile time, so there is no host surface to escape through. The ABI: the module exports <code>memory</code> and `axiom_eval(i32 input_ptr, i32 input_len, i32 output_ptr, i32 output_cap) -&gt; i32`; the input JSON is written at offset 0 (input region <code>0 .. OUTPUT_BASE</code>, 256 KB) and the module writes its JSON output at <code>output_ptr</code> (output region <code>OUTPUT_BASE .. OUTPUT_BASE + OUTPUT_CAP</code>, 256 KB — the two regions never overlap), returning bytes written (≥ 0) or −1. New deps: <code>wasmtime</code> 16 (cranelift only, scoped to this crate), <code>base64</code>; <code>wat</code> dev-only.</li>
<li><strong><code>eval_wasm</code> — the tool</strong> (<code>EvalWasmTool</code>): <code>wasm_base64</code> (required; modules arrive base64-encoded — LLMs cannot emit raw bytes) and <code>input</code> (optional, default <code>{}</code>). Trust tier <code>ExternalEffector</code>, so executing untrusted code always asks a human even though the sandbox keeps the reach small. Every failure path returns a failed <code>ToolResult</code> with an explanatory message, never a panic. Hosts register the tool themselves (the <code>use_skill</code> precedent): `amparo run<code>, the chat driver's per-task registry, </code>amparo chat dispatch`, and MCP serve (auto-deny there until an operator allows). The default registry stays wasmtime-free — pinned at its 17 tools by a test.</li>
<li><strong>Preflight honesty for the sandbox</strong>: a static override in <code>classify</code> labels <code>eval_wasm</code> <code>read_only</code> despite its ExternalEffector tier — a pure, bounded computation that observes and modifies nothing outside its own sandbox. Display-only (I1): the tier, the gate and the approval all stay exactly as they were.</li>
<li><strong><code>eval_wasm</code> writes no ledger row</strong> — it never leaves the machine; the <code>LedgerSink</code>'s "what left the machine" contract is pinned by a test.</li>
<li><strong>The ledger quota lever</strong> in <code>amparo-privacy</code>: <code>LedgerQuota</code> and <code>LedgerStore::open_with_quota</code> (the existing <code>open</code> = unbounded, unchanged). After write + flush, an over-quota file rotates: the oldest rows drop until the file is ~<code>max_bytes / 2</code> (one rotation per burst, not per append), the rewrite is atomic (tmp + rename), and a <strong>rotation marker row</strong> — <code>LedgerKind::Rotated</code> with <code>dropped_rows</code> — is appended as the newest row, so it survives the rotation it describes: the reviewer sees exactly what was lost and why (I3). The newest data row is always kept. <code>LedgerSummary</code> gains <code>rotations</code> and <code>rows_dropped</code>.</li>
<li><strong>The quota sidecar</strong>: the enforced bound lives in <code>&lt;privacy&gt;/quota</code> beside the ledger — a bounded open writes it, an unbounded open removes it, so the reviewer surface never reports a bound that is not currently enforced. New free functions <code>recorded_quota</code> and <code>read_ledger</code>: <code>amparo privacy</code> reads through them and never opens a store, so reading never creates, rewrites or rotates the ledger.</li>
<li><strong><code>--ledger-max-bytes N</code> on <code>amparo run</code></strong> — plain bytes or <code>K</code>/<code>M</code>/<code>G</code> suffixes (1024-based); zero, garbage or overflow → usage error, exit 2. <code>amparo privacy</code> reports the bound: <code>ledger: &lt;bytes&gt; bytes (quota &lt;N&gt; | unbounded)</code> and, when rotations exist, <code>rotations: &lt;n&gt; (rows dropped &lt;m&gt;)</code>; the tail renders marker rows as <code>rotated  dropped &lt;m&gt; rows</code>.</li>
<li><strong>Per-tenant chat quotas</strong>: `UserProfile.ledger_max_bytes: Option&lt;u64&gt;<code> in the TOML chat config (zero rejected at load — </code>None` is how a profile says "unbounded"). The driver opens each tenant's ledger with the quota; tenants rotate independently, and allowlist mode stays unbounded.</li>
</ul>]]></description>
    </item>
    <item>
      <title>0.5.0</title>
      <link>https://amparo.ellmstack.dev/changelog</link>
      <guid isPermaLink="false">amparo-0.5.0</guid>
      <pubDate>Sat, 29 Aug 2026 00:00:00 +0000</pubDate>
      <description><![CDATA[<p>Instrumentation &amp; hardening (M7): the always-on privacy ledger, session
persistence, and preflight blast-radius classification — three
instruments that answer, for every executed action, *who allowed it and
under what policy* (see <code>docs/m7-instrumentation.md</code>).</p>
<h3>Added</h3>
<ul>
<li>The <strong>privacy ledger</strong> in <code>amparo-privacy</code>: <code>LedgerKind</code> (<code>network_call</code> / <code>pii_strip</code>), <code>LedgerRow</code>, the append-only <code>LedgerStore</code> (one JSON line per row, flushed; <code>read_all</code> skips a crash's partial final line; <code>summary()</code> aggregates), <code>privacy_dir</code> and <code>site_host_only</code> (scheme + host only — path, query and fragment dropped). Rows land in <code>&lt;workspace&gt;/.amparo/privacy/ledger.jsonl</code>. <strong>Always-on in both hosts</strong>, <code>--growth</code> or not: the <code>LedgerSink</code> in <code>amparo-agent</code> (an <code>EventSink</code> consumer) tracks <code>web_search</code>, <code>fetch_url</code> and <code>run_command</code> from request to execution and appends one row per execution attempt — tool, host at most (never a path, query or command), outcome (<code>ok</code> / <code>error</code> / <code>denied</code>), and the human gate's answer (<code>human_approved</code> / <code>human_denied</code>, absent when no human was asked). A human denial writes its row *immediately* (the execution never happens; the denial is the audit answer), a policy-blocked call writes none, and observational/local tools write none. PII strips are rows too — per-category counts, never values — via the new <code>AgentEvent::PrivacyStripped</code>. Open/write failures warn once per task (<code>[ledger] …</code>) and never fail the task.</li>
<li><strong><code>amparo privacy [--workspace DIR] [--tenant T] [--last N]</code></strong> — the reviewer's front door: summary first (network calls, PII strips, human-approved, human-denied, per-tool and per-category breakdowns), then the newest rows (default 10, tenant <code>cli</code>). Exit codes follow the <code>amparo run</code> contract: 2 usage, 1 runtime, 0 ok.</li>
<li><strong>Preflight blast radius</strong> in <code>amparo-agent</code>: <code>BlastRadius</code> (<code>read_only</code> &lt; <code>workspace_local</code> &lt; <code>network</code> &lt; <code>system_wide</code> &lt; <code>destructive</code>) and <code>classify</code> — the tool's trust tier seeds the class; argument inspection can only raise it (a <code>run_command</code> matching <code>PathPolicy::check_command_blocked</code> → <code>destructive</code>; network-transfer utilities <code>curl</code>/<code>wget</code>/<code>nc</code>/<code>scp</code>/<code>rsync</code>/<code>ssh</code> as whole shell words → at least <code>network</code>; file tools given an absolute path outside the workspace, <code>/tmp</code> and <code>/dev/shm</code> → at least <code>system_wide</code>; an unknown tool → <code>system_wide</code>). The label rides on <code>ApprovalRequest::blast_radius</code> (<code>Option</code> — non-agent sites pass <code>None</code>) and renders in the approval copy: a `[preflight] blast radius: …` line in CLI approvals, the same line in chat approval messages. <strong>Display-only by design (I1)</strong>: classification runs after the gate has decided and feeds nothing back — a wrong label can only misdescribe the approval text, never allow or block anything.</li>
<li><strong>Session persistence</strong> in <code>amparo-agent</code>: <code>Checkpoint</code> (version 1; tenant, <code>task_id</code> <code>sess-&lt;nanos&gt;-&lt;pid&gt;</code>, <code>started_at</code>, PII-stripped prompt, <code>status</code> running/complete/failed, the conversation without system-role messages and with <code>tool_calls</code> arguments stripped, <code>LoopState</code> — <code>last_tool_name</code>, <code>same_tool_count</code>, <code>empty_turn_retried</code>, <code>last_good_summary</code>, <code>used_tool_names</code>, <code>steps_used</code> — and the stripped final answer), the <code>CheckpointStore</code> trait and <code>JsonCheckpointStore</code> at <code>&lt;workspace&gt;/.amparo/sessions/&lt;tenant ':' → '-'&gt;/&lt;task_id&gt;.json</code> — one file per task, atomic tmp + rename, no index ("latest" = scan by <code>started_at</code>), corrupt files skipped with a warn. A snapshot saves once per loop iteration and at every terminal return (a crash loses at most one turn); failures warn, never fail the task. <code>Agent::resume</code> re-prepends the *current* system prompt (I5 — checkpoints store none), restores the loop state, emits <code>TaskResumed</code> (never <code>TaskStarted</code>, and a resume opens no new notebook record — the checkpoint is the session trail), and the resumed loop re-judges every call through the gate chain. Every fresh CLI run checkpoints too — a crash mid-task is resumable.</li>
<li><strong><code>amparo run --resume</code></strong> — resumes the newest incomplete checkpoint for tenant <code>cli</code>; no task argument (with one → usage error, exit 2), none → exit 1, a stale <code>Running</code> (&gt; 7 days) is skipped with a warn, never resumed. Configuration comes from the current flags.</li>
<li><strong>Chat continuity</strong> (W8): when a new chat task starts, the driver loads the tenant's newest complete checkpoint and hands the agent <code>continuity_context</code> — the last 6 user/assistant messages (tool messages skipped), truncated, plus the task's last good tool summary, as one string — injected via <code>Agent::with_continuity</code> as <strong>one user-role message</strong> between the system prompt and the task prompt. Per-tenant only; chat never reads <code>Running</code> checkpoints. Chat tasks checkpoint through the same store, one format for both hosts.</li>
<li><strong>The minimal stderr subscriber</strong> in <code>amparo-cli</code> (the <code>tracing</code> 0.1 crate, already a workspace dependency): the binary installs a ~50-line <code>Subscriber</code> that prints agent <code>warn</code>/<code>error</code> events to stderr as-is (each already carries its <code>[tag]</code>), so a checkpoint save failure or a corrupt session file is visible to the operator; <code>info</code>/<code>debug</code> stay silent.</li>
</ul>]]></description>
    </item>
    <item>
      <title>0.4.0</title>
      <link>https://amparo.ellmstack.dev/changelog</link>
      <guid isPermaLink="false">amparo-0.4.0</guid>
      <pubDate>Sat, 29 Aug 2026 00:00:00 +0000</pubDate>
      <description><![CDATA[<p>Controlled growth (M6): the lab notebook, the verification case library,
gated skills with metrics and retirement, and rollup + archival over the
cold archive — one <code>--growth</code> opt-in, PII-stripped records, the cold
archive never modified.</p>
<h3>Added</h3>
<ul>
<li>New <code>amparo-notebook</code> crate — the lab notebook (M6a): every completed or failed task is recorded as a PII-stripped, tenant-tagged JSON run record (task text, tool-sequence hash, per-call gate log, verification, truncated answer, duration, token-cost estimate), written through the <code>EventSink</code> seam into the <code>Memory</code> trait. Includes <code>NotebookSink</code> (the event consumer) and <code>JsonlStore</code> (append-only local store).</li>
<li><code>--growth</code> / <code>--no-growth</code> on <code>amparo run</code> and <code>amparo chat</code> — records land in <code>&lt;workspace&gt;/.amparo/notebook/records.jsonl</code>, tagged <code>cli</code> or <code>platform:user_id</code>. Off by default: without <code>--growth</code>, no record file is ever created.</li>
<li><code>FanoutSink</code> in <code>amparo-agent</code> — fans every event out to several sinks (plus <code>truncate</code>/<code>TRUNCATE</code> re-exports).</li>
<li>The verification case library (M6b): <code>CaseLibrary</code> / <code>EvidenceCase</code> / <code>evidence_section</code> in <code>amparo-agent</code> (the <code>Agent::with_case_library</code> seam) and <code>CaseRetriever</code> in <code>amparo-notebook</code> — prior same-tenant records are retrieved into the self-verification prompt as read-only observations, never instructions, never in the action loop. <code>--growth</code> now also reads: on both <code>amparo run</code> and <code>amparo chat</code>, prior <code>cli</code> / <code>platform:user_id</code> records feed the verification prompt (still off by default).</li>
<li>Gated skills (M6c): <code>SkillSpec</code> / <code>SkillStep</code> / <code>UseSkillTool</code> in <code>amparo-tools</code>; loop-side expansion in <code>amparo-agent</code> (the <code>Agent::with_skills</code> seam) — <code>use_skill</code> expands into its ordered steps, each gated, executed and recorded individually, so a skill can never grant its steps an exemption. <code>SkillSet</code> + <code>Proposer</code> in <code>amparo-notebook</code> (per-tenant adoptions under <code>&lt;workspace&gt;/.amparo/skills/</code>; recurring VERIFIED sequences distill into inert proposals). New <code>amparo skill add|propose|list|show|adopt</code> CLI: candidates are operator-authored TOML, adoption runs the same policy check as a tool call plus human approval rendering the full step plan, and nothing is adopted automatically. Skill execution requires <code>--growth</code> on <code>amparo run</code> / <code>amparo chat</code> (write + read + act — still off by default); without it, <code>use_skill</code> is never registered.</li>
<li>Skill metrics and retirement (M6d): per-skill running records derived at read time from the run records — uses, VERIFIED rate, mean steps, per-step gate denials, last use — with no new write path. A public <code>dry_run_gate</code> in <code>amparo-agent</code> dry-runs a call through the gate chain without executing or asking approval (Escalate is not drift); the adoption log is now <code>SkillLogEvent</code> (<code>adopt</code>/<code>retire</code> events, adopt rows byte-identical to M6c, last event per skill wins the fold). Startup drift re-check at every <code>--growth</code> task start (<code>run</code> + <code>chat</code>, the task's own policy engine, trust ceiling and registry) — a skill whose step plan would now be denied retires before registration, with a retire event and a <code>[growth]</code> notice (a write failure warns, never fails the task). New <code>amparo skill check|retire</code>: <code>check</code> re-runs drift then performance (VERIFIED rate below <code>--min-verified-rate</code> 0.5 over the last <code>--window</code> 20 uses, 3-use floor) against every adopted skill, retires by default (<code>--dry-run</code> reports only), writes <code>rechecks.jsonl</code>, and exits 0 even when retirements fire (cron-able); <code>retire &lt;name&gt; [--reason ...]</code> is the operator lever and refuses unknown names. <code>amparo skill list</code> shows a compact metrics tail; <code>show</code> keeps retired skills inspectable with status, metrics, the last policy re-check and the full retirement history. Retirement is always disable + notify, never deletion.</li>
<li>Rollup and archival (M6e): a <strong>hot layer</strong> over the cold archive. Under <code>&lt;workspace&gt;/.amparo/notebook/</code>, beside the untouched <code>records.jsonl</code>: <code>hot.jsonl</code> (same <code>MemoryEntry</code> shape, **same id and <code>created_at</code> as the cold entry**, content = the record capped by the ladder — final answer → 300 chars, per-step reasons/summaries → 120, first 12 tool calls, task text → 120, then skeleton rungs — so <code>--max-bytes</code> (default 4096, floor 1024) is a guarantee), <code>hot-hashes.jsonl</code> (the <code>(tenant_id, tool_sequence_hash)</code> dedupe index), <code>rollup.json</code> (<code>RollupState</code> — promotion offset and last-fold stamp, atomic saves), <code>promoted.jsonl</code> (operator promotions) and <code>rollup.lock</code> (cross-process; task-start rollups skip silently when held, operator commands report busy, stale &gt;10 min reclaimed). Every <code>--growth</code> task start (<code>amparo run</code> and <code>amparo chat</code>) promotes the cold tail into the hot layer — byte-offset tracked, each record scanned once; the predicate: a gate event of interest (approval, denial, escalation) or a tool sequence not yet represented — and folds daily (24 h since the last fold; rows older than <code>--days</code>, default 90, fold into the cold archive; operator-promoted rows are exempt). <code>CaseRetriever</code> now reads the hot layer under <code>--growth</code>; the cold archive is never modified. New `amparo notebook list|promote|rollup<code> CLI: </code>list` shows the cold archive newest-first with promotion state, <code>promote &lt;id&gt;</code> pins one record (idempotent, exit 0; unknown ids exit 1), <code>rollup</code> forces promote + fold (cron-able, exit 0; <code>--dry-run</code> reports the same numbers and writes nothing). Sync-relay guidance documented in <code>docs/m6-controlled-growth.md</code> §4.1: the relay moves the hot layer and <code>.amparo/skills/*</code>; <code>records.jsonl</code> stays local and authoritative.</li>
</ul>]]></description>
    </item>
    <item>
      <title>0.3.0</title>
      <link>https://amparo.ellmstack.dev/changelog</link>
      <guid isPermaLink="false">amparo-0.3.0</guid>
      <pubDate>Fri, 28 Aug 2026 00:00:00 +0000</pubDate>
      <description><![CDATA[<p>Multi-tenant identity: the chat face moves from a single-operator
allowlist to a TOML tenant directory, with per-user policy, trust
ceilings, workspaces and attributed approvals.</p>
<h3>Added</h3>
<ul>
<li><strong>The TOML chat profile</strong> (<code>--chat-config &lt;path&gt;</code> / <code>AMPARO_CHAT_CONFIG</code>, the flag wins): <code>[users."platform:user_id"]</code> sections are the tenant directory — a user without an entry is refused exactly as an unlisted user is today. Per-user <code>trust_ceiling</code> (falling back to the <code>--trust-ceiling</code> flag) and per-user <code>workspace</code> (a relative subpath under the workspace root, defaulting to <code>users/&lt;platform&gt;-&lt;user_id&gt;/</code>; absolute and <code>..</code> paths are rejected at load). The file is read once at startup; an empty directory and the legacy <code>AMPARO_CHAT_ALLOWLIST</code> interplay are both warned about at startup.</li>
<li><strong>Per-user policy checks</strong> — with <code>--policy-url</code>, every task gets a fresh engine session-tagged <code>platform:user_id</code>, so engine-side audit rows carry the chat user (legacy allowlist mode included).</li>
<li><strong>Per-user workspaces</strong> — directory-mode tasks run in their own workspace directory, enforced by explicit <code>PathPolicy</code> injection (<code>PathPolicy::from_root</code>, <code>with_policy</code> on the 14 workspace-bound tools, <code>default_registry_with_policy</code>) — never process-global environment mutation. The default <code>users/</code> path is re-checked per task so a hostile user id cannot escape the root.</li>
<li><strong>Strict press attribution</strong> — <code>ApprovalButtonPress</code> carries the pressing user's id; presses from anyone but the requester are rejected with a polite toast and never consume the pending approval.</li>
<li><strong>Binary-level M5 e2e</strong> — the shipped <code>amparo chat telegram</code> process against mock Telegram and LLM servers: config flag/env precedence and exit codes, the per-user workspace proven with <code>pwd</code>, the wrong-user toast followed by the requester's own deciding press, refusal of unknown users, and a per-user ceiling blocking the tool without ever asking.</li>
</ul>
<h3>Changed</h3>
<ul>
<li><code>ChatDriver::new</code> takes <code>Tenants</code> (directory or legacy allowlist) and <code>PolicySource</code> (shared engine or per-task wire engine); <code>allowlisted()</code> becomes <code>allows()</code>, and per-task parts are resolved before the busy claim so a refused user never holds a chat busy.</li>
<li>Wrong-user Slack presses now reply ephemerally (the requester's buttons stay up — previously the outcome edit replaced them) and Discord sends a toast message (previously silent).</li>
</ul>]]></description>
    </item>
    <item>
      <title>0.2.0</title>
      <link>https://amparo.ellmstack.dev/changelog</link>
      <guid isPermaLink="false">amparo-0.2.0</guid>
      <pubDate>Fri, 28 Aug 2026 00:00:00 +0000</pubDate>
      <description><![CDATA[<p>Chat adapters: the agent now runs from Telegram, Discord, and Slack — the
same loop, gate chain and approval seam behind one transport trait, with
inline-button approval.</p>
<h3>Added</h3>
<ul>
<li><strong>The chat layer</strong> (<code>amparo-chat</code>): one <code>ChatTransport</code> seam (text out, approval messages with inline buttons, outcome edits, a receive loop), a <code>ChatDriver</code> that turns a normalized message into a per-task agent run (allowlist, one task per chat, panic-proof task boundary), an <code>ApprovalRouter</code> for routing button presses back to the waiting gate (an atomic take — a second press is already decided), a <code>ChatApprovalGate</code> with inline Approve/Deny buttons and a 60 s auto-deny timeout, and a <code>ChatEventSink</code> forwarding the agent's <code>[tag]</code> progress lines into the chat (the final answer bypasses the sink, so it can never be lost).</li>
<li><strong>Three hand-rolled adapters</strong> — no platform SDKs, rustls-only websockets: <strong>Telegram</strong> via getUpdates long polling (inline keyboards, <code>answerCallbackQuery</code> acks, 409/401 → exit), <strong>Discord</strong> via the gateway websocket (message intents, heartbeat discipline, resume-first reconnects, message components, interaction callbacks), and <strong>Slack</strong> via Socket Mode (envelope acks before processing, block-kit buttons, bot-echo suppression). Every approval flows through the same inline Approve/Deny buttons.</li>
<li><strong><code>amparo chat telegram|discord|slack</code></strong> — the <code>amparo</code> CLI's fourth subcommand. Tokens come from <code>AMPARO_CHAT_*</code> environment variables (never argv); <code>AMPARO_CHAT_ALLOWLIST</code> is a fail-closed single-operator allowlist (empty = every message refused); <code>AMPARO_CHAT_TELEGRAM_BASE</code> points the Telegram adapter at a self-hosted Bot API server.</li>
<li><strong>Canonical event formatting</strong> — <code>format_event</code> moved into <code>amparo-agent</code>, so the CLI and every chat platform render identical <code>[tag]</code> lines.</li>
<li><strong>Full end-to-end coverage</strong> — a binary-level e2e test drives the real <code>amparo chat telegram</code> process against in-test mock LLM and Telegram servers: message → approval keyboard → button press → outcome edit → final answer, with the tool executed against a real workspace.</li>
</ul>]]></description>
    </item>
    <item>
      <title>0.1.0</title>
      <link>https://amparo.ellmstack.dev/changelog</link>
      <guid isPermaLink="false">amparo-0.1.0</guid>
      <pubDate>Fri, 28 Aug 2026 00:00:00 +0000</pubDate>
      <description><![CDATA[<p>First release: the <code>amparo</code> CLI, the agent loop, and the MCP surface — an open
agent that acts under policy, bring your own LLM.</p>
<h3>Added</h3>
<ul>
<li><strong>Provider layer</strong> (<code>amparo-inference</code>): one <code>InferenceProvider</code> trait; <code>OpenAIProvider</code> for any OpenAI-compatible endpoint (Ollama, vLLM, OpenRouter, Together, Groq — including an Ollama-native <code>/api/chat</code> branch); <code>AnthropicProvider</code> for the native Anthropic Messages API, translated to the same contract (incl. <code>tool_use</code>/<code>tool_result</code> and SSE streaming). Fail-closed configuration, per-request and stream-idle timeouts, a <code>max_tokens</code> clamp, and an optional model allowlist.</li>
<li><strong>The agent loop</strong> (<code>amparo-agent</code>): native <code>tool_calls</code> (not text-parsed ReAct) behind the deny-by-default gate chain — trust ceiling → policy gate → human approval — with parallel tool batches, retry ×2, max steps plus conversation trimming, and VERIFIED/INCOMPLETE self-verification. Events flow through an <code>EventSink</code> seam; privacy runs per turn (PII strip/restore).</li>
<li><strong>Policy</strong> (<code>amparo-policy</code>): the <code>PolicyEngine</code> seam with a deny-all default, <code>WirePolicyEngine</code> for the open policy-check wire protocol (<code>POST /check {tool_name, target} → {verdict, reason, enforced}</code>), and <code>AllowAllPolicyEngine</code> as the explicit named opt-in.</li>
<li><strong>Tools</strong> (<code>amparo-tools</code>): the registry plus the portable tool set (web, filesystem, shell, git, tests, build, memory), each with a trust tier that drives the approval gate.</li>
<li><strong>MCP, both directions</strong> (<code>amparo-mcp</code>): <code>McpServer</code> exposes an Amparo registry to external clients over stdio JSON-RPC 2.0 — every <code>tools/call</code> runs the same gate chain; <code>McpClient</code> spawns an external server and mounts its tools at <code>ExternalEffector</code>, so remote tools cannot skip approval. Ships the <code>amparo-mcp-serve</code> binary.</li>
<li><strong>Memory and privacy</strong> (<code>amparo-memory</code>, <code>amparo-privacy</code>): a memory interface with a built-in store (Engram is the recommended backend, never a dependency); privacy policy evaluation with blocked/allowed domain routing and the Secure Minions PII strip/restore primitives.</li>
<li><strong>The installable CLI</strong> (<code>amparo-cli</code>, the <code>amparo</code> binary): <code>amparo run "task"</code> drives the loop end-to-end with fail-closed BYO-LLM environment wiring, interactive terminal approval (y/N, 60 s timeout, EOF/non-terminal stdin auto-denies) with <code>--auto-approve</code>/<code>--auto-deny</code> overrides; <code>amparo mcp-serve</code> is the same implementation as the standalone <code>amparo-mcp-serve</code> binary; <code>amparo version</code> prints the version. stdout carries the final answer only — progress, gate decisions and the report go to stderr.</li>
<li><strong>API stability</strong>: <code>#![warn(missing_docs)]</code> on every crate, so an undocumented public item cannot ship; the two-gate policy (<code>cargo test --workspace</code> and <code>cargo doc --workspace --no-deps</code>, both with zero warnings) is documented in <code>VERSIONING.md</code>; MSRV declared at Rust 1.85.</li>
<li><strong>TLS</strong>: rustls everywhere — <code>openssl-sys</code> is out of the dependency graph, so <code>cargo install</code> needs no OpenSSL headers.</li>
</ul>
<h3>Distribution</h3>
<p>The repository is private; there is no crates.io publication yet. Install
from source:</p>
<p>```sh
cargo install --path crates/amparo-cli
```</p>]]></description>
    </item>
  </channel>
</rss>
