From a744dc0ae2fe1c55ed8087eacc315d03b67f8399 Mon Sep 17 00:00:00 2001 From: Coding-Dev-Tools <274649000+Coding-Dev-Tools@users.noreply.github.com> Date: Tue, 3 Nov 2026 00:44:29 -0500 Subject: [PATCH 1/7] fix(diagnostics): identify damaged databases instead of blaming the key A user-host report showed a forked receipt chain followed by the provider going unavailable with EncryptionError. Since 1.7.6 receipt damage is audit-only, but there was no supported way to see it outside the host log, and every encrypted-open failure was reported as a wrong key. Extract receipt_chain_state/verify_receipt_chain so the install doctor verifies chains read-only through its own connection and reports damage with recovery guidance, bounded to 20k receipts and 25 scopes. Classify EncryptionError with stable codes; driver text is still never rendered because it can echo paths or the PRAGMA key. Refresh the checksum-bound offline evidence artifact and its rendered SVG/PNG surfaces because tracked producer files changed. Refs discussion #151. Agent: opencode --- BENCHMARKS.md | 12 +- CHANGELOG.md | 9 + README.md | 2 +- docs/RECALL_RECOVERY.md | 48 ++ docs/SQLITE_DURABILITY.md | 6 + ...fixtures-receipt-diagnostics-20261103.json | 701 ++++++++++++++++++ ...s-receipt-diagnostics-20261103.json.sha256 | 1 + docs/images/context-efficiency.png | Bin 61488 -> 61516 bytes docs/images/context-efficiency.svg | 4 +- .../images/evidence-backed-agent-examples.png | Bin 273392 -> 273064 bytes .../images/evidence-backed-agent-examples.svg | 4 +- engraphis/backends/encrypted_db.py | 90 ++- engraphis/core/store.py | 371 ++++----- scripts/init.py | 139 +++- tests/test_benchmark_evidence.py | 4 +- tests/test_encryption_dependency.py | 66 ++ tests/test_init.py | 92 ++- tests/test_receipts.py | 9 +- 18 files changed, 1349 insertions(+), 209 deletions(-) create mode 100644 docs/benchmark-evidence/offline-fixtures-receipt-diagnostics-20261103.json create mode 100644 docs/benchmark-evidence/offline-fixtures-receipt-diagnostics-20261103.json.sha256 diff --git a/BENCHMARKS.md b/BENCHMARKS.md index 609810838..0716e972c 100644 --- a/BENCHMARKS.md +++ b/BENCHMARKS.md @@ -94,14 +94,14 @@ interpretation and do not count as additional benchmark-quality gains. ### Public numeric evidence registry Every exact public aggregate retained below comes from the checked-in, public-safe -[`offline-fixtures-automatic-browser-access-20261101.json`](docs/benchmark-evidence/offline-fixtures-automatic-browser-access-20261101.json) artifact. Its +[`offline-fixtures-receipt-diagnostics-20261103.json`](docs/benchmark-evidence/offline-fixtures-receipt-diagnostics-20261103.json) artifact. Its SHA-256 is -`8df20fd0a97f20070ac26628e19b5ae1be653cd7aa9c7399d424cedf0e839a01`, also recorded in the +`7679701565f8897a12bb9d7f65918d8faa1d694757957b8628ef635b640a9bed`, also recorded in the adjacent `.sha256` file. The artifact contains no raw questions, answers, prompts, customer data, or per-record content fingerprints. The fixture-suite digest is -`0179ada4cfffd827e2d1cf2cce1b5110862ac9a5cafbbf0db6dcd6e975e864ce`. The artifact defines +`f7d8108881da709c157b627e21a0720dd83e63b450ec3f9dd4d7816b33884e56`. The artifact defines the digest algorithm and records the SHA-256 of every suite and dataset file. Each evidence ID also binds its exact command through `sha256(UTF-8 exact command)`: @@ -134,7 +134,7 @@ retrieval-quality improvement. It does not qualify browser behavior, hosted serv external models, or provider billing. The registered artifact was produced with: ```bash -python -m scripts.export_offline_evidence --output docs/benchmark-evidence/offline-fixtures-automatic-browser-access-20261101.json +python -m scripts.export_offline_evidence --output docs/benchmark-evidence/offline-fixtures-receipt-diagnostics-20261103.json ``` The earlier release-review predecessors, @@ -203,13 +203,13 @@ grounded checks in separate panels; provider billing and MCP transport are not m the SVG and matching PNG with: ```bash -python scripts/render_benchmark_report.py --report docs/benchmark-evidence/offline-fixtures-automatic-browser-access-20261101.json --output docs/images/context-efficiency.svg --png-output docs/images/context-efficiency.png +python scripts/render_benchmark_report.py --report docs/benchmark-evidence/offline-fixtures-receipt-diagnostics-20261103.json --output docs/images/context-efficiency.svg --png-output docs/images/context-efficiency.png ``` The companion examples are also generated from that artifact with: ```bash -python -m scripts.render_benchmark_examples --report docs/benchmark-evidence/offline-fixtures-automatic-browser-access-20261101.json --output docs/images/evidence-backed-agent-examples.svg --png-output docs/images/evidence-backed-agent-examples.png +python -m scripts.render_benchmark_examples --report docs/benchmark-evidence/offline-fixtures-receipt-diagnostics-20261103.json --output docs/images/evidence-backed-agent-examples.svg --png-output docs/images/evidence-backed-agent-examples.png ``` The historical-to-executable mapping is in [`docs/BENCHMARK_CHANGE_COVERAGE.md`](docs/BENCHMARK_CHANGE_COVERAGE.md). diff --git a/CHANGELOG.md b/CHANGELOG.md index 4959df255..2ddff73f7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,15 @@ All notable changes to Engraphis are documented here. Format loosely follows ## [Unreleased] +- Classify encrypted-database open failures (`database_damaged`, `database_busy`, + `database_unavailable`, `key_rejected`, `driver_missing`, and key-file codes) so a damaged or + locked SQLCipher file is no longer reported as a wrong key, and surface the exact condition in + `engraphis-init --check`. +- Verify operation-receipt chains read-only in `engraphis-init --check` (bounded by workspaces and + receipts), so a forked, disconnected, or truncated ledger is visible to the operator with + recovery guidance while memory writes and recall continue with a content-free + `receipt_warning`. + - Browsers no longer enter a token. The dashboard opens automatically for any browser that can reach it; the deployment's network reachability is the dashboard boundary. Session issuance still requires the same-origin custom header and a published Host, and only an diff --git a/README.md b/README.md index ff315afe2..a0821d021 100644 --- a/README.md +++ b/README.md @@ -51,7 +51,7 @@ Connect a coding agent over MCP with the [agent setup guide](https://github.com/ The current registered artifact contains three deterministic offline fixture runs. It separates context size, retrieval quality, and grounded decision checks; these small fixtures do not establish general task performance.

- Three registered offline fixtures: structure-aware chunking reduced mean retrieved context from 740.3 to 214.3 tokens per question (71.1%, 18 questions); the serialized JSON-shape proxy fell from 24,590 to 11,138 tokens across 26 payload samples and 260 recalls. Candidate and packed retrieval quality (Recall@5, Hit@5, and answer-token recall) are shown separately (each 1.000). Grounded checks show 5/5 answerable queries grounded and 6/6 abstention queries rejected, including a 1/1 quarantined-evidence probe; 11/11 decisions were correct. MCP transport and provider billing were not measured. Artifact SHA-256 prefix 8df20fd0a97f; see the benchmark guide for the full checksum. + Three registered offline fixtures: structure-aware chunking reduced mean retrieved context from 740.3 to 214.3 tokens per question (71.1%, 18 questions); the serialized JSON-shape proxy fell from 24,590 to 11,138 tokens across 26 payload samples and 260 recalls. Candidate and packed retrieval quality (Recall@5, Hit@5, and answer-token recall) are shown separately (each 1.000). Grounded checks show 5/5 answerable queries grounded and 6/6 abstention queries rejected, including a 1/1 quarantined-evidence probe; 11/11 decisions were correct. MCP transport and provider billing were not measured. Artifact SHA-256 prefix 7679701565f8; see the benchmark guide for the full checksum.
Three offline fixtures separate context reduction, candidate and packed retrieval quality, and grounded behavior. The chart shows the artifact checksum prefix; see the benchmark guide for the full checksum and reproduction steps.

diff --git a/docs/RECALL_RECOVERY.md b/docs/RECALL_RECOVERY.md index 79850b011..231ff5144 100644 --- a/docs/RECALL_RECOVERY.md +++ b/docs/RECALL_RECOVERY.md @@ -23,6 +23,54 @@ Do not edit `provenance` in SQLite. Schema 11 automatically preserves the old ex contract and recovers the known historical local-agent downgrade. Unknown and external evidence stays pending; quarantined evidence is never included in bulk approval. +## Operation receipt chains after a crash, restore, or file-level damage + +Host logs may show `sqlite3.IntegrityError: receipt chain has no unique structural head`, followed +by `receipt_warning` entries with code `receipt_chain_integrity_failure`. That means the +hash-chained operation ledger (not your memories) is damaged: two receipts claim the same +predecessor (a fork), a receipt is missing (a disconnection), or the independently stored +head/count no longer matches. + +Since 1.7.6 this is an audit-only condition. Remember, recall, prefetch, imports, and graph work +still complete; affected responses carry `receipt: null` plus a content-free `receipt_warning`. +A host that marks the whole provider unavailable on this error should be upgraded. + +Receipt chains are append-only evidence. Engraphis will not guess a predecessor, rewrite hashes, or +delete rows to make verification pass. Recover in this order: + +1. Stop every process that has the database open (agent host, dashboard, MCP server). +2. Preserve a copy of the database and its `-wal`/`-shm` sidecars (and the key file, if encrypted). + Do not run `VACUUM`, `rekey`, or a migration against the original. +3. Run `engraphis-init --check` (point `ENGRAPHIS_DB_PATH` at the preserved copy). It verifies + current-schema receipt chains read-only and reports damaged ones as `operation receipts`, + including the consequence for your data. +4. Verify one workspace with `engraphis_verify_receipts` (or the Inspector) and export it with + `engraphis_export_receipts` before changing anything. +5. Restore your most recent verified backup, or keep the damaged copy for diagnosis and accept + that this workspace's receipt verification remains invalid. + +Never regenerate `ENGRAPHIS_DB_KEY` to "fix" this. A changed key makes an encrypted database +unreadable; it does not repair a receipt chain. + +### `EncryptionError` after an integrity error + +`EncryptionError` covers every failure to honor encryption, not only a wrong key. Check its `code` +before touching the key: + +| code | meaning | +| --- | --- | +| `database_damaged` | The key was applied; SQLite reports a malformed database image. Preserve the file and restore a verified backup. | +| `database_busy` | Another process holds the database. Close it and retry. | +| `database_unavailable` | Path, permissions, disk space, or mount problem. Not a key problem. | +| `key_rejected` | Wrong key, or a plaintext database opened with a key. The key must match the file. | +| `key_config_invalid` | The key could not be applied (format or driver problem). | +| `key_file_empty` / `key_file_unreadable` | The configured key file is empty or could not be read safely. | +| `driver_missing` | Install `engraphis[encryption]` or provision a compatible SQLCipher driver. | + +A `-wal`/`-shm` file shown as `(deleted)` while a process still holds it means the database file +was replaced or removed underneath a running process (a restore, a copied database, or a +filesystem tool). Always stop writers before replacing a database file. + ## Legacy v1 migration Migrate a flat v1 database through the staged v2 importer: diff --git a/docs/SQLITE_DURABILITY.md b/docs/SQLITE_DURABILITY.md index 02067979e..8a6b921ff 100644 --- a/docs/SQLITE_DURABILITY.md +++ b/docs/SQLITE_DURABILITY.md @@ -49,3 +49,9 @@ change does not certify hardware power-loss protection, backup restoration, remo vector publication, or a recovery-time objective. Measure throughput and latency with the selected policy recorded; do not compare an unlabelled NORMAL baseline to FULL or weaken the default to meet a benchmark target. + +Replacing or deleting a database file while a writer still has it open is unsupported: the +running process keeps the old inode (and its `-wal`/`-shm` files can appear as `(deleted)`), +while a new process creates a different database at the same path. Stop every Engraphis process +before restoring or swapping a database. If a receipt chain is damaged after such an event, see +[`RECALL_RECOVERY.md`](RECALL_RECOVERY.md#operation-receipt-chains-after-a-crash-restore-or-file-level-damage). diff --git a/docs/benchmark-evidence/offline-fixtures-receipt-diagnostics-20261103.json b/docs/benchmark-evidence/offline-fixtures-receipt-diagnostics-20261103.json new file mode 100644 index 000000000..14d03e3f2 --- /dev/null +++ b/docs/benchmark-evidence/offline-fixtures-receipt-diagnostics-20261103.json @@ -0,0 +1,701 @@ +{ + "environment": { + "embedding": "deterministic", + "numpy": "2.4.6", + "platform": "win32", + "python": "3.11.17", + "vector_backend": "numpy" + }, + "generated_on": "2026-11-03", + "privacy": { + "contains_answers": false, + "contains_customer_data": false, + "contains_per_record_fingerprints": false, + "contains_prompts": false, + "contains_raw_questions": false + }, + "runs": [ + { + "boundary": "Deterministic offline retrieval fixture; normalized-character token estimator; not external QA or provider billing.", + "command": "python -m eval.chunking_eval --dataset eval/datasets/longdoc.jsonl --k 5", + "config_digest": "c1c8196aa7e1568ef3844a9fb2d76b87f342c39108e32d6ad144b885a76143b8", + "config_digest_method": "sha256(UTF-8 exact command)", + "id": "offline-chunking", + "result": { + "chunked": { + "max_stored_tokens": 59, + "mean_context_tokens": 214.3, + "mean_evidence_tokens": 42.4, + "memories": 24, + "recall_at_k": 1.0 + }, + "context_reduction_pct": 71.1, + "documents": 6, + "k": 5, + "questions": 18, + "token_counter": "engraphis.chars4.v1", + "whole": { + "max_stored_tokens": 213, + "mean_context_tokens": 740.3, + "mean_evidence_tokens": 162.2, + "memories": 6, + "recall_at_k": 1.0 + } + } + }, + { + "boundary": "Deterministic offline CodeMem fixture; serialized JSON-shape payload proxies, not MCP transport responses, provider billing, or latency claims.", + "command": "python -m eval.performance --dataset eval/datasets/codemem.jsonl --k 5 --iterations 10 --json", + "config_digest": "bbe4aca81e58d4830e50a8fc7729a1d15b71d97a6299bccd79432b7f119677d7", + "config_digest_method": "sha256(UTF-8 exact command)", + "id": "offline-performance", + "result": { + "answer_token_recall": 1.0, + "compact_serialized_payload_tokens": 11138, + "dataset_cases": 14, + "full_serialized_payload_tokens": 24590, + "hit_at_k": 1.0, + "k": 5, + "max_context_tokens": 108, + "mean_context_tokens": 85.38, + "memories": 44, + "packed_quality": { + "answer_token_recall": 1.0, + "hit_at_k": 1.0, + "recall_at_k": 1.0, + "sample_count": 26 + }, + "payload_boundary": { + "kind": "serialized_json_shape_proxy", + "mcp_envelope_serialized": false, + "token_counter": "engraphis.regex.v1", + "transport_measured": false + }, + "quality_scope": { + "packed": "packed_quality fields score only chunks admitted to reader context", + "retrieved": "legacy quality fields score all candidate chunks returned before context packing" + }, + "questions": 26, + "recall_at_k": 1.0, + "saved_serialized_payload_tokens": 13452, + "serialized_payload_savings_ratio": 0.5471, + "timed_recalls": 260, + "token_budget": 1500, + "token_counter": "engraphis.regex.v1" + } + }, + { + "boundary": "Deterministic offline support/abstention fixture; not a frontier-model answer-quality score.", + "command": "python -m eval.grounded", + "config_digest": "590442e51e3642c10489165759919dc86ffac62c182937330c153e7f8d5fc26f", + "config_digest_method": "sha256(UTF-8 exact command)", + "id": "offline-grounded", + "result": { + "abstained": 6, + "answerable": 5, + "decision_accuracy": 1.0, + "grounded": 5, + "off_topic": 6, + "quarantine_hits": 1, + "quarantined": 1 + } + } + ], + "schema": "engraphis-public-offline-fixtures/v1", + "suite": { + "digest": "f7d8108881da709c157b627e21a0720dd83e63b450ec3f9dd4d7816b33884e56", + "digest_method": "sha256(canonical compact JSON mapping each sorted path to its file SHA-256)", + "files": { + "engraphis/__init__.py": "54c83cf75637c6d12314b474aaeab66ffd2bf817f066d6148ec8aa3bf2c4952a", + "engraphis/ai_context.py": "4dfd5d39eb95d05c591d1981e9e855eced73232f53efd9fe9966e0e08957d030", + "engraphis/app.py": "44d68ad8c0ff46baed01978c9b04e40b8be5031609d5a69e205b7f05da3777fb", + "engraphis/backends/__init__.py": "a9f22b9278362904166614081f1df78469d453601b298ce4e8afdba8a3722b25", + "engraphis/backends/codegraph.py": "83e723a91068d23694092fbe00157fcb2597061d453eb36f955bf55bc5b4d35e", + "engraphis/backends/embedder_api.py": "56a6bceea4f757325dcea987b0339e41d3875634b1ae5103f0537a358cf5878b", + "engraphis/backends/embedder_deterministic.py": "ee2476c33ba83a9bba5d7caaa4dbb40861ee22cbdb1cf3c1092ca6c018a6c38f", + "engraphis/backends/embedder_st.py": "c497b47038227516a0bc43d4bc0a91fc46d267bbffb0382c0b893e44f016d6b4", + "engraphis/backends/encrypted_db.py": "1fadee106525de5da0fc8823bd1ac991a82ae0c65b6df90a8c5ef21a1b826c54", + "engraphis/backends/extractor.py": "f2e3455ab7f14caee1d5b5c4ef071e498e90118f1b0ddcb8510c969583b0fc57", + "engraphis/backends/graph_extractor.py": "88561efa0d3fabc447a0a005b10e36261379d46218cf62d905e6928cd2fda676", + "engraphis/backends/jev_decision.py": "31d4a789a013226f5be3eb47c54d3ca34af09b1c77b7c9ab1f57622035951d08", + "engraphis/backends/jev_query_planner.py": "c64818474c15acf5d42f673f8084f0fbf782e78b23b1f8cb9a32d6d044d5af63", + "engraphis/backends/jev_transport.py": "e65368169d53998d3e02cc78a42ed7c92fd27749cc1c9b4f95aa856e618a0ee9", + "engraphis/backends/model_source.py": "8c3c7681f95214a2bbabd8de222e5ee11f42fe13402d27365654ae75fb363d4e", + "engraphis/backends/postgres_schema.py": "8468578c3add701d30d5eaa36d768ded2375d116e55f1836e09f6107a07a267e", + "engraphis/backends/query_planner.py": "bbdd77afc9b5523421b85b2ae63c8da7f5a7b777265450e0d21708a83e7bb23c", + "engraphis/backends/reranker.py": "747761d6cbfa421388974bcfd98d844f92391d80f4bf6a4feca00b0c7a6908ca", + "engraphis/backends/resources.py": "47cc867c3aecc8bd95fa284bc5bb04715f3339c19a0a11512973ef6171c95944", + "engraphis/backends/retention.py": "381d9371e3951d762f8b55eb54711de5697642acb39de99a714f246c059ecbd0", + "engraphis/backends/sync_folder.py": "e4f70a92a17f6a365910670df041e6e3ca421d44ada2827917cd66b4dc067bfa", + "engraphis/backends/sync_relay.py": "b8b9ad265453aba17ba7c27a355e12a793469b3e44cb217943c6fad9382a3006", + "engraphis/backends/vector_numpy.py": "c598831bea547824cfe08844816fa79857d3617cb0631238f95dad955a424f72", + "engraphis/backends/vector_sqlitevec.py": "6148e14ceaacc19239b64c642a3fba0e98797c78cec356210157afadf475a08b", + "engraphis/build_info.py": "624c22471e56d4c4047160808c4245488292af611564d1a63ca437605bbb414f", + "engraphis/cloud_authz.py": "e80500579cb3a1d5fbf30814dc94e3e3967e50b311e8ed2fa56afcc13f7eb565", + "engraphis/cloud_features.py": "90e876f8993d99f01760cd01ae9fb63140e3f65036706e8fb0c8224560416330", + "engraphis/cloud_session.py": "cc6ab27c6af69b5c676e721ab06c520730e205f5bbffe27818650edfb71d4b63", + "engraphis/commercial.py": "184f312066a9e682e51a0abeff042f1c0e8eed2d47470157b23930b5a17633aa", + "engraphis/config.py": "7a1aa04246bda341f26866c5145854412e8c157d9880212fe1d7579ac986b764", + "engraphis/core/__init__.py": "dd5143729c3939237f04636f437032b1f2d3a5f7d82c91bbc2a5a283c3f0ebaa", + "engraphis/core/adaptive_context.py": "cc5ce48109bb0d5230a5b2b8424b829c853feec5b5d5b82596413f2279b0c9f9", + "engraphis/core/browsing.py": "cfae752d52ef51b17c4ffbe44dde62d5b0e1ed0ca34ec0e1788f3991d94bf05f", + "engraphis/core/codegraph_export.py": "4641074258d7b23498f92dd45053a0fbb111863eaad2001c08e5e3c2dc2fd54f", + "engraphis/core/conflicts.py": "28530be25a4af0bffd8f609b965b33ca7f93199a70887789b2148fda8a61a486", + "engraphis/core/consolidate.py": "f66eedfe08319a64b761261ccf1c99b163eba564d6d4060534bf8c575aecf232", + "engraphis/core/context.py": "8cc15746e4de88bce7d34db8c11dfaa545f17305788e2167a286179171717fb9", + "engraphis/core/diagnostics.py": "5ba449bdf5087d8ccb5808379596e695c8f405da497372e99f2bac509f96e8e4", + "engraphis/core/documents.py": "9b4a5dce557bbd25217c61b51d5838a8b7c693c77f7a700d010ed607cb39b150", + "engraphis/core/engine.py": "c908dea96113a97dcd56679f62c9e587b28130204888b16e4c8e64d68f47d3fe", + "engraphis/core/evidence.py": "97912b52a3d22de909218f83c09c92572ed695d7b3c2117e5330271c6d84a7bd", + "engraphis/core/fsutil.py": "6db770fa8bd3e1a57dfa70eb8e8bc46d48c2dc53ead1b0b43eeecf085ef58cfc", + "engraphis/core/graph_layers.py": "64d74ab01c77119f6343ba6f1d6a84f9653f1a5d34d47ce7966f3ac31b29d2ea", + "engraphis/core/graph_policy.py": "ec5b373d01adb2de87df31d9f543130018e9a73faaaed239a184f14a32646615", + "engraphis/core/graph_scene.py": "b79c53e156b11102d64196ac5082c6549c13fe0cc57b9760d66872459a0f2110", + "engraphis/core/graphrank.py": "1279a58396104d3f906bfd5ec75b32efedfefe52201467bf19d80be3517017a5", + "engraphis/core/grounded.py": "0d6b60ef16f3cd65b7032bdb60ba853dac364d0df58a74c932ecf12aec98e4b7", + "engraphis/core/ids.py": "e47eeadfaf560bc7638e1879fc491fa82d981cd42b9e4b05ef87033b2d7dfa60", + "engraphis/core/interfaces.py": "bd5f7f4751a035348cf99cae3e8be64f205cb2cb8fb6eb3bd7e56f5099d81f0f", + "engraphis/core/mutations.py": "3d32b63f530204ae0394f3ebafbdf667cd86143a863ca808527d1e754d3a246e", + "engraphis/core/obsidian.py": "39fa18707972563581b06fa129e03841200b8bc05a114876cbbb7f352a885bee", + "engraphis/core/poisoning.py": "e91b6b23d2717680b3f25a7a540ba04e19e815e4ce4744bdb70da9bada21e031", + "engraphis/core/query_planner.py": "249062d67392ab7c203cc71e9040e99bee91bf570604e90949149a93cb652120", + "engraphis/core/read_snapshots.py": "be08e63a88bd38ed91d61b28657a65201c38994856db2798b209a73151dd202a", + "engraphis/core/recall.py": "b56dc6736414cab451bb195df7948a6088c7ae5e14f5961d57daffb4a5bafb28", + "engraphis/core/relocation.py": "86bbd292b374539b480dddc1a9ac19292fbc17ff9fa8e1fe6ff69c91ee7c8bb9", + "engraphis/core/resolve.py": "f01a6f55e44320ab04b97e516342f20155668863b2fc4765305e066d87586524", + "engraphis/core/retention_policy.py": "864c03bdb6e743cd0002c706de471e920f1fa1f1a9918ab343ef4c2042b47429", + "engraphis/core/retrieval_policy.py": "d169eb442115bd06c1e1da776fa6c1849e0795e658edd732a8d6821326b46b03", + "engraphis/core/savings.py": "e34a8f4a26c313a1e439dada16b7fd4a0c3fada2038e2f4aed03978b53310bbc", + "engraphis/core/schema.py": "8384672f2d5406abc5969a3e134e14684d71dbeb44685f8198023b8e3f03be77", + "engraphis/core/scoring.py": "bf0cf02521ab3dae6de4a0e7c9892bf3feb02f73de22478c7fb0c64ce55ab0d3", + "engraphis/core/secrets.py": "78ab40870d6c5918c189307028b107df239d08cd918abe08c85fa15b58fa1663", + "engraphis/core/source_paths.py": "2e71de7f19f260b335137602f07a28a80efa5d65e3d2497215356ba5f172b47e", + "engraphis/core/spec_crawl.py": "23359f9e28a2a68a3abe59f1e705b8be1f6c72880d708123bc85391029e769e5", + "engraphis/core/store.py": "f42e407803b3d9701c1d4975bcef8c8b69e3a4749b1303e6c4b4d6ae31c06a15", + "engraphis/core/sync.py": "0d28dd697eb8e8acdb7fed3e346fcba8fce23cbf4606eb11de68cb1fb001fcf2", + "engraphis/core/textutil.py": "acd65031729fa5d91d09527b8eb52518b83cfce77a55e6b7ae2d94686e35c1a6", + "engraphis/core/user_model.py": "3147ec8ee7cfd855783f639874b63f331cdc822bd8bd9298cfb326dd18666026", + "engraphis/core/vector_repair.py": "a8c1812de4e3ed288eda3e54a505e136d6ebd3ec296788bcbb8804b11e13cfc8", + "engraphis/core/vector_search.py": "75800052e573e9af01c6fd098eaf8647c3c45cd7eb2601dae05d42359e19b234", + "engraphis/dashboard_app.py": "77546609aa29031dbf935228ee00162b3635f4c3438d2a5922bfe94eb211a084", + "engraphis/dashboard_assets/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "engraphis/device_connect.py": "c8cd0a22e9fd2d92a65bd74047cc3a8b7159bfc9a297cb639c1c7f1699f8802e", + "engraphis/document_import.py": "5378d17355cbf97db304ea8ac2d1978206b244b553c7e0645f8a060d272664b5", + "engraphis/engines/__init__.py": "111232af583889195c5f5a60298e32484348608e81d2bdd822fd3ecd2a33c1e4", + "engraphis/engines/embedder.py": "998b65dd566966bb6581fd9f09cdf46c58a3923ef5df3073ccdaa541153a3581", + "engraphis/engines/ingest.py": "1a5d4b52c13e533864329f9fff11c0f6ebc299ff7093a9d9275e5c9626a39ce2", + "engraphis/engines/intelligence.py": "b561589b98deb98271f104dfec6276aeba13c4815e627259be5f8769f2e7ad82", + "engraphis/engines/recall.py": "f979580d065599c07acbc3add59f71e52d9a186a2104b0e257daa7959ea88d26", + "engraphis/engines/reweight.py": "91ec5815f5d356a7068c36133d24405334450f361f902f99275bed3ccebffd49", + "engraphis/engines/thoughts.py": "4adb9c8a9bcfe736cb42fff9b5ce24631da6ec473d178e83f1c176fde3b8b814", + "engraphis/factory.py": "be26899074d80b2f96fff9a5a70969894e818dc71e2cfdf4f7ee965f1f4cc550", + "engraphis/file_guard.py": "44eccaeca3afef7149bfdfea2321c325472253ee39a9e0bc24c720b3da3d685b", + "engraphis/graphdata.py": "f5dc93395f52203c66deaeec8bc58d5c1f990a35a535151e0b9a84cecf7d8430", + "engraphis/hosted_client.py": "5b56b0d2384cf24ecb409090af15f09e437ec8ab5a98bed931384daff46b4f48", + "engraphis/http_deadline.py": "bb29ac2d1e2fb49829e8484bb17f8abe98c23895ef17c68abe135d13f5df0099", + "engraphis/http_security.py": "5cd96551778d7a964ce1d87c6bce032a5e70c7a1779fb2f6731432b96f5f069b", + "engraphis/inspector/__init__.py": "720cac28b8a6019d0a0c53809d5905b7d6eb9d4ecbac767505904c6ec3c39071", + "engraphis/inspector/app.py": "8aefe8a935397dde3c6466e691a7e6a7203ebe42e07aa00c332b733465e59e04", + "engraphis/licensing.py": "7e73c28b0e1c3536e2080a129af614f838d2cae3ec3a48a3d8ed7e5153c9e935", + "engraphis/llm/__init__.py": "f3096d2ddd652b99e6fc0b4c5a8786d9bbaba41b259df0b44ce93b57bde3a8b1", + "engraphis/llm/client.py": "0c84000033d3f85b0e0161f100c271c35b591e1480701455ec65c7d3f111ec42", + "engraphis/local_auth.py": "8fd6d42594454d68c99bf25054adcfbf2379d4e08f17affd36af994fc7ad78bc", + "engraphis/logging_setup.py": "f7d2edc756458a852e0401453e9c71b785aba08fa7859aacbdc23fb30dc7982a", + "engraphis/managed_processing.py": "6d33cdfd10800d9552fcfae3c2b071d2b39fe10fb69d1a8e5d9ed6026882197e", + "engraphis/mcp_classic_cli.py": "778122b121a1c654b8f85810fad05d9c8d7799cf13903d7dc274938b51008da0", + "engraphis/mcp_cli.py": "f94e63805aa05cb913e48200819473b86b9623b2b958578f41f025adb0f1a21e", + "engraphis/mcp_http_cli.py": "c0605c53ff53ffde8d98d02ca2812e3c2c792a34e260b0a197433fb0ed2455af", + "engraphis/mcp_server.py": "b1a35b28e81ac44c0812d2569e662b1626704b00c7e6dce67cd70428cef25bea", + "engraphis/mcp_stdio_bridge.py": "a2e64883f4737415c633e9a69c1f3bdafe1860e1ee97d579dfc16814dd36c480", + "engraphis/models.py": "6e76e97db0aca3805c6f582ea78cc6e1c0ccd2665e16fd8ab81eb91b94141b51", + "engraphis/netutil.py": "15cd7c9d0792b5a95d0a722d94e433cf6d07182777f26bdf19f047270673b08b", + "engraphis/observability.py": "a3a6945bf33a0d8e216da56ca7efec0031b82dc64a66cff36f0b2161f5db4981", + "engraphis/obsidian_import.py": "cff5d117a592ceb8aa822e60a6142878da2f3aec1fcba44a5d64853c7bb0cee7", + "engraphis/private_state.py": "7485570efcaee8a1dc00b64ebfdc23178517235ae17609aa78db8bdda7e45fe3", + "engraphis/read_only_api.py": "dab1721cf39b7d374462002f5b770d53510a642d955eb4c9a972969e39841bf8", + "engraphis/redirector.py": "5ba964b81f09008c9369180cb49d247519000763274aabc5e046215b12fa641d", + "engraphis/routes/__init__.py": "f0d59080212cfa0d9b50877bca28e5832d1ae917899500d52c68b1c8821231af", + "engraphis/routes/memory.py": "9ca066e1762eeeaafd9790ed57d730efcaceb57742dbe0c830ad8764500ff9e5", + "engraphis/routes/v2_api.py": "3ad160f611a0047c101173a73c0eb56498d2dd46afdd68073ef175207e5639ff", + "engraphis/routes/vault.py": "503304c177cf4d81bec5a9836731c0c32b62159175dea6d0fb8ce30c97a2bc6b", + "engraphis/service.py": "dec62fa2912c19b13c904e1d7585babc5c55edfd995846f0bab33d62a20dff85", + "engraphis/service_context.py": "0cd84538ca18b07d6440fa4cb0147510eacef2af5cf04b7a0519f2ac15a7ff87", + "engraphis/stores/__init__.py": "48ee4326c8f28eecf46f558b7aea21adb779226a7038299017ab90196d5d84be", + "engraphis/stores/graph.py": "ebf603b54cf8450e7c9a7319bd05db2f39fcda8491f5969d6af3e8da61571bc2", + "engraphis/stores/ledger.py": "df5cbb30d977decc0a9c3a2365c9c115cfbb5fe951bae48446436661c80e5d30", + "engraphis/stores/vaults.py": "2c986129b9d1e7aab33e18a3b9a278eb5ad2236895587f69b5816a5d96796bc3", + "engraphis/stores/vectors.py": "45a1baca381fc647548cc89424eb36d5853f7562c275b191b35b99339a190b7d", + "engraphis/update_check.py": "ffbf5ef682fb15177915073ebc0b5eab4dee9092bbf0f58d36a8a54c6605ccf4", + "eval/__init__.py": "639f0c6d9d6aac8ff6dc605a34a0a301058905cc53bff4eaed5912247f0e7c56", + "eval/ablation.py": "16f159dee75d2f96cc42f230c2403fa19ea0bda7091c4823660da553463a194a", + "eval/adversarial_memory_security.py": "35dd8d981bcbad50e9815465be420b05b62a9dea28a78eb6cc1e09ec51c320c4", + "eval/agent_benchmarks.py": "8e88943e45a1b2083b366cbb0329326ef918b119a2366ed395c404c2b7ef98b0", + "eval/benchmark.py": "b71832affdf87d23bc1db7b522a669a7888571b3989b03a235c70f5da9952bd6", + "eval/benchmark_analysis.py": "1200791425d029695fee0b7da1f188a8962f337aa31eb4217d50e4503641d2b3", + "eval/benchmark_campaign.py": "033dbebfb47df5fcd3a6588b29ea4ab5fd44d4387ae45334379278ab30f3e57f", + "eval/campaign_adapters.py": "2bdb51de0dcdfceb1be2a649d84541780ac4d1fc0f9b7585004d87490073213e", + "eval/campaign_api.py": "323be4e1d9520e8047ab54c9cbb158013785402b06ee4e93e55227971c54bf95", + "eval/campaign_candidate.py": "88541840d16c3b7368586ed0ee3566054214b1f594826b746b28d0e407cb812a", + "eval/campaign_continuation.py": "7f77a20ac8f85cd96729bba0cf45872c98a3421af005bd735434b7b416dd7e1f", + "eval/campaign_ledger.py": "ba3079ba541cb1f70929a76f147d2bc5e8b264103a1cd7faad97685e0ecc7790", + "eval/campaign_oracle.py": "9ea5479786efd9caa2b59e612e891a93dd34b500a2b36aa20bcf749fb6d4d48a", + "eval/campaign_storage.py": "ebcd1f4aeceaa5ff9304494c64dd31fe31637ceb107d8cc39fa292e3b14055d4", + "eval/capacity_matrix.py": "8c25bd97754c1d7a8468a687042a3c87cc9c0299cf01c19b80dacfec2be4e552", + "eval/chunking_eval.py": "a16544353940c0a8c40cea3b9932d3399b35ea5994b809b78f5dbe4a952c467f", + "eval/closed_history.py": "beadb9dbfa97af2ca41efc737f41e8758f4c4635f468e6c7a2ac5c0c95f73f3d", + "eval/code_agent_ab.py": "d98bba6b77700ff6bf86ff0d2cf518a67e5a2676ef51bbddabbb2ff26e1f3aaa", + "eval/code_arm.py": "d211166fce1b8a4173848e1617873b7e84aadeff43746effe7483c54bbdb6f1d", + "eval/codex_oauth.py": "f235fca482de4201d1850bbfb583765ac5f1a057b4ad7e2d504d036bfc03f391", + "eval/coding_acceptance.py": "4b39cbcb60d7fba503cca597399cf9d04ad9567a43cdb950015ccf552e6a2773", + "eval/coding_corpus.py": "7b5205e8544578fe99d9d9cf6cfc34e40e5d1d049238d502ff7bc6f3c14948f5", + "eval/consolidation_ranking.py": "917b578d4e0bcb929bf1a1a37611acf7716a520c076abf4ade0d8d12a1c455de", + "eval/context_economy.py": "709ac7cc866855f96d7717ab2bea12e8b0d3a140d15fb978a2a929ad085931f2", + "eval/context_efficiency_guardrails.py": "22afd1a6fe17219e74701dc587ec35f569a5bf22bea270944525f51746723f14", + "eval/datasets/codemem.jsonl": "341313023c22850a2e14f02742b571ad1deca824f886a1654a59541304c01f3c", + "eval/datasets/coding_memory_v1/oracles/atlas-green--code_relationships.py": "969aa71235e0ae3cb764b9ee12b789184cedb5fea6fcc0d86e5dcb2c0498fa56", + "eval/datasets/coding_memory_v1/oracles/atlas-green--condition_values.py": "648702646fb62990168e98fba0dc6a27be128876548fa7035a59d71aee9da02d", + "eval/datasets/coding_memory_v1/oracles/atlas-green--corrections.py": "c7132c7fe39b7afbdf11065037369c0f3c38a22e69d8742e8afddfd5785d2e61", + "eval/datasets/coding_memory_v1/oracles/atlas-green--long_documents.py": "65dffb25944cb612872655f12db16af73f7a095e7abcf6a7cc5d31dc91f083b1", + "eval/datasets/coding_memory_v1/oracles/atlas-green--multilingual.py": "ff49874a691bfc30c082695ee6d3c89b9f2e1e5eecf086026110770d8e1b0452", + "eval/datasets/coding_memory_v1/oracles/atlas-green--paraphrases.py": "dbdcf5d7abf136aa5816df8465bfde842284aef5ef002e955cee05ae69d574ca", + "eval/datasets/coding_memory_v1/oracles/atlas-green--poisoning.py": "58b8ca4a37f55c4d112640414dc82620e32e643ab0aa539260884a5ecc50740f", + "eval/datasets/coding_memory_v1/oracles/atlas-green--scope_boundaries.py": "c1e92b10b40004c95abd3dfbada1a38fe0ce56300ecddca8360b51a1e6671458", + "eval/datasets/coding_memory_v1/oracles/atlas-green--temporal_history.py": "a64e4e5a29ce1040cf53e94571491325347f443d72a93f05bcf87c2878665952", + "eval/datasets/coding_memory_v1/oracles/atlas-green--unsupported_questions.py": "c8b415052ea7320e1df3c5475a676d4dd24602c45dbf1868fba6880846e029ee", + "eval/datasets/coding_memory_v1/oracles/atlas-north--code_relationships.py": "bb4305d15a81b4b4ef80b374acb59fee6fc32b213b101dd4b173ac4144d8570d", + "eval/datasets/coding_memory_v1/oracles/atlas-north--condition_values.py": "4bc5b1aff48ef6ad5ece1bd915f6949f72d365bb4815c4d82515b438bc644112", + "eval/datasets/coding_memory_v1/oracles/atlas-north--corrections.py": "7851502a9bc842f840bee1001e42140ee1df08be2730b563b1b197285d14742c", + "eval/datasets/coding_memory_v1/oracles/atlas-north--long_documents.py": "6204123eded20fc0c7aa59b312f8b5bd29aa88a720f7318e32b3c6a17651b57b", + "eval/datasets/coding_memory_v1/oracles/atlas-north--multilingual.py": "5c989b34fdad1e076b61ffb1f7a1371dc635f44a537abc743b765d5f6009004e", + "eval/datasets/coding_memory_v1/oracles/atlas-north--paraphrases.py": "fd189285d0cde9991dea461af9cd18b4203656a69da1298494fccf972ed8a58b", + "eval/datasets/coding_memory_v1/oracles/atlas-north--poisoning.py": "66305d69419736bdbb11a896543933604d898cd5394d94eedda8b92e632a09ac", + "eval/datasets/coding_memory_v1/oracles/atlas-north--scope_boundaries.py": "d9fbbe3924b5178f32f1937485af3b8db242a0e3a08c7bf41a3d2a57f34a8879", + "eval/datasets/coding_memory_v1/oracles/atlas-north--temporal_history.py": "7e7be0d6e558a71d3baad14a8812a6c998ad25e3f52c997a301333e0711f820e", + "eval/datasets/coding_memory_v1/oracles/atlas-north--unsupported_questions.py": "91f574dd678bf244a1fa6f9708e945259bb8ff8e2eb213426be5c3b187be3c3b", + "eval/datasets/coding_memory_v1/oracles/atlas-violet--code_relationships.py": "d0998e13db38d1fae9be6d254431b4fe592f89eb03ee25705f4c70985cd48c89", + "eval/datasets/coding_memory_v1/oracles/atlas-violet--condition_values.py": "f97610aee190dca03b01aa48ffe4aa3f75e099a59fdd867db98f743f4a376729", + "eval/datasets/coding_memory_v1/oracles/atlas-violet--corrections.py": "b7c31f7a9a1f978321ca5445997e0d284deea770c85f6d81a80204e548ad53ea", + "eval/datasets/coding_memory_v1/oracles/atlas-violet--long_documents.py": "20ec2b41cd3b6691164bce096dff16f7bf5be674f922cb637120c0347e60d0b6", + "eval/datasets/coding_memory_v1/oracles/atlas-violet--multilingual.py": "131e7dc978d3f2f7df395b6f1b4a9150051c0ca93c6dc5c386e58d46c8fdc573", + "eval/datasets/coding_memory_v1/oracles/atlas-violet--paraphrases.py": "1e7d9da36e6634ca2d2b11dc97468bd671df133345dcb76859e1dc3cb77dc664", + "eval/datasets/coding_memory_v1/oracles/atlas-violet--poisoning.py": "48460691ac0086c47102eec85387ac03bee9190ac83e3dafc4babc79b71d9bd7", + "eval/datasets/coding_memory_v1/oracles/atlas-violet--scope_boundaries.py": "b96207e9b1fd09f49218102a5b88fde5b9656604df4cfd314e86426e3b6d6075", + "eval/datasets/coding_memory_v1/oracles/atlas-violet--temporal_history.py": "7a196ff9a1db05b0c8f3a83086ae44cdc580dc58e4acc217d1db2575d9711b88", + "eval/datasets/coding_memory_v1/oracles/atlas-violet--unsupported_questions.py": "724564b5b75b9425b6889faae6d5e650bd5c940016c2b96054e5359d9d8b0887", + "eval/datasets/coding_memory_v1/oracles/atlas-west--code_relationships.py": "14295f735bcac4d4ae738eedfbd8fad4593afc914bc835233ec35e2263d43613", + "eval/datasets/coding_memory_v1/oracles/atlas-west--condition_values.py": "8ac70d9ca0c9122567bb9986d2cc5798104966df20e262b8087920e8fb5d82a2", + "eval/datasets/coding_memory_v1/oracles/atlas-west--corrections.py": "89d6a1a7781465c311830b7e769d2a76df41ab5c703ff662b3df1cc4c9a01668", + "eval/datasets/coding_memory_v1/oracles/atlas-west--long_documents.py": "38e67a26f69707089f852657a810ef1fca0395beb5511fff67c30f21ed63df57", + "eval/datasets/coding_memory_v1/oracles/atlas-west--multilingual.py": "bccf6579681d45940542ccb1f936ea1b225f4cf551a3ef31f3f8b1a026856e51", + "eval/datasets/coding_memory_v1/oracles/atlas-west--paraphrases.py": "89c8ce16c682553b27652e176b67a06f3228ae6209fbc8a7264260680f5651a9", + "eval/datasets/coding_memory_v1/oracles/atlas-west--poisoning.py": "fe9beb97d27eacdd87056fa1c9b04c62e2facae9354e765b912de60b35021621", + "eval/datasets/coding_memory_v1/oracles/atlas-west--scope_boundaries.py": "2eb3b1130c54644fcbade52da8f50cad1376a07b51856a962522913cbcbb7649", + "eval/datasets/coding_memory_v1/oracles/atlas-west--temporal_history.py": "116ee20af475da329cb535b0a02bd19ea1e7518b5b780ec12ba396ec896cc116", + "eval/datasets/coding_memory_v1/oracles/atlas-west--unsupported_questions.py": "080969e026215b9fe4ede6d0b4d02e9143e1293bea5d7383681e478426ddf121", + "eval/datasets/coding_memory_v1/oracles/borealis-green--code_relationships.py": "3965671e5e2432c8236c250f7982d4f478d8f1fe1268ffe57c555cf5fbbcd414", + "eval/datasets/coding_memory_v1/oracles/borealis-green--condition_values.py": "92994990327445258984ee6bf1d95f47359ff4b194e1f36e193980a523e6c5e0", + "eval/datasets/coding_memory_v1/oracles/borealis-green--corrections.py": "c5b473f962deacd6d59ea9967626e029888abbdb48f320704bcb6de876ce3f46", + "eval/datasets/coding_memory_v1/oracles/borealis-green--long_documents.py": "54aee2c1031c17b5678db2b0955eda8fafe5990aa9e52f02936584a9b75b9ab1", + "eval/datasets/coding_memory_v1/oracles/borealis-green--multilingual.py": "c458ac5c63e440e3d9486d096278759920d636f73e1901258679251afd86eb60", + "eval/datasets/coding_memory_v1/oracles/borealis-green--paraphrases.py": "250d027660f268cef1caa745cf3155de6e2f4adfaed02ba675eaa83224ffeee8", + "eval/datasets/coding_memory_v1/oracles/borealis-green--poisoning.py": "978f16eb5033c602ec09ed57ba0667edec9ac3e26accc1ce1456bc893ac9e6a7", + "eval/datasets/coding_memory_v1/oracles/borealis-green--scope_boundaries.py": "4b73a2e5dc5ac815c14e08a34acefd2ad426282cfae379ca2fd3060a3784c9b7", + "eval/datasets/coding_memory_v1/oracles/borealis-green--temporal_history.py": "6636d511bbcca51ca85c4eb0ec5ae956b3baaa5f9af89458ceb86329115743bd", + "eval/datasets/coding_memory_v1/oracles/borealis-green--unsupported_questions.py": "5cd4f70627bc60572c720a797bff122ef9bf219f32d265f694affa33b1f9f4fe", + "eval/datasets/coding_memory_v1/oracles/borealis-north--code_relationships.py": "769903aeac3ce360d845ddf1a6e89192737dbb460e1f2e251230b74d40763dd6", + "eval/datasets/coding_memory_v1/oracles/borealis-north--condition_values.py": "f6e69f525fd240a09152989b9d8a529e9abe076e7fc2c98d10bd84f9c6da2b56", + "eval/datasets/coding_memory_v1/oracles/borealis-north--corrections.py": "987ece1c7507a69a973c1e051b27c1d3b104ecda79df48959e49a62da31aa137", + "eval/datasets/coding_memory_v1/oracles/borealis-north--long_documents.py": "011134140cc486a3c089b407adbfbf5b958e1f8c1bffd572c29b8cd5d7f07f7a", + "eval/datasets/coding_memory_v1/oracles/borealis-north--multilingual.py": "1d29536a4a5836f64f56adcd1a965db85bc3371a12ad5bbb5644520c9d9606c8", + "eval/datasets/coding_memory_v1/oracles/borealis-north--paraphrases.py": "09a5fad90c24f75f2f448854c8778aab896c67854c97f884c2d689ead808583e", + "eval/datasets/coding_memory_v1/oracles/borealis-north--poisoning.py": "c1bec2ed9b31103e39db9dbab2be8db401cb0874bb3ce8573a0841a054e28a46", + "eval/datasets/coding_memory_v1/oracles/borealis-north--scope_boundaries.py": "faf51b368d7ba9406442edde29e290c931e67bff2799783dfd27f2226020e15b", + "eval/datasets/coding_memory_v1/oracles/borealis-north--temporal_history.py": "da52f43530149a4a293065674623fa49f83b37cd84b9871044b3bf32762fd8c1", + "eval/datasets/coding_memory_v1/oracles/borealis-north--unsupported_questions.py": "be0abc698692c9fe82f40f76a4694c472882a53843894dddd631ab1ab77aae45", + "eval/datasets/coding_memory_v1/oracles/borealis-violet--code_relationships.py": "9497b7d2373bb9f858e994f7919cff72e7e3c4c4eaa1efda287524b1a182d1f4", + "eval/datasets/coding_memory_v1/oracles/borealis-violet--condition_values.py": "aea13e4b7721019e7bc1f54878bb25b9f27a8a5044fe444e86ba5460deb13e09", + "eval/datasets/coding_memory_v1/oracles/borealis-violet--corrections.py": "bfd446d431afb6c05ad9d8775b90887947b85981037756e05262f3b101d41e7b", + "eval/datasets/coding_memory_v1/oracles/borealis-violet--long_documents.py": "eae829a374c0cd6237fac4ec52fec0d98d7340ec0b307b9b0b0c90c116d4b472", + "eval/datasets/coding_memory_v1/oracles/borealis-violet--multilingual.py": "e821f91999b5f12cff53fdaa956af4e0bb2f929497f95b4e2ceb57a6621e58f7", + "eval/datasets/coding_memory_v1/oracles/borealis-violet--paraphrases.py": "1a99c15898831a1c9aacab951868101d57e59e91ed90a2777015a26dbcc3043c", + "eval/datasets/coding_memory_v1/oracles/borealis-violet--poisoning.py": "411877fc07b053201a63aa4e45cb499344abccb0a562c236d53a672078b1eb63", + "eval/datasets/coding_memory_v1/oracles/borealis-violet--scope_boundaries.py": "b198dfb636910953ba372edf1cd6db382d4e5725bf7f820171be8c63f9fedf7d", + "eval/datasets/coding_memory_v1/oracles/borealis-violet--temporal_history.py": "3de5efd6ef661572191c6aa426fb10caf9f4bed4aa495b6298815aab3acdce22", + "eval/datasets/coding_memory_v1/oracles/borealis-violet--unsupported_questions.py": "baf5dd85e86a97dedb0b395b5c574ea5ffcdad7a46bbbe9787cfdc33d6c7a06c", + "eval/datasets/coding_memory_v1/oracles/borealis-west--code_relationships.py": "48e53b3a561f91fdf4eedb2d63aad65eece8ae80188bb3d5bdcaf184fb0b6945", + "eval/datasets/coding_memory_v1/oracles/borealis-west--condition_values.py": "2dd7a524bd2a2aa2fbf212246880d4e4ecf03f503b70f25179f3ab6bab28a07a", + "eval/datasets/coding_memory_v1/oracles/borealis-west--corrections.py": "7d3080e05544e10243040fe0a969a8c524e4262f8f5c38abdc3d8e06c67fdabd", + "eval/datasets/coding_memory_v1/oracles/borealis-west--long_documents.py": "fc42e935879e7a81f6eef9fe72f8683244501ae10654008a7863d397ee73058c", + "eval/datasets/coding_memory_v1/oracles/borealis-west--multilingual.py": "19181c442ce5f6941485190ecf050e455914dae988f86efa42665cdef0d835a2", + "eval/datasets/coding_memory_v1/oracles/borealis-west--paraphrases.py": "f7a5cb842aae3d2bc8d62ea40325b332a8bd4f013b76bac8bf154fedb3dbcbe1", + "eval/datasets/coding_memory_v1/oracles/borealis-west--poisoning.py": "285a2526a6be4e556a564899e9a376ae1286fef6f0ad22eb9289fba1905a2b68", + "eval/datasets/coding_memory_v1/oracles/borealis-west--scope_boundaries.py": "fb994823743ff5b4853b142c17779778bdb11296958e033e5c6be598833a4cf5", + "eval/datasets/coding_memory_v1/oracles/borealis-west--temporal_history.py": "f30a74c85ad654a7450e20d07baceb3b4fb2a56b454d7b43b854bef3cdb2cee6", + "eval/datasets/coding_memory_v1/oracles/borealis-west--unsupported_questions.py": "01cbbd0b21a745720514aea6a21a98a3cccdbf0c0518bd5508019433b964776b", + "eval/datasets/coding_memory_v1/oracles/cinder-green--code_relationships.py": "8b08e7c65289515a9eee2ae5f4cd60ce94350e9a561225bef0e8f8815ea28f79", + "eval/datasets/coding_memory_v1/oracles/cinder-green--condition_values.py": "92e7d75d38f5dbcc685075e947005a9ae5a410207e7f17aa770b034b8690cbe5", + "eval/datasets/coding_memory_v1/oracles/cinder-green--corrections.py": "424cbf0924a8cbc1a59a6d7bc82b5527e41f52c8ee9f558ea3e398ab4d913919", + "eval/datasets/coding_memory_v1/oracles/cinder-green--long_documents.py": "2a52df9cef3ee66357847322794a382c927b828c6ab1a2819a799ff55d53657b", + "eval/datasets/coding_memory_v1/oracles/cinder-green--multilingual.py": "e47e791dd15b045a6a958ca114b1359ca8537e160274b015214746a2564363c6", + "eval/datasets/coding_memory_v1/oracles/cinder-green--paraphrases.py": "9e92a79ec00231cb381ce58e389dfdb2520a17ecf1e9c2e39e79501449e9ddd0", + "eval/datasets/coding_memory_v1/oracles/cinder-green--poisoning.py": "33213e6f194332969da88f4748c8f26ef93219b6c4bc710a142082f8147ccfcc", + "eval/datasets/coding_memory_v1/oracles/cinder-green--scope_boundaries.py": "8d2489da1fba137981e45e42c843f9cbd17e851c82ddcdc5904b4a86af0d9692", + "eval/datasets/coding_memory_v1/oracles/cinder-green--temporal_history.py": "29403fd08f0de7da3dfcc6c181e1f448a5c5264e95ff3924f3066508d85468b1", + "eval/datasets/coding_memory_v1/oracles/cinder-green--unsupported_questions.py": "ba21014188f9ae849e0bb79babe163194703c4cf01f0600f4c9f71058e7fed56", + "eval/datasets/coding_memory_v1/oracles/cinder-north--code_relationships.py": "46280659557bd08afff185352438bb63dfd308dabf5c7c1862c2f442bd171d48", + "eval/datasets/coding_memory_v1/oracles/cinder-north--condition_values.py": "6c62d673b1b6cb4b5c89ff2af4ecf57c2c9d73074a01ab92b8fde57815cab783", + "eval/datasets/coding_memory_v1/oracles/cinder-north--corrections.py": "8245b196072389517fad6d76ed7709d7e12eb52e08732e4ddcfc40fc1d986879", + "eval/datasets/coding_memory_v1/oracles/cinder-north--long_documents.py": "6790c7b89d80ab909d9e1a31b59bfa5cd199cddabed2e159daf7c5c049ff9a43", + "eval/datasets/coding_memory_v1/oracles/cinder-north--multilingual.py": "9532bbd14506cac0edcc09f474e7c96a402903de947e0e76f5c8aab4bff37646", + "eval/datasets/coding_memory_v1/oracles/cinder-north--paraphrases.py": "cc904350ae7d40e26d91d482ac15d87ec8022d4c82905021dd38003c5fa2d067", + "eval/datasets/coding_memory_v1/oracles/cinder-north--poisoning.py": "e8e451c49c98744b4a484141f86d337db4ec5326264b1500893a20c3451c5ae4", + "eval/datasets/coding_memory_v1/oracles/cinder-north--scope_boundaries.py": "50d42fc48aa70d55b6a3b39c9bf17437726f37e1e5ae9870d415fccad92c7bae", + "eval/datasets/coding_memory_v1/oracles/cinder-north--temporal_history.py": "14b1336fbaed11cad455f588bbe078effc31f5f357f670e1f189ec3d78b172f5", + "eval/datasets/coding_memory_v1/oracles/cinder-north--unsupported_questions.py": "0a2f740700a990d22b4c3051aa3e4260e3df4ce65931d419052f74e05c750a67", + "eval/datasets/coding_memory_v1/oracles/cinder-violet--code_relationships.py": "0e29053f11fe618c01fa90a274688f18ba8a7f3cfea56757a96d5a5bc95b61a8", + "eval/datasets/coding_memory_v1/oracles/cinder-violet--condition_values.py": "ca57ac4e22e9b4739217020d7792ac642326935b8db9305ead2ec5bea2a6e456", + "eval/datasets/coding_memory_v1/oracles/cinder-violet--corrections.py": "9edbfaa58a99776e835c325e50a77df5b4b302b3ddd0932dc2a349a77b4100b5", + "eval/datasets/coding_memory_v1/oracles/cinder-violet--long_documents.py": "4b3fbe7380201285ac4b4a1ec4d8238c5663b97af01cbdc8ffff8fdf2a9467c5", + "eval/datasets/coding_memory_v1/oracles/cinder-violet--multilingual.py": "b0bba5ecf669f3368ef29373b755dc1296502665584574c771ebf4664debb5ce", + "eval/datasets/coding_memory_v1/oracles/cinder-violet--paraphrases.py": "e146f7aa1ffabf3b313251b59c59e1836c0cd17901d07fc6ef5334eaccf8bf36", + "eval/datasets/coding_memory_v1/oracles/cinder-violet--poisoning.py": "692ee07e750fb08652a1ca6bc6dcbbef77219701585f1336c6037dae5a7c5767", + "eval/datasets/coding_memory_v1/oracles/cinder-violet--scope_boundaries.py": "8ca0b911d38a72e4ecce6871ba3d0c7dbab433b0541cb64b116f7a88d521554c", + "eval/datasets/coding_memory_v1/oracles/cinder-violet--temporal_history.py": "6ba5400c5435af64c80a7366fdf49fea132f1011f9d84f9d0cc5f2f2f30a67ac", + "eval/datasets/coding_memory_v1/oracles/cinder-violet--unsupported_questions.py": "1d3324db0a085be30ce3dff1a2ec3866b8592a5ef6b43f39211a2e3a59fc1cd6", + "eval/datasets/coding_memory_v1/oracles/cinder-west--code_relationships.py": "0149333a3fb0f8d60939bf927921305eae447f7511c74f042ff3c880eadff825", + "eval/datasets/coding_memory_v1/oracles/cinder-west--condition_values.py": "3158243f9f7f0b92aaee4164379159b46143acd11fc3d41a708630c1d33ea22c", + "eval/datasets/coding_memory_v1/oracles/cinder-west--corrections.py": "a9fbb89aa629a23e7a1fddf4fca09da40365bbc7a9a6192d805e498fe17f93ff", + "eval/datasets/coding_memory_v1/oracles/cinder-west--long_documents.py": "3c702c653a8ddff84eb5c24a77567aa63d2069c48ed174bcf29065c0ddb30ec6", + "eval/datasets/coding_memory_v1/oracles/cinder-west--multilingual.py": "b8f5f7e00e6edac3f8e0b7e8bc09ea6c7d661370fa2226eb1795306ce3c634a7", + "eval/datasets/coding_memory_v1/oracles/cinder-west--paraphrases.py": "10361be87e9d9cb98d2ff1bbc18252178d475a5c5f2efb77bc81dea05109df05", + "eval/datasets/coding_memory_v1/oracles/cinder-west--poisoning.py": "889a5a43604f7d1677ee0f28683abfc48a8a597b052f558c1b4d19c5f33444ec", + "eval/datasets/coding_memory_v1/oracles/cinder-west--scope_boundaries.py": "b84e78812baaa92422226c06d78cfe7c1be260e2bfcf9c5bb1ed73f2f3cc136b", + "eval/datasets/coding_memory_v1/oracles/cinder-west--temporal_history.py": "9d99a981fca0c801a0a52e2af24cd4262dad72f4f34cd84894784285d241bd05", + "eval/datasets/coding_memory_v1/oracles/cinder-west--unsupported_questions.py": "43b8f854961d9cca5cb83b53b9e07d22388d0c7db406dc8e4f8cfdaa5f89fffd", + "eval/datasets/coding_memory_v1/oracles/delta-green--code_relationships.py": "97a08f2bf9b1a11f95a062f7df27d69db3e9dbc421837347acfe62111af02885", + "eval/datasets/coding_memory_v1/oracles/delta-green--condition_values.py": "5e53695288b84a526c61b748b170f6d6219f94a942edc0887ea6157535ad8d52", + "eval/datasets/coding_memory_v1/oracles/delta-green--corrections.py": "c6d16232ebe6bdb0b5c94db1ba4f108257b47ee8f8b96cda5f8e17212022b542", + "eval/datasets/coding_memory_v1/oracles/delta-green--long_documents.py": "bedb6f495437beada344ce6cdd92954108aa4c639622fd1cc294d2a1139b26d9", + "eval/datasets/coding_memory_v1/oracles/delta-green--multilingual.py": "dbf84f4e8c016c5e60fdac5fd3ac2faf79fb0534f5c2ac534491acc31d5f3530", + "eval/datasets/coding_memory_v1/oracles/delta-green--paraphrases.py": "ea8f00c86e8fbc2ab10a448f48019146181d0e55438def6f47f41f0ef3181be4", + "eval/datasets/coding_memory_v1/oracles/delta-green--poisoning.py": "4eb4e289e944e7762c39ce85a8fc2582e310de6a86cbc1cc36b44112653140ea", + "eval/datasets/coding_memory_v1/oracles/delta-green--scope_boundaries.py": "2fe9b96deecfb488430abd9657adc6b3ea092b18f32c41c1669d29175ec6e860", + "eval/datasets/coding_memory_v1/oracles/delta-green--temporal_history.py": "531cbb953400584356cb6525bc1a2e559f7ec3867d845c736d8026a046b91e7d", + "eval/datasets/coding_memory_v1/oracles/delta-green--unsupported_questions.py": "edebf25c2168411fdefae1307a71d829224dba3dbc7ea000054416e52123d5a3", + "eval/datasets/coding_memory_v1/oracles/delta-north--code_relationships.py": "9357ad0d5f6a1cc787f980d0d506b07808538179f71ae3e8ce1c3eb655d24e35", + "eval/datasets/coding_memory_v1/oracles/delta-north--condition_values.py": "4199f7409696a260b1531f4ec7cfd15555b4d58d06b0b8a88b1c7621fe7d328b", + "eval/datasets/coding_memory_v1/oracles/delta-north--corrections.py": "b8bd58db7c5ae93d33e6223cc34f0e09d58831361ac013ec08277fe2c102ac17", + "eval/datasets/coding_memory_v1/oracles/delta-north--long_documents.py": "0f08b744446b1c52e8fe86626ab05c171377f17c46e604cb2ea647ed5d0daddd", + "eval/datasets/coding_memory_v1/oracles/delta-north--multilingual.py": "ada9eb404190d05637cea59e3f8ed541aea2c9e0f9be0d166885d6789351f7a3", + "eval/datasets/coding_memory_v1/oracles/delta-north--paraphrases.py": "04ec2e8308f47f46e38c89f4a2fe44deef4a0b20a1c8517395ad00983bf7361c", + "eval/datasets/coding_memory_v1/oracles/delta-north--poisoning.py": "d72ea7b98db47e4de03884e2786206dc3da5cc08e5203c86b1d38a2d48c2fd47", + "eval/datasets/coding_memory_v1/oracles/delta-north--scope_boundaries.py": "cedb3234b22a40a7b889f50cfb5751fbba31e46f541b19feb2c4759c8007bac3", + "eval/datasets/coding_memory_v1/oracles/delta-north--temporal_history.py": "2f7c1bd4ebc051eee411d11251aff2ee5fee602a0800410bc0dbd2e86e99628b", + "eval/datasets/coding_memory_v1/oracles/delta-north--unsupported_questions.py": "f1f0b9858c806956122144c6e769f3baf9f87f07ff92eec98eb58d8019e6fba2", + "eval/datasets/coding_memory_v1/oracles/delta-violet--code_relationships.py": "79579639a60d96365781e853d1319a2bf85d9a2b4d8639410da7e3c1efc364b2", + "eval/datasets/coding_memory_v1/oracles/delta-violet--condition_values.py": "27defbfe7c237f971ae29e8f000f098eae32a11f3afad06f2bf3b27af432d902", + "eval/datasets/coding_memory_v1/oracles/delta-violet--corrections.py": "b3b685a5c33d1086e8ab4258d26c5ec9aa7396b8250a250351f0a6057fcf2075", + "eval/datasets/coding_memory_v1/oracles/delta-violet--long_documents.py": "4a5ce75372c8762bcd7986f8ceef454800c3636124ffd9de3a8315d210ae85f1", + "eval/datasets/coding_memory_v1/oracles/delta-violet--multilingual.py": "7ef19f25e97a24522b79a9d7e7f3d2d9e395a1fc2c60564e2f66081533bdacb5", + "eval/datasets/coding_memory_v1/oracles/delta-violet--paraphrases.py": "b6e00019bead0a5bc0facdac886b48fccfd3cccaa254267fda780e933d57904f", + "eval/datasets/coding_memory_v1/oracles/delta-violet--poisoning.py": "3e3e1e1fe04ae3e0c615ca21e794fead5d06f887d6fe7347fb9122684c8b8017", + "eval/datasets/coding_memory_v1/oracles/delta-violet--scope_boundaries.py": "336ae66910d533344712a2d87126f53df48d43eae9e7d17797938c3f9c560204", + "eval/datasets/coding_memory_v1/oracles/delta-violet--temporal_history.py": "a81cb5d2a5eb521dfd161ffbd21bd366777b1239f2d3412acc77b0a9c7c7d82b", + "eval/datasets/coding_memory_v1/oracles/delta-violet--unsupported_questions.py": "3341895608073374b98e67a88489abb9020fed5bb692231a0a61e15c8de0ddf1", + "eval/datasets/coding_memory_v1/oracles/delta-west--code_relationships.py": "e8f17f3838cb74e1ade0cfd6edf540dc086507b51ca67c2020ed490d75dbffe5", + "eval/datasets/coding_memory_v1/oracles/delta-west--condition_values.py": "a935a6e7a4081737eef76a93d54b34dc885b4699e02d797f1be78286dd642a2b", + "eval/datasets/coding_memory_v1/oracles/delta-west--corrections.py": "ee407e5aa8d57254bb7029d537bb2b59a297958cdff9bf271de3787b6448241e", + "eval/datasets/coding_memory_v1/oracles/delta-west--long_documents.py": "55fa08b75982d0547f0c2f13cdc6522d0fbd33efb4af9ae8c6ada11b2d46f321", + "eval/datasets/coding_memory_v1/oracles/delta-west--multilingual.py": "ce8a3e45ad8417ca1798579a22e12eeb7d170a11ebfb865746388eb0e7dca848", + "eval/datasets/coding_memory_v1/oracles/delta-west--paraphrases.py": "e6458588e20e76596e24be6cc838bd4b0d5a47b05b7bf0bb6017b0cecd0d6a68", + "eval/datasets/coding_memory_v1/oracles/delta-west--poisoning.py": "4c70955a2650d13eba24d6e933f765e0bd40da0a1cd8b920416989a76bdaf2d7", + "eval/datasets/coding_memory_v1/oracles/delta-west--scope_boundaries.py": "cfe9120c49224b6f9ed86ea7d85e496000bef0648c92c7fd474c540e892863cd", + "eval/datasets/coding_memory_v1/oracles/delta-west--temporal_history.py": "3527edc8a85c71f889ff0b2ac8bf9ab005ad5bb6c95bcd917dedae44aa3efc72", + "eval/datasets/coding_memory_v1/oracles/delta-west--unsupported_questions.py": "8c40923b8d4bd6bfc78ff6548ef9a840319b7ee4ea967554f26a8d24a4349935", + "eval/datasets/coding_memory_v1/oracles/ember-green--code_relationships.py": "cc7f82ad85363ee35ccd75744159bf4c4f07965e396bff07989cca7a55071679", + "eval/datasets/coding_memory_v1/oracles/ember-green--condition_values.py": "3fa888c125812f1488de9e021358cd531a9d50bb7d4b6173be3656268474b635", + "eval/datasets/coding_memory_v1/oracles/ember-green--corrections.py": "7cc639d4bd7b287ed0e3f2188491d6e4866e021b21874b34b365c1eb2b2d6d68", + "eval/datasets/coding_memory_v1/oracles/ember-green--long_documents.py": "972e7879a30ab26f86f09bce6dc9853c7f96124d0bbe5062111607496bcc9a95", + "eval/datasets/coding_memory_v1/oracles/ember-green--multilingual.py": "0f0585bd3264de3d5fed5928b54ff4fb87f71b2edb1d35c83a534251121124b4", + "eval/datasets/coding_memory_v1/oracles/ember-green--paraphrases.py": "a3c15541bb87cc862d1c9764cfdc1ffebbc831876b55167470ecb3b66519740a", + "eval/datasets/coding_memory_v1/oracles/ember-green--poisoning.py": "ace5b7b12c4ca068739af4bc5e215af1af7fe153399ff17ffb7a67b8b9aa1f94", + "eval/datasets/coding_memory_v1/oracles/ember-green--scope_boundaries.py": "daf132e130c5743d67129c1564f329fbb2670d1242648920b646a83bc2236e4b", + "eval/datasets/coding_memory_v1/oracles/ember-green--temporal_history.py": "1438f09f0ae41b1f6b42930518203a36c7de31a870b684dbb7f16e71a37c784b", + "eval/datasets/coding_memory_v1/oracles/ember-green--unsupported_questions.py": "da27396afbc1529c61972f739cefb26dcaf97bea529c084518a95080eac4fe78", + "eval/datasets/coding_memory_v1/oracles/ember-north--code_relationships.py": "debaf885151c3f47660053ff50e015338b8aa9bca089d7e901ee8bc34f7085e6", + "eval/datasets/coding_memory_v1/oracles/ember-north--condition_values.py": "109a7ee90c04da83f57a94a9a724dcf98a3e96405c7a456686c198ff5d85a69c", + "eval/datasets/coding_memory_v1/oracles/ember-north--corrections.py": "c246ba011c68d3ad0a4ee1d14231304a7f17f28027f1764e60da93062cf1cd3c", + "eval/datasets/coding_memory_v1/oracles/ember-north--long_documents.py": "176200cf2a011c7231ed4cadacbe86b1eeb695fbbee282bf4c4a96430d98fe84", + "eval/datasets/coding_memory_v1/oracles/ember-north--multilingual.py": "e850a910515e71a27edff26f7bedf39d8cb75c726e48357a2c41d4da051be6e9", + "eval/datasets/coding_memory_v1/oracles/ember-north--paraphrases.py": "0a6f03310e44abcf4ca61d69e22edbd21ce47a17629adf4f5079f7565534b469", + "eval/datasets/coding_memory_v1/oracles/ember-north--poisoning.py": "a81ee758fc57bcc58bf0bf2a38dae8b6eb4aeb14949063206e32cdff6f301dfb", + "eval/datasets/coding_memory_v1/oracles/ember-north--scope_boundaries.py": "fe42df24612c66b175f1c98794ed9f13a1a7819418e4aeb5ff98531515221de1", + "eval/datasets/coding_memory_v1/oracles/ember-north--temporal_history.py": "410f9770097228724a5af1357003ea23caefe1226275d030d46efa535c42d306", + "eval/datasets/coding_memory_v1/oracles/ember-north--unsupported_questions.py": "eebe43443938693a74081661d2ff3733b39c6dac5c7ecd88c4dbab5ad18689ee", + "eval/datasets/coding_memory_v1/oracles/ember-violet--code_relationships.py": "c00a30669fbd1c740b44e17c7c7369582c28a16da3aeec9d27420b8aef7c553a", + "eval/datasets/coding_memory_v1/oracles/ember-violet--condition_values.py": "1645e7c56c870f4367c263ccc9041181b1d187a698252203ca651235db59e684", + "eval/datasets/coding_memory_v1/oracles/ember-violet--corrections.py": "735f1d125d2ade6f3eabbf1082a408fe6afd4328cd954833e23df275df498e37", + "eval/datasets/coding_memory_v1/oracles/ember-violet--long_documents.py": "e1ded8e9641639443a6b14da18b2e0cbcddc6fc6045bf0b57404b59b59327dce", + "eval/datasets/coding_memory_v1/oracles/ember-violet--multilingual.py": "fbf04cda8e1231c7ac4c64d4c1856f7bfafcb6f1d016de0b1bdbbaaafcfe03b6", + "eval/datasets/coding_memory_v1/oracles/ember-violet--paraphrases.py": "2fd495def306778073df30b20dbc7a1500c06c90638acf6d9b9280db78dec53a", + "eval/datasets/coding_memory_v1/oracles/ember-violet--poisoning.py": "335674216a418254a01a7f5505344af20ad499a5fe41cb9c8654a0705574a18e", + "eval/datasets/coding_memory_v1/oracles/ember-violet--scope_boundaries.py": "078d60287888903e90343d144d8f54e08a3751ffcba82fd827ca0c9f8ee75fe7", + "eval/datasets/coding_memory_v1/oracles/ember-violet--temporal_history.py": "f7ad47f3253fe922977cbd0b63679abf00559f1b2dc895246e09840652b2c41b", + "eval/datasets/coding_memory_v1/oracles/ember-violet--unsupported_questions.py": "b91545082a31952351bc4c08d6cc09132432126dfd772952c818396bd7a68af1", + "eval/datasets/coding_memory_v1/oracles/ember-west--code_relationships.py": "74fb3e0d99a5bd4f54b8b57f6040ce7978a81bffa1ae70f9069fb2322e6ea581", + "eval/datasets/coding_memory_v1/oracles/ember-west--condition_values.py": "26d605b42d18cf029dfdc29124eea898e0dfa5642c43c1c775007fb443ba6cda", + "eval/datasets/coding_memory_v1/oracles/ember-west--corrections.py": "65c2ef8544b42ecb6b3ea9ce8bc95ec5f0ea8bb07c2b74d52e7880d99962f563", + "eval/datasets/coding_memory_v1/oracles/ember-west--long_documents.py": "4c8e54c7ec930d6cc782fa1a0519a0f1e0e916e3929902e13199775cefdaf338", + "eval/datasets/coding_memory_v1/oracles/ember-west--multilingual.py": "356e407f47d39e7f53337ffc4f27c68935a2c831301dc3dd006b2939cc61decd", + "eval/datasets/coding_memory_v1/oracles/ember-west--paraphrases.py": "c91cf574cf8b5aadaa6c9331c3f8f4a8fec7ec33c227f20894f463d81d110a7d", + "eval/datasets/coding_memory_v1/oracles/ember-west--poisoning.py": "5f6b8740798a7a093994748ef4b11e0e1de0be562c7f09790e1f0ebb9ed21f7d", + "eval/datasets/coding_memory_v1/oracles/ember-west--scope_boundaries.py": "5e20c5154818cc29b7e71444054d67836eddc882f2f17be8aed78afe31a9037f", + "eval/datasets/coding_memory_v1/oracles/ember-west--temporal_history.py": "fc51d1a39693d1f7fb2db4bf1ffe865d4c44848c159188cdcfda54618c0d8fe2", + "eval/datasets/coding_memory_v1/oracles/ember-west--unsupported_questions.py": "079c421cae974bcccc2107dac74421dd5bc2d1b40f7d91cb79a8014af526d260", + "eval/datasets/coding_memory_v1/oracles/fjord-green--code_relationships.py": "b5e4a284bad1d7ff81d5be9cf1faaaaf5e718cb51624189482624721cb459f1b", + "eval/datasets/coding_memory_v1/oracles/fjord-green--condition_values.py": "63c152b8da5655ae5c701cbb51099340d5f5103fcc47840239098a7366d24151", + "eval/datasets/coding_memory_v1/oracles/fjord-green--corrections.py": "b7b33b8de153c205ff4fdaadd5538eae8a22fd4401e70db9f645dae205c62cba", + "eval/datasets/coding_memory_v1/oracles/fjord-green--long_documents.py": "624cf014bc795a24e2f1f4fa2f9dbf11d2ad1c38f81cbc58ab3948f96c48c8c4", + "eval/datasets/coding_memory_v1/oracles/fjord-green--multilingual.py": "f8d2483fd9266946c980764feba0eda27c95ea2ff11222a4dd4247fb537f1f00", + "eval/datasets/coding_memory_v1/oracles/fjord-green--paraphrases.py": "151be253b730d5c5b4bbcabcce3bd218477241ebf0f1bd5fe9dc8d70798feb6f", + "eval/datasets/coding_memory_v1/oracles/fjord-green--poisoning.py": "e7b0efca3813bbb1d4ea9cefa752b4318942b118bb61ee9dcbbf640daa66644d", + "eval/datasets/coding_memory_v1/oracles/fjord-green--scope_boundaries.py": "56177cfb45f32c548be83cc78197fcf5b53826f01cca69e471535476184ae8f9", + "eval/datasets/coding_memory_v1/oracles/fjord-green--temporal_history.py": "2c79d26467eb8443d743bb8823cc1f2a5d1e5da7c887754505baa130e37f1b2e", + "eval/datasets/coding_memory_v1/oracles/fjord-green--unsupported_questions.py": "fb5f222f4e0715379591f559036c0549f67a7b6e3491e880c6cf8d5b91fa9000", + "eval/datasets/coding_memory_v1/oracles/fjord-north--code_relationships.py": "07402078200db9ab15fc61a72e31d9df0938edfb1e9d5b7e532b422dccab15aa", + "eval/datasets/coding_memory_v1/oracles/fjord-north--condition_values.py": "8c1549b5a444c9cbf1304fcca13b79263bf8d9f0b4f41c16da539b3bf5da3bf4", + "eval/datasets/coding_memory_v1/oracles/fjord-north--corrections.py": "a2634af73134c025de1b6239756ee2051e505e0a535221947babef5ba161fa13", + "eval/datasets/coding_memory_v1/oracles/fjord-north--long_documents.py": "4f80f3673afd1320d16a6bd45fefc85522d6f4bb889ff96f6e99410580feec09", + "eval/datasets/coding_memory_v1/oracles/fjord-north--multilingual.py": "3a9c953fbe76eb7374992a6a81d35b8dc0a635c02b90f328af140bbda2acc180", + "eval/datasets/coding_memory_v1/oracles/fjord-north--paraphrases.py": "ec2d9d7bbd678be73946cba8177184839505cd891df410ae8e676fc254df2ca5", + "eval/datasets/coding_memory_v1/oracles/fjord-north--poisoning.py": "6d9b91b4d53925d58d3c0cea694f591572a53184f23816101af0aa644aa1b86d", + "eval/datasets/coding_memory_v1/oracles/fjord-north--scope_boundaries.py": "2e98afa064e8a295a78bea654014a92734696a235fb0538732bb8d85799c7287", + "eval/datasets/coding_memory_v1/oracles/fjord-north--temporal_history.py": "4059ab8e427c2b6fd68155992404fbf493c7ebdf0a4357aa5c5f4ff0c67808f9", + "eval/datasets/coding_memory_v1/oracles/fjord-north--unsupported_questions.py": "79f14e18e282c1b6bbe21ef2affc9dd8a259a171c2e4975d42363f7c91689c1e", + "eval/datasets/coding_memory_v1/oracles/fjord-violet--code_relationships.py": "9ea8a9ade1001bcd43929782c3410f35fc5056e438342ca848fe0b1b0ff56170", + "eval/datasets/coding_memory_v1/oracles/fjord-violet--condition_values.py": "09e223a1c0bff6c95a5863fdc28dc00ed96b90887cd635d014712058079ccbfe", + "eval/datasets/coding_memory_v1/oracles/fjord-violet--corrections.py": "f822c373fc4a93189fdce09999a9dcbe8797b0c0dc780eddd4e342edf21dc14e", + "eval/datasets/coding_memory_v1/oracles/fjord-violet--long_documents.py": "983aeaae1e14937a740218228778be3e1938cdf1ff9228eba44064336188f297", + "eval/datasets/coding_memory_v1/oracles/fjord-violet--multilingual.py": "6e929f8846cfc756c71bcc21bc0f27ef8fc23270d21292fc35d70e6bbd31c066", + "eval/datasets/coding_memory_v1/oracles/fjord-violet--paraphrases.py": "c644d9f01edbd213447618bc269f56804450901faf4f4660c1f666d4e9b4ada4", + "eval/datasets/coding_memory_v1/oracles/fjord-violet--poisoning.py": "cf3849312f96b0072efdfd4e858abb24d7e6a808bf883bcc9dde5dd5a4594364", + "eval/datasets/coding_memory_v1/oracles/fjord-violet--scope_boundaries.py": "373d10e20713bdc26849706525f765f2be4ab57e87e576e39a1a1dd4e7feed88", + "eval/datasets/coding_memory_v1/oracles/fjord-violet--temporal_history.py": "4d2c36480cc04d43aa8f535bf998968b474f2f596d5349dcd42684c5a667c6a4", + "eval/datasets/coding_memory_v1/oracles/fjord-violet--unsupported_questions.py": "b381d2016dea525eebd8de8d026dc48ad2080b93823a95d6e2bb06d29cc86775", + "eval/datasets/coding_memory_v1/oracles/fjord-west--code_relationships.py": "e1e10ed9ee8a4d5c2e798c2bf04cba5c39eaa3873901bd93724af166ba2479ae", + "eval/datasets/coding_memory_v1/oracles/fjord-west--condition_values.py": "427764a62d2e11f6c9fe4b2cdcd32fa92f61749403dff648601a47187e38787a", + "eval/datasets/coding_memory_v1/oracles/fjord-west--corrections.py": "4aa041aa87f4c8628d113bab855a151c2e38bd6919cd02224827fd07d060fb34", + "eval/datasets/coding_memory_v1/oracles/fjord-west--long_documents.py": "89875548e6952ce810d8983b0eaf2724bf8f329bf44e4d9193cfa3405e1ce80e", + "eval/datasets/coding_memory_v1/oracles/fjord-west--multilingual.py": "8b5c14cdb26941d1ae25701bc99efc5fe4ae6e50fed3c65b193952444f1593d1", + "eval/datasets/coding_memory_v1/oracles/fjord-west--paraphrases.py": "bcdc9a5f1fe45ef63fd5e6b728c83c7a8d05c7b80957e3276c074700b409e924", + "eval/datasets/coding_memory_v1/oracles/fjord-west--poisoning.py": "5504e1bab0865fb3041c88f151938789d79422eff91a3ce72f4a41fc14e2f897", + "eval/datasets/coding_memory_v1/oracles/fjord-west--scope_boundaries.py": "63f226631a663dca0ef62a4f32bb98fbe1e30e3b1f030ee42c3f7f2bf6a09af0", + "eval/datasets/coding_memory_v1/oracles/fjord-west--temporal_history.py": "75c9582a16db2c89f12506bd7a4115fcd871c6da63e80429a8e70e31c1f5f862", + "eval/datasets/coding_memory_v1/oracles/fjord-west--unsupported_questions.py": "05e4ba65be7cb4e6500af5e14721c70f93fd7390abe58a60f6f8e4f8e9b0ae5c", + "eval/datasets/coding_memory_v1/oracles/grove-green--code_relationships.py": "bd5619518e839f84ca03297c4f7842a2944d8b445ec1863a8dfc68e4c2114a03", + "eval/datasets/coding_memory_v1/oracles/grove-green--condition_values.py": "67377847b6ff1ab865f5c25351fcfa1ee13e6ec6a089444cb3eb0709b660148b", + "eval/datasets/coding_memory_v1/oracles/grove-green--corrections.py": "c07e8a03c6c47818f9c574d4f21b27668989711fb5e0af3b5cf1a87b293abcbd", + "eval/datasets/coding_memory_v1/oracles/grove-green--long_documents.py": "3f75fc2158c66a19dbbd19209fc80026339abe93a2282ff55c7c01429ac549f3", + "eval/datasets/coding_memory_v1/oracles/grove-green--multilingual.py": "19b1f800b38a11cf0f50df6da3a011806098561e32a279b78e1806de9b8c11fb", + "eval/datasets/coding_memory_v1/oracles/grove-green--paraphrases.py": "a5996376b9f589283dbe301dd1e0644af92a8b6a0222b51b99ed6fbb83570782", + "eval/datasets/coding_memory_v1/oracles/grove-green--poisoning.py": "c4dee0969e39b52dc9b697e45587c8cd84926a97c9a48f7211c0a00571bfb657", + "eval/datasets/coding_memory_v1/oracles/grove-green--scope_boundaries.py": "8d45422f0a1a2115deb90f560409fdad1105b7cae1f03052f8ebac404ba9517c", + "eval/datasets/coding_memory_v1/oracles/grove-green--temporal_history.py": "abc1b386173c8252ac10f11a36fbcdd6d91c731686108a6d32c62d17955d21b9", + "eval/datasets/coding_memory_v1/oracles/grove-green--unsupported_questions.py": "1ee09741a1c94a1fa83c7edf5b6c07d8da2e45826f748d899ec71917e8369d54", + "eval/datasets/coding_memory_v1/oracles/grove-north--code_relationships.py": "fd75d734a5f45a5ec54b125c1d379df67c5531659f212a2f5c1c5e88169167e0", + "eval/datasets/coding_memory_v1/oracles/grove-north--condition_values.py": "c600c0e5f50e352406b99527b050f4df6dc8ab8920b5b946de190ad7629b5e6d", + "eval/datasets/coding_memory_v1/oracles/grove-north--corrections.py": "4d6fdbe4f5eb9c8e4bfc2a974e6c3cbf1d124d0b20d3f050c84e766f507b4be7", + "eval/datasets/coding_memory_v1/oracles/grove-north--long_documents.py": "5b0bbb9f0a90eb377cbfe45ff6f37077a9ce32c7c6730720f0bfd49b844fdf4b", + "eval/datasets/coding_memory_v1/oracles/grove-north--multilingual.py": "525ea2fdad66d66d7b90892e9424ebae281c1dabfb49f2668784a687b7ec894e", + "eval/datasets/coding_memory_v1/oracles/grove-north--paraphrases.py": "1105e52a8c8d3ff79c64b59ec5f23d66d742b8dcecf91e8e62060895c7371f4a", + "eval/datasets/coding_memory_v1/oracles/grove-north--poisoning.py": "7d32e533f6dac57e5d651868386686d2b78e7db1cc29b1860fede23fc9c49813", + "eval/datasets/coding_memory_v1/oracles/grove-north--scope_boundaries.py": "7bea4129589ea34ba10667a7035b126a32b423ec7f3b1ff2b10f6ffc9aa1c2ee", + "eval/datasets/coding_memory_v1/oracles/grove-north--temporal_history.py": "859b6504250f5746ce197e4a0a7c1006f1c2257b939c53a493f9d36f6d2b7d6a", + "eval/datasets/coding_memory_v1/oracles/grove-north--unsupported_questions.py": "6b893c4c124f071e9ad5d9946448b070c2b2f5f10da6f058e60840c18510fd0f", + "eval/datasets/coding_memory_v1/oracles/grove-violet--code_relationships.py": "09bb7df6b115b83cb98a4274b2d883e81b809643ed4f912b383a9e09a7f7a1f9", + "eval/datasets/coding_memory_v1/oracles/grove-violet--condition_values.py": "a68c126f0d7b2fc0950baa5df095d7d513cdb8359883404bad3c1c84c8de666d", + "eval/datasets/coding_memory_v1/oracles/grove-violet--corrections.py": "46024b5fb187aaf3574d380af1a3bb2aa44028f89377f810ca5e98ee03c3b9aa", + "eval/datasets/coding_memory_v1/oracles/grove-violet--long_documents.py": "ec8d401f6437fbaa3b11ddb96973b31944ff374da677f7aae26c0e79e6a1315b", + "eval/datasets/coding_memory_v1/oracles/grove-violet--multilingual.py": "d14e8bf66593ceab7b691119b8a82f211933e2b3f6ebf8a25a75e61ffedbc1c0", + "eval/datasets/coding_memory_v1/oracles/grove-violet--paraphrases.py": "963b7321a32931f238d12e7cdc3849f64682c7cb704b77f4b849543cc40b6df0", + "eval/datasets/coding_memory_v1/oracles/grove-violet--poisoning.py": "39e5edc76a122d37d90b1af28c2293e2f8b0c38e1bdb6460340c0bc2a71ad87d", + "eval/datasets/coding_memory_v1/oracles/grove-violet--scope_boundaries.py": "472298607415aedfb9a36bad7783e7b7e3bccc339ee44cd83746d782ca99e411", + "eval/datasets/coding_memory_v1/oracles/grove-violet--temporal_history.py": "dcf793210a0a547c25587bba70e01d4d56576604856ed084eb5fa466b8be7b83", + "eval/datasets/coding_memory_v1/oracles/grove-violet--unsupported_questions.py": "bbe1769dc0afbe220e8069ef21b85164c31ecff6c3c7f5bf6d18d1e48f5f3c72", + "eval/datasets/coding_memory_v1/oracles/grove-west--code_relationships.py": "5374fb66cd91be79b5eabd2696ada5fee89605d8a1fc84ce36bd6a8ca82cbe5e", + "eval/datasets/coding_memory_v1/oracles/grove-west--condition_values.py": "2e807846b760b65afe0f5e48947463a66c15b8d89899a2724cddd865311760b0", + "eval/datasets/coding_memory_v1/oracles/grove-west--corrections.py": "bca219aae5f85e3f5504d5b39646efe89ff7dc6c007a4323abd5c5154cb4d5a9", + "eval/datasets/coding_memory_v1/oracles/grove-west--long_documents.py": "19c3ca44f78ef79ca1b907c80214e11a8195e994893f1de683b5d9796eb29d6b", + "eval/datasets/coding_memory_v1/oracles/grove-west--multilingual.py": "7842efb41f88fcd49e7a2350e9b44a3a1946bf73e5cb103e4a91a11e810301a2", + "eval/datasets/coding_memory_v1/oracles/grove-west--paraphrases.py": "fd70e8a20da665073ad172219bd57b24e53575dccc344adc1fa0190770ffc6cd", + "eval/datasets/coding_memory_v1/oracles/grove-west--poisoning.py": "473acfa06852554dee20445dd1a86df4e626e0363a7b72de57d68819314bf938", + "eval/datasets/coding_memory_v1/oracles/grove-west--scope_boundaries.py": "3ba0e93abf07498c570cd81b0c4f753bb75b6d4f4bf6c786461bad1a3b301286", + "eval/datasets/coding_memory_v1/oracles/grove-west--temporal_history.py": "334bc7b057f570efd20896d282e54c986f5b6c642930057c8520f84b721ed5e9", + "eval/datasets/coding_memory_v1/oracles/grove-west--unsupported_questions.py": "5102eb2dadcce0dcc0ba157495d4fa10c4983e4bf6627aff25bbd547a08115ca", + "eval/datasets/coding_memory_v1/oracles/helios-green--code_relationships.py": "d376cda89c44b3282a86afe7cd01583f0b53f2c80cd92b47bc2fa4a19271e9ee", + "eval/datasets/coding_memory_v1/oracles/helios-green--condition_values.py": "7b6eaf2f46a5b2f1ae6e8e6da0bcfe6044fea5639df158e137fb8ba32b929909", + "eval/datasets/coding_memory_v1/oracles/helios-green--corrections.py": "2e849f6c926aa0ce1b2e69ce8cdc5ba9c50feb89b33b257fc71bf3aa3b8461b8", + "eval/datasets/coding_memory_v1/oracles/helios-green--long_documents.py": "884c17b7d20d986708c0eff2821cc7b76c5f7b16afd26c43ca80fb507e2f3ea7", + "eval/datasets/coding_memory_v1/oracles/helios-green--multilingual.py": "1182048aef0dcfbc416a004bc43f49f1d2a5de4132e7243ab7d147f54aab1acc", + "eval/datasets/coding_memory_v1/oracles/helios-green--paraphrases.py": "a73950ae7fceacfaa6c7c7a26c8379f9164a29ebea883cede9222e72c506db5e", + "eval/datasets/coding_memory_v1/oracles/helios-green--poisoning.py": "60c5112afb7b84ee5513f2657f42d0eacb0607ba345c28618af3b3b3eaab753e", + "eval/datasets/coding_memory_v1/oracles/helios-green--scope_boundaries.py": "dab035b811e7e9a60bf447d1ea26f6db915c320b5dc2dd04cda4b6f1023296be", + "eval/datasets/coding_memory_v1/oracles/helios-green--temporal_history.py": "c22dd57bc7cd659863231b164192caf6c4929c5baaf8edd8be8cac00268e5847", + "eval/datasets/coding_memory_v1/oracles/helios-green--unsupported_questions.py": "f78986b8093eab4ecc8c1319d359498d64177db55a4e38cf8d0b32d1e5af4b2a", + "eval/datasets/coding_memory_v1/oracles/helios-north--code_relationships.py": "81721c60bd4e3466600442214147ae6641ebc05ae414965f4a09b89cc74cb8d6", + "eval/datasets/coding_memory_v1/oracles/helios-north--condition_values.py": "8fbcf116c1f0d480d61b2974e7bef73acef053d45bf0ac3b12e6ea48b017e43f", + "eval/datasets/coding_memory_v1/oracles/helios-north--corrections.py": "beae8d6c5828c1f225e5e953236136a217ee48e67cebc82049caf8f1c60cfdcc", + "eval/datasets/coding_memory_v1/oracles/helios-north--long_documents.py": "019e7ff3b9efcb461c1c68acc657b2438cc2d63d4b4165c0caa4523c440f12bc", + "eval/datasets/coding_memory_v1/oracles/helios-north--multilingual.py": "a48324390ab60686c2b1417f0c049f47601737c55fdf000b696ccc5de260ccab", + "eval/datasets/coding_memory_v1/oracles/helios-north--paraphrases.py": "9e2552399b988a6d722ccfaec7cfbea10853164336fadc9e9146dbae788a2d8a", + "eval/datasets/coding_memory_v1/oracles/helios-north--poisoning.py": "75eecaa42c302898581d3bef8aee00fa1dec6c3bc968c0014a522196e18c7681", + "eval/datasets/coding_memory_v1/oracles/helios-north--scope_boundaries.py": "8bd32a30cd1c2e346375bf2b37ef4908c3feeb67a80ec52ed3108bfe9cbd548e", + "eval/datasets/coding_memory_v1/oracles/helios-north--temporal_history.py": "1d1118cb5d438f91bb027acaa3b64f867e93cca7eb0f480bb83073584fafd97a", + "eval/datasets/coding_memory_v1/oracles/helios-north--unsupported_questions.py": "02a854bf89085637e366f801d2be6ab26b9e18296f13531485d08c2a64621c7c", + "eval/datasets/coding_memory_v1/oracles/helios-violet--code_relationships.py": "e32101847ccad5cd18fbc2c667ba4d1b63e3e7f8067dc89de5f485546dfef525", + "eval/datasets/coding_memory_v1/oracles/helios-violet--condition_values.py": "c8ef59dc9dcfe3aecc990d82fe44f1f93b7adc8c65f1fc38e79b934d351635c6", + "eval/datasets/coding_memory_v1/oracles/helios-violet--corrections.py": "a3a36bb554a6f4a83ea612bbbe9c0ee4ee1a08071e490a9a909c46d3049bb379", + "eval/datasets/coding_memory_v1/oracles/helios-violet--long_documents.py": "f9fa707bb28b4d7ea96e3bacfa22f24502b571a76a79defd147fe9d5205963a2", + "eval/datasets/coding_memory_v1/oracles/helios-violet--multilingual.py": "cfbfe0535e905dd58434bc25d84e7746dbdf1fc50d72d0fa96cc78677eaf18b9", + "eval/datasets/coding_memory_v1/oracles/helios-violet--paraphrases.py": "1743c8cbf0564d91dfb4db88b694072fdc9bb5a0b9f26f578649af7f539ee393", + "eval/datasets/coding_memory_v1/oracles/helios-violet--poisoning.py": "02cf390520757473e9d677f5c1f0934ecf50645ad5edcdc64caec23721a5d99e", + "eval/datasets/coding_memory_v1/oracles/helios-violet--scope_boundaries.py": "5ab003dfc23cb00d0a70195541a780a4b80b1d496728b2b88f7f154310e67a6d", + "eval/datasets/coding_memory_v1/oracles/helios-violet--temporal_history.py": "209e95f62c39d3f2223ed00a378788e396d04de392324455cd9d19dedb3a87ad", + "eval/datasets/coding_memory_v1/oracles/helios-violet--unsupported_questions.py": "a3ff0703297e2f743917e74abeffc0c922a6241318f37d63a49242c6bcc18059", + "eval/datasets/coding_memory_v1/oracles/helios-west--code_relationships.py": "4d8543e4c49d59f844830ccee3fcea0643ee17ce43f51863b94a1b4fbc9825ea", + "eval/datasets/coding_memory_v1/oracles/helios-west--condition_values.py": "f2efa7fd803df0e10c440d5ec2d98dedf5e02e23a4cedddcde5ea722f4039e40", + "eval/datasets/coding_memory_v1/oracles/helios-west--corrections.py": "181c42640cee47db2b5c779bdf8fe2e84103a92459d7f6cb0d9587458e421406", + "eval/datasets/coding_memory_v1/oracles/helios-west--long_documents.py": "49337830deb24f9d6b11ba4e01f397c0cf1a40a15524f1ce33354a2c61615072", + "eval/datasets/coding_memory_v1/oracles/helios-west--multilingual.py": "f1e4ef8be6bba13457a2da1c3a2082f81bba765f2cf1f07b93eb20edf5e2debe", + "eval/datasets/coding_memory_v1/oracles/helios-west--paraphrases.py": "29abfff3b323496fb62e739cdc0be2aada1b7e1467a141cc8d19a1d54c1ab130", + "eval/datasets/coding_memory_v1/oracles/helios-west--poisoning.py": "14b7f874c10681ff3829b2659cbe7500250369652d11b26c842e62718093ca0f", + "eval/datasets/coding_memory_v1/oracles/helios-west--scope_boundaries.py": "decbdb3cd7ab0c40c0aadb1ecdeef9e2ee2586dd8802b9bb16235f211ce05ee1", + "eval/datasets/coding_memory_v1/oracles/helios-west--temporal_history.py": "6e7c2369157234374d3f9633b7e772b4a3bff405d418c6e1336e56c87ebf28de", + "eval/datasets/coding_memory_v1/oracles/helios-west--unsupported_questions.py": "379716803a300bbecd3fc8f1661bdac2fcd8a98a2bb18514d8de168bd57fa144", + "eval/datasets/coding_memory_v1/oracles/island-green--code_relationships.py": "833962801d594247d5c5644df0e6aee877d452127ea5c0dbc22c81512132ab21", + "eval/datasets/coding_memory_v1/oracles/island-green--condition_values.py": "cfbb4ea0a4dd12d69686178ba8246bd08b93743dc5395cd0479984a53655d909", + "eval/datasets/coding_memory_v1/oracles/island-green--corrections.py": "4c7ee2538df9988864e24d9b86805d26b64d1a37638b57abdd1761e24c4ab340", + "eval/datasets/coding_memory_v1/oracles/island-green--long_documents.py": "f73cd2b884539680c500af9a5c8cc00d1dc9f1d2ef7bda61f6c8253b2bf799ed", + "eval/datasets/coding_memory_v1/oracles/island-green--multilingual.py": "e954fa56aa819ef9088c8d6da4abd647b689956c4ff4a7b0b8e31078522f2702", + "eval/datasets/coding_memory_v1/oracles/island-green--paraphrases.py": "4cde223eac40d4a2962aa2ace533b8761c5b82784767b0307c884ac5e6ad0576", + "eval/datasets/coding_memory_v1/oracles/island-green--poisoning.py": "08725e295848b2c58c9e6d6a0554a2dd4a6506af75039205c444251d11a62acb", + "eval/datasets/coding_memory_v1/oracles/island-green--scope_boundaries.py": "257cd579af07491748f5de88d04f8af85056b0448d348e59c7b0cbb28754fb1b", + "eval/datasets/coding_memory_v1/oracles/island-green--temporal_history.py": "e93105f0639a83b300a6de3bbae56166eedac2e58122f7e84ce5a40a074db6b6", + "eval/datasets/coding_memory_v1/oracles/island-green--unsupported_questions.py": "a707cee90fb3f0cabd0e692399a361f59da0fc4e9e7bbf0cde702e05579f0b3f", + "eval/datasets/coding_memory_v1/oracles/island-north--code_relationships.py": "f00e022ce900e4ad0fcec93f2dbf016337a4d83afacbcf739f61f71316723173", + "eval/datasets/coding_memory_v1/oracles/island-north--condition_values.py": "c704a03a70253585a67823826dc0a6ebaf6323a8880c00af80c148c150176daf", + "eval/datasets/coding_memory_v1/oracles/island-north--corrections.py": "e2cadf6e4f365f94cf0f440758b173da4d14ba12c03a84351e0608f0c60bccc5", + "eval/datasets/coding_memory_v1/oracles/island-north--long_documents.py": "300246fd3d85d73b63066d133361bc784ae26b46f4197c5c41ac960eb37f9508", + "eval/datasets/coding_memory_v1/oracles/island-north--multilingual.py": "f8aba8e14ed3c420e90e701880ea38e924e1219e3cc3643ba8bde17cb31ddef6", + "eval/datasets/coding_memory_v1/oracles/island-north--paraphrases.py": "503ce2c132c666bb67920e90d4a898f5a73f1d66779cd83c1885812f223e30e5", + "eval/datasets/coding_memory_v1/oracles/island-north--poisoning.py": "3369d956e5af12bc6be80828c4c6ab03ccf80c79e38dea3162b4e2c09771ff30", + "eval/datasets/coding_memory_v1/oracles/island-north--scope_boundaries.py": "9b4f6186f71713175f1eb5aacb0b6258c746b2297ca7c692f54bf5feabd5d261", + "eval/datasets/coding_memory_v1/oracles/island-north--temporal_history.py": "7c22b0edbf8edb7a511a581ffe6f1ce14454ef3feb527eca90d1386ce00b2c6b", + "eval/datasets/coding_memory_v1/oracles/island-north--unsupported_questions.py": "c1e5d526b0c6b88394f19a8b8edca7dae1db718b24be2d57374eb3769bbd1f24", + "eval/datasets/coding_memory_v1/oracles/island-violet--code_relationships.py": "9672d608ea0c8623ff1fe760f943173b2fdac5dc4ea4b7ae0dba33436d860254", + "eval/datasets/coding_memory_v1/oracles/island-violet--condition_values.py": "cfea91adcc9b3f9eb28cc24cf310e10b436f81cff51297f161b9a3b59a21f62c", + "eval/datasets/coding_memory_v1/oracles/island-violet--corrections.py": "075d48a2dfd91af8328982fe475ec915c00b75db38819aff3b4c2343147e2bfc", + "eval/datasets/coding_memory_v1/oracles/island-violet--long_documents.py": "85cc789277e59925b36ea3bd82e23f989e79fb0be2e55083e2656e518641dd3c", + "eval/datasets/coding_memory_v1/oracles/island-violet--multilingual.py": "84615b70eb74eb2180dd8a89d8d34c11e904030bfaa26c57e2e6c968daf64d29", + "eval/datasets/coding_memory_v1/oracles/island-violet--paraphrases.py": "5244d32ffc4127d519e2f5a875ffaecf7bc51eaa1426d2aacbc42490cf291d22", + "eval/datasets/coding_memory_v1/oracles/island-violet--poisoning.py": "9bde478876ea0be60555d5bd26d3790e4f305327ece10bf6c258bc564354fa1d", + "eval/datasets/coding_memory_v1/oracles/island-violet--scope_boundaries.py": "8e8b0a64342963b4dbc000578a72c0e0801fc1ca4af140f09b55d4cdb03b1274", + "eval/datasets/coding_memory_v1/oracles/island-violet--temporal_history.py": "3e187ce259c7f57f6dba4a28c3ecdf31fbe6458f7d908b22cb8b5ce8325437ae", + "eval/datasets/coding_memory_v1/oracles/island-violet--unsupported_questions.py": "0e60f29a47953c5d434c159eece26bbb03cc837842c493111b34310f9cff4693", + "eval/datasets/coding_memory_v1/oracles/island-west--code_relationships.py": "c19c42978fa5f9c6e1f41886eb1cc0fdb7e12304ba58b160b4cbe1975640acfe", + "eval/datasets/coding_memory_v1/oracles/island-west--condition_values.py": "07c4c591e08f5248f3016dc16cecfea7dd75bebff1a61599f258938d7031043d", + "eval/datasets/coding_memory_v1/oracles/island-west--corrections.py": "a7d93dfb579ec4f58cf2f6d29a6d0ab7dbf1a17f6ae49b98902a5ea22525e649", + "eval/datasets/coding_memory_v1/oracles/island-west--long_documents.py": "20a50dd63e04ce751115fca671b9da7ebbf240e26f39d494583cb405a03b750c", + "eval/datasets/coding_memory_v1/oracles/island-west--multilingual.py": "65d82c4d7968e0e597509c692015bdac0b92841c726d87560d9caa32429176f4", + "eval/datasets/coding_memory_v1/oracles/island-west--paraphrases.py": "feb7eb2e8592162d7d1f433610cb02ba04cd9797191d65a9f0e5bc4f4711c4ed", + "eval/datasets/coding_memory_v1/oracles/island-west--poisoning.py": "3d7c2c6a7f247f3e8158d5ba007bbbdf027a74e9a2c52f52953b3ccf2c21f17f", + "eval/datasets/coding_memory_v1/oracles/island-west--scope_boundaries.py": "ff0f36139f6da5428b9538d108aded522e62ca803035ebef78e11255cf55e08b", + "eval/datasets/coding_memory_v1/oracles/island-west--temporal_history.py": "42cb1c552ce01308b47d526c6e6993fb3270239b392cfa9f5e42b8e0fcfb3f82", + "eval/datasets/coding_memory_v1/oracles/island-west--unsupported_questions.py": "911b4257380640d35d6e2fc5292e235eedc113f5e71d8e71e8ccab7c72d4f290", + "eval/datasets/coding_memory_v1/oracles/juniper-green--code_relationships.py": "373395d3b70f64f8102609d27ce10c4aeb937ac73988c2ce2be1e283df94ef82", + "eval/datasets/coding_memory_v1/oracles/juniper-green--condition_values.py": "f04794a96b06d537550227ec308b1f02405cdab42d7d3ae4db4afd2c25bee8cd", + "eval/datasets/coding_memory_v1/oracles/juniper-green--corrections.py": "5a354a8c8efdbc498d25ffbf6095fee7b3d7d638134b452c7c9ecb186b4e1bc7", + "eval/datasets/coding_memory_v1/oracles/juniper-green--long_documents.py": "8610157668d6a90c794a956f6f0b432f03e3559238599ad1cb412f6f1a2f7e46", + "eval/datasets/coding_memory_v1/oracles/juniper-green--multilingual.py": "dcb99335fa18b008197532809ff7127be2cda600848e10c8bfe4677bd896f90e", + "eval/datasets/coding_memory_v1/oracles/juniper-green--paraphrases.py": "7a4cb1bbe3de2e6fe6a33b1f78829f4aeb21d21c2e8d4d3399d3d98958ba0909", + "eval/datasets/coding_memory_v1/oracles/juniper-green--poisoning.py": "ba73c2c6f04590538e16c50be527da60f5949781bc8833a3c18a7c597146eb10", + "eval/datasets/coding_memory_v1/oracles/juniper-green--scope_boundaries.py": "9d09c0851a3906b6281cb1c5811714224161e27a1aa46edd81c5659e7f93cd3d", + "eval/datasets/coding_memory_v1/oracles/juniper-green--temporal_history.py": "fd05595a452952dca2d0089f5701a60145a3b5705b78a5ff2f8f9f13fbf1c259", + "eval/datasets/coding_memory_v1/oracles/juniper-green--unsupported_questions.py": "f58bfcb89db799dc1578fc8791b49e97e7f22b30e720aeaf661f0ad2e6a9660c", + "eval/datasets/coding_memory_v1/oracles/juniper-north--code_relationships.py": "a84f02bd3c2b9c367248dae8aa3580d83de6a1beb6fe464553eb8a2bfb172b75", + "eval/datasets/coding_memory_v1/oracles/juniper-north--condition_values.py": "d70311ae339556f18240f824d0f0bd0024060c2698fcca8fe7cb540e8a8bc727", + "eval/datasets/coding_memory_v1/oracles/juniper-north--corrections.py": "fa290629339007fdd4230a76d49fa294a218c5f0cf6b90c3074678b0f16c539a", + "eval/datasets/coding_memory_v1/oracles/juniper-north--long_documents.py": "e30025d68ae3b48b566bf3fb2aea5e3603f5f7ab658da8fcb2256d3b1964c85a", + "eval/datasets/coding_memory_v1/oracles/juniper-north--multilingual.py": "b8bb0199c4acfe71df8d4e7df75bdd37e038cc72aa82bc6df10715819aeb3686", + "eval/datasets/coding_memory_v1/oracles/juniper-north--paraphrases.py": "aabf70509d3b0602d43c9590f04fa3ad1370a40c266d20f64a5ece15d4c79a51", + "eval/datasets/coding_memory_v1/oracles/juniper-north--poisoning.py": "ebfb9d7bf3c50eed8f44b73e391496ef377942de389b6feb64c7a1c0f9646444", + "eval/datasets/coding_memory_v1/oracles/juniper-north--scope_boundaries.py": "06c2c9d57ade79139c9b0d20568703477bb9e3a934f2f8900ad717ef157e5844", + "eval/datasets/coding_memory_v1/oracles/juniper-north--temporal_history.py": "0d97ba6d7a5d7741bf248b599ed18ae81b1c0053ad53cbc64934f31e9c3461aa", + "eval/datasets/coding_memory_v1/oracles/juniper-north--unsupported_questions.py": "526648ba7a4edc21bc2eb589c1e5c98a20fb07aa692687de4c3845e559be8096", + "eval/datasets/coding_memory_v1/oracles/juniper-violet--code_relationships.py": "c73fd24d0adbeda6c7afeb15ab07296c0438402893e8d442af26865106d6e8ea", + "eval/datasets/coding_memory_v1/oracles/juniper-violet--condition_values.py": "4aadae570ef84f976d49ba1fc92574ef55630b1c3017e90dbec1f85e6fb74c4a", + "eval/datasets/coding_memory_v1/oracles/juniper-violet--corrections.py": "07236b2a88df73c8d0f6afbe7d972fdc18f8c835a852f5c20748a369c77d82e2", + "eval/datasets/coding_memory_v1/oracles/juniper-violet--long_documents.py": "54d3201b621b168ce03963c423942f142cb403f9d90234f063ff33b7add4d2dc", + "eval/datasets/coding_memory_v1/oracles/juniper-violet--multilingual.py": "e0cc26d743cbea8e98261895474879d078fd53941c075606bfc6e52ee098db93", + "eval/datasets/coding_memory_v1/oracles/juniper-violet--paraphrases.py": "a1f836316408052cab1253b56b5caa6b1432fe6eeaabbb5872da97ec9bde633a", + "eval/datasets/coding_memory_v1/oracles/juniper-violet--poisoning.py": "bc09271eadf77f1b6c03fff20ca9c0f0a42a99d1c5600e6b7843f0a0d549ed5d", + "eval/datasets/coding_memory_v1/oracles/juniper-violet--scope_boundaries.py": "17a7258bed9efe1c86a05756c5b5d17926fd72f01de4b2addd5d2fb4550f4da5", + "eval/datasets/coding_memory_v1/oracles/juniper-violet--temporal_history.py": "8bda8599d861857204c48b55729607c8cb0cb3ae2701eb18fb600d398468cafe", + "eval/datasets/coding_memory_v1/oracles/juniper-violet--unsupported_questions.py": "10cdbb013f78f0e54c31a6e76f03d8d15a6063fd7971a2d803a390890c036ad2", + "eval/datasets/coding_memory_v1/oracles/juniper-west--code_relationships.py": "f0a682d413b3d8418c664db2775de4eef35240c508cc71ca6bbc055cd0c8dbe6", + "eval/datasets/coding_memory_v1/oracles/juniper-west--condition_values.py": "200a4b78f201e992ac10112a194c97b88841dc5930e3e7de9bfc076bf4c69fa7", + "eval/datasets/coding_memory_v1/oracles/juniper-west--corrections.py": "41ea9b5a5f40d8703343f4570593c3f27905e5ff70fae57bdbdb5fcccc585d22", + "eval/datasets/coding_memory_v1/oracles/juniper-west--long_documents.py": "7d8cdd01c5d90ad62e9da93a99febead683e3f01c5ca37315382a2fb0f512d76", + "eval/datasets/coding_memory_v1/oracles/juniper-west--multilingual.py": "1815adc5946bf9e7dba16786d8c29f9466537b7a43b1c392f927b61c56da54ee", + "eval/datasets/coding_memory_v1/oracles/juniper-west--paraphrases.py": "5d0110bc2098d2143303476fb5c5714086d0278b848110c63aa626e0b88311cc", + "eval/datasets/coding_memory_v1/oracles/juniper-west--poisoning.py": "4e16d9de9931cffc6915805b4fc375dad3ffb7fad17eb893205ce6d07102bcb3", + "eval/datasets/coding_memory_v1/oracles/juniper-west--scope_boundaries.py": "6c5d875184c890a922ee18ac423598fdcece109a91e9e62d5a7ccc82d2eaaa70", + "eval/datasets/coding_memory_v1/oracles/juniper-west--temporal_history.py": "3404fdbb5cc4693333d48d27da928531279d148ffe1cdadbdd741dfc1a8d38d3", + "eval/datasets/coding_memory_v1/oracles/juniper-west--unsupported_questions.py": "b85a4cf14f9596cd14b480acd323772fc91084ad1413fa27435bdb7ffe5baa6b", + "eval/datasets/longdoc.jsonl": "7f5ade95e1f283d0db8cf78e53ed8995d3534f847e616d2c0005fd8da37ac790", + "eval/desktop_memory.py": "65a3b5fcd248948c209baa115b275a6db40b386be5f0828bf9ca611787fa02a6", + "eval/engine_capacity.py": "1940da1c3a435d81c7b7a1a689e966c23b92e4f94af115b45813dd30e2c04eec", + "eval/evidence_contracts.py": "03969fdf01e78135c66e698d3f523e5312ccca3737dd3036b37a2c6676ba4ea2", + "eval/external.py": "98500a97c18152b1b86a270062033c48176224d7468ac0f703ddc7de862a30e2", + "eval/external_checkpoints.py": "484e298039bfa04eef86bd7145428c312aaeaf059f5bdf8da7ae29d1150008d7", + "eval/extractor_quality.py": "50820fe7f821d111e17e4e77a3d1159e7d6979d110b052c4f254a5b309c2652a", + "eval/fts_insert_scaling.py": "6088997e0f85d430fafb74509e24a960fbd59c06c8b2839285942b97d2116724", + "eval/graph_every_bench.py": "79da573c5edf315f71bfab412d3ea283b8da45d1fdabc78ddd19302ead09e4f0", + "eval/graph_traversal.py": "b094f75c3a1d75ba3cf19e372187d692d3c595a1d9bde19bbe95ae0c79a5175f", + "eval/grounded.py": "053d5193b716a2c3e507fcd44057d392de910a4442b46bd7cc1f30ac0ba68541", + "eval/handoff_quality.py": "7daf635510764e236f48ca7e2537a85d8ebc1ad995513144329c1f0236405937", + "eval/harness.py": "8c96c26a121dfc2d9ea051a05861af8951c93a732dba9cb13de0de178414b016", + "eval/hosted_evidence.py": "7946cd8c1e3aa291268271b2aa11210d5f09c9b05ba635aa0b64bef07bdcee45", + "eval/hosted_ledger.py": "a53036d12ff671371c148910a816fb20c7e7f3346250a303722352f47b06c476", + "eval/hosted_luna.py": "4dbf02a65eec38bcde92d82952a0b372abbac1f68378d11a04528f4a31a835dc", + "eval/jev_recall_quality.py": "3c31080767c2bc0301f1085c837eaa60ac6e1f5ae8eeb6ff908e573a630510cd", + "eval/local_benchmark_queue.py": "d87ace3cf9ed59d65e57917e5387ff226c1b8a4abef8b98719c5de3aa1b8dddc", + "eval/local_capacity_campaign.py": "ab4935263f9d24c4e38bd4f16164b4c4c07eff91c695809b6fc75299d4f03d62", + "eval/longmemeval_v2.py": "defb4d47f453aa4615a8f101b82df3fadf64f0f9eae0ce1021d86d3750dad437", + "eval/longmemeval_v2_evidence.py": "8486bd4dcfdd8b1f7a14c32fe4f427c980ccdcc3aa2eb018d7dfc40305412eff", + "eval/longmemeval_v2_matrix.py": "ca085cd59481813cce5dbdfbc94f40f67173ac3a1bc3dce6f6d3e09eb7b08153", + "eval/metrics.py": "16857e2cf6ed339cb57a26c9bfa1879444b4d279bd972e5a9fa644ed1308afe0", + "eval/native_coverage_scaling.py": "1df274ae8f0a8266282fd5b530167aa749ab67024349bf97c6c0a5a4987bfa77", + "eval/performance.py": "dccc55c26dc396f9fee98defd152900cf8aabeb5d6198bc711e0eb8f472ecc57", + "eval/performance_engine.py": "3d37cf0a5989c8fa6e8b6ab7ae0b2e0d5410a8b922d2f3130c1a7794d93dcbb1", + "eval/planned_recall.py": "f9a87291ecb181045b98ca65fe55db820bf7cee4ae4f4d087ca3458afdd7837e", + "eval/proactive_ranking.py": "8610541f1d547f9c0eb46d078dbcaa670c0a97157acc37f96ec08b482cb7a6ab", + "eval/productivity.py": "6d4644ebdc44472aeb3879963774fab276bfa269b781139c46ded48774a22717", + "eval/public_readiness.py": "5ce8a18d0bfe09e75e88548a589b8fc6d2cbf04c0f8cd1ce51cd9519136a2751", + "eval/redteam_poisoning.py": "fce120cc3adf2ee966b59cd3ea7f20d242a0af49b52492143543130fe14c0cc8", + "eval/reinforcement.py": "72ed766775a2658eaa728afec51c0ac22d97a90e813111954df66a6ec50f2bef", + "eval/repair_discovery.py": "db05496fbbcb0df86c5cdc2f0c85fb6b6605b0cace6cb44ae2add10b72784b5f", + "eval/resolver_reworded_corrections.py": "a9054778a37b2175b46f04b674b4779e358931eee5d2ae52bbc5f953d234fb9e", + "eval/resource_hierarchy.py": "5ab6c989bb143c4386749c45a33c190447e829c1b5657e8c0aca30f34bd69461", + "eval/rework_statistics.py": "e12c14288797c5cf2dd93d51f606244287bc2f4ff8bd401f1d1b072efd1f9529", + "eval/run_longmemeval_v2.py": "866863d9f8f7ce8f7c3c36741ab324faa5fae417827f907c655eace38de0f5ac", + "eval/spec_crawl.py": "da3b079ab5674ce1004b0dafcfd471da9897c951a03456ba704b720b719f3295", + "eval/task_pairs.py": "fddc54804e8837ec0731813297fb55825458317f898e29176e16f3f5a2f527fd", + "eval/user_journeys.py": "a1c8436d4a6871baff21f9aa4f6ac1545def60d946cd3b1454c3d9cd8661a049", + "eval/vector_scale.py": "3f9c327d9eca1a857512ddc208e972933be1a7d4fa7fa0017aca0cbf8fe7bb6d", + "eval/vector_scale_storage.py": "24040fd1b96b9f9cd43ea37b0b37ea118a02bd096aff77fc67ff8b0df769b1dc", + "eval/vector_scan_plan.py": "34fba3d029bfc78134e8c9c450b019ff56c3bdcbeb921400888077cb44e9b848", + "scripts/export_offline_evidence.py": "4e10c2b3d5f6a2024a7f95f7b87ce47ec2cb4ef31c55611b2e5cf35502ebc0ba" + } + } +} diff --git a/docs/benchmark-evidence/offline-fixtures-receipt-diagnostics-20261103.json.sha256 b/docs/benchmark-evidence/offline-fixtures-receipt-diagnostics-20261103.json.sha256 new file mode 100644 index 000000000..62d365143 --- /dev/null +++ b/docs/benchmark-evidence/offline-fixtures-receipt-diagnostics-20261103.json.sha256 @@ -0,0 +1 @@ +7679701565f8897a12bb9d7f65918d8faa1d694757957b8628ef635b640a9bed offline-fixtures-receipt-diagnostics-20261103.json diff --git a/docs/images/context-efficiency.png b/docs/images/context-efficiency.png index 8a12db21860f3705a92a9f17e895b1beb7743e00..8c6927dfbc95b2232d6d7a8ae960a82918323048 100644 GIT binary patch delta 16919 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 delta 16891 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 J_T}4;{|hCe>eB!K diff --git a/docs/images/context-efficiency.svg b/docs/images/context-efficiency.svg index b5dbee069..b4ddf43b4 100644 --- a/docs/images/context-efficiency.svg +++ b/docs/images/context-efficiency.svg @@ -1,6 +1,6 @@ Offline context, retrieval, and grounded benchmark results -Artifact-driven offline benchmark report with context efficiency, retrieval quality, and grounded behavior reported separately. Structure-aware chunking reports 740.3 to 214.3 retrieved tokens per question. The JSON-shape payload proxy reports 24,590 full versus 11,138 compact tokens; MCP transport was not measured; provider billing was not measured. Retrieved candidate quality is Recall@5 1.000, Hit@5 1.000, and answer-token recall 1.000 across 26 questions. Packed context quality is Recall@5 1.000, Hit@5 1.000, and answer-token recall 1.000 across 26 questions. Grounded checks score 5 of 5 answerable queries grounded and 6 of 6 abstention queries rejected, including 1 of 1 quarantined-evidence checks. Decision accuracy is 1.000 across 11 decisions. Source artifact SHA-256 8df20fd0a97f20070ac26628e19b5ae1be653cd7aa9c7399d424cedf0e839a01. +Artifact-driven offline benchmark report with context efficiency, retrieval quality, and grounded behavior reported separately. Structure-aware chunking reports 740.3 to 214.3 retrieved tokens per question. The JSON-shape payload proxy reports 24,590 full versus 11,138 compact tokens; MCP transport was not measured; provider billing was not measured. Retrieved candidate quality is Recall@5 1.000, Hit@5 1.000, and answer-token recall 1.000 across 26 questions. Packed context quality is Recall@5 1.000, Hit@5 1.000, and answer-token recall 1.000 across 26 questions. Grounded checks score 5 of 5 answerable queries grounded and 6 of 6 abstention queries rejected, including 1 of 1 quarantined-evidence checks. Decision accuracy is 1.000 across 11 decisions. Source artifact SHA-256 7679701565f8897a12bb9d7f65918d8faa1d694757957b8628ef635b640a9bed. @@ -65,6 +65,6 @@ Quarantined-evidence probe 1 / 1 abstained Decision accuracy 1.000 (11 / 11); the quarantine probe is included in the abstention total. -SOURCE SHA-256 8df20fd0a97f +SOURCE SHA-256 7679701565f8 JSON proxy only; MCP transport not measured; provider billing not measured. diff --git a/docs/images/evidence-backed-agent-examples.png b/docs/images/evidence-backed-agent-examples.png index 69f12ff7eb9f1084a815ea0e22e017f5ed0455aa..3a7c515ce939c85e44ad928a79430ab20feb3a6b 100644 GIT binary patch delta 38640 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 diff --git a/docs/images/evidence-backed-agent-examples.svg b/docs/images/evidence-backed-agent-examples.svg index 4ba7cbd09..3dea7c2ef 100644 --- a/docs/images/evidence-backed-agent-examples.svg +++ b/docs/images/evidence-backed-agent-examples.svg @@ -1,6 +1,6 @@ Three evidence-backed Engraphis agent behaviors - A three-card summary of deterministic offline fixtures. Focused context returns 740.3 to 214.3 tokens while retaining Recall at 5 of 1.000. A grounded answer returns support for 5/5 answerable questions. An unsupported question safely abstains for 6/6 off-topic questions. Reproduce with eval.chunking_eval and eval.grounded. Exact commands and config digests are registered in BENCHMARKS.md. Public-safe artifact SHA-256: 8df20fd0a97f20070ac26628e19b5ae1be653cd7aa9c7399d424cedf0e839a01. + A three-card summary of deterministic offline fixtures. Focused context returns 740.3 to 214.3 tokens while retaining Recall at 5 of 1.000. A grounded answer returns support for 5/5 answerable questions. An unsupported question safely abstains for 6/6 off-topic questions. Reproduce with eval.chunking_eval and eval.grounded. Exact commands and config digests are registered in BENCHMARKS.md. Public-safe artifact SHA-256: 7679701565f8897a12bb9d7f65918d8faa1d694757957b8628ef635b640a9bed. @@ -47,5 +47,5 @@ Reproduce: eval.chunking_eval + eval.grounded - SHA256 8df20fd0a97f20070ac26628e19b5ae1be653cd7aa9c7399d424cedf0e839a01 + SHA256 7679701565f8897a12bb9d7f65918d8faa1d694757957b8628ef635b640a9bed diff --git a/engraphis/backends/encrypted_db.py b/engraphis/backends/encrypted_db.py index c0961918c..50f50456d 100644 --- a/engraphis/backends/encrypted_db.py +++ b/engraphis/backends/encrypted_db.py @@ -30,7 +30,60 @@ class EncryptionError(RuntimeError): """Encryption was requested (a key is set) but could not be honored — missing driver, - wrong key, or an unreadable key file. Fail loud: never silently fall back to plaintext.""" + wrong key, an unreadable key file, or an encrypted database that cannot be read. + + Fail loud: never silently fall back to plaintext. ``code`` classifies the failure so + callers can give accurate guidance without parsing the message; in particular + ``database_damaged`` and ``database_busy`` mean the configured key was applied and the + problem is the file or another process, not the key. + """ + + def __init__(self, message: str, *, code: str = "encryption_error") -> None: + super().__init__(message) + self.code = code + + +def _open_failure(exc: Exception, *, stage: str) -> tuple[str, str]: + """Classify an encrypted-open failure without ever rendering driver text. + + Driver messages are only inspected for stable SQLite phrases: they can echo paths or + the key statement, so they must not reach the operator. Returns ``(code, message)``. + """ + message = str(exc).casefold() + if "malformed" in message or "corrupt" in message: + return ( + "database_damaged", + "could not open the encrypted database — SQLite reports a malformed database " + "image. The configured key was applied; preserve a copy of the database and " + "restore a verified backup.", + ) + if "locked" in message or "busy" in message: + return ( + "database_busy", + "could not open the encrypted database — another process is holding it. " + "Close other Engraphis processes and retry; this is not a key problem.", + ) + if stage == "connect": + return ( + "database_unavailable", + "could not initialize the encrypted database connection (path, permissions, " + "or disk). This is not a key problem.", + ) + if any( + term in message + for term in ("disk i/o", "unable to open", "read-only", "readonly") + ): + return ( + "database_unavailable", + "could not open the encrypted database — the file could not be read (path, " + "permissions, disk space, or mount). This is not a key problem.", + ) + return ( + "key_rejected", + "could not open the encrypted database — wrong ENGRAPHIS_DB_KEY, or " + "the file is not SQLCipher-encrypted (an existing plaintext DB cannot be " + "opened with a key; migrate it first).", + ) def _resolve_key() -> Optional[str]: @@ -53,10 +106,13 @@ def _resolve_key() -> Optional[str]: ) or "").strip() except OSError: raise EncryptionError( - "ENGRAPHIS_DB_KEY_FILE could not be read safely" + "ENGRAPHIS_DB_KEY_FILE could not be read safely", + code="key_file_unreadable", ) from None if not key: - raise EncryptionError("ENGRAPHIS_DB_KEY_FILE is empty") + raise EncryptionError( + "ENGRAPHIS_DB_KEY_FILE is empty", code="key_file_empty" + ) return key return None @@ -219,7 +275,8 @@ def open_read_only(self, path: str): target = Path(path).resolve(strict=True).as_uri() + "?mode=ro&immutable=1" except OSError: raise EncryptionError( - "could not initialize the encrypted database connection" + "could not initialize the encrypted database connection", + code="database_unavailable", ) from None return self._open(target, uri=True, read_only=True) @@ -229,7 +286,8 @@ def open_read_snapshot(self, path: str, *, timeout: float): target = Path(path).resolve(strict=True).as_uri() + "?mode=ro" except OSError: raise EncryptionError( - "could not initialize the encrypted database connection" + "could not initialize the encrypted database connection", + code="database_unavailable", ) from None return self._open(target, uri=True, read_only=True, timeout=timeout) @@ -239,10 +297,9 @@ def _open(self, target: str, *, uri: bool, read_only: bool, timeout: float = 30) options["uri"] = True try: raw = self._driver.connect(target, **options) - except Exception: # noqa: BLE001 - raise EncryptionError( - "could not initialize the encrypted database connection" - ) from None + except Exception as exc: # noqa: BLE001 + code, detail = _open_failure(exc, stage="connect") + raise EncryptionError(detail, code=code) from None try: raw.execute(self._pragma) # MUST be the first statement except Exception: # noqa: BLE001 @@ -253,7 +310,8 @@ def _open(self, target: str, *, uri: bool, read_only: bool, timeout: float = 30) # Suppress the driver message (`from None`): a PRAGMA syntax error can echo the # statement text, which contains the key. Never surface key material. raise EncryptionError( - "failed to apply the database key — check the ENGRAPHIS_DB_KEY format" + "failed to apply the database key — check the ENGRAPHIS_DB_KEY format", + code="key_config_invalid", ) from None try: if read_only: @@ -263,16 +321,13 @@ def _open(self, target: str, *, uri: bool, read_only: bool, timeout: float = 30) # Touch the header so a wrong key / plaintext-vs-encrypted mismatch fails now, # with a clear message, instead of deep inside an unrelated query later. raw.execute("SELECT count(*) FROM sqlite_master").fetchone() - except Exception: # noqa: BLE001 + except Exception as exc: # noqa: BLE001 try: raw.close() except Exception: # noqa: BLE001 pass - raise EncryptionError( - "could not open the encrypted database — wrong ENGRAPHIS_DB_KEY, or " - "the file is not SQLCipher-encrypted (an existing plaintext DB cannot be " - "opened with a key; migrate it first)." - ) from None + code, detail = _open_failure(exc, stage="header") + raise EncryptionError(detail, code=code) from None raw.row_factory = self._driver.Row return _TranslatingConnection(raw) @@ -291,7 +346,8 @@ def make_connector(key: str) -> _EncryptedConnector: "On CPython manylinux x86-64, install it with: pip install " "\"engraphis[encryption]\". On macOS, Windows, Linux ARM, or musl, " "provision a compatible sqlcipher3 driver separately. Engraphis will not " - "fall back to plaintext." + "fall back to plaintext.", + code="driver_missing", ) from None pragma = _key_pragma(key) diff --git a/engraphis/core/store.py b/engraphis/core/store.py index 5addf97b1..7428fa51e 100644 --- a/engraphis/core/store.py +++ b/engraphis/core/store.py @@ -833,6 +833,196 @@ def safe_hash(value: Any, *, allow_empty: bool = False) -> str: return {**payload, "hash": raw_hash} +def receipt_chain_state(conn, workspace_id: str) -> dict: + """Reconstruct one receipt chain from immutable predecessor hashes. + + SQLite ``rowid`` is physical placement, not durable ordering: VACUUM and table + rewrites may renumber it. The receipt payload already carries the true linked-list + order, while ``receipt_chain_heads`` anchors the expected tail. This helper keeps + traversal independent of storage layout and returns a deterministic fallback order + when corruption makes a single chain impossible. + + It is a module-level function so read-only diagnostics (the install doctor) can + verify a database through their own connection without opening a writable Store. + Only ``SELECT`` statements are issued; ``conn`` must return rows addressable by + column name (``sqlite3.Row``). + """ + rows = [dict(row) for row in conn.execute( + "SELECT id, sequence, payload, prev_hash, receipt_hash " + "FROM operation_receipts " + "WHERE workspace_id=?", + (workspace_id,), + ).fetchall()] + + def text(value: Any) -> str: + return value if isinstance(value, str) else str(value or "") + + def stable_key(row: dict) -> tuple[str, str]: + material = "\0".join(( + text(row.get("receipt_hash")), + text(row.get("prev_hash")), + text(row.get("id")), + hashlib.sha256(text(row.get("payload")).encode("utf-8")).hexdigest(), + )) + return hashlib.sha256(material.encode("utf-8")).hexdigest(), material + + children: dict[str, list[dict]] = {} + for row in rows: + children.setdefault(text(row.get("prev_hash")), []).append(row) + for candidates in children.values(): + candidates.sort(key=stable_key) + + structure_errors: list[dict] = [] + ordered: list[dict] = [] + roots = children.get("", []) + if rows and len(roots) != 1: + structure_errors.append({ + "index": 0, + "id": "", + "error": "chain_root_count", + }) + if len(roots) == 1: + current = roots[0] + visited_hashes: set[str] = set() + while current is not None: + receipt_hash = text(current.get("receipt_hash")) + if receipt_hash in visited_hashes: + structure_errors.append({ + "index": len(ordered), + "id": text(current.get("id")), + "error": "chain_cycle", + }) + break + visited_hashes.add(receipt_hash) + ordered.append(current) + successors = children.get(receipt_hash, []) + if len(successors) > 1: + structure_errors.append({ + "index": len(ordered) - 1, + "id": text(current.get("id")), + "error": "chain_fork", + }) + break + current = successors[0] if successors else None + + ordered_identity = {id(row) for row in ordered} + if len(ordered) != len(rows): + structure_errors.append({ + "index": len(ordered), + "id": "", + "error": "chain_disconnected", + }) + ordered.extend(sorted( + (row for row in rows if id(row) not in ordered_identity), + key=stable_key, + )) + + row_errors: list[dict] = [] + for index, row in enumerate(ordered): + if type(row.get("sequence")) is not int or row["sequence"] != index + 1: + row_errors.append({ + "index": index, + "id": text(row.get("id")), + "error": "sequence_mismatch", + }) + raw = text(row.get("payload")) + stored_hash = text(row.get("receipt_hash")) + if hashlib.sha256(raw.encode("utf-8")).hexdigest() != stored_hash: + row_errors.append({ + "index": index, + "id": text(row.get("id")), + "error": "hash_mismatch", + }) + try: + payload = json.loads(raw) + except (TypeError, ValueError, RecursionError): + payload = None + if ( + not isinstance(payload, dict) + or payload.get("id") != row.get("id") + or payload.get("prev_hash") != row.get("prev_hash") + ): + row_errors.append({ + "index": index, + "id": text(row.get("id")), + "error": "payload_mismatch", + }) + if _public_receipt_row(row).get("invalid_payload") is True: + row_errors.append({ + "index": index, + "id": text(row.get("id")), + "error": "payload_schema_invalid", + }) + + structurally_valid = not structure_errors and len(ordered) == len(rows) + head = ( + text(ordered[-1].get("receipt_hash")) + if ordered and structurally_valid else "" + ) + return { + "rows": ordered, + "head": head, + "structure_errors": structure_errors, + "row_errors": row_errors, + "errors": [*row_errors, *structure_errors], + } + + +def verify_receipt_chain(conn, *, workspace_id: str, expected_head: str = "", + expected_count: Optional[int] = None) -> dict: + """Verify one workspace's receipt chain against hashes and its stored anchor. + + Like :func:`receipt_chain_state`, this is read-only and usable with any + ``sqlite3``-compatible connection that returns named columns. + """ + chain = receipt_chain_state(conn, workspace_id) + rows = chain["rows"] + errors: list[dict] = list(chain["errors"]) + head = str(chain["head"] or "") + anchor = conn.execute( + "SELECT receipt_count, head_hash, integrity_error " + "FROM receipt_chain_heads WHERE workspace_id=?", + (workspace_id,), + ).fetchone() + if rows and anchor is None: + errors.append({"index": len(rows), "id": "", "error": "missing_anchor"}) + elif anchor is not None: + anchor_count = anchor["receipt_count"] + if type(anchor_count) is not int or anchor_count < 0 or anchor_count != len(rows): + errors.append({ + "index": len(rows), "id": "", "error": "anchor_count_mismatch", + }) + if str(anchor["head_hash"]) != head: + errors.append({ + "index": len(rows), "id": "", "error": "anchor_head_mismatch", + }) + if str(anchor["integrity_error"] or ""): + errors.append({ + "index": len(rows), "id": "", "error": "anchor_integrity_error", + }) + expected_head = str(expected_head or "").strip() + if expected_head and head != expected_head: + errors.append({ + "index": len(rows), "id": "", "error": "expected_head_mismatch", + }) + if expected_count is not None: + try: + external_count = max(0, int(expected_count)) + except (TypeError, ValueError, OverflowError): + external_count = -1 + if external_count != len(rows): + errors.append({ + "index": len(rows), "id": "", "error": "expected_count_mismatch", + }) + return { + "valid": not errors, + "count": len(rows), + "head": head, + "anchored": anchor is not None, + "errors": errors, + } + + def _fts5_available(conn: sqlite3.Connection | _SerializedConnection) -> bool: try: conn.execute("CREATE VIRTUAL TABLE IF NOT EXISTS _fts_probe USING fts5(x)") @@ -9305,133 +9495,8 @@ def _backfill_receipt_sequences(self) -> None: ) def _receipt_chain_state(self, workspace_id: str) -> dict: - """Reconstruct one receipt chain from immutable predecessor hashes. - - SQLite ``rowid`` is physical placement, not durable ordering: VACUUM and table - rewrites may renumber it. The receipt payload already carries the true linked-list - order, while ``receipt_chain_heads`` anchors the expected tail. This helper keeps - traversal independent of storage layout and returns a deterministic fallback order - when corruption makes a single chain impossible. - """ - rows = [dict(row) for row in self.conn.execute( - "SELECT id, sequence, payload, prev_hash, receipt_hash " - "FROM operation_receipts " - "WHERE workspace_id=?", - (workspace_id,), - ).fetchall()] - - def text(value: Any) -> str: - return value if isinstance(value, str) else str(value or "") - - def stable_key(row: dict) -> tuple[str, str]: - material = "\0".join(( - text(row.get("receipt_hash")), - text(row.get("prev_hash")), - text(row.get("id")), - hashlib.sha256(text(row.get("payload")).encode("utf-8")).hexdigest(), - )) - return hashlib.sha256(material.encode("utf-8")).hexdigest(), material - - children: dict[str, list[dict]] = {} - for row in rows: - children.setdefault(text(row.get("prev_hash")), []).append(row) - for candidates in children.values(): - candidates.sort(key=stable_key) - - structure_errors: list[dict] = [] - ordered: list[dict] = [] - roots = children.get("", []) - if rows and len(roots) != 1: - structure_errors.append({ - "index": 0, - "id": "", - "error": "chain_root_count", - }) - if len(roots) == 1: - current = roots[0] - visited_hashes: set[str] = set() - while current is not None: - receipt_hash = text(current.get("receipt_hash")) - if receipt_hash in visited_hashes: - structure_errors.append({ - "index": len(ordered), - "id": text(current.get("id")), - "error": "chain_cycle", - }) - break - visited_hashes.add(receipt_hash) - ordered.append(current) - successors = children.get(receipt_hash, []) - if len(successors) > 1: - structure_errors.append({ - "index": len(ordered) - 1, - "id": text(current.get("id")), - "error": "chain_fork", - }) - break - current = successors[0] if successors else None - - ordered_identity = {id(row) for row in ordered} - if len(ordered) != len(rows): - structure_errors.append({ - "index": len(ordered), - "id": "", - "error": "chain_disconnected", - }) - ordered.extend(sorted( - (row for row in rows if id(row) not in ordered_identity), - key=stable_key, - )) - - row_errors: list[dict] = [] - for index, row in enumerate(ordered): - if type(row.get("sequence")) is not int or row["sequence"] != index + 1: - row_errors.append({ - "index": index, - "id": text(row.get("id")), - "error": "sequence_mismatch", - }) - raw = text(row.get("payload")) - stored_hash = text(row.get("receipt_hash")) - if hashlib.sha256(raw.encode("utf-8")).hexdigest() != stored_hash: - row_errors.append({ - "index": index, - "id": text(row.get("id")), - "error": "hash_mismatch", - }) - try: - payload = json.loads(raw) - except (TypeError, ValueError, RecursionError): - payload = None - if ( - not isinstance(payload, dict) - or payload.get("id") != row.get("id") - or payload.get("prev_hash") != row.get("prev_hash") - ): - row_errors.append({ - "index": index, - "id": text(row.get("id")), - "error": "payload_mismatch", - }) - if _public_receipt_row(row).get("invalid_payload") is True: - row_errors.append({ - "index": index, - "id": text(row.get("id")), - "error": "payload_schema_invalid", - }) - - structurally_valid = not structure_errors and len(ordered) == len(rows) - head = ( - text(ordered[-1].get("receipt_hash")) - if ordered and structurally_valid else "" - ) - return { - "rows": ordered, - "head": head, - "structure_errors": structure_errors, - "row_errors": row_errors, - "errors": [*row_errors, *structure_errors], - } + """Compatibility wrapper over :func:`receipt_chain_state`.""" + return receipt_chain_state(self.conn, workspace_id) def record_receipt(self, operation: str, *, workspace_id: str = "", repo_id: str = "", actor: str = "system", @@ -10115,52 +10180,12 @@ def _add(target: dict, usage: dict) -> None: def verify_receipts(self, *, workspace_id: str, expected_head: str = "", expected_count: Optional[int] = None) -> dict: - chain = self._receipt_chain_state(workspace_id) - rows = chain["rows"] - errors: list[dict] = list(chain["errors"]) - head = str(chain["head"] or "") - anchor = self.conn.execute( - "SELECT receipt_count, head_hash, integrity_error " - "FROM receipt_chain_heads WHERE workspace_id=?", - (workspace_id,), - ).fetchone() - if rows and anchor is None: - errors.append({"index": len(rows), "id": "", "error": "missing_anchor"}) - elif anchor is not None: - anchor_count = anchor["receipt_count"] - if type(anchor_count) is not int or anchor_count < 0 or anchor_count != len(rows): - errors.append({ - "index": len(rows), "id": "", "error": "anchor_count_mismatch", - }) - if str(anchor["head_hash"]) != head: - errors.append({ - "index": len(rows), "id": "", "error": "anchor_head_mismatch", - }) - if str(anchor["integrity_error"] or ""): - errors.append({ - "index": len(rows), "id": "", "error": "anchor_integrity_error", - }) - expected_head = str(expected_head or "").strip() - if expected_head and head != expected_head: - errors.append({ - "index": len(rows), "id": "", "error": "expected_head_mismatch", - }) - if expected_count is not None: - try: - external_count = max(0, int(expected_count)) - except (TypeError, ValueError, OverflowError): - external_count = -1 - if external_count != len(rows): - errors.append({ - "index": len(rows), "id": "", "error": "expected_count_mismatch", - }) - return { - "valid": not errors, - "count": len(rows), - "head": head, - "anchored": anchor is not None, - "errors": errors, - } + return verify_receipt_chain( + self.conn, + workspace_id=workspace_id, + expected_head=expected_head, + expected_count=expected_count, + ) # ── sync state (device identity + per-peer cursors) ───────────────────────── def get_sync_state(self, key: str) -> Optional[str]: diff --git a/scripts/init.py b/scripts/init.py index 8bee06f7e..f63c04759 100644 --- a/scripts/init.py +++ b/scripts/init.py @@ -25,7 +25,7 @@ import sqlite3 import sys from pathlib import Path -from typing import Any, Optional +from typing import Any, Callable, Optional from engraphis.backends.encrypted_db import connector_from_env from engraphis.private_state import ( @@ -37,6 +37,8 @@ _HEX64 = set("0123456789abcdef") _MAX_JEV_KEY_CHARS = 4096 +_DOCTOR_RECEIPT_SCOPES = 25 +_DOCTOR_RECEIPT_ROWS = 20_000 def _ok(label: str, detail: str = "") -> None: @@ -58,6 +60,99 @@ def _try_import(name: str): return None +def _report_receipt_chains( + conn: Any, report: Callable[[str, str, str, str], None] +) -> None: + """Verify operation-receipt chains read-only, one bounded scope at a time. + + Receipt continuity is audit evidence, not a prerequisite for memory work. Reporting a + damaged chain here gives an operator the same diagnosis the host logs show, without + opening a writable Store, running migrations, or changing any row. + """ + try: + tables = { + str(row[0]) + for row in conn.execute( + "SELECT name FROM sqlite_master WHERE type='table' " + "AND name IN ('operation_receipts','receipt_chain_heads')" + ).fetchall() + } + if tables != {"operation_receipts", "receipt_chain_heads"}: + return + previous_row_factory = getattr(conn, "row_factory", None) + conn.row_factory = sqlite3.Row + try: + total = int( + conn.execute("SELECT COUNT(*) FROM operation_receipts").fetchone()[0] or 0 + ) + if not total: + report("receipt_chain", "optional", "operation receipts", + "no operation receipts recorded yet") + return + if total > _DOCTOR_RECEIPT_ROWS: + report( + "receipt_chain", "optional", "operation receipts", + f"{total} receipts; too large for the install check. Verify one " + "workspace with engraphis_verify_receipts.", + ) + return + scope_total = int( + conn.execute( + "SELECT COUNT(DISTINCT workspace_id) FROM operation_receipts " + "WHERE workspace_id IS NOT NULL" + ).fetchone()[0] or 0 + ) + scopes = conn.execute( + "SELECT workspace_id, COUNT(*) AS receipts FROM operation_receipts " + "WHERE workspace_id IS NOT NULL " + "GROUP BY workspace_id ORDER BY receipts DESC LIMIT ?", + (_DOCTOR_RECEIPT_SCOPES,), + ).fetchall() + from engraphis.core.store import verify_receipt_chain + + invalid = sum( + 1 + for row in scopes + if not verify_receipt_chain( + conn, workspace_id=str(row["workspace_id"]) + )["valid"] + ) + sampled = f"{len(scopes)} of {scope_total} workspace chain(s)" + if invalid: + scope_note = f"{invalid} of {len(scopes)} checked workspace chain(s)" + if len(scopes) != scope_total: + scope_note += f" ({scope_total} total)" + report( + "receipt_chain", "fail", "operation receipts", + f"{scope_note} fail verification. Memory writes and recall " + "keep working and report a receipt_warning; preserve a copy of this " + "database, export receipts, then restore your most recent verified " + "backup before relying on receipt evidence.", + ) + elif len(scopes) < scope_total: + report( + "receipt_chain", "optional", "operation receipts", + f"{sampled} verified over {total} receipt(s); the newest scopes were " + "checked because the install check is bounded.", + ) + else: + report( + "receipt_chain", "ok", "operation receipts", + f"{sampled} verified over {total} receipt(s)", + ) + finally: + try: + conn.row_factory = previous_row_factory + except Exception: + pass + except Exception: + report( + "receipt_chain", "optional", "operation receipts", + "Receipt chains could not be verified in this process; run the doctor with " + "the matching Engraphis version.", + ) + + def cmd_check(*, json_output: bool = False) -> int: """Doctor: report what's installed and whether the configured DB is usable.""" checks: list[dict[str, str]] = [] @@ -66,6 +161,46 @@ def report(code: str, status: str, label: str, detail: str = "") -> None: checks.append({"code": code, "status": status, "label": label, "detail": detail}) def fail_database(exc: Exception) -> None: + encryption_code = str(getattr(exc, "code", "") or "") + if encryption_code == "database_damaged": + report( + "database_damaged", "fail", "database readable", + "The encrypted database file is damaged. Stop every Engraphis process, " + "preserve a copy of the database and its key, then restore your most recent " + "verified backup. Do not regenerate the key or delete the file.", + ) + return + if encryption_code == "database_busy": + report( + "database_locked", "fail", "database readable", + "Another process is holding the encrypted database. Close other Engraphis " + "processes and retry.", + ) + return + if encryption_code in { + "key_rejected", "key_config_invalid", "key_file_empty", "key_file_unreadable", + }: + report( + "database_key", "fail", "database readable", + "The configured encryption key did not unlock this database. Restore the key " + "that created it (ENGRAPHIS_DB_KEY or ENGRAPHIS_DB_KEY_FILE); Engraphis never " + "falls back to plaintext.", + ) + return + if encryption_code == "driver_missing": + report( + "database_key", "fail", "database readable", + "ENGRAPHIS_DB_KEY is set but the SQLCipher driver is missing. " + "Install the encryption extra: pip install \"engraphis[encryption]\".", + ) + return + if encryption_code == "database_unavailable": + report( + "database_unavailable", "fail", "database readable", + "The database file could not be opened (path, permissions, disk space, or " + "mount). This is not a key problem.", + ) + return message = str(exc).lower() code = "database_locked" if "locked" in message or "busy" in message else "database_unwritable" detail = "Close the process holding the database lock and retry." if code == "database_locked" else ( @@ -134,6 +269,8 @@ def fail_database(exc: Exception) -> None: else: report("database_schema", "ok", "database schema", f"version {version}; supported through {SCHEMA_VERSION}") + if version == SCHEMA_VERSION: + _report_receipt_chains(conn, report) else: report("database_schema", "optional", "database schema", "Not initialized; the engine will initialize the database on first use.") diff --git a/tests/test_benchmark_evidence.py b/tests/test_benchmark_evidence.py index b731002f3..b64905520 100644 --- a/tests/test_benchmark_evidence.py +++ b/tests/test_benchmark_evidence.py @@ -34,8 +34,8 @@ ROOT = Path(__file__).resolve().parents[1] -PUBLIC_OFFLINE_ARTIFACT = "offline-fixtures-automatic-browser-access-20261101.json" -PUBLIC_OFFLINE_SHA = "8df20fd0a97f20070ac26628e19b5ae1be653cd7aa9c7399d424cedf0e839a01" +PUBLIC_OFFLINE_ARTIFACT = "offline-fixtures-receipt-diagnostics-20261103.json" +PUBLIC_OFFLINE_SHA = "7679701565f8897a12bb9d7f65918d8faa1d694757957b8628ef635b640a9bed" @pytest.fixture(scope="module") diff --git a/tests/test_encryption_dependency.py b/tests/test_encryption_dependency.py index 3fc12d93e..ea3929a25 100644 --- a/tests/test_encryption_dependency.py +++ b/tests/test_encryption_dependency.py @@ -255,3 +255,69 @@ def test_connection_lifecycle_translates_driver_errors(): with pytest.raises(sqlite3.OperationalError, match="connection exit failed"): connection.__exit__(None, None, None) + + +@pytest.mark.parametrize( + ("driver_message", "expected_code", "expected_phrase"), + [ + ("database disk image is malformed", "database_damaged", "restore a verified backup"), + ("database is locked", "database_busy", "another process"), + ("file is not a database", "key_rejected", "wrong ENGRAPHIS_DB_KEY"), + ("disk I/O error", "database_unavailable", "not a key problem"), + ], +) +def test_open_failure_classifies_damage_separately_from_a_bad_key( + monkeypatch, tmp_path, driver_message, expected_code, expected_phrase, +): + """A damaged or busy file must not be reported as a wrong encryption key.""" + marker = "sqlcipher-driver-secret" + key = "inline-key-secret" + private_path = tmp_path / "private-customer.db" + + class _Raw: + def execute(self, statement): + if statement.startswith("PRAGMA"): + return self + raise RuntimeError(f"{marker} {driver_message}:{private_path}") + + def fetchone(self): + return (1,) + + def close(self): + pass + + driver = types.SimpleNamespace(connect=lambda *_a, **_k: _Raw(), Row=object) + monkeypatch.setattr(encrypted_db.importlib, "import_module", lambda _name: driver) + connector = encrypted_db.make_connector(key) + + with pytest.raises(encrypted_db.EncryptionError) as exc_info: + connector(str(private_path)) + + assert exc_info.value.code == expected_code + assert expected_phrase in str(exc_info.value) + for secret in (marker, key, str(private_path)): + assert secret not in str(exc_info.value) + + +def test_connect_failure_reports_busy_without_blaming_the_key(monkeypatch, tmp_path): + key = "inline-key-secret" + + def connect(*_args, **_kwargs): + raise RuntimeError(f"database is locked:{tmp_path / 'private-customer.db'}") + + driver = types.SimpleNamespace(connect=connect, Row=object) + monkeypatch.setattr(encrypted_db.importlib, "import_module", lambda _name: driver) + connector = encrypted_db.make_connector(key) + + with pytest.raises(encrypted_db.EncryptionError) as exc_info: + connector(str(tmp_path / "private-customer.db")) + + assert exc_info.value.code == "database_busy" + assert key not in str(exc_info.value) + + +def test_encryption_error_defaults_to_a_stable_code(): + error = encrypted_db.EncryptionError("message") + assert isinstance(error, RuntimeError) + assert error.code == "encryption_error" + assert encrypted_db.EncryptionError("message", code="custom").code == "custom" diff --git a/tests/test_init.py b/tests/test_init.py index f1e4ca7ad..29befb4f5 100644 --- a/tests/test_init.py +++ b/tests/test_init.py @@ -1,6 +1,7 @@ """engraphis-init — onboarding command. Runs on the numpy-only gate (stdlib only).""" -import os +import hashlib import json +import os import sqlite3 from pathlib import Path import subprocess @@ -357,6 +358,95 @@ def test_doctor_rejects_a_newer_database_schema(tmp_path, monkeypatch, capsys): assert any(check["code"] == "schema_newer" for check in report["checks"]) +def _fork_receipt_chain(path, *, workspace_name: str = "team") -> None: + """Seed a current-schema database with one forked receipt chain.""" + from engraphis.core.ids import new_id + from engraphis.core.store import Store + + store = Store(str(path)) + try: + workspace = store.get_or_create_workspace(workspace_name) + first = store.record_receipt("remember", workspace_id=workspace) + second = store.record_receipt("recall", workspace_id=workspace) + fork = dict(second) + fork.pop("hash") + fork["id"] = new_id("receipt") + fork["prev_hash"] = first["hash"] + fork["ts_ms"] += 1 + payload = json.dumps( + fork, sort_keys=True, separators=(",", ":"), ensure_ascii=False + ) + store.conn.execute( + "INSERT INTO operation_receipts(id, ts, operation, workspace_id, repo_id, " + "sequence, scope_digest, actor, target_count, status, payload, prev_hash, " + "receipt_hash) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?)", + ( + fork["id"], fork["ts_ms"] / 1000.0, fork["operation"], workspace, "", + 999, fork["scope_digest"], fork["actor_digest"], fork["target_count"], + fork["status"], payload, fork["prev_hash"], + hashlib.sha256(payload.encode("utf-8")).hexdigest(), + ), + ) + store.conn.commit() + finally: + store.close() + + +def test_doctor_verifies_a_healthy_receipt_chain(tmp_path, monkeypatch, capsys): + _fresh_settings(monkeypatch, tmp_path) + from engraphis.core.store import Store + + store = Store(str(tmp_path / "doc.db")) + try: + workspace = store.get_or_create_workspace("team") + store.record_receipt("remember", workspace_id=workspace) + store.record_receipt("recall", workspace_id=workspace) + finally: + store.close() + + assert main(["--check", "--json"]) == 0 + report = json.loads(capsys.readouterr().out) + receipt = next(check for check in report["checks"] if check["code"] == "receipt_chain") + assert receipt["status"] == "ok" + assert "1 of 1 workspace chain(s) verified over 2 receipt(s)" in receipt["detail"] + + +def test_doctor_reports_a_forked_receipt_chain_as_audit_damage( + tmp_path, monkeypatch, capsys, +): + _fresh_settings(monkeypatch, tmp_path) + _fork_receipt_chain(tmp_path / "doc.db") + + assert main(["--check", "--json"]) == 1 + report = json.loads(capsys.readouterr().out) + receipt = next(check for check in report["checks"] if check["code"] == "receipt_chain") + assert receipt["status"] == "fail" + assert "receipt_warning" in receipt["detail"] + assert "restore" in receipt["detail"] + + +def test_doctor_distinguishes_a_damaged_encrypted_database_from_a_bad_key( + tmp_path, monkeypatch, capsys, +): + _fresh_settings(monkeypatch, tmp_path) + from engraphis.backends.encrypted_db import EncryptionError + + def damaged_connector(_path): + raise EncryptionError( + "could not open the encrypted database", code="database_damaged" + ) + + monkeypatch.setattr(init_script, "connector_from_env", lambda: damaged_connector) + assert main(["--check", "--json"]) == 1 + report = json.loads(capsys.readouterr().out) + check = next( + item for item in report["checks"] if item["code"] == "database_damaged" + ) + assert check["status"] == "fail" + assert "damaged" in check["detail"] + assert "key" in check["detail"] + + def test_doctor_reports_lock_contention_without_leaving_probe_state(tmp_path, monkeypatch, capsys): _fresh_settings(monkeypatch, tmp_path) path = tmp_path / "doc.db" diff --git a/tests/test_receipts.py b/tests/test_receipts.py index bfe50a0b0..fabb1a639 100644 --- a/tests/test_receipts.py +++ b/tests/test_receipts.py @@ -5,6 +5,7 @@ import pytest +from engraphis.core import store as store_module from engraphis.core.ids import new_id from engraphis.core.store import Store from engraphis.service import MemoryService, ValidationError @@ -522,13 +523,13 @@ def test_reopening_anchored_receipts_does_not_reconstruct_chains(tmp_path, monke store.close() calls = [] - original = Store._receipt_chain_state + original = store_module.receipt_chain_state - def tracked(self, workspace_id): + def tracked(conn, workspace_id): calls.append(workspace_id) - return original(self, workspace_id) + return original(conn, workspace_id) - monkeypatch.setattr(Store, "_receipt_chain_state", tracked) + monkeypatch.setattr(store_module, "receipt_chain_state", tracked) reopened = Store(db) try: assert calls == [] From 3d048d497ad95553b2dd602d05c6e8b959f7123e Mon Sep 17 00:00:00 2001 From: Coding-Dev-Tools <274649000+Coding-Dev-Tools@users.noreply.github.com> Date: Tue, 3 Nov 2026 23:18:54 -0500 Subject: [PATCH 2/7] fix(docs): normalize merged line endings and re-stamp PNG provenance The merge that brought main's 1.7.12 release into this branch rewrote BENCHMARKS.md, README.md, and both benchmark SVGs with CRLF line endings, which changed their exact bytes under the repository's -text policy, and left the PNG provenance hashes bound to the pre-normalization SVG bytes. Restore LF bytes, rebuild the sidecar with its required LF terminator, and re-stamp the artifact/SVG SHA-256 tEXt chunks so the checksum-bound evidence tests compare the committed bytes. Agent: opencode --- BENCHMARKS.md | 1210 ++++++++--------- README.md | 196 +-- ...s-receipt-diagnostics-20261103.json.sha256 | 2 +- docs/images/context-efficiency.png | Bin 61488 -> 61488 bytes docs/images/context-efficiency.svg | 140 +- .../images/evidence-backed-agent-examples.png | Bin 273392 -> 273392 bytes .../images/evidence-backed-agent-examples.svg | 102 +- 7 files changed, 825 insertions(+), 825 deletions(-) diff --git a/BENCHMARKS.md b/BENCHMARKS.md index b277a7ef0..91949626d 100644 --- a/BENCHMARKS.md +++ b/BENCHMARKS.md @@ -1,605 +1,605 @@ -# Benchmarks - -This guide explains what Engraphis measures, how to reproduce each evaluation, and the limits of -those results. When this document and the code disagree, the code is the source of truth. - -The current expansion has a separate [results and workload report](docs/BENCHMARK_EXPANSION_RESULTS.md), -[execution runbook](docs/BENCHMARK_EXPANSION_RUNBOOK.md), and -[proposed stage budgets](docs/BENCHMARK_STAGE_BUDGETS.md). Completed external retrieval diagnostics -are review artifacts with explicit denominators and uncertainty. The coding pilot uses Codex -OAuth only and retains fixture exclusions and interrupted calls. Official QA, competitor -scores and capacity qualification remain separate experiments. - -For the locked operator sequence for a public canonical run, see -[`docs/PUBLIC_BENCHMARK_RUNBOOK.md`](docs/PUBLIC_BENCHMARK_RUNBOOK.md). - -The current measured improvement priorities and their evidence boundaries are in -[`docs/BENCHMARK_IMPROVEMENT_PRIORITIES.md`](docs/BENCHMARK_IMPROVEMENT_PRIORITIES.md). -The implementation exposes three opt-in comparison controls: `packing_mode="coverage"` -for complete evidence units across sources, source-bound `exact_value` fields on the -Smart/Classic/service write paths, and `retrieval_recipe="conversation"` or `"long_session"` -for the measured depth/budget starting points. `"legacy"` packing and `"default"` -retrieval remain the defaults until development, validation and untouched-holdout gates -show a workload-specific benefit. - -`python -m eval.evidence_contracts` checks exact-action validation and compares -legacy and coverage packing on small deterministic development fixtures. It runs -in the full offline CI matrix and the NumPy-only Python 3.9 job. These fixtures -test boundary correctness; they do not estimate external QA or model task success. The -[current diagnostic](docs/benchmark-evidence/evidence-contracts-20260921-v11.json) -withholds the unchanged oversized, unpunctuated fixture at its 24-token budget: -no fitting sentence boundary proves that the remaining condition text can be dropped. -The prior literal-preservation result remains in the historical artifact. Tight budgets -can therefore return fewer exact values under the complete-group contract. -Action contracts also require the host to supply the actual source text when creating -and validating a contract. The source revision digest and literal offsets must match; -four additional diagnostic cases reject forged or stale bindings. Authorization -remains the responsibility of the trusted host. -Eight fixed query-window cases also check that unrelated bound values cannot displace the -requested evidence. Their original retention expectations remain unchanged: 4/8 passed on -`0244c45f`, 8/8 on `c3a86295`, and 5/8 under the current complete-source rule. Three tight -bound-value cases now withhold their binding. A separate source-completeness gate checks -the same eight inputs, improving from 5/8 to 8/8, including roomy binding controls. -These development fixtures and their source hashes remain separate from external quality -measurements. - -Five safe-withholding cases improve from 0/5 against `65f4becd` to 5/5. A separate -five-case retention population exposes a tradeoff: the earlier v8 implementation -retained the expected literals in 5/5 cases, while the complete-unit rule retains -0/5 at those unchanged tight budgets. These unpunctuated records do not fit as -complete units. Their original inputs, expected literals and outcomes remain in -the diagnostic; safe omission is not counted as successful literal retention. - -Eight distant-restriction cases improve from 5/8 on `98e8f5c2` to 8/8, including -roomy retention controls. Exact binding now requires the complete meaningful source, -regardless of language or recognized qualifier words. Boundary whitespace may be trimmed -only outside the literal. Coverage withholds the bound occurrence when that source cannot -fit, while it may still select other unbound evidence. This also prevents -`Only use ALPHA in production` from becoming `Only use ALPHA`. Complete-unit safety -remains 16/16 against 8/16 on `98e8f5c2` in its separate diagnostic and CI gate. -Headers, titles and source attribution remain inside the budget; expansion cannot restore -an incomplete binding. This conservative rule retains source text without inferring its -meaning or authorizing a downstream action. - -The default legacy packer also withholds exact binding metadata when its selected -excerpt omits part of the complete source. Eight fixed cases improve -from 4/8 on `833e3e92` to 8/8: four incomplete bindings are suppressed and four -complete bindings remain. All eight retain identical context text and token -accounting. The literal can still appear as ordinary text; this diagnostic measures -binding safety, not a retrieval or answer-quality gain. - -Twelve additional French, Chinese and English-synonym cases exercise both modes at fixed -budgets. Against `c3a86295`, the complete binding contract improves from 10/12 to 12/12 -for legacy and from 5/12 to 12/12 for coverage. Unsafe bindings fall from two to zero -and seven to zero, respectively. Coverage literal leaks fall from seven to zero; all five -roomy controls retain their exact bindings in both modes. Independent token accounting -matches the rendered context and all budgets are honored. The gate separately rejects -omitted roomy bindings, incorrect spans and token-accounting errors. This is a fixed -development population, not a claim of general multilingual understanding. - -Corpus replay rejects non-boolean trust and answerability labels before execution. -Direct campaign records also require boolean trust labels and reject contradictory -nested trust metadata. Peer evidence IDs must name a recorded source; returned -backend IDs must agree with that source or resolve through recorded episode lineage. -Frozen campaign scope and trust take precedence over peer labels, and unrecognized labels -remain unknown. Core/service recall results report the recipe's effective output -limit as `effective_k`; recall receipts preserve it as `metadata.k` together with -`retrieval_recipe` and normalized `packing_mode`, independently of the number of -results actually returned. Historical receipts without a mode remain valid and -do not acquire an inferred value. -Campaign adapters report the frozen manifest's evaluated revision; direct adapters -retain `unknown` provenance unless explicitly bound. These checks protect result -interpretation and do not count as additional benchmark-quality gains. - -### Public numeric evidence registry - -Every exact public aggregate retained below comes from the checked-in, public-safe -[`offline-fixtures-receipt-diagnostics-20261103.json`](docs/benchmark-evidence/offline-fixtures-receipt-diagnostics-20261103.json) artifact. Its -SHA-256 is -`f95e35b9f2c8f848925df0d0db0bd34898280b9cd10a884ed188b11d1708dcb2`, also recorded in the -adjacent `.sha256` file. The artifact contains no raw questions, answers, prompts, customer data, -or per-record content fingerprints. - -The fixture-suite digest is -`f66eca5e293df53e73504c3c70aee793812059dafc485475a9a4e9a1cd61b836`. The artifact defines -the digest algorithm and records the SHA-256 of every suite and dataset file. Each evidence ID -also binds its exact command through `sha256(UTF-8 exact command)`: - -| Evidence ID | Exact command | Config digest | -|---|---|---| -| `offline-chunking` | `python -m eval.chunking_eval --dataset eval/datasets/longdoc.jsonl --k 5` | `c1c8196aa7e1568ef3844a9fb2d76b87f342c39108e32d6ad144b885a76143b8` | -| `offline-performance` | `python -m eval.performance --dataset eval/datasets/codemem.jsonl --k 5 --iterations 10 --json` | `bbe4aca81e58d4830e50a8fc7729a1d15b71d97a6299bccd79432b7f119677d7` | -| `offline-grounded` | `python -m eval.grounded` | `590442e51e3642c10489165759919dc86ffac62c182937330c153e7f8d5fc26f` | - -The source manifest covers every Python file under `engraphis/` and `eval/`, the two -fixture datasets, and the official exporter. Each rerun must use a new artifact basename. -The canonical chart aliases are regenerated from that new artifact and carry its checksum; -historical per-run artifacts and their preserved chart bytes remain unchanged. The current -PNG views were freshly rendered from the registered artifact with CairoSVG 2.9.1 and -Cairo 1.18.4 on Windows at each SVG canvas size, with embedded artifact and SVG checksums. -The fresh evaluator run used Windows CPython 3.14.7 and NumPy 2.5.4. Its 590-file manifest -binds the producer after the 1.7.10 release-review fixes: Command Code file-guard tool -ids, fail-closed hook errors, symlink-then-parent and Windows alias targets, the linear -Spec Studio heading pattern, closure-limit and browse-version handling, the equal-start -supersede guard, bounded history candidate windows for `why` and `timeline`, raw-path UNC/device -rejection across import, vault, code-index and source-id surfaces, automatic browser sessions with -single-use link fallback, a -partitioned sync closure budget, Windows source-read timestamp revalidation, non-blocking -engine write guards and atomic receipt transactions, worker-isolated MCP tool -execution, loopback stdio-to-HTTP MCP bridging, exact-identifier recall priority, and -`prompt_eligible`/`review_state` remember reporting. Dashboard assets, tests, documentation, and generated images -are outside this producer manifest. The three measured fixture results are unchanged; -this rerun establishes current source and render provenance without claiming a -retrieval-quality improvement. It does not qualify browser behavior, hosted services, -external models, or provider billing. The registered artifact was produced with: - -```bash -python -m scripts.export_offline_evidence --output docs/benchmark-evidence/offline-fixtures-receipt-diagnostics-20261103.json -``` - -The earlier release-review predecessors, -[`offline-fixtures-automatic-browser-access-20261031.json`](docs/benchmark-evidence/offline-fixtures-automatic-browser-access-20261031.json) -(SHA-256 `99b615d8ce7712b5efbfa7c11db810de695cb0e5a3e44293532a5505133e03b0`), -[`offline-fixtures-release-review-20261026-1.7.11.json`](docs/benchmark-evidence/offline-fixtures-release-review-20261026-1.7.11.json) -(SHA-256 `befc75b82980205a4fbe9074509d82ab5b84b1bcc4fade7c4dc1195dc9bc00c2`), -[`offline-fixtures-release-review-20261022-v4.json`](docs/benchmark-evidence/offline-fixtures-release-review-20261022-v4.json) -(SHA-256 `38582da42a04bd1c637724d6351fa516ab867a263a6848d502b2fa37934a3edf`), -[`offline-fixtures-release-review-20261022-v3.json`](docs/benchmark-evidence/offline-fixtures-release-review-20261022-v3.json) -(SHA-256 `aac955672785c01ab9ec60e841d6f566469ce126d3f95c2927aa8b8616a99fb0`), -[`offline-fixtures-release-review-20261022-v2.json`](docs/benchmark-evidence/offline-fixtures-release-review-20261022-v2.json) -(SHA-256 `4a7f9117f3126edaa765f08f4ab9a4e65788041931a4209e50d8ca70197a8a93`) and -[`offline-fixtures-release-review-20261022.json`](docs/benchmark-evidence/offline-fixtures-release-review-20261022.json) -(SHA-256 `bbdbf607143e776d599a013cca6ca0ab8326398e0ffadd4bb65ba2948fa3920e`), -remain bound to their original digests. They are superseded -by this artifact; their recorded results are identical. - -The earlier [guard-installer snapshot artifact](docs/benchmark-evidence/offline-fixtures-pr270-guard-installer-20261020.json) -remains bound to SHA-256 `047cbdfee604c54c2fe211f7cac54a532563ceecadc310f368dec331bf443ce3` and its original -CPython 3.12.14 / NumPy 2.4.6 execution and CairoSVG 2.9.1 / Cairo 1.18.4 rendering. -It predates the release-review fixes and remains historical and unchanged; -its results and rendered bytes are not relabeled as this fresh run. - -The earlier [browser-session snapshot artifact](docs/benchmark-evidence/offline-fixtures-reconciled-browser-session-20261019.json) -remains bound to SHA-256 `b3a65eea3559a1b994e6e4b81ade81db914ce1ae94b947ed7454e5cf8550998f` and its original -CPython 3.11.17 / NumPy 2.4.6 execution and Playwright Chromium 156.0.8078.4 rendering. -It predates the policy-parent canonicalization fix and remains historical and unchanged; -its results and rendered bytes are not relabeled as this fresh run. - -The earlier [Automation snapshot artifact](docs/benchmark-evidence/offline-fixtures-reconciled-automation-20261019.json) -remains bound to SHA-256 `694765e499a089bdcc00ed0b2b6f763bd3f5606f0f4cb6aa3061338d2cb012a1`. It predates the browser-session -cookie-response correction and remains unchanged. - -The earlier [protected-path snapshot artifact](docs/benchmark-evidence/offline-fixtures-reconciled-path-guard-20261019.json) -remains bound to SHA-256 `10d5c8111647190479aa7dd16831e5e741873d43fc9385190e1f195937a4084d`. It predates the Automation -policy and dashboard recovery corrections and remains unchanged. - -The earlier [canonical LF snapshot artifact](docs/benchmark-evidence/offline-fixtures-reconciled-canonical-lf-20261019.json) -remains bound to SHA-256 `26d75c8a38127edbc51e6c51792cd85f04bf707c32bb1ff8d7c33ae95fb4c34d`. It predates the Windows protected-path -case-matching correction and remains unchanged. - -The earlier [reconciled snapshot artifact](docs/benchmark-evidence/offline-fixtures-reconciled-release-20261019.json) -remains bound to SHA-256 `4de6ca8011e45146d98d855cedca6f7e3f84d8c7e75852405f36ac0f74e96f6d`. -It predates canonical LF normalization of the two Python producer files and remains unchanged. - -The historical [governed-history artifact](docs/benchmark-evidence/offline-fixtures-governed-history-20261018.json) -remains bound to SHA-256 `23203af13a30a78df9f2bf849f2cefdad1c426a9f494ebb0128abd5fa6e19ce9`. -The separate [main-branch runtime artifact](docs/benchmark-evidence/offline-fixtures-pr260-nested-uri-redaction-runtime-20261018-e2ebfb06550f.json) -remains bound to SHA-256 `434ec054dff38104a553ddb88b38a983c97227491e92ba482a7f3cd4ff5b560b` -and its original runtime revision `e2ebfb06550fdcda0eb03590aecd9cf5e27a6dbc`. -Neither historical run is relabeled as evidence for this reconciliation. - -External, model-dependent, latency, consolidation, and productivity numbers are not included in -this offline registry unless a redacted immutable artifact with the same three bindings exists. Use -the [public benchmark runbook](docs/PUBLIC_BENCHMARK_RUNBOOK.md) to produce registry evidence. -Completed retrieval-only diagnostics are documented separately in the -[benchmark expansion results](docs/BENCHMARK_EXPANSION_RESULTS.md); absence from this registry -means no number is claimed in this offline registry. - -The context-efficiency chart is generated from the registry values and the selected report schema. -Historical LoCoMo, graph, handoff, consolidation, and security figures remain preserved in their -source artifacts but are omitted from the current chart until each has a matching immutable, -public-safe artifact. The chart reports context size, candidate and packed retrieval quality, and -grounded checks in separate panels; provider billing and MCP transport are not measured. Regenerate -the SVG and matching PNG with: - -```bash -python scripts/render_benchmark_report.py --report docs/benchmark-evidence/offline-fixtures-receipt-diagnostics-20261103.json --output docs/images/context-efficiency.svg --png-output docs/images/context-efficiency.png -``` - -The companion examples are also generated from that artifact with: - -```bash -python -m scripts.render_benchmark_examples --report docs/benchmark-evidence/offline-fixtures-receipt-diagnostics-20261103.json --output docs/images/evidence-backed-agent-examples.svg --png-output docs/images/evidence-backed-agent-examples.png -``` -The historical-to-executable mapping is in -[`docs/BENCHMARK_CHANGE_COVERAGE.md`](docs/BENCHMARK_CHANGE_COVERAGE.md). - -Fresh diagnostics retain explicit source-case identities so confidence intervals -cluster whole conversations even when question IDs do not encode their case. -Metrics with no eligible questions remain `null` (unscored), including fresh and -resumed runs. Artifact parsing, checksum validation, and queue receipts bind the -same byte snapshot. Comparisons require consistent dataset and repair bindings -while allowing the producer implementation to change between versions. - -## What we measure today (all offline, no API key) - -Most Engraphis evals score **retrieval**, not end-to-end QA. The separate productivity benchmark -runs a complete offline agent attempt and correction loop, but it is not an official -frontier-model QA score. - -- **Correctness gate**: `eval/harness.py` over `eval/datasets/sample.jsonl` and - `codemem.jsonl` (conflict resolution) and `graph_multihop.jsonl` (multi-hop graph recall). - Runs on the deterministic embedder, so it is a plumbing/regression floor, not a public - performance claim. This is the gate CI enforces. -- **Ablation**: `eval/ablation.py`: vector-only vs. 1-hop graph vs. Personalized-PageRank arm, - to show the graph arm actually earns its place. -- **External benchmarks**: `eval/external.py` loads **LoCoMo** and **LongMemEval** and pushes - them through the *real* `MemoryEngine` write path (conflict resolution + evolution) and hybrid - recall with a real sentence-transformers embedder. It reports `recall_at_k` / `hit_at_k` / - `answer_token_recall`: i.e. *did the evidence come back*, not *did an LLM answer correctly*. - It retains source categories and abstention/no-evidence questions as explicit exclusions from - retrieval-only aggregates rather than silently dropping them. `eval.longmemeval_v2` is a local, - text-only adapter for the official LongMemEval-V2 `insert(trajectory)` / `query(query, - query_image=None)` memory interface; it does not download data or call a model. -- **Grounded**: `eval/grounded.py`: answerable → cite, off-topic → abstain. Exact fixture - outcomes are evidence ID `offline-grounded` in the registry above. -- **Chunking (quality per token)**: `eval/chunking_eval.py` over `eval/datasets/longdoc.jsonl` - ingests a multi-topic corpus twice: once as one memory per document (`whole`) and once with - sub-file `ChunkingExtractor` (`chunked`), then queries both through the real recall pipeline. - The checked-in corpus is explicitly marked trusted eval data so the measurement isolates - chunking from the production trust gate, which excludes arbitrary raw imports from normal - agent context. On the deterministic embedder, **recall@5 is 1.000 for both modes; mean - retrieved top-5 content falls from 740.3 to 214.3 tokens (526.0 fewer, 71.1% lower, about - 3.5× smaller), while the smallest returned evidence-holding memory falls from 162.2 to 42.4 - tokens (119.8 fewer, 73.9% lower, about 3.8× smaller).** These aggregates are evidence ID - `offline-chunking` in the registry above. Pass `--embed-model - sentence-transformers/all-MiniLM-L6-v2` to run a model-dependent experiment; do not publish - that result without a new immutable artifact and pinned model revision. -- **Full-pipeline latency + quality**: `eval/performance.py` times the shipped semantic + - lexical + graph + fusion + scoring + rerank + packing path after warmup, with reinforcement - disabled so repeated measurements do not mutate their corpus. It reports p50/p95/p99 latency, - retrieval quality, packed context tokens, and full/compact JSON-shape payload proxies in one - JSON-safe schema. Its legacy `quality` fields score all candidate chunks returned before - context packing; additive `packed_quality` fields score only chunks admitted to reader context. - Payload proxies are sampled once per question, independently of the number of timed iterations; - they are not serialized MCP envelopes or transport responses. In the - registered CodeMem run, 26 payload samples total **24,590** full-proxy - `engraphis.regex.v1` tokens versus **11,138** compact-proxy tokens, avoiding **13,452** proxy - tokens (**54.71% lower**), while 260 recalls are timed. Packed context across the same 26 - samples averages **85.38** tokens and reaches **108** under a 1,500-token cap; Recall@5, - hit@5, and answer-token recall remain 1.000 for the legacy candidate-page view. The registered - v9 artifact predates `packed_quality` and retains no inferred packed-quality result. - The new artifact reports candidate and packed Recall@5, Hit@5, and answer-token recall - separately; all six aggregates are 1.000 on these 26 questions. - These aggregates are evidence ID - `offline-performance` in the registry above. `--filler-memories`, `--candidate-k`, and - `--retrieval-profile` make scaling and routing experiments executable, but their results need - separate evidence before publication. -- **Exact vector scale envelope**: `eval/vector_scale.py` measures the production - `NumpyVectorIndex` directly at requested corpus sizes with deterministic normalized vectors and - queries. It records a corpus fingerprint, result hashes, environment, and observed - p50/p95/p99 search envelopes. It intentionally has no pass/fail latency threshold: the output - describes the measured machine and workload, not a universal capacity cutoff. Pair it with - `eval/performance.py` before making a deployment decision because direct vector search excludes - the rest of the recall pipeline. Its `engraphis-vector-scale/v1` JSON is a local diagnostic, not - an `engraphis-benchmark/v2` public evidence artifact. -- **Proactive ranking calibration**: `eval/proactive_ranking.py` compares the previous and current - importance-retention floors on a small deterministic queryless-ranking fixture. It reports - top-1 accuracy and minimum expected margins for that fixture only. It is a scoring regression, - not evidence of general recall quality or user-task performance. -- **Workload context economy**: `eval/context_economy.py` compares three executable strategies - across every question in a workload: uncapped full-history replay, a contiguous recency window - at the same hard budget, and shipped Engraphis hybrid recall + packing. It reports evidence and - answer-token quality, cumulative reader-context tokens, a conservative total that charges one - complete source-token pass to indexing, and the query-count break-even point. The default is - deterministic/offline; `--embed-model` enables a real retrieval model, while - `--format locomo|longmemeval` reuses the established external loaders. -- **Agent productivity**: `eval/productivity.py` compares a capped full-history baseline, - always-on retrieval, and - adaptive context through a complete answer-and-correction loop. It reports completed tasks, - first-attempt errors, abstentions, corrections, agent turns, memory calls, wall-clock latency, - and all question/context/output tokens. The bundled agent is deterministic, receives no gold - answer, and is identified in every report; inject a real agent callable for model-specific - results. Optional provider telemetry is reported separately from the deterministic token - counter and is not a provider billing estimate. -- **Dashboard graph layout settle**: `eval/graph_every_bench.py` drives the Every-node - dashboard engine's real worker (`engraphis-graph-every-worker.js`) through a - `prepare → settled` round-trip over synthetic node/link loads and reports wall-clock settle - time plus the scaling ratio across sizes. It measures initial layout cost only: camera pans - and zooms never touch the worker (they are GPU-uniform updates), so no per-frame number can - come out of this harness and none should be quoted. Results are host- and Node-version - dependent local diagnostics, not registered public evidence; run the harness on the target - class of machine before quoting a figure. - -The context-economy and productivity tools intentionally report when a small workload does not -benefit from memory, and the external loaders expose retrieval-quality tradeoffs rather than -hiding them. Their prior local results are not retained as public numbers because no matching -redacted immutable artifact is checked in. Run the registered protocol and publish the resulting -artifact before making a quantitative claim. - -### Reproduce - -```bash -# Correctness gate (deterministic, no download) -python -m pytest tests/ -q -python -m eval.harness --dataset eval/datasets/sample.jsonl --k 5 -python -m eval.harness --dataset eval/datasets/codemem.jsonl --k 5 -python -m eval.harness --dataset eval/datasets/graph_multihop.jsonl --k 5 -python -m eval.ablation -python -m eval.performance --dataset eval/datasets/codemem.jsonl --k 5 --iterations 10 -python -m eval.performance --dataset eval/datasets/codemem.jsonl --k 5 \ - --candidate-k 25 --candidate-depth adaptive --retrieval-profile auto --iterations 10 -python -m eval.context_economy --dataset eval/datasets/codemem.jsonl \ - --token-budget 512 --k 5 -python -m eval.productivity --dataset eval/datasets/codemem.jsonl \ - --max-context-tokens 512 --retrieval-token-budget 256 -python -m eval.performance --dataset eval/datasets/codemem.jsonl --k 5 \ - --iterations 5 --filler-memories 1000 -# Direct NumPy search envelope at representative corpus sizes; timings are machine-specific. -python -m eval.vector_scale --sizes 1000,10000,100000 --queries 20 --iterations 3 --json -# Deterministic queryless-ranking calibration fixture. -python -m eval.proactive_ranking -# Canonical latency/resource protocol: requires >=1,000 queries and five processes. -python -m eval.performance --dataset fixed-1000-plus.jsonl --acceptance-matrix --processes 5 - -# External retrieval diagnostics (downloads all-MiniLM-L6-v2; not QA/leaderboard results) -python -m eval.external --dataset longmemeval_s.json --format longmemeval --k 10 -python -m eval.external --dataset locomo10.json --format locomo --k 10 -# Complete external-dataset coverage with an immutable embedding revision. These runs are -# retrieval-only diagnostics, not official benchmark-harness or leaderboard results. Completed -# public-safe artifacts and measured results are listed in the benchmark expansion report. -python -m eval.external --dataset longmemeval_s.json --format longmemeval --canonical \ - --embed-revision <40-character-model-commit> --json external-longmemeval.json -python -m eval.external --dataset locomo10.json --format locomo --canonical --no-resolve \ - --embed-revision <40-character-model-commit> \ - --locomo-repair-manifest eval/datasets/locomo10_repair_manifest.json \ - --json external-locomo.json -python -m eval.context_economy --dataset locomo10.json --format locomo \ - --embed-model sentence-transformers/all-MiniLM-L6-v2 --token-budget 512 --k 10 --no-resolve -``` - -Canonical external mode requires an exact lowercase 40-character embedding commit and a semantic -embedder; dependency or model-load failure is fatal instead of silently falling back to hashing. -Every report records `embedding`, `dataset_sha256`, `source_cases`, `normalized_cases`, and -`configuration` provenance so a result can be attributed to the actual data and retrieval setup. - -The official ten-conversation LoCoMo JSON contains delimiter-packed IDs, two mechanical ID -typos, and three references that cannot be normalized syntactically. The adapter normalizes only -the unambiguous forms. The checked-in repair manifest is bound to the official source SHA-256, -names every remaining replacement/removal, must be fully consumed, and is recorded in the JSON -report with its own hash. Any source update, unused repair, or unresolved ID fails the run. This -repairs retrieval references only; it does not claim to correct LoCoMo's semantic answer labels. - -The earlier private pinned retrieval diagnostic was the pre-publication state. Current complete -LoCoMo and LongMemEval retrieval diagnostics are retained as separate public-safe artifacts in the -[benchmark expansion report](docs/BENCHMARK_EXPANSION_RESULTS.md), with source, model, configuration -and checksum boundaries. Those values remain evidence-retrieval metrics, not end-to-end QA accuracy -or an official LoCoMo leaderboard score. - -## Jev-assisted route-selection probe (provider-backed, exploratory) - -On 2026-10-03, the configured BYOK client ran `eval/jev_recall_quality.py` against 40 generated -public synthetic tasks (10 each for lexical identifiers, graph relationships, temporal changes, -and procedural context). Each call sent only the task query and at most two locally generated -routes; no recalled memory bodies or stored user data were sent. Jev selected a route on all 40 -calls, but the baseline and Jev-assisted means were identical: nDCG@5 **0.740773**, Recall@5 -**0.75**, and answer-token coverage **0.75**. Every paired 95% interval was zero, so the -improvement/non-inferiority gate did not pass. This synthetic probe does not establish a benefit -on held-out user workloads, and no retrieval-quality improvement is claimed. - -The harness caps a run at 40 provider requests, makes at most one request per task, and requires -explicit `--allow-remote` consent. Repeating it can consume BYOK provider usage: - -```bash -python -m eval.jev_recall_quality --allow-remote --max-requests 40 --timeout 8 -``` - -This exploratory provider run is separate from the registered offline artifact and the benchmark -graphic below; it is not a general benchmark or customer-workload evaluation. - -## What we do NOT yet claim - -- **No official end-to-end LLM QA accuracy.** The deterministic productivity agent measures the - complete local control loop, not a frontier answering model. Official LoCoMo / LongMemEval QA - still requires a pinned answering model and evaluator. -- **No hosted-service latency comparison.** The in-repo p50/p95/p99 benchmark covers the local - reference pipeline and records its environment; unlike environments are not compared. -- **No neutral third-party ranking.** We have not run an external eval platform. -- **No provider bill estimate.** Context-economy counts reader evidence under its named counter. - It excludes system/tool prompts, questions, completions, prompt caching, provider pricing, - compute, and storage. Its indexing-inclusive total is a conservative text-volume proxy. - -Every publishable run should emit the `engraphis-benchmark/v2` envelope: dataset/config hashes, -per-question records, explicit exclusions, fixed-budget context curves, and deterministic -stratified or paired bootstrap confidence intervals. Every run names its token counter. -Noncanonical offline fixtures may identify a deterministic estimate; canonical public evidence -requires the exact pinned reader tokenizer and immutable model revision. The lightweight CI -fixtures validate that machinery; they are not a claim about external benchmark performance. - -Public journey and external retrieval exports identify producer code by unique -repository-relative source names. Private dataset, conversation, repair, and -LongMemEval-V2 inputs use stable role names such as `inputs/dataset` or -`inputs/haystack`; their directories are never published. -Each name remains bound to its SHA-256 and byte count. The shared envelope keeps -basename-only defaults for other callers and historical artifacts; exporters opt in -with explicit `source_names` and verify the completed envelope against evaluated bytes. - -The benchmark context metric reads strict recall usage fields rather than inferring prompt size: -`budget_tokens`, `context_tokens`, `source_tokens`, `saved_tokens`, `savings_ratio`, -`packed_count`, `omitted_count`, and `token_counter`. Use `engraphis_recall_context` for a -hard-budget prompt packet; legacy `engraphis_recall` remains available in full or compact response -mode for compatibility. - -### Canonical public artifacts - -Use `python -m eval.benchmark --input report.json --output artifacts/run.json` to validate a -report and write sorted, immutable JSON plus `run.json.sha256`. The command permits an identical -retry but refuses to replace a different artifact at the same path. For an official -LongMemEval-V2 run, add `--canonical`: this requires a profile with an exact benchmark repository -revision, dataset revision, reader model revision, and embedding model revision. The checked-in -profile pins immutable upstream commits; replacing any revision with a mutable tag fails -validation. Canonical profiles label the baseline (`no_retrieval`, `lexical_only`, `dense_only`, -`dense_lexical_rrf`, `full_hybrid`, `full_history`, `no_graph`, `no_reranker`, -`no_temporal_resolution`, or `whole_document`) and declare the required fixed context-budget -matrix: 256, 512, 1024, 2048, and 4096 tokens. Canonical in-repo reports rerun every question at -all five budgets and validate each aggregate against its per-question evidence. The checked-in -LongMemEval-V2 memory-module configuration sets the official adapter's operating point to 1,024 -tokens; that single official point must not be presented as a five-point curve. - -`eval.external --canonical` refuses `--limit` and rejects a normalized output that omitted source -cases. Retrieval-only abstention/no-evidence records remain visible in the artifact's -`exclusions`; they are not counted as evidence-retrieval scores. - -Official LongMemEval-V2 output can be converted into a public-safe QA artifact with -`python -m eval.longmemeval_v2_evidence`. The exporter requires the completion manifest written by -the pinned runner after a successful, complete official run. It binds the exact per-question -output, questions, haystack, trajectories, memory configuration, matrix manifest, seed, clean -official checkout, and recorded environment. The public artifact keeps the official QA score, -fixed-reader context token count, aggregate source-file digests, repository state, and artifact -checksum. It removes raw questions, answers, prompts, reader output, and retrieved context, and -does not publish per-record content fingerprints. See the -[`public benchmark runbook`](docs/PUBLIC_BENCHMARK_RUNBOOK.md) for the end-to-end operator sequence. - -### LongMemEval-V2 memory-module adapter - -`eval.longmemeval_v2.EngraphisLongMemEvalV2Memory` follows the official -`memory_modules.memory.Memory` interface at LongMemEval-V2 commit -`6f020ac2fc3275e46c706d3406e02c3ed79b7be2`. When imported in that environment, its -`@register_memory` decorator registers `memory_type="engraphis"`; use the checked-in -[`eval/configs/longmemeval_v2_engraphis.json`](eval/configs/longmemeval_v2_engraphis.json) -with the official harness. The config pins `Qwen/Qwen3-Embedding-8B` to revision -`1d8ad4ca9b3dd8059ad90a75d4983776a23d44af`; mutable embedding revisions are rejected, and a -canonical adapter run fails instead of relabeling the deterministic offline fallback as Qwen. -First materialize the six declared variants at all five token budgets: - -```bash -python -m eval.longmemeval_v2_matrix \ - --output "$ENGRAPHIS_EVIDENCE_RUN_DIR/configs" -``` - -This writes a 30-run manifest: balanced, planner, episodic-cap, planner-plus-episodic-cap, and -matched `context_k=2` comparators for both capped variants, each at 256, 512, 1,024, 2,048, and -4,096 evidence tokens. Run each manifest cell through `python -m eval.run_longmemeval_v2` with all -eight `--engraphis-*` completion-receipt arguments. The wrapper imports the adapter before the -official registry builds the memory module, forces the pinned reader processor revision, and -delegates the remaining official harness arguments unchanged. Only after a successful return does -it verify that the output question IDs exactly cover the source question IDs and write the -immutable execution manifest. - -The checked-in configuration is canonical only when the adapter resolves the pinned Qwen reader -processor at `c202236235762e1c871ad0ccb60c8ee5ba337b9a`. The wrapper refuses a dirty or non-pinned -official checkout and refuses to start if the optional processor dependency or immutable revision -is unavailable; the local regex counter is never silently relabeled as a reader budget. The -recorded budget counts each returned context item's content with that reader tokenizer (without -prompt framing or inter-item separators), so it is a hard **evidence-item content** budget, not a -claim about total chat-prompt tokens. Packed sources are returned as separate context items, -preserving the largest fitting evidence prefix instead of dropping one oversized monolithic item. -Every official per-question row reports inserted and retrieved counts by memory type. A -memory-type-cap claim additionally requires at least two populated inserted types, so a nominal cap -over a single-type workload cannot qualify as evidence. The adapter does not download benchmark -data or call the reader/evaluator; the official harness owns those steps. - -## External evidence status and remaining executions - -1. **Run the official LongMemEval-V2 reader and evaluator.** The adapter, pinned runner, and - redacted evidence exporter are implemented. The exact upstream commit boots in an isolated - Python 3.11 environment and the wrapper reaches the official harness CLI. The dataset, pinned - Qwen reader, and embedding assets require substantial storage and compute; no canonical QA - score is claimed until that run completes. -2. **Publish production-backend latency.** Run `eval/performance.py` with the real embedder and - sqlite-vec/backend configuration on a fixed machine class and corpus scale. -3. **Run the fixed-budget curve on the complete official datasets.** The v2 harness now measures - every question at 256, 512, 1,024, 2,048, and 4,096 evidence tokens and validates the - per-question records, aggregates, and pinned reader-tokenizer identity. Publish the curve only - after complete official runs produce immutable artifacts for every point. -4. **Run an external evaluation platform** once (1)–(3) exist. - -Do not make all evidence lanes variants of explicit factual recall. Executable offline adapters -now cover: - -- [MemoryAgentBench](https://github.com/HUST-AI-HYZ/MemoryAgentBench): incremental multi-turn - learning, long-range understanding, and conflict/consolidation inputs. -- [LoCoMo-Plus](https://github.com/xjtuleeyf/Locomo-Plus): an old implicit constraint must affect - a later response even when the later cue does not restate the remembered fact. -- [Mem2ActBench](https://github.com/Cantaloupe-M/Mem2ActBench): memory must select a tool and - ground its arguments, not merely return a passage. The current adapter measures retrieval and - expected tool-argument context coverage, not generated tool-call success. - -```bash -python -m eval.agent_benchmarks --dataset memoryagentbench.json \ - --format memoryagentbench -python -m eval.agent_benchmarks --dataset locomo_plus.json \ - --format locomo_plus -python -m eval.agent_benchmarks --dataset qa_dataset.jsonl \ - --conversations toolmem_conversation.jsonl --format mem2actbench \ - --artifact artifacts/mem2actbench.json -``` - -Use `--artifact` on any of these commands to write a redacted, immutable evidence envelope plus -an adjacent SHA256 file. The ordinary console/`--json` report is private run material and may -contain source questions for debugging. - -### Upstream-data diagnostics and publication scope - -The LoCoMo-Plus and MemoryAgentBench adapters have been exercised against upstream data and remain -queued for their own public-safe retrieval artifacts. Rerun each pending adapter with `--artifact`, -publish the redacted immutable envelope and checksum, and add its suite/config binding before -quoting a number. Mem2ActBench's declared small retrieval diagnostic is complete and has a checked-in -artifact; its exclusion and memory-cardinality figures are source-preparation metadata in the -public source lock, not product or action-success metrics. None of these lanes is an official -leaderboard, answer-quality, or marketing result. - -The MemoryAgentBench loader accepts both its aligned public JSON export and the Hugging Face -dataset-server `rows[].row` envelope. Rows without gold evidence remain useful for answer-token -coverage, but are excluded from retrieval aggregates and counted separately as -`retrieval_scored_questions`. - -For paired code-agent runs, execute the same tasks with the same model, tools, machine, and -deterministic success oracle under `full_history` and `engraphis`. Then analyze the content-free -run records with: - -```bash -python -m eval.code_agent_ab --full-history full-history.jsonl \ - --engraphis engraphis.jsonl --output paired-report.json -``` - -The analyzer rejects unmatched task IDs and different success oracles, then reports paired -bootstrap intervals for task success, input/output/tool tokens, retries, latency, and optional -cost. Its aggregate output does not echo task IDs or oracle commands. It does not launch an agent -or invent a task-success oracle. - -## Optimization experiments to run before changing defaults - -1. **Budget-aware packing**: compare full source, safe summary, sentence-aligned safe summary - excerpt, and raw-source excerpt at fixed budgets. Gate on support/answer retention and - qualifier preservation, not token count alone. -2. **Adaptive retrieval work**: `--candidate-depth adaptive` is an opt-in performance experiment. - It keeps wider graph/code pools and reduces routine lexical/balanced pools while reporting the - requested and actual depth. A local experiment motivated this option, but no public number is - retained because its machine-specific artifact is not in the evidence registry. Keep the - default fixed until complete external categories meet predeclared quality margins. -3. **Packing-pressure consolidation**: prioritize memory families that are frequently recalled, - repeatedly omitted, or costly per useful token. Count write/index/storage cost as well as later - reader-context savings. -4. **Tokenizer-aware ingestion**: implemented behind the chunk extractor. The dependency-free - default remains `engraphis.chars4.v1`; an explicitly configured Hugging Face reader tokenizer - enforces prose chunk and overlap budgets and records its identity in chunk metadata. Continue - measuring tokens-to-evidence, recall, and storage/index growth together before recommending a - model-specific default. -5. **Bulk ingestion**: add batch embedding plus a transaction-aware vector upsert path, then rerun - the complete MemoryAgentBench Test-Time Learning input. Gate this on identical stored-memory, - provenance, graph-link, and temporal-resolution outcomes, not throughput alone. -6. **Scoped caches**: benchmark query embeddings and repeat-recall results keyed by workspace, - repo, time anchors, profile, and corpus version. Test invalidation correctness before claiming - latency gains. -7. **Privacy-safe real usage**: use `engraphis_context_savings` to let each workspace inspect - aggregate source/context/saved tokens already present in content-free receipts. Keep unlike - token counters separate and require a valid receipt chain before treating totals as auditable. - -## Evaluation question - -The predeclared question is whether the full vector + lexical/BM25 + sparse PPR graph + calibrated -rerank pipeline, bi-temporal resolution, and grounded abstention produce higher evidence recall -per injected token than the registered baselines. The answer must come from a complete, -machine-readable artifact with paired confidence intervals; otherwise the release reports -“no demonstrated improvement.” +# Benchmarks + +This guide explains what Engraphis measures, how to reproduce each evaluation, and the limits of +those results. When this document and the code disagree, the code is the source of truth. + +The current expansion has a separate [results and workload report](docs/BENCHMARK_EXPANSION_RESULTS.md), +[execution runbook](docs/BENCHMARK_EXPANSION_RUNBOOK.md), and +[proposed stage budgets](docs/BENCHMARK_STAGE_BUDGETS.md). Completed external retrieval diagnostics +are review artifacts with explicit denominators and uncertainty. The coding pilot uses Codex +OAuth only and retains fixture exclusions and interrupted calls. Official QA, competitor +scores and capacity qualification remain separate experiments. + +For the locked operator sequence for a public canonical run, see +[`docs/PUBLIC_BENCHMARK_RUNBOOK.md`](docs/PUBLIC_BENCHMARK_RUNBOOK.md). + +The current measured improvement priorities and their evidence boundaries are in +[`docs/BENCHMARK_IMPROVEMENT_PRIORITIES.md`](docs/BENCHMARK_IMPROVEMENT_PRIORITIES.md). +The implementation exposes three opt-in comparison controls: `packing_mode="coverage"` +for complete evidence units across sources, source-bound `exact_value` fields on the +Smart/Classic/service write paths, and `retrieval_recipe="conversation"` or `"long_session"` +for the measured depth/budget starting points. `"legacy"` packing and `"default"` +retrieval remain the defaults until development, validation and untouched-holdout gates +show a workload-specific benefit. + +`python -m eval.evidence_contracts` checks exact-action validation and compares +legacy and coverage packing on small deterministic development fixtures. It runs +in the full offline CI matrix and the NumPy-only Python 3.9 job. These fixtures +test boundary correctness; they do not estimate external QA or model task success. The +[current diagnostic](docs/benchmark-evidence/evidence-contracts-20260921-v11.json) +withholds the unchanged oversized, unpunctuated fixture at its 24-token budget: +no fitting sentence boundary proves that the remaining condition text can be dropped. +The prior literal-preservation result remains in the historical artifact. Tight budgets +can therefore return fewer exact values under the complete-group contract. +Action contracts also require the host to supply the actual source text when creating +and validating a contract. The source revision digest and literal offsets must match; +four additional diagnostic cases reject forged or stale bindings. Authorization +remains the responsibility of the trusted host. +Eight fixed query-window cases also check that unrelated bound values cannot displace the +requested evidence. Their original retention expectations remain unchanged: 4/8 passed on +`0244c45f`, 8/8 on `c3a86295`, and 5/8 under the current complete-source rule. Three tight +bound-value cases now withhold their binding. A separate source-completeness gate checks +the same eight inputs, improving from 5/8 to 8/8, including roomy binding controls. +These development fixtures and their source hashes remain separate from external quality +measurements. + +Five safe-withholding cases improve from 0/5 against `65f4becd` to 5/5. A separate +five-case retention population exposes a tradeoff: the earlier v8 implementation +retained the expected literals in 5/5 cases, while the complete-unit rule retains +0/5 at those unchanged tight budgets. These unpunctuated records do not fit as +complete units. Their original inputs, expected literals and outcomes remain in +the diagnostic; safe omission is not counted as successful literal retention. + +Eight distant-restriction cases improve from 5/8 on `98e8f5c2` to 8/8, including +roomy retention controls. Exact binding now requires the complete meaningful source, +regardless of language or recognized qualifier words. Boundary whitespace may be trimmed +only outside the literal. Coverage withholds the bound occurrence when that source cannot +fit, while it may still select other unbound evidence. This also prevents +`Only use ALPHA in production` from becoming `Only use ALPHA`. Complete-unit safety +remains 16/16 against 8/16 on `98e8f5c2` in its separate diagnostic and CI gate. +Headers, titles and source attribution remain inside the budget; expansion cannot restore +an incomplete binding. This conservative rule retains source text without inferring its +meaning or authorizing a downstream action. + +The default legacy packer also withholds exact binding metadata when its selected +excerpt omits part of the complete source. Eight fixed cases improve +from 4/8 on `833e3e92` to 8/8: four incomplete bindings are suppressed and four +complete bindings remain. All eight retain identical context text and token +accounting. The literal can still appear as ordinary text; this diagnostic measures +binding safety, not a retrieval or answer-quality gain. + +Twelve additional French, Chinese and English-synonym cases exercise both modes at fixed +budgets. Against `c3a86295`, the complete binding contract improves from 10/12 to 12/12 +for legacy and from 5/12 to 12/12 for coverage. Unsafe bindings fall from two to zero +and seven to zero, respectively. Coverage literal leaks fall from seven to zero; all five +roomy controls retain their exact bindings in both modes. Independent token accounting +matches the rendered context and all budgets are honored. The gate separately rejects +omitted roomy bindings, incorrect spans and token-accounting errors. This is a fixed +development population, not a claim of general multilingual understanding. + +Corpus replay rejects non-boolean trust and answerability labels before execution. +Direct campaign records also require boolean trust labels and reject contradictory +nested trust metadata. Peer evidence IDs must name a recorded source; returned +backend IDs must agree with that source or resolve through recorded episode lineage. +Frozen campaign scope and trust take precedence over peer labels, and unrecognized labels +remain unknown. Core/service recall results report the recipe's effective output +limit as `effective_k`; recall receipts preserve it as `metadata.k` together with +`retrieval_recipe` and normalized `packing_mode`, independently of the number of +results actually returned. Historical receipts without a mode remain valid and +do not acquire an inferred value. +Campaign adapters report the frozen manifest's evaluated revision; direct adapters +retain `unknown` provenance unless explicitly bound. These checks protect result +interpretation and do not count as additional benchmark-quality gains. + +### Public numeric evidence registry + +Every exact public aggregate retained below comes from the checked-in, public-safe +[`offline-fixtures-receipt-diagnostics-20261103.json`](docs/benchmark-evidence/offline-fixtures-receipt-diagnostics-20261103.json) artifact. Its +SHA-256 is +`f95e35b9f2c8f848925df0d0db0bd34898280b9cd10a884ed188b11d1708dcb2`, also recorded in the +adjacent `.sha256` file. The artifact contains no raw questions, answers, prompts, customer data, +or per-record content fingerprints. + +The fixture-suite digest is +`f66eca5e293df53e73504c3c70aee793812059dafc485475a9a4e9a1cd61b836`. The artifact defines +the digest algorithm and records the SHA-256 of every suite and dataset file. Each evidence ID +also binds its exact command through `sha256(UTF-8 exact command)`: + +| Evidence ID | Exact command | Config digest | +|---|---|---| +| `offline-chunking` | `python -m eval.chunking_eval --dataset eval/datasets/longdoc.jsonl --k 5` | `c1c8196aa7e1568ef3844a9fb2d76b87f342c39108e32d6ad144b885a76143b8` | +| `offline-performance` | `python -m eval.performance --dataset eval/datasets/codemem.jsonl --k 5 --iterations 10 --json` | `bbe4aca81e58d4830e50a8fc7729a1d15b71d97a6299bccd79432b7f119677d7` | +| `offline-grounded` | `python -m eval.grounded` | `590442e51e3642c10489165759919dc86ffac62c182937330c153e7f8d5fc26f` | + +The source manifest covers every Python file under `engraphis/` and `eval/`, the two +fixture datasets, and the official exporter. Each rerun must use a new artifact basename. +The canonical chart aliases are regenerated from that new artifact and carry its checksum; +historical per-run artifacts and their preserved chart bytes remain unchanged. The current +PNG views were freshly rendered from the registered artifact with CairoSVG 2.9.1 and +Cairo 1.18.4 on Windows at each SVG canvas size, with embedded artifact and SVG checksums. +The fresh evaluator run used Windows CPython 3.14.7 and NumPy 2.5.4. Its 590-file manifest +binds the producer after the 1.7.10 release-review fixes: Command Code file-guard tool +ids, fail-closed hook errors, symlink-then-parent and Windows alias targets, the linear +Spec Studio heading pattern, closure-limit and browse-version handling, the equal-start +supersede guard, bounded history candidate windows for `why` and `timeline`, raw-path UNC/device +rejection across import, vault, code-index and source-id surfaces, automatic browser sessions with +single-use link fallback, a +partitioned sync closure budget, Windows source-read timestamp revalidation, non-blocking +engine write guards and atomic receipt transactions, worker-isolated MCP tool +execution, loopback stdio-to-HTTP MCP bridging, exact-identifier recall priority, and +`prompt_eligible`/`review_state` remember reporting. Dashboard assets, tests, documentation, and generated images +are outside this producer manifest. The three measured fixture results are unchanged; +this rerun establishes current source and render provenance without claiming a +retrieval-quality improvement. It does not qualify browser behavior, hosted services, +external models, or provider billing. The registered artifact was produced with: + +```bash +python -m scripts.export_offline_evidence --output docs/benchmark-evidence/offline-fixtures-receipt-diagnostics-20261103.json +``` + +The earlier release-review predecessors, +[`offline-fixtures-automatic-browser-access-20261031.json`](docs/benchmark-evidence/offline-fixtures-automatic-browser-access-20261031.json) +(SHA-256 `99b615d8ce7712b5efbfa7c11db810de695cb0e5a3e44293532a5505133e03b0`), +[`offline-fixtures-release-review-20261026-1.7.11.json`](docs/benchmark-evidence/offline-fixtures-release-review-20261026-1.7.11.json) +(SHA-256 `befc75b82980205a4fbe9074509d82ab5b84b1bcc4fade7c4dc1195dc9bc00c2`), +[`offline-fixtures-release-review-20261022-v4.json`](docs/benchmark-evidence/offline-fixtures-release-review-20261022-v4.json) +(SHA-256 `38582da42a04bd1c637724d6351fa516ab867a263a6848d502b2fa37934a3edf`), +[`offline-fixtures-release-review-20261022-v3.json`](docs/benchmark-evidence/offline-fixtures-release-review-20261022-v3.json) +(SHA-256 `aac955672785c01ab9ec60e841d6f566469ce126d3f95c2927aa8b8616a99fb0`), +[`offline-fixtures-release-review-20261022-v2.json`](docs/benchmark-evidence/offline-fixtures-release-review-20261022-v2.json) +(SHA-256 `4a7f9117f3126edaa765f08f4ab9a4e65788041931a4209e50d8ca70197a8a93`) and +[`offline-fixtures-release-review-20261022.json`](docs/benchmark-evidence/offline-fixtures-release-review-20261022.json) +(SHA-256 `bbdbf607143e776d599a013cca6ca0ab8326398e0ffadd4bb65ba2948fa3920e`), +remain bound to their original digests. They are superseded +by this artifact; their recorded results are identical. + +The earlier [guard-installer snapshot artifact](docs/benchmark-evidence/offline-fixtures-pr270-guard-installer-20261020.json) +remains bound to SHA-256 `047cbdfee604c54c2fe211f7cac54a532563ceecadc310f368dec331bf443ce3` and its original +CPython 3.12.14 / NumPy 2.4.6 execution and CairoSVG 2.9.1 / Cairo 1.18.4 rendering. +It predates the release-review fixes and remains historical and unchanged; +its results and rendered bytes are not relabeled as this fresh run. + +The earlier [browser-session snapshot artifact](docs/benchmark-evidence/offline-fixtures-reconciled-browser-session-20261019.json) +remains bound to SHA-256 `b3a65eea3559a1b994e6e4b81ade81db914ce1ae94b947ed7454e5cf8550998f` and its original +CPython 3.11.17 / NumPy 2.4.6 execution and Playwright Chromium 156.0.8078.4 rendering. +It predates the policy-parent canonicalization fix and remains historical and unchanged; +its results and rendered bytes are not relabeled as this fresh run. + +The earlier [Automation snapshot artifact](docs/benchmark-evidence/offline-fixtures-reconciled-automation-20261019.json) +remains bound to SHA-256 `694765e499a089bdcc00ed0b2b6f763bd3f5606f0f4cb6aa3061338d2cb012a1`. It predates the browser-session +cookie-response correction and remains unchanged. + +The earlier [protected-path snapshot artifact](docs/benchmark-evidence/offline-fixtures-reconciled-path-guard-20261019.json) +remains bound to SHA-256 `10d5c8111647190479aa7dd16831e5e741873d43fc9385190e1f195937a4084d`. It predates the Automation +policy and dashboard recovery corrections and remains unchanged. + +The earlier [canonical LF snapshot artifact](docs/benchmark-evidence/offline-fixtures-reconciled-canonical-lf-20261019.json) +remains bound to SHA-256 `26d75c8a38127edbc51e6c51792cd85f04bf707c32bb1ff8d7c33ae95fb4c34d`. It predates the Windows protected-path +case-matching correction and remains unchanged. + +The earlier [reconciled snapshot artifact](docs/benchmark-evidence/offline-fixtures-reconciled-release-20261019.json) +remains bound to SHA-256 `4de6ca8011e45146d98d855cedca6f7e3f84d8c7e75852405f36ac0f74e96f6d`. +It predates canonical LF normalization of the two Python producer files and remains unchanged. + +The historical [governed-history artifact](docs/benchmark-evidence/offline-fixtures-governed-history-20261018.json) +remains bound to SHA-256 `23203af13a30a78df9f2bf849f2cefdad1c426a9f494ebb0128abd5fa6e19ce9`. +The separate [main-branch runtime artifact](docs/benchmark-evidence/offline-fixtures-pr260-nested-uri-redaction-runtime-20261018-e2ebfb06550f.json) +remains bound to SHA-256 `434ec054dff38104a553ddb88b38a983c97227491e92ba482a7f3cd4ff5b560b` +and its original runtime revision `e2ebfb06550fdcda0eb03590aecd9cf5e27a6dbc`. +Neither historical run is relabeled as evidence for this reconciliation. + +External, model-dependent, latency, consolidation, and productivity numbers are not included in +this offline registry unless a redacted immutable artifact with the same three bindings exists. Use +the [public benchmark runbook](docs/PUBLIC_BENCHMARK_RUNBOOK.md) to produce registry evidence. +Completed retrieval-only diagnostics are documented separately in the +[benchmark expansion results](docs/BENCHMARK_EXPANSION_RESULTS.md); absence from this registry +means no number is claimed in this offline registry. + +The context-efficiency chart is generated from the registry values and the selected report schema. +Historical LoCoMo, graph, handoff, consolidation, and security figures remain preserved in their +source artifacts but are omitted from the current chart until each has a matching immutable, +public-safe artifact. The chart reports context size, candidate and packed retrieval quality, and +grounded checks in separate panels; provider billing and MCP transport are not measured. Regenerate +the SVG and matching PNG with: + +```bash +python scripts/render_benchmark_report.py --report docs/benchmark-evidence/offline-fixtures-receipt-diagnostics-20261103.json --output docs/images/context-efficiency.svg --png-output docs/images/context-efficiency.png +``` + +The companion examples are also generated from that artifact with: + +```bash +python -m scripts.render_benchmark_examples --report docs/benchmark-evidence/offline-fixtures-receipt-diagnostics-20261103.json --output docs/images/evidence-backed-agent-examples.svg --png-output docs/images/evidence-backed-agent-examples.png +``` +The historical-to-executable mapping is in +[`docs/BENCHMARK_CHANGE_COVERAGE.md`](docs/BENCHMARK_CHANGE_COVERAGE.md). + +Fresh diagnostics retain explicit source-case identities so confidence intervals +cluster whole conversations even when question IDs do not encode their case. +Metrics with no eligible questions remain `null` (unscored), including fresh and +resumed runs. Artifact parsing, checksum validation, and queue receipts bind the +same byte snapshot. Comparisons require consistent dataset and repair bindings +while allowing the producer implementation to change between versions. + +## What we measure today (all offline, no API key) + +Most Engraphis evals score **retrieval**, not end-to-end QA. The separate productivity benchmark +runs a complete offline agent attempt and correction loop, but it is not an official +frontier-model QA score. + +- **Correctness gate**: `eval/harness.py` over `eval/datasets/sample.jsonl` and + `codemem.jsonl` (conflict resolution) and `graph_multihop.jsonl` (multi-hop graph recall). + Runs on the deterministic embedder, so it is a plumbing/regression floor, not a public + performance claim. This is the gate CI enforces. +- **Ablation**: `eval/ablation.py`: vector-only vs. 1-hop graph vs. Personalized-PageRank arm, + to show the graph arm actually earns its place. +- **External benchmarks**: `eval/external.py` loads **LoCoMo** and **LongMemEval** and pushes + them through the *real* `MemoryEngine` write path (conflict resolution + evolution) and hybrid + recall with a real sentence-transformers embedder. It reports `recall_at_k` / `hit_at_k` / + `answer_token_recall`: i.e. *did the evidence come back*, not *did an LLM answer correctly*. + It retains source categories and abstention/no-evidence questions as explicit exclusions from + retrieval-only aggregates rather than silently dropping them. `eval.longmemeval_v2` is a local, + text-only adapter for the official LongMemEval-V2 `insert(trajectory)` / `query(query, + query_image=None)` memory interface; it does not download data or call a model. +- **Grounded**: `eval/grounded.py`: answerable → cite, off-topic → abstain. Exact fixture + outcomes are evidence ID `offline-grounded` in the registry above. +- **Chunking (quality per token)**: `eval/chunking_eval.py` over `eval/datasets/longdoc.jsonl` + ingests a multi-topic corpus twice: once as one memory per document (`whole`) and once with + sub-file `ChunkingExtractor` (`chunked`), then queries both through the real recall pipeline. + The checked-in corpus is explicitly marked trusted eval data so the measurement isolates + chunking from the production trust gate, which excludes arbitrary raw imports from normal + agent context. On the deterministic embedder, **recall@5 is 1.000 for both modes; mean + retrieved top-5 content falls from 740.3 to 214.3 tokens (526.0 fewer, 71.1% lower, about + 3.5× smaller), while the smallest returned evidence-holding memory falls from 162.2 to 42.4 + tokens (119.8 fewer, 73.9% lower, about 3.8× smaller).** These aggregates are evidence ID + `offline-chunking` in the registry above. Pass `--embed-model + sentence-transformers/all-MiniLM-L6-v2` to run a model-dependent experiment; do not publish + that result without a new immutable artifact and pinned model revision. +- **Full-pipeline latency + quality**: `eval/performance.py` times the shipped semantic + + lexical + graph + fusion + scoring + rerank + packing path after warmup, with reinforcement + disabled so repeated measurements do not mutate their corpus. It reports p50/p95/p99 latency, + retrieval quality, packed context tokens, and full/compact JSON-shape payload proxies in one + JSON-safe schema. Its legacy `quality` fields score all candidate chunks returned before + context packing; additive `packed_quality` fields score only chunks admitted to reader context. + Payload proxies are sampled once per question, independently of the number of timed iterations; + they are not serialized MCP envelopes or transport responses. In the + registered CodeMem run, 26 payload samples total **24,590** full-proxy + `engraphis.regex.v1` tokens versus **11,138** compact-proxy tokens, avoiding **13,452** proxy + tokens (**54.71% lower**), while 260 recalls are timed. Packed context across the same 26 + samples averages **85.38** tokens and reaches **108** under a 1,500-token cap; Recall@5, + hit@5, and answer-token recall remain 1.000 for the legacy candidate-page view. The registered + v9 artifact predates `packed_quality` and retains no inferred packed-quality result. + The new artifact reports candidate and packed Recall@5, Hit@5, and answer-token recall + separately; all six aggregates are 1.000 on these 26 questions. + These aggregates are evidence ID + `offline-performance` in the registry above. `--filler-memories`, `--candidate-k`, and + `--retrieval-profile` make scaling and routing experiments executable, but their results need + separate evidence before publication. +- **Exact vector scale envelope**: `eval/vector_scale.py` measures the production + `NumpyVectorIndex` directly at requested corpus sizes with deterministic normalized vectors and + queries. It records a corpus fingerprint, result hashes, environment, and observed + p50/p95/p99 search envelopes. It intentionally has no pass/fail latency threshold: the output + describes the measured machine and workload, not a universal capacity cutoff. Pair it with + `eval/performance.py` before making a deployment decision because direct vector search excludes + the rest of the recall pipeline. Its `engraphis-vector-scale/v1` JSON is a local diagnostic, not + an `engraphis-benchmark/v2` public evidence artifact. +- **Proactive ranking calibration**: `eval/proactive_ranking.py` compares the previous and current + importance-retention floors on a small deterministic queryless-ranking fixture. It reports + top-1 accuracy and minimum expected margins for that fixture only. It is a scoring regression, + not evidence of general recall quality or user-task performance. +- **Workload context economy**: `eval/context_economy.py` compares three executable strategies + across every question in a workload: uncapped full-history replay, a contiguous recency window + at the same hard budget, and shipped Engraphis hybrid recall + packing. It reports evidence and + answer-token quality, cumulative reader-context tokens, a conservative total that charges one + complete source-token pass to indexing, and the query-count break-even point. The default is + deterministic/offline; `--embed-model` enables a real retrieval model, while + `--format locomo|longmemeval` reuses the established external loaders. +- **Agent productivity**: `eval/productivity.py` compares a capped full-history baseline, + always-on retrieval, and + adaptive context through a complete answer-and-correction loop. It reports completed tasks, + first-attempt errors, abstentions, corrections, agent turns, memory calls, wall-clock latency, + and all question/context/output tokens. The bundled agent is deterministic, receives no gold + answer, and is identified in every report; inject a real agent callable for model-specific + results. Optional provider telemetry is reported separately from the deterministic token + counter and is not a provider billing estimate. +- **Dashboard graph layout settle**: `eval/graph_every_bench.py` drives the Every-node + dashboard engine's real worker (`engraphis-graph-every-worker.js`) through a + `prepare → settled` round-trip over synthetic node/link loads and reports wall-clock settle + time plus the scaling ratio across sizes. It measures initial layout cost only: camera pans + and zooms never touch the worker (they are GPU-uniform updates), so no per-frame number can + come out of this harness and none should be quoted. Results are host- and Node-version + dependent local diagnostics, not registered public evidence; run the harness on the target + class of machine before quoting a figure. + +The context-economy and productivity tools intentionally report when a small workload does not +benefit from memory, and the external loaders expose retrieval-quality tradeoffs rather than +hiding them. Their prior local results are not retained as public numbers because no matching +redacted immutable artifact is checked in. Run the registered protocol and publish the resulting +artifact before making a quantitative claim. + +### Reproduce + +```bash +# Correctness gate (deterministic, no download) +python -m pytest tests/ -q +python -m eval.harness --dataset eval/datasets/sample.jsonl --k 5 +python -m eval.harness --dataset eval/datasets/codemem.jsonl --k 5 +python -m eval.harness --dataset eval/datasets/graph_multihop.jsonl --k 5 +python -m eval.ablation +python -m eval.performance --dataset eval/datasets/codemem.jsonl --k 5 --iterations 10 +python -m eval.performance --dataset eval/datasets/codemem.jsonl --k 5 \ + --candidate-k 25 --candidate-depth adaptive --retrieval-profile auto --iterations 10 +python -m eval.context_economy --dataset eval/datasets/codemem.jsonl \ + --token-budget 512 --k 5 +python -m eval.productivity --dataset eval/datasets/codemem.jsonl \ + --max-context-tokens 512 --retrieval-token-budget 256 +python -m eval.performance --dataset eval/datasets/codemem.jsonl --k 5 \ + --iterations 5 --filler-memories 1000 +# Direct NumPy search envelope at representative corpus sizes; timings are machine-specific. +python -m eval.vector_scale --sizes 1000,10000,100000 --queries 20 --iterations 3 --json +# Deterministic queryless-ranking calibration fixture. +python -m eval.proactive_ranking +# Canonical latency/resource protocol: requires >=1,000 queries and five processes. +python -m eval.performance --dataset fixed-1000-plus.jsonl --acceptance-matrix --processes 5 + +# External retrieval diagnostics (downloads all-MiniLM-L6-v2; not QA/leaderboard results) +python -m eval.external --dataset longmemeval_s.json --format longmemeval --k 10 +python -m eval.external --dataset locomo10.json --format locomo --k 10 +# Complete external-dataset coverage with an immutable embedding revision. These runs are +# retrieval-only diagnostics, not official benchmark-harness or leaderboard results. Completed +# public-safe artifacts and measured results are listed in the benchmark expansion report. +python -m eval.external --dataset longmemeval_s.json --format longmemeval --canonical \ + --embed-revision <40-character-model-commit> --json external-longmemeval.json +python -m eval.external --dataset locomo10.json --format locomo --canonical --no-resolve \ + --embed-revision <40-character-model-commit> \ + --locomo-repair-manifest eval/datasets/locomo10_repair_manifest.json \ + --json external-locomo.json +python -m eval.context_economy --dataset locomo10.json --format locomo \ + --embed-model sentence-transformers/all-MiniLM-L6-v2 --token-budget 512 --k 10 --no-resolve +``` + +Canonical external mode requires an exact lowercase 40-character embedding commit and a semantic +embedder; dependency or model-load failure is fatal instead of silently falling back to hashing. +Every report records `embedding`, `dataset_sha256`, `source_cases`, `normalized_cases`, and +`configuration` provenance so a result can be attributed to the actual data and retrieval setup. + +The official ten-conversation LoCoMo JSON contains delimiter-packed IDs, two mechanical ID +typos, and three references that cannot be normalized syntactically. The adapter normalizes only +the unambiguous forms. The checked-in repair manifest is bound to the official source SHA-256, +names every remaining replacement/removal, must be fully consumed, and is recorded in the JSON +report with its own hash. Any source update, unused repair, or unresolved ID fails the run. This +repairs retrieval references only; it does not claim to correct LoCoMo's semantic answer labels. + +The earlier private pinned retrieval diagnostic was the pre-publication state. Current complete +LoCoMo and LongMemEval retrieval diagnostics are retained as separate public-safe artifacts in the +[benchmark expansion report](docs/BENCHMARK_EXPANSION_RESULTS.md), with source, model, configuration +and checksum boundaries. Those values remain evidence-retrieval metrics, not end-to-end QA accuracy +or an official LoCoMo leaderboard score. + +## Jev-assisted route-selection probe (provider-backed, exploratory) + +On 2026-10-03, the configured BYOK client ran `eval/jev_recall_quality.py` against 40 generated +public synthetic tasks (10 each for lexical identifiers, graph relationships, temporal changes, +and procedural context). Each call sent only the task query and at most two locally generated +routes; no recalled memory bodies or stored user data were sent. Jev selected a route on all 40 +calls, but the baseline and Jev-assisted means were identical: nDCG@5 **0.740773**, Recall@5 +**0.75**, and answer-token coverage **0.75**. Every paired 95% interval was zero, so the +improvement/non-inferiority gate did not pass. This synthetic probe does not establish a benefit +on held-out user workloads, and no retrieval-quality improvement is claimed. + +The harness caps a run at 40 provider requests, makes at most one request per task, and requires +explicit `--allow-remote` consent. Repeating it can consume BYOK provider usage: + +```bash +python -m eval.jev_recall_quality --allow-remote --max-requests 40 --timeout 8 +``` + +This exploratory provider run is separate from the registered offline artifact and the benchmark +graphic below; it is not a general benchmark or customer-workload evaluation. + +## What we do NOT yet claim + +- **No official end-to-end LLM QA accuracy.** The deterministic productivity agent measures the + complete local control loop, not a frontier answering model. Official LoCoMo / LongMemEval QA + still requires a pinned answering model and evaluator. +- **No hosted-service latency comparison.** The in-repo p50/p95/p99 benchmark covers the local + reference pipeline and records its environment; unlike environments are not compared. +- **No neutral third-party ranking.** We have not run an external eval platform. +- **No provider bill estimate.** Context-economy counts reader evidence under its named counter. + It excludes system/tool prompts, questions, completions, prompt caching, provider pricing, + compute, and storage. Its indexing-inclusive total is a conservative text-volume proxy. + +Every publishable run should emit the `engraphis-benchmark/v2` envelope: dataset/config hashes, +per-question records, explicit exclusions, fixed-budget context curves, and deterministic +stratified or paired bootstrap confidence intervals. Every run names its token counter. +Noncanonical offline fixtures may identify a deterministic estimate; canonical public evidence +requires the exact pinned reader tokenizer and immutable model revision. The lightweight CI +fixtures validate that machinery; they are not a claim about external benchmark performance. + +Public journey and external retrieval exports identify producer code by unique +repository-relative source names. Private dataset, conversation, repair, and +LongMemEval-V2 inputs use stable role names such as `inputs/dataset` or +`inputs/haystack`; their directories are never published. +Each name remains bound to its SHA-256 and byte count. The shared envelope keeps +basename-only defaults for other callers and historical artifacts; exporters opt in +with explicit `source_names` and verify the completed envelope against evaluated bytes. + +The benchmark context metric reads strict recall usage fields rather than inferring prompt size: +`budget_tokens`, `context_tokens`, `source_tokens`, `saved_tokens`, `savings_ratio`, +`packed_count`, `omitted_count`, and `token_counter`. Use `engraphis_recall_context` for a +hard-budget prompt packet; legacy `engraphis_recall` remains available in full or compact response +mode for compatibility. + +### Canonical public artifacts + +Use `python -m eval.benchmark --input report.json --output artifacts/run.json` to validate a +report and write sorted, immutable JSON plus `run.json.sha256`. The command permits an identical +retry but refuses to replace a different artifact at the same path. For an official +LongMemEval-V2 run, add `--canonical`: this requires a profile with an exact benchmark repository +revision, dataset revision, reader model revision, and embedding model revision. The checked-in +profile pins immutable upstream commits; replacing any revision with a mutable tag fails +validation. Canonical profiles label the baseline (`no_retrieval`, `lexical_only`, `dense_only`, +`dense_lexical_rrf`, `full_hybrid`, `full_history`, `no_graph`, `no_reranker`, +`no_temporal_resolution`, or `whole_document`) and declare the required fixed context-budget +matrix: 256, 512, 1024, 2048, and 4096 tokens. Canonical in-repo reports rerun every question at +all five budgets and validate each aggregate against its per-question evidence. The checked-in +LongMemEval-V2 memory-module configuration sets the official adapter's operating point to 1,024 +tokens; that single official point must not be presented as a five-point curve. + +`eval.external --canonical` refuses `--limit` and rejects a normalized output that omitted source +cases. Retrieval-only abstention/no-evidence records remain visible in the artifact's +`exclusions`; they are not counted as evidence-retrieval scores. + +Official LongMemEval-V2 output can be converted into a public-safe QA artifact with +`python -m eval.longmemeval_v2_evidence`. The exporter requires the completion manifest written by +the pinned runner after a successful, complete official run. It binds the exact per-question +output, questions, haystack, trajectories, memory configuration, matrix manifest, seed, clean +official checkout, and recorded environment. The public artifact keeps the official QA score, +fixed-reader context token count, aggregate source-file digests, repository state, and artifact +checksum. It removes raw questions, answers, prompts, reader output, and retrieved context, and +does not publish per-record content fingerprints. See the +[`public benchmark runbook`](docs/PUBLIC_BENCHMARK_RUNBOOK.md) for the end-to-end operator sequence. + +### LongMemEval-V2 memory-module adapter + +`eval.longmemeval_v2.EngraphisLongMemEvalV2Memory` follows the official +`memory_modules.memory.Memory` interface at LongMemEval-V2 commit +`6f020ac2fc3275e46c706d3406e02c3ed79b7be2`. When imported in that environment, its +`@register_memory` decorator registers `memory_type="engraphis"`; use the checked-in +[`eval/configs/longmemeval_v2_engraphis.json`](eval/configs/longmemeval_v2_engraphis.json) +with the official harness. The config pins `Qwen/Qwen3-Embedding-8B` to revision +`1d8ad4ca9b3dd8059ad90a75d4983776a23d44af`; mutable embedding revisions are rejected, and a +canonical adapter run fails instead of relabeling the deterministic offline fallback as Qwen. +First materialize the six declared variants at all five token budgets: + +```bash +python -m eval.longmemeval_v2_matrix \ + --output "$ENGRAPHIS_EVIDENCE_RUN_DIR/configs" +``` + +This writes a 30-run manifest: balanced, planner, episodic-cap, planner-plus-episodic-cap, and +matched `context_k=2` comparators for both capped variants, each at 256, 512, 1,024, 2,048, and +4,096 evidence tokens. Run each manifest cell through `python -m eval.run_longmemeval_v2` with all +eight `--engraphis-*` completion-receipt arguments. The wrapper imports the adapter before the +official registry builds the memory module, forces the pinned reader processor revision, and +delegates the remaining official harness arguments unchanged. Only after a successful return does +it verify that the output question IDs exactly cover the source question IDs and write the +immutable execution manifest. + +The checked-in configuration is canonical only when the adapter resolves the pinned Qwen reader +processor at `c202236235762e1c871ad0ccb60c8ee5ba337b9a`. The wrapper refuses a dirty or non-pinned +official checkout and refuses to start if the optional processor dependency or immutable revision +is unavailable; the local regex counter is never silently relabeled as a reader budget. The +recorded budget counts each returned context item's content with that reader tokenizer (without +prompt framing or inter-item separators), so it is a hard **evidence-item content** budget, not a +claim about total chat-prompt tokens. Packed sources are returned as separate context items, +preserving the largest fitting evidence prefix instead of dropping one oversized monolithic item. +Every official per-question row reports inserted and retrieved counts by memory type. A +memory-type-cap claim additionally requires at least two populated inserted types, so a nominal cap +over a single-type workload cannot qualify as evidence. The adapter does not download benchmark +data or call the reader/evaluator; the official harness owns those steps. + +## External evidence status and remaining executions + +1. **Run the official LongMemEval-V2 reader and evaluator.** The adapter, pinned runner, and + redacted evidence exporter are implemented. The exact upstream commit boots in an isolated + Python 3.11 environment and the wrapper reaches the official harness CLI. The dataset, pinned + Qwen reader, and embedding assets require substantial storage and compute; no canonical QA + score is claimed until that run completes. +2. **Publish production-backend latency.** Run `eval/performance.py` with the real embedder and + sqlite-vec/backend configuration on a fixed machine class and corpus scale. +3. **Run the fixed-budget curve on the complete official datasets.** The v2 harness now measures + every question at 256, 512, 1,024, 2,048, and 4,096 evidence tokens and validates the + per-question records, aggregates, and pinned reader-tokenizer identity. Publish the curve only + after complete official runs produce immutable artifacts for every point. +4. **Run an external evaluation platform** once (1)–(3) exist. + +Do not make all evidence lanes variants of explicit factual recall. Executable offline adapters +now cover: + +- [MemoryAgentBench](https://github.com/HUST-AI-HYZ/MemoryAgentBench): incremental multi-turn + learning, long-range understanding, and conflict/consolidation inputs. +- [LoCoMo-Plus](https://github.com/xjtuleeyf/Locomo-Plus): an old implicit constraint must affect + a later response even when the later cue does not restate the remembered fact. +- [Mem2ActBench](https://github.com/Cantaloupe-M/Mem2ActBench): memory must select a tool and + ground its arguments, not merely return a passage. The current adapter measures retrieval and + expected tool-argument context coverage, not generated tool-call success. + +```bash +python -m eval.agent_benchmarks --dataset memoryagentbench.json \ + --format memoryagentbench +python -m eval.agent_benchmarks --dataset locomo_plus.json \ + --format locomo_plus +python -m eval.agent_benchmarks --dataset qa_dataset.jsonl \ + --conversations toolmem_conversation.jsonl --format mem2actbench \ + --artifact artifacts/mem2actbench.json +``` + +Use `--artifact` on any of these commands to write a redacted, immutable evidence envelope plus +an adjacent SHA256 file. The ordinary console/`--json` report is private run material and may +contain source questions for debugging. + +### Upstream-data diagnostics and publication scope + +The LoCoMo-Plus and MemoryAgentBench adapters have been exercised against upstream data and remain +queued for their own public-safe retrieval artifacts. Rerun each pending adapter with `--artifact`, +publish the redacted immutable envelope and checksum, and add its suite/config binding before +quoting a number. Mem2ActBench's declared small retrieval diagnostic is complete and has a checked-in +artifact; its exclusion and memory-cardinality figures are source-preparation metadata in the +public source lock, not product or action-success metrics. None of these lanes is an official +leaderboard, answer-quality, or marketing result. + +The MemoryAgentBench loader accepts both its aligned public JSON export and the Hugging Face +dataset-server `rows[].row` envelope. Rows without gold evidence remain useful for answer-token +coverage, but are excluded from retrieval aggregates and counted separately as +`retrieval_scored_questions`. + +For paired code-agent runs, execute the same tasks with the same model, tools, machine, and +deterministic success oracle under `full_history` and `engraphis`. Then analyze the content-free +run records with: + +```bash +python -m eval.code_agent_ab --full-history full-history.jsonl \ + --engraphis engraphis.jsonl --output paired-report.json +``` + +The analyzer rejects unmatched task IDs and different success oracles, then reports paired +bootstrap intervals for task success, input/output/tool tokens, retries, latency, and optional +cost. Its aggregate output does not echo task IDs or oracle commands. It does not launch an agent +or invent a task-success oracle. + +## Optimization experiments to run before changing defaults + +1. **Budget-aware packing**: compare full source, safe summary, sentence-aligned safe summary + excerpt, and raw-source excerpt at fixed budgets. Gate on support/answer retention and + qualifier preservation, not token count alone. +2. **Adaptive retrieval work**: `--candidate-depth adaptive` is an opt-in performance experiment. + It keeps wider graph/code pools and reduces routine lexical/balanced pools while reporting the + requested and actual depth. A local experiment motivated this option, but no public number is + retained because its machine-specific artifact is not in the evidence registry. Keep the + default fixed until complete external categories meet predeclared quality margins. +3. **Packing-pressure consolidation**: prioritize memory families that are frequently recalled, + repeatedly omitted, or costly per useful token. Count write/index/storage cost as well as later + reader-context savings. +4. **Tokenizer-aware ingestion**: implemented behind the chunk extractor. The dependency-free + default remains `engraphis.chars4.v1`; an explicitly configured Hugging Face reader tokenizer + enforces prose chunk and overlap budgets and records its identity in chunk metadata. Continue + measuring tokens-to-evidence, recall, and storage/index growth together before recommending a + model-specific default. +5. **Bulk ingestion**: add batch embedding plus a transaction-aware vector upsert path, then rerun + the complete MemoryAgentBench Test-Time Learning input. Gate this on identical stored-memory, + provenance, graph-link, and temporal-resolution outcomes, not throughput alone. +6. **Scoped caches**: benchmark query embeddings and repeat-recall results keyed by workspace, + repo, time anchors, profile, and corpus version. Test invalidation correctness before claiming + latency gains. +7. **Privacy-safe real usage**: use `engraphis_context_savings` to let each workspace inspect + aggregate source/context/saved tokens already present in content-free receipts. Keep unlike + token counters separate and require a valid receipt chain before treating totals as auditable. + +## Evaluation question + +The predeclared question is whether the full vector + lexical/BM25 + sparse PPR graph + calibrated +rerank pipeline, bi-temporal resolution, and grounded abstention produce higher evidence recall +per injected token than the registered baselines. The answer must come from a complete, +machine-readable artifact with paired confidence intervals; otherwise the release reports +“no demonstrated improvement.” diff --git a/README.md b/README.md index 896213680..1de2bf430 100644 --- a/README.md +++ b/README.md @@ -1,98 +1,98 @@ -# Engraphis - -[![PyPI version](https://img.shields.io/pypi/v/engraphis.svg)](https://pypi.org/project/engraphis/) -[![License](https://img.shields.io/badge/license-Apache--2.0-green.svg)](https://github.com/Coding-Dev-Tools/engraphis/blob/522def372c45a26d46bc7a05829964ae0240f3a9/LICENSE) - -**Persistent, local-first memory for AI agents.** Engraphis stores scoped project knowledge, retrieves relevant evidence across vector, lexical, graph, and code search, and returns bounded context with sources an agent can inspect. - -The local engine uses SQLite and works offline. It keeps changes over time instead of silently replacing facts, and grounded recall cites retrieved memories or abstains when evidence is weak. - -

- Engraphis local knowledge graph showing relationships between remembered entities -
- Explore memories and their relationships in the local dashboard. -

- -## Install and try it - -The full local application requires Python 3.10 or newer. The NumPy-only core supports Python 3.9+. - -```bash -python -m pip install "engraphis[all]" -engraphis-dashboard -``` - -The dashboard opens at [http://127.0.0.1:8700](http://127.0.0.1:8700). Local use needs no cloud account or API key. - -To use the Python service directly: - -```python -from engraphis.service import MemoryService - -memory = MemoryService.create("engraphis.db") -memory.remember("Auth migrated from JWT to PASETO.", workspace="acme", repo="api") -hit = memory.recall("Why did we change auth?", workspace="acme", repo="api") -print(hit["context"]) -``` - -Connect a coding agent over MCP with the [agent setup guide](https://github.com/Coding-Dev-Tools/engraphis/blob/522def372c45a26d46bc7a05829964ae0240f3a9/docs/AGENT_CONNECT.md). - -## What it provides - -- **Continuity:** Organize memories by workspace, repository, and session, with a last-session handoff. -- **Useful recall:** Combine vector, lexical, graph, and code-aware retrieval, then pack results to a hard context budget. -- **Grounded answers:** Preserve provenance and temporal history; cite support or abstain when it is insufficient. -- **Temporal history:** Timeline can select known versions whose validity has ended before result limits. The dashboard uses `closed_only=true` on `GET /api/timeline` for “Only superseded versions.” Closed history prioritizes lexical matches (the literal query and up to 32 expanded terms, using the final ranker's Unicode lowercasing) before scoring at most 500 eligible candidates; semantic-only matches outside that window can be omitted. SQLite may scan and sort more rows than this candidate cap. -- **Spec Studio:** Inspect prompt specifications and scoped memory clusters with deterministic coverage, ambiguity, and conflict checks; review suggestions before changing memory. -- **Operator control:** Keep the core local and offline-capable, with explicit controls for hosted services and external LLM providers. -- **Free Jev on paid plans:** Pro and Team include managed Jev decisions up to 500 calls per person per day, with no personal provider key. - -## Benchmarks - -The current registered artifact contains three deterministic offline fixture runs. It separates context size, retrieval quality, and grounded decision checks; these small fixtures do not establish general task performance. - -

- Three registered offline fixtures: structure-aware chunking reduced mean retrieved context from 740.3 to 214.3 tokens per question (71.1%, 18 questions); the serialized JSON-shape proxy fell from 24,590 to 11,138 tokens across 26 payload samples and 260 recalls. Candidate and packed retrieval quality (Recall@5, Hit@5, and answer-token recall) are shown separately (each 1.000). Grounded checks show 5/5 answerable queries grounded and 6/6 abstention queries rejected, including a 1/1 quarantined-evidence probe; 11/11 decisions were correct. MCP transport and provider billing were not measured. Artifact SHA-256 prefix f95e35b9f2c8; see the benchmark guide for the full checksum. -
- Three offline fixtures separate context reduction, candidate and packed retrieval quality, and grounded behavior. The chart shows the artifact checksum prefix; see the benchmark guide for the full checksum and reproduction steps. -

- -| Fixture | Result | Quality check | -|---|---|---| -| Structure-aware chunking | Mean retrieved top-5 context: 740.3 → 214.3 tokens per question (71.1% lower, 18 questions) | Recall@5 = 1.000 in both modes | -| Recall payload proxy | JSON-shape proxy: 24,590 → 11,138 tokens (54.71% lower, 26 samples; 260 timed recalls) | Candidate and packed Recall@5, Hit@5, and answer-token recall are each 1.000 | -| Grounded decisions | 5/5 answerable queries grounded; 6/6 abstention queries rejected, including 1/1 quarantined-evidence check | 11/11 decisions correct | - -The payload figure is a serialized JSON-shape estimate, not an MCP transport measurement or provider billing total. See the [Benchmark methodology](https://github.com/Coding-Dev-Tools/engraphis/blob/99af3855bd6b685cbac4a4489b44b3c374129053/BENCHMARKS.md) for artifact identity, counting methods, reproduction commands, external-evaluation boundaries, and limitations. - -## Optional Jev assistance - -**Jev is included free with every paid plan: Pro and Team.** Every legitimate Pro user and each eligible Team named seat, including paid viewers, with an active paid, trial, or test entitlement receives managed Jev at no additional customer charge and without a personal provider key. Local advisory decisions stay free for everyone; BYOK, a personal TypeSafe key, is optional for non-paid or direct use and its provider charges may apply. Managed Jev is available for paid plans; the hosted control service enables it with its provider key. - -Each paid individual receives up to **500 Jev calls per person per day**, free. Usage is per person, not pooled across a Team and not monthly. Each evaluated call counts once, including a batched request; calls beyond the daily maximum are not served. Admitted attempts that fail or are interrupted remain counted, and no overage is charged. The account portal reports that member's remaining calls for the current day. - -Use **Review with Jev** in the local dashboard for evidence-support and contradiction checks; the MCP decision tool covers command and completion reviews. Remote calls need per-call consent plus a `public` or `internal` classification, and Jev cannot write memories, change grounded-recall requirements, authorize commands, or certify completion. - -Recall route selection is experimental and BYOK-only: `engraphis_recall_context` with `allow_remote=true`, or classic MCP with `planning="auto"` and `jev_assisted=true`. On 40 public synthetic tasks it changed no retrieval quality, so no retrieval-quality improvement is claimed. - -See [hosted plans and Jev details](https://github.com/Coding-Dev-Tools/engraphis/blob/522def372c45a26d46bc7a05829964ae0240f3a9/docs/HOSTED_PLANS.md#included-system-1-decision-engine-jev). - -## Guides - -- [Configuration reference](https://github.com/Coding-Dev-Tools/engraphis/blob/522def372c45a26d46bc7a05829964ae0240f3a9/docs/CONFIGURATION.md) -- [MCP tool reference](https://github.com/Coding-Dev-Tools/engraphis/blob/522def372c45a26d46bc7a05829964ae0240f3a9/docs/MCP_TOOLS.md) -- [Agent and LLM provider setup](https://github.com/Coding-Dev-Tools/engraphis/blob/522def372c45a26d46bc7a05829964ae0240f3a9/docs/LLM_PROVIDERS.md) -- [Architecture and query planning](https://github.com/Coding-Dev-Tools/engraphis/blob/522def372c45a26d46bc7a05829964ae0240f3a9/docs/ARCHITECTURE_V3.md#query-planning) -- [Docker deployment](https://github.com/Coding-Dev-Tools/engraphis/blob/522def372c45a26d46bc7a05829964ae0240f3a9/docs/DOCKER.md) -- [Document import](https://github.com/Coding-Dev-Tools/engraphis/blob/522def372c45a26d46bc7a05829964ae0240f3a9/docs/DOCUMENT_IMPORT.md) -- [Cloud Sync](https://github.com/Coding-Dev-Tools/engraphis/blob/522def372c45a26d46bc7a05829964ae0240f3a9/docs/SYNC.md) -- [Pi extension](https://github.com/Coding-Dev-Tools/engraphis/blob/522def372c45a26d46bc7a05829964ae0240f3a9/integrations/pi/README.md) -- [Spec Crawl and Spec Studio](https://github.com/Coding-Dev-Tools/engraphis/blob/99af3855bd6b685cbac4a4489b44b3c374129053/docs/SPEC_CRAWL.md) -- [Memory write review](https://github.com/Coding-Dev-Tools/engraphis/blob/522def372c45a26d46bc7a05829964ae0240f3a9/docs/WRITE_REVIEW.md) -- [Coding-agent file protection](https://github.com/Coding-Dev-Tools/engraphis/blob/99af3855bd6b685cbac4a4489b44b3c374129053/docs/FILE_PROTECTION.md) -- [Security policy](https://github.com/Coding-Dev-Tools/engraphis/blob/522def372c45a26d46bc7a05829964ae0240f3a9/SECURITY.md) -- [Hosted plans and licensing](https://github.com/Coding-Dev-Tools/engraphis/blob/522def372c45a26d46bc7a05829964ae0240f3a9/docs/HOSTED_PLANS.md) - -## License - -Engraphis is licensed under Apache-2.0. The license does not grant trademark rights. See [LICENSE](https://github.com/Coding-Dev-Tools/engraphis/blob/522def372c45a26d46bc7a05829964ae0240f3a9/LICENSE), [NOTICE](https://github.com/Coding-Dev-Tools/engraphis/blob/522def372c45a26d46bc7a05829964ae0240f3a9/NOTICE), and the [licensing guide](https://github.com/Coding-Dev-Tools/engraphis/blob/522def372c45a26d46bc7a05829964ae0240f3a9/docs/LICENSING.md). The hosted control plane and managed services are private services. +# Engraphis + +[![PyPI version](https://img.shields.io/pypi/v/engraphis.svg)](https://pypi.org/project/engraphis/) +[![License](https://img.shields.io/badge/license-Apache--2.0-green.svg)](https://github.com/Coding-Dev-Tools/engraphis/blob/522def372c45a26d46bc7a05829964ae0240f3a9/LICENSE) + +**Persistent, local-first memory for AI agents.** Engraphis stores scoped project knowledge, retrieves relevant evidence across vector, lexical, graph, and code search, and returns bounded context with sources an agent can inspect. + +The local engine uses SQLite and works offline. It keeps changes over time instead of silently replacing facts, and grounded recall cites retrieved memories or abstains when evidence is weak. + +

+ Engraphis local knowledge graph showing relationships between remembered entities +
+ Explore memories and their relationships in the local dashboard. +

+ +## Install and try it + +The full local application requires Python 3.10 or newer. The NumPy-only core supports Python 3.9+. + +```bash +python -m pip install "engraphis[all]" +engraphis-dashboard +``` + +The dashboard opens at [http://127.0.0.1:8700](http://127.0.0.1:8700). Local use needs no cloud account or API key. + +To use the Python service directly: + +```python +from engraphis.service import MemoryService + +memory = MemoryService.create("engraphis.db") +memory.remember("Auth migrated from JWT to PASETO.", workspace="acme", repo="api") +hit = memory.recall("Why did we change auth?", workspace="acme", repo="api") +print(hit["context"]) +``` + +Connect a coding agent over MCP with the [agent setup guide](https://github.com/Coding-Dev-Tools/engraphis/blob/522def372c45a26d46bc7a05829964ae0240f3a9/docs/AGENT_CONNECT.md). + +## What it provides + +- **Continuity:** Organize memories by workspace, repository, and session, with a last-session handoff. +- **Useful recall:** Combine vector, lexical, graph, and code-aware retrieval, then pack results to a hard context budget. +- **Grounded answers:** Preserve provenance and temporal history; cite support or abstain when it is insufficient. +- **Temporal history:** Timeline can select known versions whose validity has ended before result limits. The dashboard uses `closed_only=true` on `GET /api/timeline` for “Only superseded versions.” Closed history prioritizes lexical matches (the literal query and up to 32 expanded terms, using the final ranker's Unicode lowercasing) before scoring at most 500 eligible candidates; semantic-only matches outside that window can be omitted. SQLite may scan and sort more rows than this candidate cap. +- **Spec Studio:** Inspect prompt specifications and scoped memory clusters with deterministic coverage, ambiguity, and conflict checks; review suggestions before changing memory. +- **Operator control:** Keep the core local and offline-capable, with explicit controls for hosted services and external LLM providers. +- **Free Jev on paid plans:** Pro and Team include managed Jev decisions up to 500 calls per person per day, with no personal provider key. + +## Benchmarks + +The current registered artifact contains three deterministic offline fixture runs. It separates context size, retrieval quality, and grounded decision checks; these small fixtures do not establish general task performance. + +

+ Three registered offline fixtures: structure-aware chunking reduced mean retrieved context from 740.3 to 214.3 tokens per question (71.1%, 18 questions); the serialized JSON-shape proxy fell from 24,590 to 11,138 tokens across 26 payload samples and 260 recalls. Candidate and packed retrieval quality (Recall@5, Hit@5, and answer-token recall) are shown separately (each 1.000). Grounded checks show 5/5 answerable queries grounded and 6/6 abstention queries rejected, including a 1/1 quarantined-evidence probe; 11/11 decisions were correct. MCP transport and provider billing were not measured. Artifact SHA-256 prefix f95e35b9f2c8; see the benchmark guide for the full checksum. +
+ Three offline fixtures separate context reduction, candidate and packed retrieval quality, and grounded behavior. The chart shows the artifact checksum prefix; see the benchmark guide for the full checksum and reproduction steps. +

+ +| Fixture | Result | Quality check | +|---|---|---| +| Structure-aware chunking | Mean retrieved top-5 context: 740.3 → 214.3 tokens per question (71.1% lower, 18 questions) | Recall@5 = 1.000 in both modes | +| Recall payload proxy | JSON-shape proxy: 24,590 → 11,138 tokens (54.71% lower, 26 samples; 260 timed recalls) | Candidate and packed Recall@5, Hit@5, and answer-token recall are each 1.000 | +| Grounded decisions | 5/5 answerable queries grounded; 6/6 abstention queries rejected, including 1/1 quarantined-evidence check | 11/11 decisions correct | + +The payload figure is a serialized JSON-shape estimate, not an MCP transport measurement or provider billing total. See the [Benchmark methodology](https://github.com/Coding-Dev-Tools/engraphis/blob/99af3855bd6b685cbac4a4489b44b3c374129053/BENCHMARKS.md) for artifact identity, counting methods, reproduction commands, external-evaluation boundaries, and limitations. + +## Optional Jev assistance + +**Jev is included free with every paid plan: Pro and Team.** Every legitimate Pro user and each eligible Team named seat, including paid viewers, with an active paid, trial, or test entitlement receives managed Jev at no additional customer charge and without a personal provider key. Local advisory decisions stay free for everyone; BYOK, a personal TypeSafe key, is optional for non-paid or direct use and its provider charges may apply. Managed Jev is available for paid plans; the hosted control service enables it with its provider key. + +Each paid individual receives up to **500 Jev calls per person per day**, free. Usage is per person, not pooled across a Team and not monthly. Each evaluated call counts once, including a batched request; calls beyond the daily maximum are not served. Admitted attempts that fail or are interrupted remain counted, and no overage is charged. The account portal reports that member's remaining calls for the current day. + +Use **Review with Jev** in the local dashboard for evidence-support and contradiction checks; the MCP decision tool covers command and completion reviews. Remote calls need per-call consent plus a `public` or `internal` classification, and Jev cannot write memories, change grounded-recall requirements, authorize commands, or certify completion. + +Recall route selection is experimental and BYOK-only: `engraphis_recall_context` with `allow_remote=true`, or classic MCP with `planning="auto"` and `jev_assisted=true`. On 40 public synthetic tasks it changed no retrieval quality, so no retrieval-quality improvement is claimed. + +See [hosted plans and Jev details](https://github.com/Coding-Dev-Tools/engraphis/blob/522def372c45a26d46bc7a05829964ae0240f3a9/docs/HOSTED_PLANS.md#included-system-1-decision-engine-jev). + +## Guides + +- [Configuration reference](https://github.com/Coding-Dev-Tools/engraphis/blob/522def372c45a26d46bc7a05829964ae0240f3a9/docs/CONFIGURATION.md) +- [MCP tool reference](https://github.com/Coding-Dev-Tools/engraphis/blob/522def372c45a26d46bc7a05829964ae0240f3a9/docs/MCP_TOOLS.md) +- [Agent and LLM provider setup](https://github.com/Coding-Dev-Tools/engraphis/blob/522def372c45a26d46bc7a05829964ae0240f3a9/docs/LLM_PROVIDERS.md) +- [Architecture and query planning](https://github.com/Coding-Dev-Tools/engraphis/blob/522def372c45a26d46bc7a05829964ae0240f3a9/docs/ARCHITECTURE_V3.md#query-planning) +- [Docker deployment](https://github.com/Coding-Dev-Tools/engraphis/blob/522def372c45a26d46bc7a05829964ae0240f3a9/docs/DOCKER.md) +- [Document import](https://github.com/Coding-Dev-Tools/engraphis/blob/522def372c45a26d46bc7a05829964ae0240f3a9/docs/DOCUMENT_IMPORT.md) +- [Cloud Sync](https://github.com/Coding-Dev-Tools/engraphis/blob/522def372c45a26d46bc7a05829964ae0240f3a9/docs/SYNC.md) +- [Pi extension](https://github.com/Coding-Dev-Tools/engraphis/blob/522def372c45a26d46bc7a05829964ae0240f3a9/integrations/pi/README.md) +- [Spec Crawl and Spec Studio](https://github.com/Coding-Dev-Tools/engraphis/blob/99af3855bd6b685cbac4a4489b44b3c374129053/docs/SPEC_CRAWL.md) +- [Memory write review](https://github.com/Coding-Dev-Tools/engraphis/blob/522def372c45a26d46bc7a05829964ae0240f3a9/docs/WRITE_REVIEW.md) +- [Coding-agent file protection](https://github.com/Coding-Dev-Tools/engraphis/blob/99af3855bd6b685cbac4a4489b44b3c374129053/docs/FILE_PROTECTION.md) +- [Security policy](https://github.com/Coding-Dev-Tools/engraphis/blob/522def372c45a26d46bc7a05829964ae0240f3a9/SECURITY.md) +- [Hosted plans and licensing](https://github.com/Coding-Dev-Tools/engraphis/blob/522def372c45a26d46bc7a05829964ae0240f3a9/docs/HOSTED_PLANS.md) + +## License + +Engraphis is licensed under Apache-2.0. The license does not grant trademark rights. See [LICENSE](https://github.com/Coding-Dev-Tools/engraphis/blob/522def372c45a26d46bc7a05829964ae0240f3a9/LICENSE), [NOTICE](https://github.com/Coding-Dev-Tools/engraphis/blob/522def372c45a26d46bc7a05829964ae0240f3a9/NOTICE), and the [licensing guide](https://github.com/Coding-Dev-Tools/engraphis/blob/522def372c45a26d46bc7a05829964ae0240f3a9/docs/LICENSING.md). The hosted control plane and managed services are private services. diff --git a/docs/benchmark-evidence/offline-fixtures-receipt-diagnostics-20261103.json.sha256 b/docs/benchmark-evidence/offline-fixtures-receipt-diagnostics-20261103.json.sha256 index 6c0f2d688..6ae6dedff 100644 --- a/docs/benchmark-evidence/offline-fixtures-receipt-diagnostics-20261103.json.sha256 +++ b/docs/benchmark-evidence/offline-fixtures-receipt-diagnostics-20261103.json.sha256 @@ -1 +1 @@ -f95e35b9f2c8f848925df0d0db0bd34898280b9cd10a884ed188b11d1708dcb2 offline-fixtures-receipt-diagnostics-20261103.json +f95e35b9f2c8f848925df0d0db0bd34898280b9cd10a884ed188b11d1708dcb2 offline-fixtures-receipt-diagnostics-20261103.json diff --git a/docs/images/context-efficiency.png b/docs/images/context-efficiency.png index 1e864f7992f2c2e7106414c069e64a1a3f3afc2c..fdbc6c734b0de4faa24a18684fcf673da8701471 100644 GIT binary patch delta 81 zcmV~$I}v~|3;@spL;17~>h(JIQfD(?oj%--qz1D};hy9opV}3;?jf(Pv7brR)y=;6!V=!81I-(F5=!MmJwD>0|0+I`1W;NmM}sfn>&7 kIX6TH_MH*}U< -Offline context, retrieval, and grounded benchmark results -Artifact-driven offline benchmark report with context efficiency, retrieval quality, and grounded behavior reported separately. Structure-aware chunking reports 740.3 to 214.3 retrieved tokens per question. The JSON-shape payload proxy reports 24,590 full versus 11,138 compact tokens; MCP transport was not measured; provider billing was not measured. Retrieved candidate quality is Recall@5 1.000, Hit@5 1.000, and answer-token recall 1.000 across 26 questions. Packed context quality is Recall@5 1.000, Hit@5 1.000, and answer-token recall 1.000 across 26 questions. Grounded checks score 5 of 5 answerable queries grounded and 6 of 6 abstention queries rejected, including 1 of 1 quarantined-evidence checks. Decision accuracy is 1.000 across 11 decisions. Source artifact SHA-256 f95e35b9f2c8f848925df0d0db0bd34898280b9cd10a884ed188b11d1708dcb2. - - - -REGISTERED OFFLINE EVIDENCE -Offline benchmark results -3 REGISTERED OFFLINE FIXTURES -Context efficiency, retrieval quality, and grounded checks are reported separately. - -01 CONTEXT EFFICIENCY -CHUNKING + SERIALIZED JSON-SHAPE PROXY - - -Retrieved context after chunking -offline-chunking · 6 documents · 18 questions · Recall@5 1.000 both modes -71.1% fewer retrieved tokens per question -Whole - -740.3 tokens -Chunked - -214.3 tokens -Serialized JSON-shape payload proxy -offline-performance · 26 payload samples · 260 timed recalls -54.71% fewer proxy tokens -Full - -24,590 tokens -Compact - -11,138 tokens - -02 RETRIEVAL QUALITY -CANDIDATES BEFORE PACKING VS READER-ADMITTED CONTEXT - - -Retrieved candidate quality -offline-performance · before packing · n=26 -Recall@5 -Hit@5 -Answer-token recall -1.000 -1.000 -1.000 -Packed context quality -reader-admitted context · n=26 -Recall@5 -Hit@5 -Answer-token recall -1.000 -1.000 -1.000 - -03 GROUNDED CHECKS -OFFLINE DETERMINISTIC FIXTURE - - - -Answerable queries -5 / 5 grounded -Abstention queries -6 / 6 rejected -Quarantined-evidence probe -1 / 1 abstained -Decision accuracy 1.000 (11 / 11); the quarantine probe is included in the abstention total. -SOURCE SHA-256 f95e35b9f2c8 -JSON proxy only; MCP transport not measured; provider billing not measured. - + +Offline context, retrieval, and grounded benchmark results +Artifact-driven offline benchmark report with context efficiency, retrieval quality, and grounded behavior reported separately. Structure-aware chunking reports 740.3 to 214.3 retrieved tokens per question. The JSON-shape payload proxy reports 24,590 full versus 11,138 compact tokens; MCP transport was not measured; provider billing was not measured. Retrieved candidate quality is Recall@5 1.000, Hit@5 1.000, and answer-token recall 1.000 across 26 questions. Packed context quality is Recall@5 1.000, Hit@5 1.000, and answer-token recall 1.000 across 26 questions. Grounded checks score 5 of 5 answerable queries grounded and 6 of 6 abstention queries rejected, including 1 of 1 quarantined-evidence checks. Decision accuracy is 1.000 across 11 decisions. Source artifact SHA-256 f95e35b9f2c8f848925df0d0db0bd34898280b9cd10a884ed188b11d1708dcb2. + + + +REGISTERED OFFLINE EVIDENCE +Offline benchmark results +3 REGISTERED OFFLINE FIXTURES +Context efficiency, retrieval quality, and grounded checks are reported separately. + +01 CONTEXT EFFICIENCY +CHUNKING + SERIALIZED JSON-SHAPE PROXY + + +Retrieved context after chunking +offline-chunking · 6 documents · 18 questions · Recall@5 1.000 both modes +71.1% fewer retrieved tokens per question +Whole + +740.3 tokens +Chunked + +214.3 tokens +Serialized JSON-shape payload proxy +offline-performance · 26 payload samples · 260 timed recalls +54.71% fewer proxy tokens +Full + +24,590 tokens +Compact + +11,138 tokens + +02 RETRIEVAL QUALITY +CANDIDATES BEFORE PACKING VS READER-ADMITTED CONTEXT + + +Retrieved candidate quality +offline-performance · before packing · n=26 +Recall@5 +Hit@5 +Answer-token recall +1.000 +1.000 +1.000 +Packed context quality +reader-admitted context · n=26 +Recall@5 +Hit@5 +Answer-token recall +1.000 +1.000 +1.000 + +03 GROUNDED CHECKS +OFFLINE DETERMINISTIC FIXTURE + + + +Answerable queries +5 / 5 grounded +Abstention queries +6 / 6 rejected +Quarantined-evidence probe +1 / 1 abstained +Decision accuracy 1.000 (11 / 11); the quarantine probe is included in the abstention total. +SOURCE SHA-256 f95e35b9f2c8 +JSON proxy only; MCP transport not measured; provider billing not measured. + diff --git a/docs/images/evidence-backed-agent-examples.png b/docs/images/evidence-backed-agent-examples.png index dd568898cb6a31ec324e012c2dc1eb47f0a8b483..906092b24e2010331aef0a910a9ed1ae898bf23b 100644 GIT binary patch delta 93 zcmV~$s}X=O3;@vMBXF<>hV+-#vj|PvJrMOMCSV0LW3WPG7vPTf>O0kUI<7S`;$m#_ x){%`wDjCeQL3WF0jR2?=CPUf<_Lewu3@!ufV2Bvte7 - Three evidence-backed Engraphis agent behaviors - A three-card summary of deterministic offline fixtures. Focused context returns 740.3 to 214.3 tokens while retaining Recall at 5 of 1.000. A grounded answer returns support for 5/5 answerable questions. An unsupported question safely abstains for 6/6 off-topic questions. Reproduce with eval.chunking_eval and eval.grounded. Exact commands and config digests are registered in BENCHMARKS.md. Public-safe artifact SHA-256: f95e35b9f2c8f848925df0d0db0bd34898280b9cd10a884ed188b11d1708dcb2. - - - - - - - Three evidence-backed agent behaviors - Synthetic offline fixtures · reproducible outcomes · not customer data - - - - - FOCUSED CONTEXT - Find the - relevant passage - - 740.3 → 214.3 tokens - - Recall@5 1.000 - - - - - - CITED ANSWER - Answer only - with support - - 5/5 grounded - - source attached - - - - - - SAFE ABSTAIN - No support, - no answer - - 6/6 abstained - - explicitly says why - - - Reproduce: eval.chunking_eval + eval.grounded - SHA256 f95e35b9f2c8f848925df0d0db0bd34898280b9cd10a884ed188b11d1708dcb2 - + + Three evidence-backed Engraphis agent behaviors + A three-card summary of deterministic offline fixtures. Focused context returns 740.3 to 214.3 tokens while retaining Recall at 5 of 1.000. A grounded answer returns support for 5/5 answerable questions. An unsupported question safely abstains for 6/6 off-topic questions. Reproduce with eval.chunking_eval and eval.grounded. Exact commands and config digests are registered in BENCHMARKS.md. Public-safe artifact SHA-256: f95e35b9f2c8f848925df0d0db0bd34898280b9cd10a884ed188b11d1708dcb2. + + + + + + + Three evidence-backed agent behaviors + Synthetic offline fixtures · reproducible outcomes · not customer data + + + + + FOCUSED CONTEXT + Find the + relevant passage + + 740.3 → 214.3 tokens + + Recall@5 1.000 + + + + + + CITED ANSWER + Answer only + with support + + 5/5 grounded + + source attached + + + + + + SAFE ABSTAIN + No support, + no answer + + 6/6 abstained + + explicitly says why + + + Reproduce: eval.chunking_eval + eval.grounded + SHA256 f95e35b9f2c8f848925df0d0db0bd34898280b9cd10a884ed188b11d1708dcb2 + From 589109e0fdd452dab2d219e46c460b10d14332e5 Mon Sep 17 00:00:00 2001 From: Coding-Dev-Tools <274649000+Coding-Dev-Tools@users.noreply.github.com> Date: Wed, 4 Nov 2026 00:12:36 -0500 Subject: [PATCH 3/7] fix(doctor): match the factory's require_exact_backends policy The embedder check inferred exact mode from a non-empty embed_model, while the factory uses the explicit require_exact_backends setting. Honor the same policy so a configured model may fall back when exact mode is off, and a deployment that requires exact backends is checked strictly even with the default model. The regression test sets the policy it exercises. --- scripts/init.py | 2 +- tests/test_init.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/scripts/init.py b/scripts/init.py index f63c04759..9ec177183 100644 --- a/scripts/init.py +++ b/scripts/init.py @@ -316,7 +316,7 @@ def fail_database(exc: Exception) -> None: try: from engraphis.backends.embedder_st import get_embedder emb = get_embedder(settings.embed_model or None, dim=settings.embed_dim or 384, - require_exact=bool(settings.embed_model)) + require_exact=settings.require_exact_backends) emb.embed(["engraphis doctor check"]) report("embedder", "ok", "embedder functional", f"{type(emb).__name__} ({getattr(emb, 'dim', 384)}d)") except Exception as exc: diff --git a/tests/test_init.py b/tests/test_init.py index 29befb4f5..290992d56 100644 --- a/tests/test_init.py +++ b/tests/test_init.py @@ -469,6 +469,7 @@ def test_doctor_does_not_mask_a_broken_configured_embedder_or_echo_its_error(tmp import engraphis.config as cfg import engraphis.backends.embedder_st as backend monkeypatch.setattr(cfg.settings, "embed_model", "missing-configured-model") + monkeypatch.setattr(cfg.settings, "require_exact_backends", True) def fail(*_args, **kwargs): assert kwargs["require_exact"] is True From 560c56282d08f42fc9b747318c7f5e3c4c986a86 Mon Sep 17 00:00:00 2001 From: Coding-Dev-Tools <274649000+Coding-Dev-Tools@users.noreply.github.com> Date: Wed, 4 Nov 2026 00:18:52 -0500 Subject: [PATCH 4/7] fix(docker): update Python base image to 3.11.18 (#314) CVE-2026-115557 in the Python 3.11.17 runtime is fixed in 3.11.18. Pin the new multi-arch digest so the release grype gate can pass. --- Dockerfile | 2 +- tests/test_release_infrastructure.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Dockerfile b/Dockerfile index 0b5e611e4..6f7796894 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,5 @@ # Engraphis — self-hosted AI memory engine. Local-first; you bring the LLM. -FROM python:3.11-slim@sha256:b7a9e5f6a93d6d4a31f129bbd4bdee69efccb6ded0fb230d78aafaf37af98377 AS base +FROM python:3.11.18-slim@sha256:db20d8506e2f15b6db39b40b6015ca46bae855dec6dd326d462cfe79ae972893 AS base # ENGRAPHIS_HOST is deliberately NOT set here: docker-entrypoint.sh chooses IPv6 for a # Railway deployment (which injects RAILWAY_SERVICE_NAME) and 0.0.0.0 for ordinary Docker. diff --git a/tests/test_release_infrastructure.py b/tests/test_release_infrastructure.py index eaa815c93..dda9f52f3 100644 --- a/tests/test_release_infrastructure.py +++ b/tests/test_release_infrastructure.py @@ -215,8 +215,8 @@ def test_ci_and_release_audit_production_image_dependencies(): assert "tesseract-ocr" in _text("Dockerfile") assert ( _text("Dockerfile").splitlines()[1] - == "FROM python:3.11-slim@sha256:" - "b7a9e5f6a93d6d4a31f129bbd4bdee69efccb6ded0fb230d78aafaf37af98377 AS base" + == "FROM python:3.11.18-slim@sha256:" + "db20d8506e2f15b6db39b40b6015ca46bae855dec6dd326d462cfe79ae972893 AS base" ) assert "Verify production image OCR runtime" in ci assert "Verify production image OCR runtime" in release From 3adb658697d06a62b1e3424e59b822c544ee3f03 Mon Sep 17 00:00:00 2001 From: Coding-Dev-Tools <274649000+Coding-Dev-Tools@users.noreply.github.com> Date: Wed, 4 Nov 2026 18:44:42 -0500 Subject: [PATCH 5/7] docs(readme): add Pro and Team upgrade benefits (#321) ## Why upgrade to Pro or Team in the README - Adds a prominent upgrade section after the local feature list. - Explains memory health analytics, scheduled upkeep, encrypted sync, team roles/audit, and the included Jev allowance. - Shows how Jev helps agentic memory: safe vs risky actions, contradictory vs reinforcing memories, supported vs unsupported answers, and complete vs unfinished work. - Adds pricing, no-card trial details, comparison link, and Pro/Team signup links. Tests: documentation contracts, Pro CTA, plugin distribution, and skill package pass; ruff clean. --- README.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/README.md b/README.md index b38847f32..288cecf19 100644 --- a/README.md +++ b/README.md @@ -47,6 +47,18 @@ Connect a coding agent over MCP with the [agent setup guide](https://github.com/ - **Operator control:** Keep the core local and offline-capable, with explicit controls for hosted services and external LLM providers. - **Free Jev on paid plans:** Pro and Team include managed Jev decisions up to 500 calls per person per day, with no personal provider key. +## Why upgrade to Pro or Team + +The local core stays free and offline-capable. Pro and Team add hosted workflows for memory that has to follow you across agents, machines, and teammates: + +- **See memory health:** Analytics surfaces growth, retention, and decay forecasts so you can review what is becoming stale before it affects recall. +- **Keep memory maintained:** Schedule Auto Consolidation and Auto Dreaming. They produce proposals for you to review, and local governance still decides what changes. +- **Sync across machines:** Opt into encrypted Cloud Sync for eligible workspaces with a key you hold. Sync and readable managed processing are separate choices. +- **Give a team an operating picture:** Named seats, admin/member/viewer roles, scoped credentials, and a 30-day account activity log visible to owners and admins. Shared hosted Team memory and hosted MCP are not yet available. +- **Add judgment with Jev:** Pro and Team include up to 500 managed Jev calls per person per day: typed, advisory checks for risky commands, contradictory memories, evidence support, and completion claims. No personal TypeSafe key or overage charge. Jev helps an agent separate safe from risky actions, contradictory from reinforcing memories, supported from unsupported answers, and complete from unfinished work, while permissions and tests stay in charge. + +Pro is $10/month or $100/year for one owner. Team is $20/seat/month or $200/seat/year. Both start with an email-confirmed, no-card trial (7 active days for Pro, 14 active days for Team). [Compare plans](https://github.com/Coding-Dev-Tools/engraphis/blob/main/docs/HOSTED_PLANS.md) · [Start a Pro trial](https://api.engraphis.com/account?plan=pro&interval=monthly&utm_source=engraphis&utm_medium=readme&utm_campaign=pro_conversion&utm_content=readme_upgrade#billing) · [Start a Team trial](https://api.engraphis.com/account?plan=team&interval=monthly&seats=5&utm_source=engraphis&utm_medium=readme&utm_campaign=pro_conversion&utm_content=readme_team_upgrade#billing). + ## Benchmarks The current registered artifact contains three deterministic offline fixture runs. It separates context size, retrieval quality, and grounded decision checks; these small fixtures do not establish general task performance. From f9561654fdcc57fa4be38994391c6e1b12dddf96 Mon Sep 17 00:00:00 2001 From: coding-dev-tools Date: Thu, 5 Nov 2026 04:25:19 -0500 Subject: [PATCH 6/7] fix(diagnostics): verify receipt chains atomically and align the doctor - Read receipt rows and the chain anchor in one snapshot so a live writer cannot make a healthy chain look damaged. - Run the read-only receipt check before the write probe, so a preserved read-only copy still gets a receipt diagnosis. - Check the most recently written scopes, not the largest ones. - Share the factory's exact-backend policy with the doctor, including ENGRAPHIS_ENV=prod. - Give key-file failures their own actionable message and document the nonzero exit on a damaged sampled chain. --- CHANGELOG.md | 3 +- docs/RECALL_RECOVERY.md | 4 +- engraphis/core/store.py | 114 ++++++++++++++++++++------------- engraphis/factory.py | 8 ++- scripts/init.py | 48 ++++++++------ tests/test_init.py | 44 +++++++++++++ tests/test_receipt_snapshot.py | 54 ++++++++++++++++ 7 files changed, 208 insertions(+), 67 deletions(-) create mode 100644 tests/test_receipt_snapshot.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 383ee5f14..7916eecc3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,7 +12,8 @@ All notable changes to Engraphis are documented here. Format loosely follows - Verify operation-receipt chains read-only in `engraphis-init --check` (bounded by workspaces and receipts), so a forked, disconnected, or truncated ledger is visible to the operator with recovery guidance while memory writes and recall continue with a content-free - `receipt_warning`. + `receipt_warning`. The check exits nonzero when a sampled chain fails verification, including + for a preserved read-only copy; memory behavior on a damaged chain remains audit-only. ## [1.7.12] - 2026-11-02 diff --git a/docs/RECALL_RECOVERY.md b/docs/RECALL_RECOVERY.md index 231ff5144..3997c7354 100644 --- a/docs/RECALL_RECOVERY.md +++ b/docs/RECALL_RECOVERY.md @@ -43,7 +43,9 @@ delete rows to make verification pass. Recover in this order: Do not run `VACUUM`, `rekey`, or a migration against the original. 3. Run `engraphis-init --check` (point `ENGRAPHIS_DB_PATH` at the preserved copy). It verifies current-schema receipt chains read-only and reports damaged ones as `operation receipts`, - including the consequence for your data. + including the consequence for your data. The check exits nonzero when a sampled chain fails + verification; on a read-only copy it still reports the receipt diagnosis before the write + probe. 4. Verify one workspace with `engraphis_verify_receipts` (or the Inspector) and export it with `engraphis_export_receipts` before changing anything. 5. Restore your most recent verified backup, or keep the damaged copy for diagnosis and accept diff --git a/engraphis/core/store.py b/engraphis/core/store.py index 7428fa51e..96f4cca5c 100644 --- a/engraphis/core/store.py +++ b/engraphis/core/store.py @@ -968,59 +968,83 @@ def stable_key(row: dict) -> tuple[str, str]: } +def _connection_in_transaction(conn) -> bool: + """Return whether this thread owns a transaction on a shared or raw connection.""" + owned = getattr(conn, "transaction_owned_by_current_thread", None) + if callable(owned): + return bool(owned()) + return bool(getattr(conn, "in_transaction", False)) + + +@contextmanager +def _receipt_read_transaction(conn): + """Keep the chain rows and anchor on one snapshot when the caller has no transaction.""" + owns_transaction = not _connection_in_transaction(conn) + if owns_transaction: + conn.execute("BEGIN") + try: + yield + finally: + if owns_transaction and _connection_in_transaction(conn): + conn.rollback() + + def verify_receipt_chain(conn, *, workspace_id: str, expected_head: str = "", expected_count: Optional[int] = None) -> dict: """Verify one workspace's receipt chain against hashes and its stored anchor. Like :func:`receipt_chain_state`, this is read-only and usable with any - ``sqlite3``-compatible connection that returns named columns. + ``sqlite3``-compatible connection that returns named columns. When the caller is not + already in a transaction, the row scan and anchor read share one snapshot so a live + writer cannot make a healthy chain look damaged. """ - chain = receipt_chain_state(conn, workspace_id) - rows = chain["rows"] - errors: list[dict] = list(chain["errors"]) - head = str(chain["head"] or "") - anchor = conn.execute( - "SELECT receipt_count, head_hash, integrity_error " - "FROM receipt_chain_heads WHERE workspace_id=?", - (workspace_id,), - ).fetchone() - if rows and anchor is None: - errors.append({"index": len(rows), "id": "", "error": "missing_anchor"}) - elif anchor is not None: - anchor_count = anchor["receipt_count"] - if type(anchor_count) is not int or anchor_count < 0 or anchor_count != len(rows): - errors.append({ - "index": len(rows), "id": "", "error": "anchor_count_mismatch", - }) - if str(anchor["head_hash"]) != head: - errors.append({ - "index": len(rows), "id": "", "error": "anchor_head_mismatch", - }) - if str(anchor["integrity_error"] or ""): - errors.append({ - "index": len(rows), "id": "", "error": "anchor_integrity_error", - }) - expected_head = str(expected_head or "").strip() - if expected_head and head != expected_head: - errors.append({ - "index": len(rows), "id": "", "error": "expected_head_mismatch", - }) - if expected_count is not None: - try: - external_count = max(0, int(expected_count)) - except (TypeError, ValueError, OverflowError): - external_count = -1 - if external_count != len(rows): + with _receipt_read_transaction(conn): + chain = receipt_chain_state(conn, workspace_id) + rows = chain["rows"] + errors: list[dict] = list(chain["errors"]) + head = str(chain["head"] or "") + anchor = conn.execute( + "SELECT receipt_count, head_hash, integrity_error " + "FROM receipt_chain_heads WHERE workspace_id=?", + (workspace_id,), + ).fetchone() + if rows and anchor is None: + errors.append({"index": len(rows), "id": "", "error": "missing_anchor"}) + elif anchor is not None: + anchor_count = anchor["receipt_count"] + if type(anchor_count) is not int or anchor_count < 0 or anchor_count != len(rows): + errors.append({ + "index": len(rows), "id": "", "error": "anchor_count_mismatch", + }) + if str(anchor["head_hash"]) != head: + errors.append({ + "index": len(rows), "id": "", "error": "anchor_head_mismatch", + }) + if str(anchor["integrity_error"] or ""): + errors.append({ + "index": len(rows), "id": "", "error": "anchor_integrity_error", + }) + expected_head = str(expected_head or "").strip() + if expected_head and head != expected_head: errors.append({ - "index": len(rows), "id": "", "error": "expected_count_mismatch", + "index": len(rows), "id": "", "error": "expected_head_mismatch", }) - return { - "valid": not errors, - "count": len(rows), - "head": head, - "anchored": anchor is not None, - "errors": errors, - } + if expected_count is not None: + try: + external_count = max(0, int(expected_count)) + except (TypeError, ValueError, OverflowError): + external_count = -1 + if external_count != len(rows): + errors.append({ + "index": len(rows), "id": "", "error": "expected_count_mismatch", + }) + return { + "valid": not errors, + "count": len(rows), + "head": head, + "anchored": anchor is not None, + "errors": errors, + } def _fts5_available(conn: sqlite3.Connection | _SerializedConnection) -> bool: diff --git a/engraphis/factory.py b/engraphis/factory.py index 034ae1201..0617acd5d 100644 --- a/engraphis/factory.py +++ b/engraphis/factory.py @@ -44,6 +44,11 @@ def _is_prod_env() -> bool: return os.environ.get("ENGRAPHIS_ENV", "").strip().lower() == "prod" +def effective_require_exact_backends(configured: bool = False) -> bool: + """Return the exact-backend policy shared by the factory and the install doctor.""" + return bool(configured) or _is_prod_env() + + def _backend_identity(backend: Any, *, configured: str = "") -> dict: """Return the configured vs resolved identity for one constructed backend.""" resolved = type(backend).__name__ if backend is not None else "none" @@ -149,8 +154,7 @@ def create_memory_engine( for writable files; ``balanced`` explicitly requests WAL + NORMAL. In-memory databases are not persistent and read-only opens are unchanged. """ - if _is_prod_env(): - require_exact_backends = True + require_exact_backends = effective_require_exact_backends(require_exact_backends) # In exact mode "auto" must not silently degrade to the portable reference: # require the native backend so a missing extension fails closed instead. effective_vector_backend = ( diff --git a/scripts/init.py b/scripts/init.py index 9ec177183..5e921798e 100644 --- a/scripts/init.py +++ b/scripts/init.py @@ -105,7 +105,7 @@ def _report_receipt_chains( scopes = conn.execute( "SELECT workspace_id, COUNT(*) AS receipts FROM operation_receipts " "WHERE workspace_id IS NOT NULL " - "GROUP BY workspace_id ORDER BY receipts DESC LIMIT ?", + "GROUP BY workspace_id ORDER BY MAX(ts) DESC LIMIT ?", (_DOCTOR_RECEIPT_SCOPES,), ).fetchall() from engraphis.core.store import verify_receipt_chain @@ -132,8 +132,8 @@ def _report_receipt_chains( elif len(scopes) < scope_total: report( "receipt_chain", "optional", "operation receipts", - f"{sampled} verified over {total} receipt(s); the newest scopes were " - "checked because the install check is bounded.", + f"{sampled} verified over {total} receipt(s); the most recently written " + "scopes were checked because the install check is bounded.", ) else: report( @@ -177,9 +177,14 @@ def fail_database(exc: Exception) -> None: "processes and retry.", ) return - if encryption_code in { - "key_rejected", "key_config_invalid", "key_file_empty", "key_file_unreadable", - }: + if encryption_code in {"key_file_empty", "key_file_unreadable"}: + report( + "database_key_file", "fail", "database readable", + "ENGRAPHIS_DB_KEY_FILE is empty or unreadable; the database was not opened. " + "Fix or re-point the key file; do not regenerate the database key.", + ) + return + if encryption_code in {"key_rejected", "key_config_invalid"}: report( "database_key", "fail", "database readable", "The configured encryption key did not unlock this database. Restore the key " @@ -246,16 +251,8 @@ def fail_database(exc: Exception) -> None: conn.execute("PRAGMA busy_timeout=2000") conn.execute("PRAGMA user_version").fetchone() report("database_readable", "ok", "database readable", str(db)) - # A TEMP table would only probe the temp database. Exercise the main database - # under a transaction, then roll back both schema and data unconditionally. - probe = "_engraphis_doctor_" + secrets.token_hex(12) - try: - conn.execute("BEGIN IMMEDIATE") - conn.execute(f'CREATE TABLE "{probe}" (value INTEGER)') - conn.execute(f'INSERT INTO "{probe}" (value) VALUES (1)') - finally: - conn.rollback() - report("database_writable", "ok", "database writable", str(db)) + # Receipt verification is read-only. Run it before the write probe so a preserved + # read-only copy still gets a diagnosis instead of only "database unwritable". database_stage = "schema" from engraphis.core.schema import SCHEMA_VERSION has_migrations = conn.execute( @@ -274,6 +271,17 @@ def fail_database(exc: Exception) -> None: else: report("database_schema", "optional", "database schema", "Not initialized; the engine will initialize the database on first use.") + database_stage = "connection" + # A TEMP table would only probe the temp database. Exercise the main database + # under a transaction, then roll back both schema and data unconditionally. + probe = "_engraphis_doctor_" + secrets.token_hex(12) + try: + conn.execute("BEGIN IMMEDIATE") + conn.execute(f'CREATE TABLE "{probe}" (value INTEGER)') + conn.execute(f'INSERT INTO "{probe}" (value) VALUES (1)') + finally: + conn.rollback() + report("database_writable", "ok", "database writable", str(db)) except Exception as exc: if database_stage == "schema": report("database_schema", "fail", "database schema", @@ -315,8 +323,12 @@ def fail_database(exc: Exception) -> None: try: from engraphis.backends.embedder_st import get_embedder - emb = get_embedder(settings.embed_model or None, dim=settings.embed_dim or 384, - require_exact=settings.require_exact_backends) + from engraphis.factory import effective_require_exact_backends + emb = get_embedder( + settings.embed_model or None, + dim=settings.embed_dim or 384, + require_exact=effective_require_exact_backends(settings.require_exact_backends), + ) emb.embed(["engraphis doctor check"]) report("embedder", "ok", "embedder functional", f"{type(emb).__name__} ({getattr(emb, 'dim', 384)}d)") except Exception as exc: diff --git a/tests/test_init.py b/tests/test_init.py index 290992d56..544fc1981 100644 --- a/tests/test_init.py +++ b/tests/test_init.py @@ -447,6 +447,27 @@ def damaged_connector(_path): assert "key" in check["detail"] +@pytest.mark.parametrize("code", ["key_file_empty", "key_file_unreadable"]) +def test_doctor_reports_key_file_codes_separately( + tmp_path, monkeypatch, capsys, code, +): + _fresh_settings(monkeypatch, tmp_path) + from engraphis.backends.encrypted_db import EncryptionError + + def bad_connector(_path): + raise EncryptionError("private provider detail", code=code) + + monkeypatch.setattr(init_script, "connector_from_env", lambda: bad_connector) + assert main(["--check", "--json"]) == 1 + report = json.loads(capsys.readouterr().out) + check = next( + item for item in report["checks"] if item["code"] == "database_key_file" + ) + assert check["status"] == "fail" + assert "ENGRAPHIS_DB_KEY_FILE" in check["detail"] + assert "private provider detail" not in json.dumps(report) + + def test_doctor_reports_lock_contention_without_leaving_probe_state(tmp_path, monkeypatch, capsys): _fresh_settings(monkeypatch, tmp_path) path = tmp_path / "doc.db" @@ -483,6 +504,29 @@ def fail(*_args, **kwargs): assert any(check["code"] == "embedder" and check["status"] == "fail" for check in report["checks"]) +def test_doctor_uses_prod_exactness_even_when_setting_is_off( + tmp_path, monkeypatch, capsys, +): + _fresh_settings(monkeypatch, tmp_path) + import engraphis.config as cfg + import engraphis.backends.embedder_st as backend + monkeypatch.setenv("ENGRAPHIS_ENV", "prod") + monkeypatch.setattr(cfg.settings, "require_exact_backends", False) + + def fail(*_args, **kwargs): + assert kwargs["require_exact"] is True + raise RuntimeError("private-provider-secret") + + monkeypatch.setattr(backend, "get_embedder", fail) + assert main(["--check", "--json"]) == 1 + report = json.loads(capsys.readouterr().out) + assert any( + check["code"] == "embedder" and check["status"] == "fail" + for check in report["checks"] + ) + assert "private-provider-secret" not in json.dumps(report) + + def test_init_records_only_explicit_installation_intent(tmp_path, monkeypatch, capsys): from scripts import installation_profile monkeypatch.chdir(tmp_path) diff --git a/tests/test_receipt_snapshot.py b/tests/test_receipt_snapshot.py new file mode 100644 index 000000000..f2b6c93e1 --- /dev/null +++ b/tests/test_receipt_snapshot.py @@ -0,0 +1,54 @@ +"""Receipt-chain verification reads rows and anchor from one snapshot.""" +from __future__ import annotations + +from engraphis.core.store import Store, verify_receipt_chain + + +class _TracingConnection: + def __init__(self, conn): + self._conn = conn + self.statements: list[str] = [] + + def execute(self, sql, *args, **kwargs): + self.statements.append(str(sql).strip().upper()) + return self._conn.execute(sql, *args, **kwargs) + + def rollback(self): + self.statements.append("ROLLBACK") + return self._conn.rollback() + + def __getattr__(self, name): + return getattr(self._conn, name) + + +def _store_with_chain() -> tuple[Store, str]: + store = Store(":memory:") + workspace_id = store.get_or_create_workspace("snapshot") + store.record_receipt("remember", workspace_id=workspace_id) + return store, workspace_id + + +def test_verify_receipt_chain_owns_a_read_transaction() -> None: + store, workspace_id = _store_with_chain() + trace = _TracingConnection(store.conn) + try: + result = verify_receipt_chain(trace, workspace_id=workspace_id) + finally: + store.close() + assert result["valid"] is True + assert trace.statements[0] == "BEGIN" + assert trace.statements[-1] == "ROLLBACK" + + +def test_verify_receipt_chain_preserves_a_caller_owned_transaction() -> None: + store, workspace_id = _store_with_chain() + store.conn.execute("BEGIN IMMEDIATE") + trace = _TracingConnection(store.conn) + try: + result = verify_receipt_chain(trace, workspace_id=workspace_id) + assert result["valid"] is True + assert store.conn.transaction_owned_by_current_thread() is True + assert "ROLLBACK" not in trace.statements + finally: + store.conn.rollback() + store.close() From c4c9484e01ca2bc7040e62712711e106eefd58fc Mon Sep 17 00:00:00 2001 From: coding-dev-tools Date: Thu, 5 Nov 2026 04:26:06 -0500 Subject: [PATCH 7/7] docs(benchmarks): rebind receipt diagnostics evidence to the atomic doctor The read transaction and shared exactness policy change two producer files. Re-record their hashes, recompute the suite digest and artifact SHA, and repin the README, benchmark guide, tests, and both SVG/PNG provenance pairs. --- BENCHMARKS.md | 4 ++-- README.md | 2 +- ...fixtures-receipt-diagnostics-20261103.json | 6 +++--- ...s-receipt-diagnostics-20261103.json.sha256 | 2 +- docs/images/context-efficiency.png | Bin 61488 -> 61488 bytes docs/images/context-efficiency.svg | 4 ++-- .../images/evidence-backed-agent-examples.png | Bin 273392 -> 273392 bytes .../images/evidence-backed-agent-examples.svg | 4 ++-- tests/test_benchmark_evidence.py | 2 +- 9 files changed, 12 insertions(+), 12 deletions(-) diff --git a/BENCHMARKS.md b/BENCHMARKS.md index 91949626d..f069e3bbf 100644 --- a/BENCHMARKS.md +++ b/BENCHMARKS.md @@ -96,12 +96,12 @@ interpretation and do not count as additional benchmark-quality gains. Every exact public aggregate retained below comes from the checked-in, public-safe [`offline-fixtures-receipt-diagnostics-20261103.json`](docs/benchmark-evidence/offline-fixtures-receipt-diagnostics-20261103.json) artifact. Its SHA-256 is -`f95e35b9f2c8f848925df0d0db0bd34898280b9cd10a884ed188b11d1708dcb2`, also recorded in the +`62fc90a14e36622943e70f509cf2cd172239be9c862f8d51e64060ebe6f1496f`, also recorded in the adjacent `.sha256` file. The artifact contains no raw questions, answers, prompts, customer data, or per-record content fingerprints. The fixture-suite digest is -`f66eca5e293df53e73504c3c70aee793812059dafc485475a9a4e9a1cd61b836`. The artifact defines +`cee29f122abcdbf6a8874073d58840720b1724ab0a3ed125822841707b066b77`. The artifact defines the digest algorithm and records the SHA-256 of every suite and dataset file. Each evidence ID also binds its exact command through `sha256(UTF-8 exact command)`: diff --git a/README.md b/README.md index 6d35f85a5..2926d2310 100644 --- a/README.md +++ b/README.md @@ -64,7 +64,7 @@ Pro is $10/month or $100/year for one owner. Team is $20/seat/month or $200/seat The current registered artifact contains three deterministic offline fixture runs. It separates context size, retrieval quality, and grounded decision checks; these small fixtures do not establish general task performance.

- Three registered offline fixtures: structure-aware chunking reduced mean retrieved context from 740.3 to 214.3 tokens per question (71.1%, 18 questions); the serialized JSON-shape proxy fell from 24,590 to 11,138 tokens across 26 payload samples and 260 recalls. Candidate and packed retrieval quality (Recall@5, Hit@5, and answer-token recall) are shown separately (each 1.000). Grounded checks show 5/5 answerable queries grounded and 6/6 abstention queries rejected, including a 1/1 quarantined-evidence probe; 11/11 decisions were correct. MCP transport and provider billing were not measured. Artifact SHA-256 prefix f95e35b9f2c8; see the benchmark guide for the full checksum. + Three registered offline fixtures: structure-aware chunking reduced mean retrieved context from 740.3 to 214.3 tokens per question (71.1%, 18 questions); the serialized JSON-shape proxy fell from 24,590 to 11,138 tokens across 26 payload samples and 260 recalls. Candidate and packed retrieval quality (Recall@5, Hit@5, and answer-token recall) are shown separately (each 1.000). Grounded checks show 5/5 answerable queries grounded and 6/6 abstention queries rejected, including a 1/1 quarantined-evidence probe; 11/11 decisions were correct. MCP transport and provider billing were not measured. Artifact SHA-256 prefix 62fc90a14e36; see the benchmark guide for the full checksum.
Three offline fixtures separate context reduction, candidate and packed retrieval quality, and grounded behavior. The chart shows the artifact checksum prefix; see the benchmark guide for the full checksum and reproduction steps.

diff --git a/docs/benchmark-evidence/offline-fixtures-receipt-diagnostics-20261103.json b/docs/benchmark-evidence/offline-fixtures-receipt-diagnostics-20261103.json index d5587d4b8..e35d38756 100644 --- a/docs/benchmark-evidence/offline-fixtures-receipt-diagnostics-20261103.json +++ b/docs/benchmark-evidence/offline-fixtures-receipt-diagnostics-20261103.json @@ -103,7 +103,7 @@ ], "schema": "engraphis-public-offline-fixtures/v1", "suite": { - "digest": "f66eca5e293df53e73504c3c70aee793812059dafc485475a9a4e9a1cd61b836", + "digest": "cee29f122abcdbf6a8874073d58840720b1724ab0a3ed125822841707b066b77", "digest_method": "sha256(canonical compact JSON mapping each sorted path to its file SHA-256)", "files": { "engraphis/__init__.py": "1cde9f19e05ddc4f5f28517e00b0a7bd5ebf09d6be8ec2681cebf5b698195d70", @@ -171,7 +171,7 @@ "engraphis/core/secrets.py": "78ab40870d6c5918c189307028b107df239d08cd918abe08c85fa15b58fa1663", "engraphis/core/source_paths.py": "2e71de7f19f260b335137602f07a28a80efa5d65e3d2497215356ba5f172b47e", "engraphis/core/spec_crawl.py": "23359f9e28a2a68a3abe59f1e705b8be1f6c72880d708123bc85391029e769e5", - "engraphis/core/store.py": "f42e407803b3d9701c1d4975bcef8c8b69e3a4749b1303e6c4b4d6ae31c06a15", + "engraphis/core/store.py": "f3a2586c2802021901cc5052a2a782a7463dbdfa8f26487ed490c02a1e1257af", "engraphis/core/sync.py": "0d28dd697eb8e8acdb7fed3e346fcba8fce23cbf4606eb11de68cb1fb001fcf2", "engraphis/core/textutil.py": "acd65031729fa5d91d09527b8eb52518b83cfce77a55e6b7ae2d94686e35c1a6", "engraphis/core/user_model.py": "3147ec8ee7cfd855783f639874b63f331cdc822bd8bd9298cfb326dd18666026", @@ -188,7 +188,7 @@ "engraphis/engines/recall.py": "f979580d065599c07acbc3add59f71e52d9a186a2104b0e257daa7959ea88d26", "engraphis/engines/reweight.py": "91ec5815f5d356a7068c36133d24405334450f361f902f99275bed3ccebffd49", "engraphis/engines/thoughts.py": "4adb9c8a9bcfe736cb42fff9b5ce24631da6ec473d178e83f1c176fde3b8b814", - "engraphis/factory.py": "be26899074d80b2f96fff9a5a70969894e818dc71e2cfdf4f7ee965f1f4cc550", + "engraphis/factory.py": "d325b4a0879030377981e011954551c4f0da1039ed0c615ef083050ba0508990", "engraphis/file_guard.py": "44eccaeca3afef7149bfdfea2321c325472253ee39a9e0bc24c720b3da3d685b", "engraphis/graphdata.py": "f5dc93395f52203c66deaeec8bc58d5c1f990a35a535151e0b9a84cecf7d8430", "engraphis/hosted_client.py": "5b56b0d2384cf24ecb409090af15f09e437ec8ab5a98bed931384daff46b4f48", diff --git a/docs/benchmark-evidence/offline-fixtures-receipt-diagnostics-20261103.json.sha256 b/docs/benchmark-evidence/offline-fixtures-receipt-diagnostics-20261103.json.sha256 index 6ae6dedff..346e0ee7e 100644 --- a/docs/benchmark-evidence/offline-fixtures-receipt-diagnostics-20261103.json.sha256 +++ b/docs/benchmark-evidence/offline-fixtures-receipt-diagnostics-20261103.json.sha256 @@ -1 +1 @@ -f95e35b9f2c8f848925df0d0db0bd34898280b9cd10a884ed188b11d1708dcb2 offline-fixtures-receipt-diagnostics-20261103.json +62fc90a14e36622943e70f509cf2cd172239be9c862f8d51e64060ebe6f1496f offline-fixtures-receipt-diagnostics-20261103.json diff --git a/docs/images/context-efficiency.png b/docs/images/context-efficiency.png index fdbc6c734b0de4faa24a18684fcf673da8701471..d8b98b6d5cd7098691535bd4d6af3f7152b85ebd 100644 GIT binary patch delta 153 zcmWN{p#cIh3;;mE09XtYVDf32CYl$lk*3KiSlHx|%)kV=mpB9h3#V^cH_N)Y9B0OK z1)C@eGon(723bKp(Z|#fi}p~Lf0R+DU;>8d!8ws~ZakK7GO4(CBLnX+h`-O>{q{P&e*k$f&Hw-a delta 153 zcmWN}yAi@L3;@s~bFhOxmMzz$U;ze*Bx@B~*dz?VBpi1s>A0h$BIRq|x=rggU5|5i z87$V5TvLm7l1K;$0}I}*q+s0xVNoq3P}L9v8)&%Em*{u99Oi)qH7a>hYaAmfjY|ax fFCEFyipj!Mo5 Offline context, retrieval, and grounded benchmark results -Artifact-driven offline benchmark report with context efficiency, retrieval quality, and grounded behavior reported separately. Structure-aware chunking reports 740.3 to 214.3 retrieved tokens per question. The JSON-shape payload proxy reports 24,590 full versus 11,138 compact tokens; MCP transport was not measured; provider billing was not measured. Retrieved candidate quality is Recall@5 1.000, Hit@5 1.000, and answer-token recall 1.000 across 26 questions. Packed context quality is Recall@5 1.000, Hit@5 1.000, and answer-token recall 1.000 across 26 questions. Grounded checks score 5 of 5 answerable queries grounded and 6 of 6 abstention queries rejected, including 1 of 1 quarantined-evidence checks. Decision accuracy is 1.000 across 11 decisions. Source artifact SHA-256 f95e35b9f2c8f848925df0d0db0bd34898280b9cd10a884ed188b11d1708dcb2. +Artifact-driven offline benchmark report with context efficiency, retrieval quality, and grounded behavior reported separately. Structure-aware chunking reports 740.3 to 214.3 retrieved tokens per question. The JSON-shape payload proxy reports 24,590 full versus 11,138 compact tokens; MCP transport was not measured; provider billing was not measured. Retrieved candidate quality is Recall@5 1.000, Hit@5 1.000, and answer-token recall 1.000 across 26 questions. Packed context quality is Recall@5 1.000, Hit@5 1.000, and answer-token recall 1.000 across 26 questions. Grounded checks score 5 of 5 answerable queries grounded and 6 of 6 abstention queries rejected, including 1 of 1 quarantined-evidence checks. Decision accuracy is 1.000 across 11 decisions. Source artifact SHA-256 62fc90a14e36622943e70f509cf2cd172239be9c862f8d51e64060ebe6f1496f. @@ -65,6 +65,6 @@ Quarantined-evidence probe 1 / 1 abstained Decision accuracy 1.000 (11 / 11); the quarantine probe is included in the abstention total. -SOURCE SHA-256 f95e35b9f2c8 +SOURCE SHA-256 62fc90a14e36 JSON proxy only; MCP transport not measured; provider billing not measured. diff --git a/docs/images/evidence-backed-agent-examples.png b/docs/images/evidence-backed-agent-examples.png index 906092b24e2010331aef0a910a9ed1ae898bf23b..97197a90ab209dc00d7d2d2f310668a849fcf9e9 100644 GIT binary patch delta 165 zcmWN|yA8rH5CBl|k)R_BFhLrB?&GV;BC&n92Sr2+gHSRB17ro4kr1LK{d>#YEpvCj z3>2)08$D}%GT4Ac_mB5kHtYjX9?DTo}kK1{ultz(KBxRN) u?2v^shm{#7Vy+aDD6bJf6)hJWGZ1kO delta 165 zcmWN}yA1*{41i(rDCoEan1CjBUQW#|a<=0=C?Z;D*o2xfZiSR5N+cw%{H0I-G7rl< z+%Kb*SbgMTC}b3+4ZGHFZ`@{JU2ER4< diff --git a/docs/images/evidence-backed-agent-examples.svg b/docs/images/evidence-backed-agent-examples.svg index c9826205f..a8618b453 100644 --- a/docs/images/evidence-backed-agent-examples.svg +++ b/docs/images/evidence-backed-agent-examples.svg @@ -1,6 +1,6 @@ Three evidence-backed Engraphis agent behaviors - A three-card summary of deterministic offline fixtures. Focused context returns 740.3 to 214.3 tokens while retaining Recall at 5 of 1.000. A grounded answer returns support for 5/5 answerable questions. An unsupported question safely abstains for 6/6 off-topic questions. Reproduce with eval.chunking_eval and eval.grounded. Exact commands and config digests are registered in BENCHMARKS.md. Public-safe artifact SHA-256: f95e35b9f2c8f848925df0d0db0bd34898280b9cd10a884ed188b11d1708dcb2. + A three-card summary of deterministic offline fixtures. Focused context returns 740.3 to 214.3 tokens while retaining Recall at 5 of 1.000. A grounded answer returns support for 5/5 answerable questions. An unsupported question safely abstains for 6/6 off-topic questions. Reproduce with eval.chunking_eval and eval.grounded. Exact commands and config digests are registered in BENCHMARKS.md. Public-safe artifact SHA-256: 62fc90a14e36622943e70f509cf2cd172239be9c862f8d51e64060ebe6f1496f. @@ -47,5 +47,5 @@
Reproduce: eval.chunking_eval + eval.grounded - SHA256 f95e35b9f2c8f848925df0d0db0bd34898280b9cd10a884ed188b11d1708dcb2 + SHA256 62fc90a14e36622943e70f509cf2cd172239be9c862f8d51e64060ebe6f1496f diff --git a/tests/test_benchmark_evidence.py b/tests/test_benchmark_evidence.py index 9d4f02f3b..6ce29ca64 100644 --- a/tests/test_benchmark_evidence.py +++ b/tests/test_benchmark_evidence.py @@ -35,7 +35,7 @@ ROOT = Path(__file__).resolve().parents[1] PUBLIC_OFFLINE_ARTIFACT = "offline-fixtures-receipt-diagnostics-20261103.json" -PUBLIC_OFFLINE_SHA = "f95e35b9f2c8f848925df0d0db0bd34898280b9cd10a884ed188b11d1708dcb2" +PUBLIC_OFFLINE_SHA = "62fc90a14e36622943e70f509cf2cd172239be9c862f8d51e64060ebe6f1496f" @pytest.fixture(scope="module")