@arnilo/prism 0.8.0 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +62 -1
- package/README.md +13 -12
- package/dist/agent-approval.d.ts +17 -2
- package/dist/agent-approval.js +15 -6
- package/dist/agent-event-source.d.ts +9 -1
- package/dist/agent-event-source.js +10 -3
- package/dist/agent-loops.js +7 -4
- package/dist/agent-run-lifecycle.d.ts +15 -1
- package/dist/agent-run-lifecycle.js +82 -11
- package/dist/agent-run-state.d.ts +47 -6
- package/dist/agent-run-state.js +154 -6
- package/dist/agent-session/event-subscriber.d.ts +2 -0
- package/dist/agent-session/event-subscriber.js +3 -0
- package/dist/agent-session/helpers.js +14 -0
- package/dist/agent-session/session/assemble.js +281 -32
- package/dist/agent-session/session/persist.d.ts +11 -0
- package/dist/agent-session/session/persist.js +48 -16
- package/dist/agent-session/session/provider-round.d.ts +14 -4
- package/dist/agent-session/session/provider-round.js +226 -19
- package/dist/agent-session/session/tool-round.d.ts +2 -2
- package/dist/agent-session/session/tool-round.js +78 -6
- package/dist/agent-session/session/types.d.ts +44 -3
- package/dist/agent-session/session.d.ts +100 -5
- package/dist/agent-session/session.js +224 -13
- package/dist/attention-compiler.d.ts +51 -2
- package/dist/attention-compiler.js +282 -21
- package/dist/cache-helpers.d.ts +4 -2
- package/dist/cache-helpers.js +8 -6
- package/dist/checkpoint-restore.d.ts +45 -0
- package/dist/checkpoint-restore.js +54 -0
- package/dist/context-budget.d.ts +13 -1
- package/dist/context-budget.js +57 -4
- package/dist/contracts-core/agent.d.ts +52 -1
- package/dist/contracts-core/attention.d.ts +95 -0
- package/dist/contracts-core/content.d.ts +10 -0
- package/dist/contracts-core/extensions.d.ts +3 -0
- package/dist/contracts-core/guardrail-packs.d.ts +46 -0
- package/dist/contracts-core/guardrail-packs.js +2 -0
- package/dist/contracts-core/loop.d.ts +36 -0
- package/dist/contracts-core/provider.d.ts +30 -0
- package/dist/contracts-core/run-limits.d.ts +29 -1
- package/dist/contracts-core/session.d.ts +23 -5
- package/dist/contracts-core/session.js +21 -2
- package/dist/contracts-core/usage.d.ts +40 -0
- package/dist/contracts-core/usage.js +8 -0
- package/dist/contracts-core.d.ts +2 -0
- package/dist/contracts-core.js +2 -0
- package/dist/contracts-protocol.d.ts +81 -5
- package/dist/contracts-run-state.d.ts +91 -2
- package/dist/contributions.d.ts +2 -1
- package/dist/contributions.js +1 -0
- package/dist/extensions.d.ts +15 -1
- package/dist/extensions.js +68 -0
- package/dist/guardrail-packs/coding-standard.d.ts +3 -0
- package/dist/guardrail-packs/coding-standard.js +63 -0
- package/dist/guardrail-packs/destructive-commands.d.ts +3 -0
- package/dist/guardrail-packs/destructive-commands.js +46 -0
- package/dist/guardrail-packs/errors.d.ts +7 -0
- package/dist/guardrail-packs/errors.js +9 -0
- package/dist/guardrail-packs/index.d.ts +4 -0
- package/dist/guardrail-packs/index.js +15 -0
- package/dist/guardrail-packs/secrets-hygiene.d.ts +3 -0
- package/dist/guardrail-packs/secrets-hygiene.js +23 -0
- package/dist/guardrail-packs/types.d.ts +26 -0
- package/dist/guardrail-packs/types.js +2 -0
- package/dist/guardrail-packs/validation-respect.d.ts +3 -0
- package/dist/guardrail-packs/validation-respect.js +69 -0
- package/dist/guardrails.d.ts +61 -1
- package/dist/guardrails.js +377 -0
- package/dist/index.d.ts +16 -11
- package/dist/index.js +10 -7
- package/dist/input.d.ts +8 -1
- package/dist/input.js +68 -6
- package/dist/middleware.d.ts +37 -2
- package/dist/middleware.js +41 -0
- package/dist/node/session-store-jsonl.js +18 -3
- package/dist/observability.js +6 -0
- package/dist/provider-events.d.ts +8 -2
- package/dist/provider-events.js +60 -2
- package/dist/providers/openai-compatible.js +6 -3
- package/dist/run-bundle.d.ts +6 -1
- package/dist/run-bundle.js +5 -1
- package/dist/run-limits.d.ts +11 -1
- package/dist/run-limits.js +59 -0
- package/dist/session-stores.d.ts +12 -1
- package/dist/session-stores.js +21 -4
- package/dist/testing/agent-event-source-conformance.js +41 -2
- package/dist/testing/prefix-stability-conformance.d.ts +59 -0
- package/dist/testing/prefix-stability-conformance.js +172 -0
- package/dist/testing/session-store-conformance.d.ts +3 -2
- package/dist/testing/session-store-conformance.js +48 -0
- package/dist/tools.d.ts +5 -0
- package/dist/tools.js +21 -6
- package/dist/usage-estimation.d.ts +29 -0
- package/dist/usage-estimation.js +79 -0
- package/docs/agent-events.md +75 -4
- package/docs/agent-session-runtime.md +10 -6
- package/docs/attention-compiler.md +89 -8
- package/docs/caveman.md +1 -1
- package/docs/coding-agent-tools.md +1 -1
- package/docs/compaction-and-retry.md +1 -1
- package/docs/compaction-llm.md +2 -0
- package/docs/compaction-observational-memory.md +54 -7
- package/docs/durable-runs.md +46 -3
- package/docs/embeddings.md +9 -0
- package/docs/evaluations.md +5 -0
- package/docs/execution-timeline.md +79 -1
- package/docs/extensions.md +20 -3
- package/docs/guardrails.md +50 -4
- package/docs/hooks.md +282 -0
- package/docs/index.md +37 -15
- package/docs/input-and-prompt-assembly.md +4 -4
- package/docs/instruction-injection.md +1 -0
- package/docs/knowledge-sync.md +4 -0
- package/docs/live-testing.md +3 -1
- package/docs/memory-fabric.md +28 -0
- package/docs/middleware-hooks.md +90 -4
- package/docs/migrate-to-0.9.md +210 -0
- package/docs/migration.md +26 -0
- package/docs/multi-agent-patterns.md +25 -2
- package/docs/node-jsonl-session-store.md +7 -1
- package/docs/observability.md +7 -3
- package/docs/options-index.md +4 -1
- package/docs/policy-and-audit.md +26 -1
- package/docs/prefix-stability-conformance.md +143 -0
- package/docs/provider-caching.md +4 -4
- package/docs/provider-conformance.md +16 -0
- package/docs/provider-packages.md +20 -20
- package/docs/public-contracts.md +3 -2
- package/docs/rag.md +188 -3
- package/docs/release-and-install.md +45 -40
- package/docs/runs-and-usage.md +56 -10
- package/docs/scoped-agent-memory.md +270 -0
- package/docs/scoped-memory.md +138 -0
- package/docs/session-store-conformance.md +1 -2
- package/docs/session-stores.md +17 -17
- package/docs/supervisors.md +32 -12
- package/docs/tools.md +18 -1
- package/docs/wiki.md +4 -2
- package/docs/workflows.md +5 -0
- package/package.json +8 -2
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
# Scoped persistent agent memory — design concept
|
|
2
|
+
|
|
3
|
+
Status: **concept**. The implemented contract is [Scoped memory](scoped-memory.md) (`@arnilo/prism-memory/scoped`). This page is the design rationale for workspace-scoped persistent agent memory — durable facts and procedures that are recorded, updated, and used automatically during agentic work. Terminology deliberately aligns with the existing Prism memory surfaces ([memory fabric](memory-fabric.md), [observational memory](compaction-observational-memory.md), [working and semantic memory](working-and-semantic-memory.md)) — see [Relationship to existing Prism memory surfaces](#relationship-to-existing-prism-memory-surfaces).
|
|
4
|
+
|
|
5
|
+
## Problem and goals
|
|
6
|
+
|
|
7
|
+
A host running agents over a workspace — a codebase, a professional practice, a research corpus — wants the agent to accumulate durable knowledge across sessions without manual curation: conventions, environment quirks, proven procedures, corrections, user preferences. The system must:
|
|
8
|
+
|
|
9
|
+
- **Persist** durable facts and procedures per *scope of work* (workspace, codebase, content collection).
|
|
10
|
+
- **Record automatically** — post-task reflection, not user-issued "remember this" commands only.
|
|
11
|
+
- **Stay accurate at scale** — hundreds of accumulated records must not degrade routing, cost, or behavior.
|
|
12
|
+
- **Be auditable** — professional work requires provenance, review, and human override.
|
|
13
|
+
|
|
14
|
+
The canonical live experiment is the Hermes agent; its documented failure modes at scale define the requirements here.
|
|
15
|
+
|
|
16
|
+
## Case study: Hermes agent
|
|
17
|
+
|
|
18
|
+
Hermes (NousResearch/hermes-agent) is a self-improving personal agent. Its memory architecture:
|
|
19
|
+
|
|
20
|
+
| Layer | Mechanism | Design constraint |
|
|
21
|
+
| --- | --- | --- |
|
|
22
|
+
| Declarative memory | `MEMORY.md` (2,200 chars) + `USER.md` (1,375 chars), injected as a frozen snapshot at session start | Hard capacity: an overflowing write returns an **error that forces consolidation** — never silent growth |
|
|
23
|
+
| Episodic | SQLite FTS5 session search (`session_search`), ~20 ms, no LLM calls | Unlimited, never injected into prompts |
|
|
24
|
+
| Procedural | Skills: markdown `SKILL.md` under `~/.hermes/skills/` (agentskills.io format) | Name + one-line description in the system prompt; full body loaded on demand (`skill_view`) — progressive disclosure |
|
|
25
|
+
| Write loop | Background review agent after each turn, prompt biased toward action: *"most sessions produce at least one skill update"*; bar ≈ 5 tool calls / error recovery / user correction | Patch > edit > create; optional `write_approval` staging gate |
|
|
26
|
+
|
|
27
|
+
What Hermes got right: bounded always-on memory with capacity-forced consolidation, episodic search off the prompt, progressive disclosure for skill bodies, and a background reflection loop instead of user-driven memory commands.
|
|
28
|
+
|
|
29
|
+
### Documented failure modes at scale
|
|
30
|
+
|
|
31
|
+
- **Catalog inflation (issue [#22620](https://github.com/NousResearch/hermes-agent/issues/22620)):** every skill's name + category + description is injected into the system prompt on *every turn*. 243 skills ≈ 10–15K tokens per API call; a 130-skill setup ≈ 4K tokens/turn. Routing is done by making the model attend over the whole catalog, so prompt cost and selection confusion scale O(N) with library size.
|
|
32
|
+
- **Lazy-loading demand (issue [#2045](https://github.com/NousResearch/hermes-agent/issues/2045)):** 87 bundled skills ≈ 1.5–2K tokens before any user content; users manually prune (73 → 26 in one report).
|
|
33
|
+
- **No metabolism (issue [#12877](https://github.com/NousResearch/hermes-agent/issues/12877)):** skills are add-only. No decay, no invalidation, no usage-based cleanup. The library grows monotonically and useful skills are buried under throwaways.
|
|
34
|
+
- **No write-quality gate (#12877 §1):** ~5 tool calls is enough to mint a permanent "skill." A one-off debugging session becomes procedural knowledge with no consolidation or validation — working memory promoted directly to long-term memory.
|
|
35
|
+
- **Skill islands (#12877 §3):** no composition between skills; the same logic (e.g. a TDD workflow) is duplicated across many skills; association fields are decorative.
|
|
36
|
+
- **No conflict detection (#12877 §4):** only identical *names* are blocked; near-duplicate *functionality* competes for execution and produces chaotic output.
|
|
37
|
+
- **Community-validated fix inside #22620:** usage-decay scoring (`score = uses × exp(−Δdays/30)`) plus a hard character budget for the skill list cut catalog tokens ~70% at zero LLM cost — evidence that decay-weighted routing is the right primitive.
|
|
38
|
+
|
|
39
|
+
**Root cause synthesis:** Hermes solved read-path token economics (progressive disclosure) but left the *routing surface* O(N) in the prompt and gave the *write path* no quality gate, no garbage collection, no deduplication, and no conflict resolution. "Misfiring" is the routing problem: as semantic overlap across hundreds of descriptions grows, selection degrades, and stale or duplicate entries win over correct ones.
|
|
40
|
+
|
|
41
|
+
## Research basis
|
|
42
|
+
|
|
43
|
+
| System | Contribution taken |
|
|
44
|
+
| --- | --- |
|
|
45
|
+
| **Mem0** ([arXiv 2504.19413](https://doi.org/10.48550/arxiv.2504.19413)) | Write path as extraction + adjudication: an LLM decides ADD / UPDATE / DELETE / NOOP against existing memories. Solves add-only bloat; strong LoCoMo results against MemGPT/A-MEM baselines. |
|
|
46
|
+
| **Zep / Graphiti** ([arXiv 2501.13956](https://arxiv.org/html/2501.13956)) | Temporal validity as a first-class citizen: every fact carries `valid_at`/`invalid_at`; a new fact *closes* the old one rather than duplicating it. Solves fact staleness and contradiction. |
|
|
47
|
+
| **A-MEM** ([arXiv 2502.12110](https://arxiv.org/abs/2502.12110), NeurIPS'25) | Zettelkasten-style memory: notes carry structured attributes (keywords, tags, contextual descriptions), **link generation** to related notes, and **memory evolution** — new notes update the representations of old ones. Solves island isolation. |
|
|
48
|
+
| **Agent Workflow Memory** ([arXiv 2409.07429](https://arxiv.org/abs/2409.07429), ICML'25) | Procedural induction by mining *repeated* sub-routines across trajectories and abstracting out instance-specific context before storage. Fixes "one successful session becomes a skill." |
|
|
49
|
+
| **Generative Agents** ([arXiv 2304.03442](https://arxiv.org/abs/2304.03442)) | Memory-stream retrieval scored by **relevance × recency × importance**, plus periodic **reflection** that synthesizes higher-level insights once accumulated importance crosses a threshold. |
|
|
50
|
+
| **Voyager** ([arXiv 2305.16291](https://arxiv.org/html/2305.16291v2)) | Skill-library precedent: skills retrieved by embedding top-k, and a skill enters the library only after **execution verification** — promotion gates are not new. |
|
|
51
|
+
| **HippoRAG** ([NeurIPS'24](https://proceedings.neurips.cc/paper_files/paper/2024/file/6ddc001d07ca4f319af96a3024f6dbd1-Paper-Conference.pdf)) | Associative multi-hop recall via knowledge graph + personalized PageRank, for "everything connected to this" queries beyond nearest-neighbor retrieval. |
|
|
52
|
+
| **Letta sleep-time compute** ([arXiv 2504.13171](https://arxiv.org/abs/2504.13171)) | Memory consolidation moved off the interaction path: a background pass reorganizes memory between sessions, improving accuracy while cutting per-turn cost. |
|
|
53
|
+
| **Anthropic Agent Skills** ([engineering post](https://www.anthropic.com/engineering/equipping-agents-for-the-real-world-with-agent-skills)) | Progressive disclosure as a three-level spec; the always-loaded layer (name + description) is the routing surface and must stay tiny. |
|
|
54
|
+
|
|
55
|
+
## Design principles
|
|
56
|
+
|
|
57
|
+
1. **Memory is a first-class artifact of the workspace.** Versioned with the workspace, reviewable like code, and *scoped*: a record created in scope X is invisible outside X. One silo per workspace; a separate tiny global layer for user preferences (profile ≠ project).
|
|
58
|
+
2. **Typed memory, one store per type, different physics per type.** Facts are bounded and always-on; episodes are append-only and searchable; notes/insights are linked and evolving; procedures earn permanence through reuse.
|
|
59
|
+
3. **Write-heavy is the failure mode.** Writes are gated by repetition, adjudication, and conflict detection. The default write action is *no-op*.
|
|
60
|
+
4. **Reads are query-driven, never catalog-driven.** Routing happens through a retrieval tool, not attention over an injected list. Prompt cost is O(1) in library size.
|
|
61
|
+
5. **Metabolism, not accumulation.** Usage-decay garbage collection, validity windows, capacity-forced consolidation. Every record can die.
|
|
62
|
+
6. **Abstention beats misfiring.** Below a relevance floor, the memory returns "nothing relevant." An agent confidently following a *wrong* retrieved record is worse than one starting fresh.
|
|
63
|
+
|
|
64
|
+
## System description
|
|
65
|
+
|
|
66
|
+
### Scope model
|
|
67
|
+
|
|
68
|
+
The unit of memory is the **scope**: a workspace root, codebase, project directory, or content collection. Records carry a `scope` binding and are retrieved only within it — scope itself is the largest single precision filter (a query inside `~/work/api-server` never competes with records from other projects). A distinct, optional global scope holds user-profile facts; nothing crosses scope boundaries by default.
|
|
69
|
+
|
|
70
|
+
### Storage — typed records, not one bucket
|
|
71
|
+
|
|
72
|
+
```
|
|
73
|
+
<workspace>/.memory/
|
|
74
|
+
facts.md # always-on, hard character budget
|
|
75
|
+
records/*.md # searchable semantic memory: notes, procedures, insights
|
|
76
|
+
episodes.db # SQLite FTS5, append-only session logs
|
|
77
|
+
index.db # FTS5 (+ optional embeddings) over records — derived, rebuildable
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Record format — markdown with frontmatter, human-editable, git-diffable, PR-reviewable:
|
|
81
|
+
|
|
82
|
+
```markdown
|
|
83
|
+
---
|
|
84
|
+
id: rec_7f3a91c2d0b4
|
|
85
|
+
type: procedure # fact | note | procedure | insight
|
|
86
|
+
scope: ~/work/api-server
|
|
87
|
+
status: candidate # candidate → verified → archived (promotion ladder)
|
|
88
|
+
created: 2026-06-14
|
|
89
|
+
valid: [2026-06-14, ] # validity window; a superseding write closes it
|
|
90
|
+
provenance: session 8a2f, turn 41 # every claim traceable to its source
|
|
91
|
+
uses: 0 # retrieval activations
|
|
92
|
+
last_used: null
|
|
93
|
+
links: [rec_deploy_rollback, rec_pg_pool]
|
|
94
|
+
---
|
|
95
|
+
## Deploy without downtime
|
|
96
|
+
1. ... (steps abstracted from instance specifics)
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Rationale: human override and review are non-negotiable for professional work — diffs, blame, and PR review come free. This mirrors the Hermes `journey edit/delete` lesson (users *must* be able to prune) and generalizes it to full version control.
|
|
100
|
+
|
|
101
|
+
**Prism realization.** In the composed Prism stack (next section) the [memory fabric](memory-fabric.md) is the source of truth for records and the session store/observational ledger owns episodes; usage/status/staging live in a gitignored JSON ledger (`<workspace>/.memory/state.json`); `<workspace>/.memory/` markdown is a **git audit mirror** — a rendered export of fabric notes + ledger counters for diff/review — not a second storage engine. One write path, two views.
|
|
102
|
+
|
|
103
|
+
### Write path — reflect, adjudicate, gate
|
|
104
|
+
|
|
105
|
+
A background reflection pass runs post-task on the session digest (Hermes's background review; Letta's sleep-time compute), on a cheaper model. Four hard differences from Hermes:
|
|
106
|
+
|
|
107
|
+
1. **Conservative bias.** The review prompt's prior is *"most sessions update nothing"* — the explicit inversion of Hermes's action-biased prompt. Write triggers: user correction, error→recovery, a technique reused *within* the session, or an explicit "remember this."
|
|
108
|
+
2. **Adjudication before write** (Mem0). Retrieve the top-k nearest existing records; the writer decides ADD / UPDATE / MERGE / NOOP against them. Duplication is killed at write time, not by later cleanup.
|
|
109
|
+
3. **Temporal supersession** (Zep/Graphiti). A contradicting fact closes the old record's validity window — never two live records claiming opposite things. Conflict is detected *at write*, not at misfire time.
|
|
110
|
+
4. **Promotion ladder** (Voyager; #12877 recommendation). Post-task output is a `candidate` note, never a procedure. A candidate becomes `verified` after **N successful reuses** (N ≈ 2–3) logged from actual retrieval→outcome feedback; insights promoted from reflections follow the same gate. Real-but-unproven knowledge lives as a note; only proven repetition earns procedure status. This single gate eliminates most of Hermes's throwaway-skill flood, because throwaways are never reused.
|
|
111
|
+
|
|
112
|
+
After the gate: **link generation** against retrieved neighbors (A-MEM) so records compose instead of islanding, and every record carries provenance back to the session/turn that produced it.
|
|
113
|
+
|
|
114
|
+
### Read path — where misfiring is prevented
|
|
115
|
+
|
|
116
|
+
- **In the system prompt:** `facts.md` only (bounded, ~1–2K tokens) plus one line — "memory available, N records in this scope." Never the record list. Routing is a tool call, not attention over a catalog: prompt cost stays flat regardless of library size.
|
|
117
|
+
- **`memory_search(query)`** — hybrid retrieval: lexical (FTS5/BM25, milliseconds, $0) plus embeddings when available, reranked by a Generative-Agents-style score extended with usage feedback:
|
|
118
|
+
`score = relevance × exp(−Δdays/τ) × importance × (1 + log uses)`
|
|
119
|
+
The decay term is exactly the mechanism community-measured at ~70% catalog reduction inside Hermes issue #22620.
|
|
120
|
+
- **Activation budget:** top-3 results per query, and an **abstain floor** — below a similarity threshold the tool returns "no relevant memory."
|
|
121
|
+
- **Link traversal:** follow `links:` one hop for associative recall (HippoRAG-lite). No graph database — frontmatter adjacency only.
|
|
122
|
+
|
|
123
|
+
### Lifecycle — metabolism
|
|
124
|
+
|
|
125
|
+
An idle/nightly consolidation pass (cheap model, off the interaction path):
|
|
126
|
+
|
|
127
|
+
- **Garbage collection by usage decay.** Archive `candidate` records unused for ~30 days; archive `verified` records below a usage-decay threshold. Recommend-then-delete, never silent deletion (git keeps history regardless).
|
|
128
|
+
- **Near-duplicate merge.** Records flagged by embedding similarity above threshold are merged or invalidated.
|
|
129
|
+
- **Promotion/demotion** per the ladder, from logged retrieval→outcome feedback.
|
|
130
|
+
- **Capacity-forced consolidation of `facts.md`.** When a facts write would exceed the budget, the write *fails* with "consolidate first" (the Hermes overflow-error pattern) — the writer must merge or remove entries in the same action. The always-on layer can therefore never rot.
|
|
131
|
+
- **Digest.** A weekly human-readable diff of memory changes (git already provides the bookkeeping).
|
|
132
|
+
|
|
133
|
+
### Trust boundary
|
|
134
|
+
|
|
135
|
+
- **Secret redaction** of record content via the shipped needle redactor (`createSecretRedactor` / observational `secrets` / working-memory `redactJson`).
|
|
136
|
+
- **Injection/exfiltration scanning** of record content before any prompt injection (patterns, invisible Unicode) — Hermes does this for `MEMORY.md`; extend to all records. This is a new scoped primitive (`scanScopedMemoryContent`); it is not the secret redactor.
|
|
137
|
+
- **Staged approval.** Writes may be staged for human review (`write_approval`-style). Default: off for personal scopes, on for team/professional scopes.
|
|
138
|
+
- **Scope isolation.** Records never leak across workspace roots; the global user layer is opt-in per record.
|
|
139
|
+
- **Provenance on every record.** Any memory-driven decision can be traced to the session and turn that produced the record (same philosophy as observational memory's source-backed ids and the recall path).
|
|
140
|
+
|
|
141
|
+
## Failure-mode → mechanism map
|
|
142
|
+
|
|
143
|
+
| Hermes failure mode | Mechanism here | Backing |
|
|
144
|
+
| --- | --- | --- |
|
|
145
|
+
| Catalog tokens O(N)/turn | Routing via retrieval tool; never list injection | #22620, #2045; Anthropic three-level disclosure |
|
|
146
|
+
| Wrong entry wins selection | Abstain floor + top-3 budget + decay-weighted scoring | Generative Agents; measured decay fix in #22620 |
|
|
147
|
+
| Throwaway-skill flood | candidate→verified promotion after N reuses | Voyager verification; #12877 §1; AWM repetition mining |
|
|
148
|
+
| Duplicates | Write-time ADD/UPDATE/MERGE/NOOP adjudication | Mem0 |
|
|
149
|
+
| Contradictory live facts | Validity windows; supersede, don't duplicate | Zep/Graphiti |
|
|
150
|
+
| Record islands | Link generation + memory evolution on write | A-MEM; HippoRAG traversal |
|
|
151
|
+
| Unreviewable autonomous writes | Markdown + git + provenance + staged approval | Hermes `journey`/`write_approval` generalized |
|
|
152
|
+
| No cleanup | Usage-decay GC + consolidation pass | Letta sleep-time; #12877 §2 |
|
|
153
|
+
|
|
154
|
+
## Evaluation
|
|
155
|
+
|
|
156
|
+
Non-negotiable for professional use; memory must earn its complexity:
|
|
157
|
+
|
|
158
|
+
1. **Task win-rate A/B** — a fixed task suite per scope, run with memory on / off / never-consolidated. If memory does not lift win rate or reduce turns, it ships off by default.
|
|
159
|
+
2. **Retrieval precision@3** against a hand-labeled query set per workspace; alert on drops — the leading indicator of misfiring.
|
|
160
|
+
3. **Health metrics** — duplication rate, candidate→verified conversion rate, activation rate (retrieved-and-used / retrieved), and prompt token cost per turn vs. library size (target: flat).
|
|
161
|
+
4. **LoCoMo-style recall probes** for the episodic layer, the standard benchmark in the Mem0/Zep line.
|
|
162
|
+
|
|
163
|
+
## Relationship to existing Prism memory surfaces
|
|
164
|
+
|
|
165
|
+
The placement principle: **the scoped layer is a policy and lifecycle layer, not a fifth store.** Mechanism lives in the engines and the fabric; policy lives in the scoped layer. This section concretizes how the layers compose into one memory-management system.
|
|
166
|
+
|
|
167
|
+
### Layered architecture and ownership
|
|
168
|
+
|
|
169
|
+
| Layer | Surface | Owns | Never does |
|
|
170
|
+
| --- | --- | --- | --- |
|
|
171
|
+
| Raw transcript | [Session stores](session-stores.md) | append-only entries, branches, bounded lexical search | — |
|
|
172
|
+
| Episodic ledger | [Observational memory](compaction-observational-memory.md) | source-backed observations/reflections (12-hex ids, `sourceEntryIds`), exact-id recall and branch pages, optional work-scope index; observer/reflector/dropper workers are the only writers | no semantic retrieval, no wholesale prompt injection, no downstream re-observation |
|
|
173
|
+
| Memory engines | [Working and semantic memory](working-and-semantic-memory.md) | `Embedder`/vector/working-store contracts, consent lifecycle, lineage invalidation, importance, recall scoring | no policy |
|
|
174
|
+
| Durable records | [Memory fabric](memory-fabric.md) | typed notes (`fact`/`procedure`/`file`/`working`/`episode`), validity windows, consolidation folding, linker/evolution workers, five governed tools, file jail, context provider, `forget`/legal hold | no autonomy — every write is an explicit caller decision |
|
|
175
|
+
| Policy + lifecycle | [Scoped memory](scoped-memory.md) (`@arnilo/prism-memory/scoped`) | conservative post-run writer, promotion ladder, usage-decay GC, abstain floor + activation budget, workspace-root scope identity, git audit mirror, usage/status/staging JSON ledger | no store, no engine, no context-block type, no second write path |
|
|
176
|
+
| Knowledge compiler | [LLM wiki](wiki.md) (`@arnilo/prism-memory/wiki`) | regenerable `.wiki/` pages with line-anchored citations over raw sources; `wiki_ingest` / `wiki_record_insight` | session-derived experience (that is scoped memory); it is not a memory store |
|
|
177
|
+
|
|
178
|
+
**Wiki boundary / routing.** Wiki compiles *source-cited knowledge* (files, docs, papers — regenerable, `file://…#Lxx-Lyy`). Scoped memory holds *session-derived experience* (primary fabric records, provenance `sourceEntryIds`). No storage overlap: wiki writes `.wiki/` + `raw/ingest/`; scoped writes fabric notes + `<scopeRoot>/.memory/state.json` + the git mirror. Route source-cited material to wiki; route session-derived experience to scoped policy. The post-run reviewer must not file a wiki-pageable insight as a scoped fact.
|
|
179
|
+
|
|
180
|
+
Two invariants carry over unchanged: observational memory stays **episodic** (promotion out of the ledger is an explicit host write — fabric's `promotedFrom` over a closed work scope), and the fabric never widens consent or visibility.
|
|
181
|
+
|
|
182
|
+
### Ideal composition for a persistent-memory agent
|
|
183
|
+
|
|
184
|
+
```ts
|
|
185
|
+
// 1. Engines — workspace root is resourceId; threadId is a stable silo id (not the session id)
|
|
186
|
+
const memory = createMemory({ tenantId: host, resourceId: workspaceRoot, threadId: "scoped", embedder, stores });
|
|
187
|
+
// 2. Durable records — folding, links, evolution on by policy
|
|
188
|
+
const fabric = createMemoryFabric({ memory, observational, consolidate: { threshold: 0.85 },
|
|
189
|
+
linker: { enabled: true }, evolution: { enabled: true } });
|
|
190
|
+
// 3. Episodic ledger per session; work-scope index bound to the workspace
|
|
191
|
+
om.attach(session);
|
|
192
|
+
// 4. Gate fabric tools + workers to this session
|
|
193
|
+
fabric.attach(session);
|
|
194
|
+
// 5. Injection: ONLY the bounded working facts block reaches the prompt
|
|
195
|
+
registries.contextProviders.register("memory-fabric",
|
|
196
|
+
fabric.createContextProvider({ includeWorking: true, includeSemantic: false }));
|
|
197
|
+
const agent = await resolveAgentDefinition(
|
|
198
|
+
{ name: "assistant", model, context: ["memory-fabric"], tools: ["memory.recall"] },
|
|
199
|
+
{ registries, providerSource });
|
|
200
|
+
// 6. Scoped policy module (host-side, the new part): post-run review,
|
|
201
|
+
// usage logging on recall hits, idle promotion/GC jobs, .memory/ git mirror
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
End-to-end flow:
|
|
205
|
+
|
|
206
|
+
1. **During the session** — observational workers record source-backed observations/reflections; compaction renders prepared memory; the agent may call `memory.recall`/`memory.insert` directly; the prompt carries only the bounded facts block.
|
|
207
|
+
2. **After the run** — the scoped policy reviews the session digest on a cheap model, biased to no-op, and promotes the *generalizable* part through `fabric.remember` as `candidate` notes carrying `sourceEntryIds` provenance (or `reflectionId` for notes derived from a closed-scope reflection). Folding adjudicates duplicates; linker/evolution connect and refine.
|
|
208
|
+
3. **Next session** — facts block (working notes) always on; everything else via `memory.recall` (`kinds: ["procedure"]` stays opt-in, `asOf` honors validity, `budget` caps tokens). Recall hits log `uses`.
|
|
209
|
+
4. **Idle/nightly** — promotion job converts candidates with N logged successful reuses to `verified`; usage-decay GC archives the rest; the facts block is consolidated under its budget; the git mirror renders the diff for human review.
|
|
210
|
+
|
|
211
|
+
### Read/write paths across layers
|
|
212
|
+
|
|
213
|
+
| Write-path step | Owner |
|
|
214
|
+
| --- | --- |
|
|
215
|
+
| Trigger (post-run, noop-biased) | scoped policy |
|
|
216
|
+
| Extraction with provenance | observational reflection → `reflectionId`/`sourceEntryIds` |
|
|
217
|
+
| Adjudicate ADD/UPDATE/supersede | fabric consolidation folding (cosine threshold 0.85) |
|
|
218
|
+
| Close contradicted facts | fabric `validTo` + `supersedes` |
|
|
219
|
+
| Link + evolve neighbors | fabric linker/evolution workers |
|
|
220
|
+
| Status `candidate`, usage counters, staging/approval | scoped JSON ledger (`<scopeRoot>/.memory/state.json`) — not fabric note metadata |
|
|
221
|
+
|
|
222
|
+
| Read-path step | Owner |
|
|
223
|
+
| --- | --- |
|
|
224
|
+
| Always-on bounded facts | working block via the fabric context provider — the only injected surface |
|
|
225
|
+
| Query-driven records | `memory.recall` (kinds/asOf/budget) + scoped abstain floor, top-3 budget, decay-weighted scoring |
|
|
226
|
+
| Associative recall | `links` traversal (recall hits with `explain.link: true`) |
|
|
227
|
+
| Episodic specifics | observational exact-id recall / `searchConversation` lexical pages |
|
|
228
|
+
|
|
229
|
+
### Research leverage map
|
|
230
|
+
|
|
231
|
+
How the research findings land on shipped surfaces versus policy added by this concept:
|
|
232
|
+
|
|
233
|
+
| Finding | Source | Shipped in Prism | Added by this concept |
|
|
234
|
+
| --- | --- | --- | --- |
|
|
235
|
+
| Write adjudication (ADD/UPDATE/DELETE/NOOP) | Mem0 | fabric consolidation folding (insert / rewrite / supersede) | MERGE of near-duplicates in the GC pass |
|
|
236
|
+
| Importance weighting | Mem0 | `importance` / `importanceFrom` hook | — |
|
|
237
|
+
| Temporal validity windows | Zep/Graphiti | fabric `validFrom`/`validTo`, recall `asOf` | — |
|
|
238
|
+
| Invalidate, don't duplicate | Zep/Graphiti | supersede fold at write | — |
|
|
239
|
+
| Bi-temporal time (event vs ingestion) | Zep/Graphiti | `tRef` vs `ingestedAt` | — |
|
|
240
|
+
| Link generation | A-MEM | fabric linker worker | — |
|
|
241
|
+
| Memory evolution | A-MEM | fabric evolution worker | — |
|
|
242
|
+
| Relevance × recency × importance scoring | Generative Agents | fabric recall scoring (similarity/recency/importance) | usage feedback `(1 + log uses)` + exp decay |
|
|
243
|
+
| Verify before permanence | Voyager | — | promotion ladder (N reuses) |
|
|
244
|
+
| Mine repeated routines, abstract instance specifics | AWM | — | conservative review writes abstracted candidates |
|
|
245
|
+
| Off-path consolidation | Letta sleep-time | — (idle-job placement) | GC/promotion/facts consolidation on a cheap model |
|
|
246
|
+
| Tiny always-loaded layer, disclosure on demand | Anthropic skills | recall tools + working block only | abstain floor + top-3 activation budget |
|
|
247
|
+
| Hybrid BM25 + cosine + graph walk | Zep/Graphiti | partial: embedding score + link traversal; lexical on branch search | fusing lexical into one recall — open question |
|
|
248
|
+
| Known-secret redaction | runtime / OM | `createSecretRedactor`, observational `secrets`, working `redactJson` | — |
|
|
249
|
+
| Injection / exfil / invisible Unicode scan | Hermes MEMORY.md | — (redactor is needle-only) | `scanScopedMemoryContent` (pure patterns) |
|
|
250
|
+
|
|
251
|
+
### Deliberate deviations
|
|
252
|
+
|
|
253
|
+
- **Against Mem0's accumulate-and-rank-at-query.** Mem0's current algorithm appends dated variants and ranks at query time. This design sides with Zep: close the validity window at write. Rationale: the scoped layer injects few records, so each must be *the* trusted record; ranking dated variants at retrieval reintroduces the Hermes catalog/misfire problem one layer down.
|
|
254
|
+
- **Beyond both Mem0 and Zep.** Neither gates *procedures* by reuse — that is exactly the Hermes failure (#12877: ~5 tool calls = permanent skill). The promotion ladder is the addition, from Voyager/AWM.
|
|
255
|
+
- **Not adopted (deferred):** graph engines and community detection (frontmatter `links` + one-hop traversal suffice inside a scoped silo); Zep-style episodic subgraph summarization (the observational reflector already does hierarchical episodic summarization with provenance — adopting it would duplicate a shipped layer).
|
|
256
|
+
|
|
257
|
+
## Non-goals / deferred
|
|
258
|
+
|
|
259
|
+
- No vector database, graph database, or external memory provider in the core design — embeddings only where lexical retrieval measurably fails (inside a scoped silo it mostly will not).
|
|
260
|
+
- No second write path — the scoped policy writes only through the fabric, so folding, consent, and lineage rules apply to every write.
|
|
261
|
+
- No cross-scope federation or sharing; add only when a multi-workspace pattern measurably needs it.
|
|
262
|
+
- No autonomous deletion of human-authored records; GC proposes, humans dispose.
|
|
263
|
+
|
|
264
|
+
## Open questions
|
|
265
|
+
|
|
266
|
+
- Exact promotion thresholds (N reuses, decay τ, similarity floors) — must be empirically tuned per workload class (coding vs. research vs. professional ops).
|
|
267
|
+
- Whether the global user-profile layer reuses the working-memory store or a separate facts silo.
|
|
268
|
+
- Team-scope semantics: per-user silos sharing one workspace root, or one shared silo with author-attributed records.
|
|
269
|
+
|
|
270
|
+
Closed at primitive review: evaluation harness reuses `@arnilo/prism-core/governance/evals` scorer/dataset contracts and adds only scoped fixtures + `runScopedMemoryEval`.
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
# Scoped memory (`@arnilo/prism-memory/scoped`)
|
|
2
|
+
|
|
3
|
+
## What it does
|
|
4
|
+
|
|
5
|
+
`createScopedMemoryPolicy()` is a **policy and lifecycle layer** over stores a host already configured with `createMemory()` and `createMemoryFabric()`. It does not add a fifth store. It owns workspace-root identity, a conservative post-run writer, a candidate→verified promotion ladder, usage-decay GC proposals, an abstain floor plus activation budget on recall, a bounded working facts block, a git audit mirror, and a JSON usage/staging ledger at `<scopeRoot>/.memory/state.json`.
|
|
6
|
+
|
|
7
|
+
**Routing.** Source-cited knowledge belongs in [`@arnilo/prism-memory/wiki`](wiki.md) (regenerable, line-anchored). Session-derived experience belongs in scoped memory (primary fabric records with `sourceEntryIds` provenance). The post-run reviewer must not duplicate a wiki-pageable insight as a scoped fact.
|
|
8
|
+
|
|
9
|
+
## When to use it
|
|
10
|
+
|
|
11
|
+
Use it when a host wants durable facts and procedures to accumulate across sessions for one workspace without injecting the whole library into the prompt. Leave it off when an eval A/B shows no win-rate lift. Do not use it as a wiki, an observational-memory replacement, or a second vector store.
|
|
12
|
+
|
|
13
|
+
## Inputs / request
|
|
14
|
+
|
|
15
|
+
### `createScopedMemoryPolicy(options)`
|
|
16
|
+
|
|
17
|
+
| Field | Type | Required | Default | Description |
|
|
18
|
+
| :--- | :--- | :--- | :--- | :--- |
|
|
19
|
+
| `memory` | `Memory` | yes | — | `createMemory()` instance. `scope.resourceId` must equal `resolve(scopeRoot)`; `scope.threadId` is required (stable workspace silo, not a session id). |
|
|
20
|
+
| `fabric` | `MemoryFabric` | yes | — | `createMemoryFabric()` over that memory. |
|
|
21
|
+
| `scopeRoot` | `string` | yes | — | Workspace root. Create is inert: no attach, no files. |
|
|
22
|
+
| `policy` | `ScopedMemoryPolicyKnobs` | no | see knobs | Tuning. Unknown fields ignored; invalid values fail closed. |
|
|
23
|
+
|
|
24
|
+
### Knobs (`policy`)
|
|
25
|
+
|
|
26
|
+
| Knob | Default | Role |
|
|
27
|
+
| :--- | :--- | :--- |
|
|
28
|
+
| `promotion.reuseThreshold` | `2` | Flip ledger `candidate` → `verified` after this many successful recall uses. No fabric rewrite. |
|
|
29
|
+
| `decay.tauDays` | `30` | Time constant for `score = fabricScore × exp(−ageDays/tauDays) × (1 + ln(1 + uses))`. |
|
|
30
|
+
| `decay.candidateArchiveDays` | `30` | Unused candidates this old become GC archive proposals. |
|
|
31
|
+
| `activation.topK` | `3` | Recall budget after the floor. |
|
|
32
|
+
| `activation.minSimilarity` | `0.35` | Abstain floor (`hit.similarity ?? hit.score`). |
|
|
33
|
+
| `facts.block` | `"facts"` | Working-block label for `rememberFact`. |
|
|
34
|
+
| `facts.maxChars` | `2200` | Overflow throws `MemoryLimitError` (`consolidate first`). |
|
|
35
|
+
| `approval.default` | `"off"` | `"off"` writes reviewer proposals immediately; `"staged"` queues them on `pending()`. |
|
|
36
|
+
|
|
37
|
+
### Methods
|
|
38
|
+
|
|
39
|
+
| Method | Input | Notes |
|
|
40
|
+
| :--- | :--- | :--- |
|
|
41
|
+
| `recall(query, opts?)` | query string | Wraps `fabric.recall` with oversample, floor, topK, usage increment. |
|
|
42
|
+
| `reviewSession(digest, { reviewer, prompt? })` | string or entry array | One fake/real reviewer call. Strict JSON: `{kind, content, sourceEntryIds}` only; `kind` is `fact` or `procedure`; `sourceEntryIds` non-empty. Garbage → zero writes, no throw. |
|
|
43
|
+
| `promotionPass()` | — | Ledger status only. |
|
|
44
|
+
| `gcPass()` | — | Proposes archives onto `pending()`. Never deletes. Skips `legal_hold`. |
|
|
45
|
+
| `health()` | — | Counts + conversion/activation/duplication rates. |
|
|
46
|
+
| `rememberFact(text)` | non-empty string | Appends the facts block after `scanScopedMemoryContent`. |
|
|
47
|
+
| `pending()` | — | Reviewer stages and GC archives. |
|
|
48
|
+
| `approve(id)` / `reject(id)` | pending id | Approve writes/forgets; reject drops the proposal (archive restore uses `prevStatus`). |
|
|
49
|
+
| `renderMirror()` | — | Deterministic markdown under `<scopeRoot>/.memory/`; gitignores `state.json`. |
|
|
50
|
+
|
|
51
|
+
### Eval and scan
|
|
52
|
+
|
|
53
|
+
- `runScopedMemoryEval({ fixtures, fakeProvider? })` — fixture-only. Rejects `memory` / `policy` / `fabric` / `vectorStore`. Default fake answers from recall context or `"unknown"`. Reports A/B `winRate`, Precision@3 (mean `\|relevant ∩ top3\| / 3`, alert below floor 0.5), LoCoMo (`failedClosed` when `expectedId` was never seeded), and `health()`.
|
|
54
|
+
- `createScopedMemoryHealthCommand({ policy })` — `scoped-memory:health`.
|
|
55
|
+
- `scanScopedMemoryContent(text)` — `{ ok: true }` or `{ ok: false, class: "prompt-injection" \| "exfil" \| "invisible-unicode" }`. Writes fail closed on a match.
|
|
56
|
+
- `scoreScopedHit(score, uses, ageDays, tauDays)` — the read-policy formula.
|
|
57
|
+
|
|
58
|
+
## Outputs / response / events
|
|
59
|
+
|
|
60
|
+
Create returns a frozen `ScopedMemoryPolicy` (`scopeRoot`, `settings`, methods). No events; fabric/memory events are unchanged.
|
|
61
|
+
|
|
62
|
+
`recall` → `{ hits, abstained, explain }`. Empty hits + `abstained: true` when nothing clears the floor.
|
|
63
|
+
|
|
64
|
+
`reviewSession` → `{ proposed, written, staged, status: { candidate } }`.
|
|
65
|
+
|
|
66
|
+
`promotionPass` → `{ promoted }`. `gcPass` → `{ proposed, archived }` (`archived` stays 0 until the host `approve`s).
|
|
67
|
+
|
|
68
|
+
`health` → `{ notes: { candidate, verified, archived }, conversionRate, activationRate, duplicationRate }`.
|
|
69
|
+
|
|
70
|
+
`rememberFact` → `void` or `MemoryLimitError` / `MemoryValidationError`. `renderMirror` writes `notes/<id>.md`, `facts.md`, `.gitignore`.
|
|
71
|
+
|
|
72
|
+
## Request/response example
|
|
73
|
+
|
|
74
|
+
```json
|
|
75
|
+
{
|
|
76
|
+
"scopeRoot": "/tmp/workspace",
|
|
77
|
+
"policy": {
|
|
78
|
+
"promotion": { "reuseThreshold": 2 },
|
|
79
|
+
"decay": { "tauDays": 30, "candidateArchiveDays": 30 },
|
|
80
|
+
"activation": { "topK": 3, "minSimilarity": 0.35 },
|
|
81
|
+
"facts": { "block": "facts", "maxChars": 2200 },
|
|
82
|
+
"approval": { "default": "off" }
|
|
83
|
+
},
|
|
84
|
+
"recall": {
|
|
85
|
+
"hits": [{ "id": "aaaaaaaaaaaa", "kind": "fact", "content": "SSH listens on 2222", "score": 0.91 }],
|
|
86
|
+
"abstained": false
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## Implementation example
|
|
92
|
+
|
|
93
|
+
```ts
|
|
94
|
+
import { createHashEmbedder, createMemory } from "@arnilo/prism-memory";
|
|
95
|
+
import { createMemoryFabric } from "@arnilo/prism-memory/fabric";
|
|
96
|
+
import { createScopedMemoryPolicy } from "@arnilo/prism-memory/scoped";
|
|
97
|
+
|
|
98
|
+
const memory = createMemory({
|
|
99
|
+
tenantId: "host",
|
|
100
|
+
resourceId: workspaceRoot,
|
|
101
|
+
threadId: "scoped",
|
|
102
|
+
embedder: createHashEmbedder({ dimensions: 8 }),
|
|
103
|
+
});
|
|
104
|
+
const fabric = createMemoryFabric({ memory, consolidate: false });
|
|
105
|
+
const policy = createScopedMemoryPolicy({ memory, fabric, scopeRoot: workspaceRoot });
|
|
106
|
+
|
|
107
|
+
await policy.rememberFact("SSH jump host listens on 2222");
|
|
108
|
+
await policy.reviewSession(digest, { reviewer });
|
|
109
|
+
const { hits, abstained } = await policy.recall("SSH 2222");
|
|
110
|
+
await policy.promotionPass();
|
|
111
|
+
await policy.gcPass();
|
|
112
|
+
await policy.renderMirror();
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
Runnable walk (scope guard → overflow → review → recall → promotion → GC approve → mirror): `examples/scoped-memory.ts`.
|
|
116
|
+
|
|
117
|
+
Inject only the facts block: `fabric.createContextProvider({ includeWorking: true, includeSemantic: false })`.
|
|
118
|
+
|
|
119
|
+
## Extension and configuration notes
|
|
120
|
+
|
|
121
|
+
The policy is off until the host constructs it. `reviewer` is host-supplied (LLM or fake). `approval.default: "staged"` makes reviewer writes host-gated. `createScopedMemoryHealthCommand` follows the observational `om:status` command factory. Eval fixtures live next to the runner; import eval APIs from `@arnilo/prism-memory/scoped` (no `./scoped/eval` subpath).
|
|
122
|
+
|
|
123
|
+
## Security and performance notes
|
|
124
|
+
|
|
125
|
+
- **Scope identity:** create throws if `resourceId !== resolve(scopeRoot)` or `threadId` is missing. Observational session mismatch stays `fabric.attach`.
|
|
126
|
+
- **Writes fail closed:** `scanScopedMemoryContent` then the memory redactor. Matches name a class; payloads are not logged.
|
|
127
|
+
- **GC never silently deletes.** `legal_hold` notes are not proposed. Host `approve` calls `fabric.forget`.
|
|
128
|
+
- **Sizing:** one reviewer call per run; ledger I/O per recall; off by default. Activation is top-3 after the floor, not the whole library.
|
|
129
|
+
- **Mirror** skips scan failures. `state.json` is gitignored; markdown is the audit copy, not a second engine.
|
|
130
|
+
|
|
131
|
+
## Related APIs
|
|
132
|
+
|
|
133
|
+
- [Memory fabric](memory-fabric.md): typed notes this policy writes and recalls through.
|
|
134
|
+
- [Working and semantic memory](working-and-semantic-memory.md): `createMemory`, consent, redaction, `exportMemory`.
|
|
135
|
+
- [Observational memory](compaction-observational-memory.md): episodic ledger; scoped facts keep `sourceEntryIds`.
|
|
136
|
+
- [LLM wiki](wiki.md): source-cited knowledge compiler — not session-derived experience.
|
|
137
|
+
- [Scoped agent memory design concept](scoped-agent-memory.md): rationale, Hermes case study, research basis.
|
|
138
|
+
- [Evaluations](evaluations.md): trajectory/outcome scorers; scoped eval is fixture-only on this subpath.
|
|
@@ -25,8 +25,7 @@ Use this helper when implementing a DB-backed `SessionStore` (for example, the r
|
|
|
25
25
|
- session ids remain isolated (`assertSessionStoreConforms` always probes a secondary session)
|
|
26
26
|
- optional concurrent fork children of the same parent succeed when `exerciseConcurrentParentAppend: true`
|
|
27
27
|
- optional durable reopen/idempotency survival when `runSessionStoreConformance(..., { exerciseReopen: true })`
|
|
28
|
-
- optional `searchSessions`
|
|
29
|
-
- optional `searchSessions` bounds/ownership/empty-page checks when `exerciseSearchSessions: true` (`assertSessionStoreSearchSessions`)
|
|
28
|
+
- optional `searchSessions` case when `exerciseSearchSessions: true` (`assertSessionStoreSearchSessions`): invalid limit/query/kind rejection, empty-page bounds, limit cap, a written-message round-trip asserting `entryId`/`runId`/`turn`/`snippet` point at the match, one hit per session when a second entry also matches, the `kind` filter excluding non-matching entries, and ownership bounds
|
|
30
29
|
|
|
31
30
|
## Inputs / request
|
|
32
31
|
|
package/docs/session-stores.md
CHANGED
|
@@ -24,7 +24,7 @@ import type { SessionStore, SessionEntry } from "@arnilo/prism";
|
|
|
24
24
|
| `list(sessionId)` | Return all entries for one session in stored order. Development fallback for branch reads. |
|
|
25
25
|
| `get?(id)` | Return one entry by id, if present. Optional. |
|
|
26
26
|
| `readBranchPath?(query)` | Optional DB-friendly branch read. Return one branch's ancestor chain as a `PersistencePage<SessionEntry>` so the runtime can avoid `list(sessionId)`. |
|
|
27
|
-
| `searchSessions?(query)` | Optional bounded session search (`SessionSearchQuery` → `PersistencePage<SessionSearchHit>`). SQLite/Postgres implement FTS + metadata filters; memory
|
|
27
|
+
| `searchSessions?(query)` | Optional bounded session search (`SessionSearchQuery` → `PersistencePage<SessionSearchHit>`). SQLite/Postgres implement FTS + metadata filters; memory and JSONL scan linearly (JSONL re-reads its file per query). |
|
|
28
28
|
|
|
29
29
|
Public helpers:
|
|
30
30
|
|
|
@@ -110,9 +110,13 @@ Recognize it with `isSessionAppendConflict(error)`, not message text. Built-in s
|
|
|
110
110
|
- Branch semantics are parent links plus a leaf id. External UIs should keep branch handles as `(sessionId, leafId)`; RPC exposes an additional `handleId` for active handles.
|
|
111
111
|
- Development stores can omit `readBranchPath`; the runtime falls back to `list(sessionId)` and the pure in-memory branch walk. Database-backed stores should implement `readBranchPath` so `entries()`, `clone()`, and context rebuild read only the selected ancestor chain.
|
|
112
112
|
|
|
113
|
-
## Session search
|
|
113
|
+
## Session search
|
|
114
114
|
|
|
115
|
-
Bounded `SessionIndex` / `searchSessions` lists sessions by optional `workspaceRoot` (`metadata.workspaceRoot`), provider/model, label/summary, time range, ownership, and optional text `query
|
|
115
|
+
Bounded `SessionIndex` / `searchSessions` lists sessions by optional `workspaceRoot` (`metadata.workspaceRoot`), provider/model, label/summary, time range, ownership, and optional text `query`. SQLite/Postgres run indexed full-text search; the memory and JSONL stores scan linearly (case-sensitive substring, capped by the contract linear caps; JSONL re-reads and parses its file per query, see [Node JSONL session store](node-jsonl-session-store.md)). Hits require `sessionId` and may include `leafId` for `checkout`; never credentials or whole transcripts.
|
|
116
|
+
|
|
117
|
+
When a text `query` matches, the hit points at one matched entry per session (the store's best-ranked match on the indexed SQLite/Postgres paths, the first match in transcript order on the linear memory/JSONL paths): `entryId`, `runId`, and a 1-based `turn` (transcript position, `(timestamp, id)` order) locate it, `score` is the store relevance where the store has an index (higher is better; SQLite bm25 negated, Postgres `ts_rank_cd`; absent on linear stores; a non-discriminative term can legitimately score 0, so test for presence, not `> 0`), and `snippet` is bounded context around the match in that entry. Hits stay ordered by session `updatedAt` with cursor pagination, so hosts rank by `score` client-side when they want relevance order.
|
|
118
|
+
|
|
119
|
+
`SessionSearchQuery.kind` restricts which entry kinds the query may match (one kind or a list; omitted or `"any"` = all). Annotation search is `kind: ["label", "summary", "metadata", "custom"]`; `kind: "label"` without a `query` lists sessions that carry an annotation entry. Unknown kinds fail closed with `TypeError`. Indexed text is transcript message text plus label/summary - tool arguments and tool results are never indexed, so they cannot leak through search.
|
|
116
120
|
|
|
117
121
|
```ts
|
|
118
122
|
import { createMemorySessionStore, resolveSessionSearchQuery } from "@arnilo/prism";
|
|
@@ -121,32 +125,28 @@ const store = createMemorySessionStore([], { sessionSearchMode: "linear" });
|
|
|
121
125
|
const page = await store.searchSessions!({
|
|
122
126
|
workspaceRoot: "/repo",
|
|
123
127
|
query: "flake",
|
|
128
|
+
kind: "any",
|
|
124
129
|
limit: 20,
|
|
125
130
|
});
|
|
131
|
+
// [{ sessionId, leafId, entryId, runId, turn, score, snippet, ... }] // score is absent on linear stores
|
|
126
132
|
// Opt out: createMemorySessionStore([], { sessionSearchMode: "unsupported" })
|
|
127
133
|
// Raise the in-process scan caps for a small but large-query session set (defaults are the contract caps):
|
|
128
134
|
const wide = createMemorySessionStore([], { search: { maxLinearSessions: 5_000, maxLinearEntries: 50_000 } });
|
|
129
135
|
```
|
|
130
136
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
## Session search (0.0.11)
|
|
134
|
-
|
|
135
|
-
Bounded `SessionIndex` / `searchSessions` lists sessions by optional `workspaceRoot` (`metadata.workspaceRoot`), provider/model, label/summary, time range, ownership, and optional text `query` (FTS on SQLite/Postgres; case-sensitive substring on memory linear). Hits require `sessionId` and may include `leafId` for `checkout`; never credentials or whole transcripts.
|
|
137
|
+
The JSONL store exposes the same `searchSessions` contract through the same matcher, with no index:
|
|
136
138
|
|
|
137
139
|
```ts
|
|
138
|
-
import {
|
|
140
|
+
import { createJsonlSessionStore } from "@arnilo/prism/node/session-store-jsonl";
|
|
139
141
|
|
|
140
|
-
const store =
|
|
141
|
-
const page = await store.searchSessions!({
|
|
142
|
-
|
|
143
|
-
query: "flake",
|
|
144
|
-
limit: 20,
|
|
145
|
-
});
|
|
146
|
-
// Opt out: createMemorySessionStore([], { sessionSearchMode: "unsupported" })
|
|
142
|
+
const store = createJsonlSessionStore("./sessions.jsonl");
|
|
143
|
+
const page = await store.searchSessions!({ workspaceRoot: "/repo", query: "flake", limit: 20 });
|
|
144
|
+
// Every query reads and parses the file: O(corpus) time and memory, caps default to the linear caps.
|
|
147
145
|
```
|
|
148
146
|
|
|
149
|
-
Finite caps (defaults / hard): page 20/100; query string 4 KiB/16 KiB; snippet 512 B/4 KiB; cursor 1 KiB/4 KiB; memory linear sessions 1000/5000, entries 10000/50000, bytes 8 MiB/64 MiB; DB FTS candidates 1000/5000. Overflow fails closed via `resolveSessionSearchQuery`.
|
|
147
|
+
Finite caps (defaults / hard): page 20/100; query string 4 KiB/16 KiB; snippet 512 B/4 KiB; cursor 1 KiB/4 KiB; memory linear sessions 1000/5000, entries 10000/50000, bytes 8 MiB/64 MiB (also the JSONL scan caps); DB FTS candidates 1000/5000. Overflow fails closed via `resolveSessionSearchQuery`.
|
|
148
|
+
|
|
149
|
+
Sizing (plan 095): SQLite FTS5 and the Postgres `tsvector` column are maintained additively at append time (no background job). On the 100k-turn fixture in `scripts/benchmark-scenarios/session-search.mjs` (stored tool output, which is never indexed), the index is 18.8% of transcript page bytes and query p95 is 38 ms (`node scripts/benchmark.mjs --scenario session-search`; ceiling 100 ms). Stores receive already-redacted entries, so the index inherits the same redaction as session reads. Unindexed stores (memory, JSONL) trade that cost for O(corpus) per query — see `examples/session-search.ts` for both paths side by side.
|
|
150
150
|
|
|
151
151
|
## Security and performance notes
|
|
152
152
|
|
package/docs/supervisors.md
CHANGED
|
@@ -10,29 +10,49 @@ Use a supervisor when a host or agent must choose a child dynamically. Use `@arn
|
|
|
10
10
|
|
|
11
11
|
## Inputs / request
|
|
12
12
|
|
|
13
|
-
**Option surfaces** — `CreateSupervisorOptions` (ownership, child catalog, hooks, `childEvents`, limits), `SupervisorLimits` / `ResolvedSupervisorLimits` (depth, active children, child events, bytes), `DelegationWaitOptions` (`timeoutMs`, `signal`), `CreateSpawnAgentToolOptions` / `CreateDelegationControlToolOptions` (supervisor, tool name, sync/async mode), `WorktreeChildFactoryOptions` (workspace lifecycle, repository, roots), and `ObserveSupervisorLifecycleOptions` (supervisor, emit, redactor, steps).
|
|
13
|
+
**Option surfaces** — `CreateSupervisorOptions` (ownership, child catalog, hooks, `childEvents`, `childEventSink`, `signal`, limits), `SupervisorLimits` / `ResolvedSupervisorLimits` (depth, active children, child events, bytes, per-second rate), `SupervisorChildPolicy` (lifetime, report, milestone, budget share), `DelegationRequest` (child, input, thread, limits, lifetime, report, milestone, budget share, signal), `DelegationWaitOptions` (`timeoutMs`, `signal`), `SupervisorRunSummary` / `SupervisorChildSummary` (recovery counters), `CreateSpawnAgentToolOptions` / `CreateDelegationControlToolOptions` (supervisor, tool name, sync/async mode), `WorktreeChildFactoryOptions` (workspace lifecycle, repository, roots), and `ObserveSupervisorLifecycleOptions` (supervisor, emit, redactor, steps).
|
|
14
14
|
|
|
15
15
|
| API/field | Meaning |
|
|
16
16
|
| --- | --- |
|
|
17
|
-
| `createSupervisor({ ownership, children })` | Creates one ownership-scoped supervisor. |
|
|
18
|
-
| `SupervisorChild.createAgent(context)` | Child-owned factory; receives derived resource/thread IDs, narrowed permission, abort signal, and nested `delegate`. |
|
|
19
|
-
| `delegate({ childId, input, threadId?, limits?, signal? })` | Invokes one allow-listed child. Input is text and byte-bounded. |
|
|
20
|
-
| `delegateAsync({
|
|
21
|
-
| `wait(delegationId)` / `cancel(delegationId)` | Joins one local async child (capped at supervisor timeout) or aborts it. Unknown and foreign IDs share one denial. |
|
|
22
|
-
| `createSpawnAgentTool({ supervisor, name? })` | Returns non-exclusive `spawn_agent` tool for a parent model. Its closed schema exposes only host child IDs, input, optional thread ID, and `
|
|
17
|
+
| `createSupervisor({ ownership, children, signal? })` | Creates one ownership-scoped supervisor. Aborting `signal` (session end) ends every running child and closes the event stream. |
|
|
18
|
+
| `SupervisorChild.createAgent(context)` / `policy` | Child-owned factory; receives derived resource/thread IDs, narrowed permission, abort signal, and nested `delegate`. `policy` carries host ceilings/defaults for lifetime, report, milestone cadence, and budget share. |
|
|
19
|
+
| `delegate({ childId, input, threadId?, limits?, lifetime?, report?, milestone?, budgetShare?, signal? })` | Invokes one allow-listed child. Input is text and byte-bounded. Sync `delegate()` rejects `lifetime: "session"`. |
|
|
20
|
+
| `delegateAsync({ ... })` | Starts one local child and returns `{ delegationId, status: "running" }`. `lifetime: "session"` detaches the child from the caller signal so it survives caller turns. |
|
|
21
|
+
| `wait(delegationId)` / `cancel(delegationId)` | Joins one local async child (capped at supervisor timeout) or aborts it, session-lifetime included. Unknown and foreign IDs share one denial. |
|
|
22
|
+
| `createSpawnAgentTool({ supervisor, name? })` | Returns non-exclusive `spawn_agent` tool for a parent model. Its closed schema exposes only host child IDs, input, optional thread ID, `mode`, `lifetime`, `report`, `milestone.everyTurns`, and `budgetShare`. |
|
|
23
23
|
| `createWaitAgentTool` / `createCancelAgentTool` | Return `wait_agent` / `cancel_agent` tools for host-owned async handles. |
|
|
24
24
|
| `Supervisor.childIds` | Frozen advertised child-id list the spawn tool's schema enum is built from; model arguments cannot extend it. |
|
|
25
25
|
| `hooks.before` | May reject, modify redacted input, or narrow limits/policy. |
|
|
26
26
|
| `hooks.after` | Observes redacted terminal summary; failures cannot alter settled result. |
|
|
27
|
-
| `limits` | Depth 4/16, active children 4/32, input 64 KiB/1 MiB, steps 8/64, tools 32/256, tokens 20k/1m, timeout 60s/30m, event queue 128/4096, child events/delegation 256/4096, child-event bytes 32 KiB/256 KiB default/hard. Over-cap `delegate()` throws `SupervisorLimitError` before incrementing `activeChildren`. Hook rejection and timeout decrement the count exactly once (no leaked timers). |
|
|
27
|
+
| `limits` | Depth 4/16, active children 4/32, input 64 KiB/1 MiB, steps 8/64, tools 32/256, tokens 20k/1m, timeout 60s/30m, event queue 128/4096, child events/delegation 256/4096, child-event bytes 32 KiB/256 KiB, child events/second 10/1000 default/hard. Over-cap `delegate()` throws `SupervisorLimitError` before incrementing `activeChildren`. Hook rejection and timeout decrement the count exactly once (no leaked timers). |
|
|
28
28
|
|
|
29
29
|
## Outputs / response / events
|
|
30
30
|
|
|
31
|
-
`delegate()` returns the child's `AgentRunResult` or throws its `AgentRunError`/a supervisor denial or limit error. `delegateAsync()` returns a local running handle; `wait()` returns its result (or `{ status: "cancelled" }` after `cancel()`), and stays idempotent while its terminal record is retained (bounded by `limits.maxQueuedEvents`; an evicted or foreign id returns the same non-enumerating error). `subscribe()` emits bounded `delegation_started`, `delegation_finished`, `delegation_rejected`, and `delegation_error` metadata events.
|
|
31
|
+
`delegate()` returns the child's `AgentRunResult` or throws its `AgentRunError`/a supervisor denial or limit error. A failure (an error or a run-limit death) publishes `child_failed` before the terminal `delegation_error`: the redacted `reason`, the terminal `status` and `stopReason`, the plan-086/087 `RunLimitBreach` (`limit`, `maximum`, `observed`) in `limit` when a configured ceiling fired, and terminal `usage`. Host cancels, policy denials, and hook rejections are not failures and never emit it. `delegateAsync()` returns a local running handle; `wait()` returns its result (or `{ status: "cancelled" }` after `cancel()`), and stays idempotent while its terminal record is retained (bounded by `limits.maxQueuedEvents`; an evicted or foreign id returns the same non-enumerating error). `subscribe()` emits bounded `delegation_started`, `delegation_finished`, `delegation_rejected`, and `delegation_error` metadata events, plus the opt-in child-event family below. Aborting `CreateSupervisorOptions.signal` aborts every running child (session- and task-lifetime) and closes the stream. Hosts routing child events onto a parent session stream pass `childEventSink`; it receives the identical payload the supervisor stream carries — a redacted, capped, rate-coalesced `AgentEvent` tagged with `child: { childId, delegationId, depth }` (contract type `ChildEventOrigin`) — so a parent subscriber can route it with `event.child` and no per-type handling. Hosts may project the lifecycle events through observability `handleDelegation()` using the parent Prism run ID; no OpenTelemetry dependency enters this package.
|
|
32
|
+
|
|
33
|
+
### Child lifetime, reporting, and budget share
|
|
34
|
+
|
|
35
|
+
Every `SupervisorChild` may carry a `policy` of host ceilings/defaults; a `DelegationRequest` may only narrow them (report is clamped to the ceiling, `everyTurns` can only be raised, budget share takes the lower value, and session lifetime is denied unless the host enabled it). Defaults are exactly the 0.8 behavior: `lifetime: "task"`, `report: "on-complete"`, no milestone, no share.
|
|
36
|
+
|
|
37
|
+
- **Lifetime.** `task` children stay linked to the caller signal. `session` children must be started with `delegateAsync` (`spawn_agent` routes them to it automatically): they detach from the caller and ancestor-child signals, keep running across parent turns, hold an `activeChildren` slot until they end, and stop on `cancel(delegationId)` or when `CreateSupervisorOptions.signal` aborts.
|
|
38
|
+
- **Report.** `on-complete` publishes nothing per turn (the default). `milestones` publishes `child_milestone` (`turn`, redacted `childEvent`) when `milestone.everyTurns` divides the child turn or a host `milestone.predicate` matches; with neither configured it reports the milestone event subset. `stream` publishes every per-turn provider/tool/turn event as `delegation_child_event` — never per-token `message_delta` or full `message_*` payloads; the supervisor-wide `childEvents: true` does the same for children without their own policy.
|
|
39
|
+
- **Budget share.** `budgetShare` (0, 1] scales the inherited `maxSteps`/`maxToolCalls`/`maxTokens`/`timeoutMs` before `narrowSupervisorLimits` clamps them, so a child can never exceed its parent or host limits. It is a fraction of the *inherited supervisor limits*, not of live parent-run usage (the tool boundary carries no parent budget snapshot).
|
|
40
|
+
- **Caps and rate.** Projected child events pass the supervisor `redactor` first, are capped by `limits.maxChildEventsPerDelegation` and `limits.maxChildEventBytes`, and are rate-coalesced to `limits.maxChildEventsPerSecond` (default 10/s per child, floor 1/s window). A capped child publishes one `delegation_child_events_capped`; coalescing publishes `delegation_child_events_coalesced` with the dropped count when a window closes or the pump stops, so gaps are never silent. Size the rate to the host event loop: 10/s per child is trivial for a UI; raise it only for a child whose tool events are the UI.
|
|
32
41
|
|
|
33
42
|
### Child event passthrough (opt-in)
|
|
34
43
|
|
|
35
|
-
`createSupervisor({ childEvents: true })` projects
|
|
44
|
+
`createSupervisor({ childEvents: true })` raises the default report ceiling to `stream` for children without their own `policy.report`: it projects redacted, size-capped child `AgentEvent`s onto the same stream as `delegation_child_event` (tagged `childId`, `delegationId`, `depth`). Covered: run start/finish/`suspended`/`denied`, tool-execution started/finished/error/blocked, and turn/provider-turn started/finished — not per-token `message_delta` or full `message_*` payloads. Default off: the stream is byte-identical to today (no subscribe, no allocation). Per-child `policy.report` is authoritative; a model request can only lower it. Caps: `limits.maxChildEventsPerDelegation` (256/4096), `limits.maxChildEventBytes` (32 KiB/256 KiB), and `limits.maxChildEventsPerSecond` (10/1000); exceeding count/bytes drops further child events and emits one `delegation_child_events_capped` marker, exceeding the rate coalesces into `delegation_child_events_coalesced` (never throws). Events pass through the supervisor `redactor` before emission. Children never receive supervisor internals or store/subscription access. Resume-path rebuilds (`resumeNestedRun`) replay the persisted report/every-turns/share policy onto the rebuilt child session, so a delegation that suspended for approval keeps projecting milestones after the root run resumes; counters restart per pump, so each attempt gets the full cap.
|
|
45
|
+
|
|
46
|
+
### Recovery telemetry
|
|
47
|
+
|
|
48
|
+
`summary()` returns one frozen row per allow-listed child — `{ childId, attempts, retries, failures, failureRadius, outcome }` — maintained incrementally (O(1) per delegation, O(children) to read) and cumulative for the supervisor's lifetime, so a host can diff snapshots per root run or watch a long-lived supervisor without host-side aggregation.
|
|
49
|
+
|
|
50
|
+
- `outcome` is `idle` before the first delegation, `running` while any is live, otherwise the `delegation_finished.status` vocabulary (`succeeded`/`failed`/`aborted`/`suspended`/`denied`) or `rejected` for a hook denial. Resuming a suspended run updates the outcome but is not a new attempt.
|
|
51
|
+
- `attempts` counts started delegations, hook rejections included. `retries` counts attempts started after a `failed`/`aborted` outcome — the recovery re-dispatch metric.
|
|
52
|
+
- `failures` counts delegations that died on an error or a limit; host cancels, denials, and hook rejections are excluded.
|
|
53
|
+
- `failureRadius` is the blast radius of the child's most recent failure: task-lifetime descendant delegations still live at that moment. An unrelated or already-finished child is not counted, and a session-lifetime child is detached from the failed subtree by design.
|
|
54
|
+
|
|
55
|
+
Failure attribution is the same object the `child_failed` event carries, so a host that only keeps the summary and one that only keeps events read the same taxonomy.
|
|
36
56
|
|
|
37
57
|
## Request/response example
|
|
38
58
|
|
|
@@ -63,7 +83,7 @@ const result = await supervisor.delegate({ childId: "research", input: "Check so
|
|
|
63
83
|
|
|
64
84
|
## Model-facing spawn tool
|
|
65
85
|
|
|
66
|
-
`createSpawnAgentTool({ supervisor })` turns the same host-owned child allow-list into non-exclusive `spawn_agent` tool calls, so independent calls use the parent session's `toolConcurrency`. The schema has only `childId`, `input`, optional `threadId`,
|
|
86
|
+
`createSpawnAgentTool({ supervisor })` turns the same host-owned child allow-list into non-exclusive `spawn_agent` tool calls, so independent calls use the parent session's `toolConcurrency`. The schema has only `childId`, `input`, optional `threadId`, `mode: "sync" | "async"` (default `sync`), `lifetime: "task" | "session"`, `report`, `milestone.everyTurns`, and `budgetShare`; unknown children and malformed policy args fail closed as standard tool errors before delegation. Model arguments cannot supply child tools, identity, scopes, predicate functions, or higher limits. Async returns only a local `{ delegationId, status: "running" }` handle; a `lifetime: "session"` spawn is always async. Install `wait_agent` once per handle for wait-all, or `cancel_agent` to abort it (also the explicit end for a session-lifetime child); cancellation is terminally reported by `wait_agent`. Parent-run abort propagates to running task-lifetime children only. Handles are in-process, ownership-scoped, and bounded — they do not survive host restart.
|
|
67
87
|
|
|
68
88
|
```ts
|
|
69
89
|
import { createAgent } from "@arnilo/prism";
|
|
@@ -109,7 +129,7 @@ Supervisors propagate parent `identity` and `effectStore` to every child agent/r
|
|
|
109
129
|
- [A2A interoperability](a2a.md): separate remote protocol boundary. `A2ATaskLifecycle` adapts host durable agent/workflow state directly; it does not route A2A execution through local supervisor child planning.
|
|
110
130
|
- [Workflows](workflows.md): preferred deterministic orchestration.
|
|
111
131
|
- [Coding workspaces](coding-workspaces.md): opt-in per-child worktree isolation via `createWorktreeChildFactory`.
|
|
112
|
-
- [Coding agent tools](coding-agent-tools.md): opt-in `observeSupervisorLifecycle` bridges supervisor `delegation_*` events to coding `subagent_started` / `subagent_stopped` for host timelines.
|
|
132
|
+
- [Coding agent tools](coding-agent-tools.md): opt-in `observeSupervisorLifecycle` bridges supervisor `delegation_*` events to coding `subagent_started` / `subagent_stopped` for host timelines; `supervisor.summary()` covers recovery counters (`retries`, `failures`, `failureRadius`) that the lifecycle bridge does not carry.
|
|
113
133
|
- Examples: [`examples/autonomous-coding-loop.ts`](../examples/autonomous-coding-loop.ts) — per-child models, factory returns `Agent`; [`examples/spawn-agent-tool.ts`](../examples/spawn-agent-tool.ts) — two model-requested explore children in one tool turn.
|
|
114
134
|
- [Working and semantic memory](working-and-semantic-memory.md): child scope construction.
|
|
115
135
|
- [Host security](host-security.md): permission and credential boundaries.
|