From 510b907c77a8239feec519d036e559e507332e08 Mon Sep 17 00:00:00 2001 From: Christian Pojoni <34570565+5queezer@users.noreply.github.com> Date: Sat, 18 Jul 2026 12:55:17 +0200 Subject: [PATCH 1/2] docs: scope policy guidance in MCP instructions, README, and ADRs 0021/0022 - mcp.rs: add scope policy sentence (explicit override vs resolved scope, scope_walk semantics) to the MCP tool instructions card - scope_resolution.rs: fix stale migration reference 0084 -> 0085 - README: document ADR-0021/0022 scoped memory parameters and scope_walk usage - ADR 0021/0022: content updates to match implementation - mcp_instructions.rs: pin the new instruction text - autoresearch.jsonl: append run logs 539-540 --- README.md | 4 ++- autoresearch.jsonl | 2 ++ crates/aver-server/src/mcp.rs | 1 + crates/aver-server/src/scope_resolution.rs | 2 +- crates/aver-server/tests/mcp_instructions.rs | 6 ++++ ...1-scope-as-first-class-memory-dimension.md | 33 ++++++++++--------- doc/adr/0022-connection-scope-resolution.md | 26 +++++++-------- 7 files changed, 44 insertions(+), 30 deletions(-) diff --git a/README.md b/README.md index a002463..9cca2e7 100644 --- a/README.md +++ b/README.md @@ -39,7 +39,7 @@ The goal is a trustworthy substrate for coding agents that can: - **Candidate claim workflow** — episodic events can produce staged claims that are promoted or rejected explicitly. - **Observation continuity surfaces** — episodic events can produce privacy-checked, source-backed observations that are recallable by ID, summarized by compaction, and coverage-accounted across full session event ranges. - **Continuity reliability controls** — coverage accounting, deterministic `catch-up`, gap warnings in summaries, destructive prune operations blocked when gaps remain, prune markers preserved in logs, and audit-aware observation recall. -- **MCP/OAuth server** — `aver-server` exposes memory tools over Streamable HTTP MCP behind a local OAuth-style token flow, including the ADR-0008 five-tool surface with validated recall/write/event/extraction-trigger parameters, observation projection tools, explicit unsupported-scope errors, persisted confidence overrides, and recall subgraphs with confidence floors. +- **MCP/OAuth server** — `aver-server` exposes memory tools over Streamable HTTP MCP behind a local OAuth-style token flow, including the ADR-0008 five-tool surface with validated recall/write/event/extraction-trigger parameters, observation projection tools, ADR-0021/0022 scoped memory parameters and connection defaults, persisted confidence overrides, and recall subgraphs with confidence floors. - **Evaluation harnesses** — fixture evaluation plus a BEAM100K runner using local Ollama for embeddings, answer generation, and judging. ## Quick Start @@ -219,6 +219,8 @@ MCP currently exposes 18 tools through a progressive discovery card so agents ke Aver's MCP guide is intentionally proactive but selective: agents should recall first, then record durable user-shared preferences, project facts, and long-lived working context even when the user does not say "remember this" explicitly. Identity details should be recorded only when they are necessary, user-shared, and not sensitive personal data. When durability is uncertain, agents should prefer `record_event` over `remember_claim`, and they must not store secrets, credentials, sensitive personal data, transient chat, or facts they cannot explain with provenance. +Scope is a first-class memory dimension. Write tools such as `remember_claim`, `add_triple`, `record_event`, `record_observation`, and `propose_candidate_claim` accept an optional `scope`; when omitted through MCP, the request's ADR-0022 resolved scope applies, falling back to `global` for clients that send no scope signal. Read tools (`recall`, `expand`) accept `scope` plus `scope_walk`: use `ancestors` for normal project memory, `exact` for isolation, `descendants` for subtree audits, and `any` only for intentional cross-scope search. Vector chunks do not carry their own scope; they attach to a claim and inherit the claim's effective scope. + CLI-only continuity and maintenance surfaces (`catch-up`, `compaction-summary`) are implemented in `aver-cli`; MCP exposes `record_observation`, `assemble_compaction_summary`, and the observation audit tools above, while claim-maintenance tasks stay available through the four advanced tools when agents explicitly need them. Adapter boundaries are explicit in `aver-server` via the `adapters` module (`Pi`, `ClaudeCode`, `CodexOpenAi`, `OpenCode`, `Mcp`, `JsonlCliHarness`) so host runtimes can be added without leaking host-specific logic into `aver-core`. diff --git a/autoresearch.jsonl b/autoresearch.jsonl index 5401ad3..c3ec68a 100644 --- a/autoresearch.jsonl +++ b/autoresearch.jsonl @@ -565,3 +565,5 @@ {"run":536,"commit":"74552c9","metric":807,"metrics":{"tests_green":807,"tests_total":807,"milestone_index":9,"loc_core":6951,"commit_count_total":600,"memory_error_rate":0,"unsupported_claim_rate":0,"mean_precision_at_k":1,"mean_recall_at_k":1,"beam_questions":0,"beam_rubric_checks":0,"beam_abstention_pct":0,"beam_contradiction_resolution_pct":0,"beam_event_ordering_pct":0,"beam_information_extraction_pct":0,"beam_instruction_following_pct":0,"beam_knowledge_update_pct":0,"beam_multi_session_reasoning_pct":0,"beam_preference_following_pct":0,"beam_summarization_pct":0,"beam_temporal_reasoning_pct":0},"status":"keep","description":"Reject non-positive predicate closure children","timestamp":1779052727528,"segment":28,"confidence":6.3538461538461535,"asi":{"hypothesis":"Predicate closure child_id links should reject non-positive values so predicate ontology transitive-closure rows get precise diagnostics before foreign-key handling.","red_evidence":"`cargo test -q -p aver-core --test migrations predicate_closure_child_id_must_be_positive` failed for the intended reason after using the seeded `uses` predicate type: child_id=0 produced a generic foreign-key error.","green_evidence":"Targeted predicate closure child-id test passed; full migrations test passed with 110 tests; `./autoresearch.sh` passed with checks green and metrics tests_green=807 tests_total=807.","implementation_notes":"Added migration `0115_predicate_closure_child_id_positive_checks.sql` with insert/update triggers rejecting predicate_closure.child_id <= 0.","anti_overfit_note":"This is a general predicate ontology closure integrity invariant and does not use benchmark expected outputs or labels."}} {"run":537,"commit":"74fbeab","metric":808,"metrics":{"tests_green":808,"tests_total":808,"milestone_index":9,"loc_core":6951,"commit_count_total":601,"memory_error_rate":0,"unsupported_claim_rate":0,"mean_precision_at_k":1,"mean_recall_at_k":1,"beam_questions":0,"beam_rubric_checks":0,"beam_abstention_pct":0,"beam_contradiction_resolution_pct":0,"beam_event_ordering_pct":0,"beam_information_extraction_pct":0,"beam_instruction_following_pct":0,"beam_knowledge_update_pct":0,"beam_multi_session_reasoning_pct":0,"beam_preference_following_pct":0,"beam_summarization_pct":0,"beam_temporal_reasoning_pct":0},"status":"keep","description":"Reject non-positive predicate closure ancestors","timestamp":1779052824413,"segment":28,"confidence":6.369230769230769,"asi":{"hypothesis":"Predicate closure ancestor_id links should reject non-positive values so predicate ontology transitive-closure rows get precise diagnostics before foreign-key handling.","red_evidence":"`cargo test -q -p aver-core --test migrations predicate_closure_ancestor_id_must_be_positive` failed because ancestor_id=0 produced a generic foreign-key error.","green_evidence":"Targeted predicate closure ancestor-id test passed; full migrations test passed with 111 tests; `./autoresearch.sh` passed with checks green and metrics tests_green=808 tests_total=808.","implementation_notes":"Added migration `0116_predicate_closure_ancestor_id_positive_checks.sql` with insert/update triggers rejecting predicate_closure.ancestor_id <= 0.","anti_overfit_note":"This is a general predicate ontology closure integrity invariant and does not use benchmark expected outputs or labels."}} {"run":538,"commit":"5547e6f","metric":809,"metrics":{"tests_green":809,"tests_total":809,"milestone_index":9,"loc_core":6951,"commit_count_total":602,"memory_error_rate":0,"unsupported_claim_rate":0,"mean_precision_at_k":1,"mean_recall_at_k":1,"beam_questions":0,"beam_rubric_checks":0,"beam_abstention_pct":0,"beam_contradiction_resolution_pct":0,"beam_event_ordering_pct":0,"beam_information_extraction_pct":0,"beam_instruction_following_pct":0,"beam_knowledge_update_pct":0,"beam_multi_session_reasoning_pct":0,"beam_preference_following_pct":0,"beam_summarization_pct":0,"beam_temporal_reasoning_pct":0},"status":"keep","description":"Reject blank predicate alias notes","timestamp":1779052989417,"segment":28,"confidence":6.384615384615385,"asi":{"hypothesis":"Predicate alias notes are optional, but when present they should be nonblank so alias provenance remains meaningful and auditable.","red_evidence":"`cargo test -q -p aver-core --test migrations predicate_alias_note_must_not_be_blank_when_set` failed because direct SQL accepted a whitespace-only note.","green_evidence":"Targeted predicate alias note test passed; full migrations test passed with 112 tests; `./autoresearch.sh` passed with checks green and metrics tests_green=809 tests_total=809.","implementation_notes":"Added migration `0117_predicate_alias_note_nonblank_checks.sql` with insert/update triggers rejecting non-null trimmed-empty predicate_alias.note values.","anti_overfit_note":"This is a general predicate alias auditability invariant and does not use benchmark expected outputs or labels."}} +{"run":539,"commit":"54da71f","metric":810,"metrics":{"tests_green":810,"tests_total":810,"milestone_index":9,"loc_core":6951,"commit_count_total":603,"memory_error_rate":0,"unsupported_claim_rate":0,"mean_precision_at_k":1,"mean_recall_at_k":1,"beam_questions":0,"beam_rubric_checks":0,"beam_abstention_pct":0,"beam_contradiction_resolution_pct":0,"beam_event_ordering_pct":0,"beam_information_extraction_pct":0,"beam_instruction_following_pct":0,"beam_knowledge_update_pct":0,"beam_multi_session_reasoning_pct":0,"beam_preference_following_pct":0,"beam_summarization_pct":0,"beam_temporal_reasoning_pct":0},"status":"keep","description":"Reject self-parent ontology extensions","timestamp":1779053189044,"segment":28,"confidence":6.351145038167939,"asi":{"hypothesis":"Ontology extension audit rows should reject self-parenting predicate relationships so the extension log cannot encode a cyclic immediate parent edge.","red_evidence":"`cargo test -q -p aver-core --test migrations ontology_extension_parent_must_not_self_reference` failed because direct SQL accepted predicate='uses' with parent='uses'.","green_evidence":"Targeted ontology extension self-parent test passed; full migrations test passed with 113 tests; `./autoresearch.sh` passed with checks green and metrics tests_green=810 tests_total=810.","implementation_notes":"Added migration `0118_ontology_extension_parent_not_self_checks.sql` with insert/update triggers rejecting ontology_extension_log rows where parent equals predicate.","anti_overfit_note":"This is a general ontology hierarchy audit invariant and does not use benchmark expected outputs or labels."}} +{"run":540,"commit":"54da71f","metric":471,"metrics":{"tests_green":471,"tests_total":472,"milestone_index":9,"loc_core":6951,"commit_count_total":604,"memory_error_rate":0,"unsupported_claim_rate":0,"mean_precision_at_k":0,"mean_recall_at_k":0,"beam_questions":0,"beam_rubric_checks":0,"beam_abstention_pct":0,"beam_contradiction_resolution_pct":0,"beam_event_ordering_pct":0,"beam_information_extraction_pct":0,"beam_instruction_following_pct":0,"beam_knowledge_update_pct":0,"beam_multi_session_reasoning_pct":0,"beam_preference_following_pct":0,"beam_summarization_pct":0,"beam_temporal_reasoning_pct":0},"status":"crash","description":"Predicate type created_via trigger broke legacy migration replay","timestamp":1779053310404,"segment":28,"confidence":6.4,"asi":{"hypothesis":"Predicate type created_via should reject blank creation-source strings for auditability.","rollback_reason":"`./autoresearch.sh` failed because vector_index migration replay hit `error in trigger predicate_type_created_via_nonblank_insert after drop column: no such column: NEW.created_via`; older migration replay paths may temporarily lack the audit column, so this trigger placement is unsafe.","next_action_hint":"Do not retry a trigger that references predicate_types.created_via without first understanding the legacy vector_index replay/drop-column path; prefer a different invariant on stable columns or a migration-safe implementation.","error_details":"crates/aver-core/tests/vector_index.rs migration_backfills_existing_vector_chunks_rows failed after targeted migrations tests passed.","anti_overfit_note":"The attempted invariant was general auditability, not benchmark-specific, but implementation violated migration replay compatibility."}} diff --git a/crates/aver-server/src/mcp.rs b/crates/aver-server/src/mcp.rs index 830c212..db67d76 100644 --- a/crates/aver-server/src/mcp.rs +++ b/crates/aver-server/src/mcp.rs @@ -500,6 +500,7 @@ fn mcp_tool_instructions() -> String { "Default active set: {}. ", "Decision policy: recall before answering when durable memory may matter; remember_claim only for explicit stable long-term facts; record_event for useful raw history; record_observation and assemble_compaction_summary for source-backed continuity. ", "Self-healing writes: predicates are strict, but unknown-predicate errors include fuzzy/semantic suggestions, accepted aliases, and a retry hint; retry with a suggested predicate rather than inventing new predicates or silently rewriting claims. Use `relates_to` as the safest generic fallback when no precise predicate fits. ", + "Scope policy: pass `scope` only for an explicit per-call override; otherwise the request's resolved scope applies. For scoped reads, use `scope_walk=ancestors` for normal project memory, `exact` for isolation, `descendants` for subtree audits, and `any` only for cross-scope search. ", "Proactive memory policy: record durable user-shared preferences, project facts, and long-lived working context even without a direct remember command; only record identity details when they are necessary, user-shared, and not sensitive personal data; prefer record_event over remember_claim when durability is uncertain. Avoid storing secrets, credentials, sensitive personal data, transient chat, or facts you cannot explain with provenance. ", "Default workflows: recall existing memory before answering or updating; record_event -> should_extract_memories -> propose_candidate_claim/list_candidate_claims -> promote_candidate_claim or reject_candidate_claim for event-to-claim promotion; record_observation -> recall_observation or observation_coverage -> assemble_compaction_summary for continuity and compaction. ", "Graph navigation tools available: {}. Progressively load graph navigation tools only after recall returns an entity or you already know an anchor: expand; use add_triple instead of remember_claim only when confidence/source control is required. ", diff --git a/crates/aver-server/src/scope_resolution.rs b/crates/aver-server/src/scope_resolution.rs index b9dde7a..2c8e165 100644 --- a/crates/aver-server/src/scope_resolution.rs +++ b/crates/aver-server/src/scope_resolution.rs @@ -37,7 +37,7 @@ impl ResolvedScope { } /// Validate a scope candidate without going through sqlite. Mirrors the -/// `[A-Za-z0-9_/-]` charset enforced by migration 0084. +/// `[A-Za-z0-9_/-]` charset enforced by migration 0085. fn validate(scope: &str, source: &'static str) -> anyhow::Result<()> { if scope.trim().is_empty() { anyhow::bail!("{source}: scope must not be blank"); diff --git a/crates/aver-server/tests/mcp_instructions.rs b/crates/aver-server/tests/mcp_instructions.rs index 1769118..65e63e9 100644 --- a/crates/aver-server/tests/mcp_instructions.rs +++ b/crates/aver-server/tests/mcp_instructions.rs @@ -23,6 +23,12 @@ fn mcp_instructions_are_a_progressive_discovery_card() { assert!(instructions.contains( "unknown-predicate errors include fuzzy/semantic suggestions, accepted aliases, and a retry hint" )); + assert!(instructions.contains( + "Scope policy: pass `scope` only for an explicit per-call override; otherwise the request's resolved scope applies." + )); + assert!(instructions.contains( + "For scoped reads, use `scope_walk=ancestors` for normal project memory, `exact` for isolation, `descendants` for subtree audits, and `any` only for cross-scope search." + )); assert!(instructions.contains( "Progressively load graph navigation tools only after recall returns an entity or you already know an anchor: expand; use add_triple instead of remember_claim only when confidence/source control is required." )); diff --git a/doc/adr/0021-scope-as-first-class-memory-dimension.md b/doc/adr/0021-scope-as-first-class-memory-dimension.md index 48443b1..5ce73bf 100644 --- a/doc/adr/0021-scope-as-first-class-memory-dimension.md +++ b/doc/adr/0021-scope-as-first-class-memory-dimension.md @@ -75,24 +75,26 @@ the existing two ACTIVE claims valid. ### Schema change -A new migration `migrations/00NN_scope_column.sql` adds: +Migration `migrations/0085_scope_column.sql` adds: ```sql -ALTER TABLE claims ADD COLUMN scope TEXT NOT NULL DEFAULT 'global'; -ALTER TABLE events ADD COLUMN scope TEXT NOT NULL DEFAULT 'global'; -ALTER TABLE observations ADD COLUMN scope TEXT NOT NULL DEFAULT 'global'; - -CREATE INDEX claims_scope ON claims(scope); -CREATE INDEX events_scope ON events(scope); -CREATE INDEX observations_scope ON observations(scope); +ALTER TABLE claims ADD COLUMN scope TEXT NOT NULL DEFAULT 'global'; +ALTER TABLE episodic_events ADD COLUMN scope TEXT NOT NULL DEFAULT 'global'; +ALTER TABLE observations ADD COLUMN scope TEXT NOT NULL DEFAULT 'global'; +ALTER TABLE candidate_claims ADD COLUMN scope TEXT NOT NULL DEFAULT 'global'; + +CREATE INDEX claims_scope ON claims(scope); +CREATE INDEX episodic_events_scope ON episodic_events(scope); +CREATE INDEX observations_scope ON observations(scope); +CREATE INDEX candidate_claims_scope ON candidate_claims(scope); ``` Triggers analogous to `claims_agent_id_*_insert/update` (`migrations/0060`–`0061`) enforce `scope` non-blank and a charset of `[A-Za-z0-9_/-]` (note the `/` — paths are first-class). -The candidate-claim staging table inherits a `scope` column on the same -migration so candidates carry the writer's intended scope through promotion. +The candidate-claim staging table carries the writer's intended scope through +promotion. ### Path convention @@ -129,11 +131,12 @@ this one. ### Write-path semantics -`add_triple`, `remember_claim`, `record_event`, `record_observation`, -`propose_candidate_claim`, and `add_vector_chunk` accept an optional `scope`. -When omitted: `'global'` (preserves today's behavior). `promote_candidate_claim` -copies the candidate's `scope` onto the durable claim; it is not a separate -parameter on promote. +`add_triple`, `remember_claim`, `record_event`, `record_observation`, and +`propose_candidate_claim` accept an optional `scope`. When omitted: `'global'` +(preserves today's behavior). `promote_candidate_claim` copies the candidate's +`scope` onto the durable claim; it is not a separate parameter on promote. +`add_vector_chunk` does not accept a separate scope because vector chunks attach +to a claim and inherit the claim's effective scope. ### Migration diff --git a/doc/adr/0022-connection-scope-resolution.md b/doc/adr/0022-connection-scope-resolution.md index f4328c5..087b94e 100644 --- a/doc/adr/0022-connection-scope-resolution.md +++ b/doc/adr/0022-connection-scope-resolution.md @@ -8,11 +8,11 @@ Proposed ## Context -ADR-0021 introduces `scope` as a first-class column on `claims`, `events`, and -`observations`, with a hierarchical path convention (`global`, `proj/`, -`proj//branch/`, `session/`). It deliberately defers the -question of *how a client picks the scope* to a follow-on ADR. This is that -ADR. +ADR-0021 introduces `scope` as a first-class column on `claims`, +`episodic_events`, `observations`, and `candidate_claims`, with a hierarchical +path convention (`global`, `proj/`, `proj//branch/`, +`session/`). It deliberately defers the question of *how a client picks the +scope* to a follow-on ADR. This is that ADR. The motivating asymmetry is between two kinds of clients: @@ -98,9 +98,9 @@ headers via HTTP 400 — failing fast rather than silently writing under `'global'`. `X-Aver-Scope-Default` follows the same validation but is only consulted when the request has neither a tool parameter nor `X-Aver-Scope`. -The header is recorded on every write into `events.source` (or analogous -provenance fields) so that scope decisions are auditable from the JSONL log -alone, independent of the SQLite materialization. +The header is recorded on every event write into `episodic_events.source` (or +analogous provenance fields) so that scope decisions are auditable from the +JSONL log alone, independent of the SQLite materialization. ### `aver-scope-shim` @@ -196,7 +196,7 @@ manually passing parameters. use, not just Claude Code. Hermes, Pi, and Codex harnesses get the same isolation the day they switch their MCP URL to the shim. - (+) Resolution is fully observable: `X-Aver-Scope` recorded in - `events.source`, scope value persisted on every row. ADR-0019's + `episodic_events.source`, scope value persisted on every row. ADR-0019's JSONL-as-source-of-truth invariant survives — the log can be replayed and every row's scope reconstructed. - (+) Backwards compatibility is bounded and explicit. A pre-Layer-2 client @@ -211,10 +211,10 @@ manually passing parameters. negligible, but there is a startup-time cost on first MCP call. Mitigation: the shim is a single static Rust binary; cold start is sub-100ms. - (−) The shim derives scope from `git config remote.origin.url`. Repositories - without an origin (fresh clones, local-only experiments) fall back to the - worktree basename, which is brittle if two projects share the same dir - name in different parents. Mitigation: `AVER_DEFAULT_SCOPE` override; explicit - `--scope` flag on the shim. + without an origin (fresh clones, local-only experiments) fall back to hashing + the absolute worktree root. That avoids basename collisions but means moving a + local-only repository changes its derived scope. Mitigation: + `AVER_DEFAULT_SCOPE` override; explicit `--scope` flag on the shim. - (−) The read-default flip is observable to clients who happened to depend on cross-project bleed. ADR-0021 documented this as a forthcoming change; this ADR is where the change actually ships. Anyone whose workflow relied From c989c277b892ff3872bae9a49967e1742bf36966 Mon Sep 17 00:00:00 2001 From: Christian Pojoni <34570565+5queezer@users.noreply.github.com> Date: Sat, 18 Jul 2026 13:05:21 +0200 Subject: [PATCH 2/2] docs(adr): mark 0021 and 0022 as Accepted (implemented) --- doc/adr/0021-scope-as-first-class-memory-dimension.md | 2 +- doc/adr/0022-connection-scope-resolution.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/adr/0021-scope-as-first-class-memory-dimension.md b/doc/adr/0021-scope-as-first-class-memory-dimension.md index 5ce73bf..527bc3a 100644 --- a/doc/adr/0021-scope-as-first-class-memory-dimension.md +++ b/doc/adr/0021-scope-as-first-class-memory-dimension.md @@ -4,7 +4,7 @@ Date: 2026-05-10 ## Status -Proposed +Accepted ## Context diff --git a/doc/adr/0022-connection-scope-resolution.md b/doc/adr/0022-connection-scope-resolution.md index 087b94e..974851f 100644 --- a/doc/adr/0022-connection-scope-resolution.md +++ b/doc/adr/0022-connection-scope-resolution.md @@ -4,7 +4,7 @@ Date: 2026-05-10 ## Status -Proposed +Accepted ## Context