@arnilo/prism 0.6.0 → 0.8.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 +79 -5
- package/README.md +12 -11
- package/dist/agent-approval.d.ts +4 -0
- package/dist/agent-approval.js +5 -1
- package/dist/agent-definitions.js +1 -0
- package/dist/agent-run-lifecycle.js +39 -4
- package/dist/agent-run-state.d.ts +18 -0
- package/dist/agent-run-state.js +39 -9
- package/dist/agent-session/helpers.js +6 -1
- package/dist/agent-session/session/assemble.js +159 -7
- package/dist/agent-session/session/persist.d.ts +16 -0
- package/dist/agent-session/session/persist.js +64 -4
- package/dist/agent-session/session/provider-round.d.ts +3 -3
- package/dist/agent-session/session/provider-round.js +12 -6
- package/dist/agent-session/session/tool-round.js +5 -1
- package/dist/agent-session/session/types.d.ts +22 -1
- package/dist/agent-session/session.d.ts +16 -0
- package/dist/agent-session/session.js +42 -3
- package/dist/artifacts.d.ts +39 -1
- package/dist/artifacts.js +73 -0
- package/dist/attention-compiler.d.ts +121 -0
- package/dist/attention-compiler.js +479 -0
- package/dist/checkpoints.js +7 -11
- package/dist/cli-init.js +20 -6
- package/dist/context-budget.d.ts +20 -1
- package/dist/context-budget.js +10 -1
- package/dist/contracts-core/agent.d.ts +7 -0
- package/dist/contracts-core/attention.d.ts +66 -0
- package/dist/contracts-core/attention.js +2 -0
- package/dist/contracts-core/compaction.d.ts +59 -0
- package/dist/contracts-core/compaction.js +77 -1
- package/dist/contracts-core/content.d.ts +5 -0
- package/dist/contracts-core/loop.d.ts +42 -0
- package/dist/contracts-core/provider.d.ts +4 -0
- package/dist/contracts-core/run-limits.d.ts +2 -0
- package/dist/contracts-core.d.ts +1 -0
- package/dist/contracts-core.js +1 -0
- package/dist/contracts-protocol.d.ts +44 -3
- package/dist/contracts-run-state.d.ts +32 -5
- package/dist/evidence-grounding.d.ts +29 -0
- package/dist/evidence-grounding.js +162 -0
- package/dist/host-composition.d.ts +91 -0
- package/dist/host-composition.js +279 -0
- package/dist/index.d.ts +13 -6
- package/dist/index.js +7 -4
- package/dist/input.d.ts +13 -1
- package/dist/input.js +40 -1
- package/dist/provider-events.d.ts +3 -1
- package/dist/provider-events.js +2 -2
- package/dist/providers/transport.d.ts +3 -1
- package/dist/providers/transport.js +36 -0
- package/dist/redaction.js +18 -2
- package/dist/run-bundle.d.ts +89 -0
- package/dist/run-bundle.js +149 -0
- package/dist/secure-agent.d.ts +2 -0
- package/dist/secure-agent.js +6 -1
- package/dist/testing/state-concurrency-conformance.js +5 -12
- package/dist/tool-result-fold.d.ts +12 -0
- package/dist/tool-result-fold.js +13 -6
- package/dist/tools.d.ts +10 -0
- package/dist/tools.js +41 -0
- package/docs/acp-agent.md +42 -11
- package/docs/acp.md +2 -1
- package/docs/ag-ui.md +10 -3
- package/docs/agent-definitions.md +9 -1
- package/docs/agent-events.md +4 -1
- package/docs/agent-loops.md +33 -0
- package/docs/agent-session-runtime.md +8 -7
- package/docs/attention-compiler.md +272 -0
- package/docs/cli-rpc.md +4 -2
- package/docs/coding-agent-tools.md +1 -1
- package/docs/coding-security.md +6 -3
- package/docs/coding-tools.md +0 -1
- package/docs/coding-workspaces.md +22 -0
- package/docs/compaction-and-retry.md +36 -4
- package/docs/compaction-observational-memory.md +63 -10
- package/docs/connected-apps.md +116 -0
- package/docs/context-and-skills.md +17 -2
- package/docs/conversations.md +1 -1
- package/docs/core.md +1 -1
- package/docs/dev-inspector.md +4 -0
- package/docs/device-adapters.md +1 -0
- package/docs/diagrams.md +6 -6
- package/docs/document-reader.md +18 -10
- package/docs/documents.md +40 -11
- package/docs/durable-runs.md +87 -0
- package/docs/enterprise-postgres-state.md +6 -2
- package/docs/evaluations.md +168 -4
- package/docs/execution-timeline.md +186 -0
- package/docs/guardrails.md +33 -0
- package/docs/history/0.7.0-primitive-review.md +254 -0
- package/docs/history/079-messaging-primitive-review.md +391 -0
- package/docs/history/080-messaging-followon-primitive-review.md +234 -0
- package/docs/history/081-connected-apps-primitive-review.md +74 -0
- package/docs/history/083-prism-work-primitive-review.md +84 -0
- package/docs/history/084-primitive-review.md +96 -0
- package/docs/history/085-honesty-and-cut-primitive-review.md +91 -0
- package/docs/history/README.md +5 -0
- package/docs/history/migration-0.0.md +2 -2
- package/docs/history/release-handoffs.md +75 -1
- package/docs/host-compositions.md +149 -0
- package/docs/host-security.md +2 -2
- package/docs/hosted-sandboxes.md +94 -0
- package/docs/index.md +82 -45
- package/docs/input-and-prompt-assembly.md +1 -0
- package/docs/knowledge-sync.md +84 -0
- package/docs/language-intelligence.md +1 -1
- package/docs/live-testing.md +8 -3
- package/docs/mcp-tools.md +3 -1
- package/docs/memory-fabric.md +416 -0
- package/docs/messaging-channel-operations.md +166 -0
- package/docs/messaging-channels.md +150 -0
- package/docs/migrate-to-0.5.md +1 -1
- package/docs/migrate-to-0.6.md +1 -0
- package/docs/migrate-to-0.7.md +345 -0
- package/docs/migrate-to-0.8.md +124 -0
- package/docs/migration.md +43 -1
- package/docs/model-registry.md +12 -2
- package/docs/model-routing.md +79 -4
- package/docs/multi-agent-patterns.md +20 -6
- package/docs/observability.md +52 -1
- package/docs/openapi-tools.md +1 -1
- package/docs/operations.md +14 -4
- package/docs/options-index.md +47 -3
- package/docs/peer-dependencies.md +12 -10
- package/docs/postgres-persistence.md +1 -1
- package/docs/process-sessions.md +3 -1
- package/docs/prompt-registry.md +1 -1
- package/docs/provider-caching.md +4 -2
- package/docs/provider-conformance.md +1 -1
- package/docs/provider-layer.md +2 -2
- package/docs/provider-packages.md +22 -22
- package/docs/providers/bedrock.md +71 -7
- package/docs/providers/neuralwatt.md +5 -1
- package/docs/providers/openai.md +1 -1
- package/docs/rag.md +24 -8
- package/docs/realtime-voice.md +87 -0
- package/docs/release-and-install.md +53 -45
- package/docs/run-bundle.md +92 -0
- package/docs/runs-and-usage.md +17 -2
- package/docs/server.md +7 -3
- package/docs/sheets.md +9 -9
- package/docs/signal-channel.md +112 -0
- package/docs/speech.md +7 -1
- package/docs/sqlite-persistence.md +1 -1
- package/docs/supervisors.md +33 -5
- package/docs/telegram-channel.md +157 -0
- package/docs/testing.md +2 -2
- package/docs/thinking-and-reasoning.md +3 -1
- package/docs/tools.md +6 -5
- package/docs/web-tools.md +2 -1
- package/docs/wiki.md +1 -1
- package/docs/work-artifacts-and-review.md +14 -4
- package/docs/work-connectors.md +12 -10
- package/docs/work-sandbox.md +115 -0
- package/docs/work-tools.md +50 -18
- package/docs/workflows.md +69 -1
- package/docs/working-and-semantic-memory.md +25 -14
- package/package.json +5 -3
- package/templates/README.md +2 -0
- package/templates/business-worker/README.md.tmpl +19 -0
- package/templates/business-worker/env.example.tmpl +1 -0
- package/templates/business-worker/gitignore.tmpl +11 -0
- package/templates/business-worker/manifest.json +12 -0
- package/templates/business-worker/package.json.tmpl +23 -0
- package/templates/business-worker/src/agent.ts.tmpl +92 -0
- package/templates/business-worker/src/index.ts.tmpl +13 -0
- package/templates/business-worker/src/tests/agent.test.ts.tmpl +77 -0
- package/templates/business-worker/tsconfig.json.tmpl +15 -0
- package/templates/personal-assistant/README.md.tmpl +18 -0
- package/templates/personal-assistant/env.example.tmpl +1 -0
- package/templates/personal-assistant/gitignore.tmpl +11 -0
- package/templates/personal-assistant/manifest.json +11 -0
- package/templates/personal-assistant/package.json.tmpl +23 -0
- package/templates/personal-assistant/src/agent.ts.tmpl +65 -0
- package/templates/personal-assistant/src/index.ts.tmpl +13 -0
- package/templates/personal-assistant/src/tests/agent.test.ts.tmpl +28 -0
- package/templates/personal-assistant/tsconfig.json.tmpl +15 -0
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# Knowledge synchronization
|
|
2
|
+
|
|
3
|
+
## What it does
|
|
4
|
+
|
|
5
|
+
`syncKnowledge` in `@arnilo/prism-memory/rag` pages an injected `KnowledgeConnector`, applies source upserts/deletes/ACL/withhold actions through existing `replaceSource` / `deleteSource` / `setSourceAccess`, and compare-and-swaps an opaque resume cursor on a host `CheckpointStore` **only after that page is fully committed**. Unchanged content hashes skip embedding. Source freshness (`current` / `stale` / `unavailable`) is recorded on ingestion status. `createGoogleDriveConnector` is the first connector: Drive `files.list` bootstrap plus `changes.list` incremental sync.
|
|
6
|
+
|
|
7
|
+
## When to use it
|
|
8
|
+
|
|
9
|
+
Use it when a host must keep a RAG corpus aligned with an enterprise file source without a full re-index and without trusting notification payloads as authorization. Do not use it as a general crawler, as a substitute for work-connector file tools, or as a way to infer group membership from Drive.
|
|
10
|
+
|
|
11
|
+
## Inputs / request
|
|
12
|
+
|
|
13
|
+
| API/field | Meaning |
|
|
14
|
+
| --- | --- |
|
|
15
|
+
| `syncKnowledge({ connector, checkpoints, checkpoint, store, embedder, scope, ... })` | Runs at most `maxPages` committed pages. |
|
|
16
|
+
| `KnowledgeConnector.listChanges({ cursor, limit, signal })` | Returns `{ changes, resumeCursor, done }`. `done` means caught up. |
|
|
17
|
+
| `KnowledgeChange` | `upsert` (text + contentHash + grants), `delete`, `acl`, or `withhold` (`stale` / `unavailable`). |
|
|
18
|
+
| `createGoogleDriveConnector({ tokenProvider, resolveAccess, driveId?, folderId? })` | Drive adapter. Token is resolved per request. `resolveAccess` is host-owned ACL mapping. |
|
|
19
|
+
| `onInvalidCursor` | `"resync"` (default) restarts bootstrap once; `"fail"` throws `RagSyncCursorError`. |
|
|
20
|
+
| `pageSize` / `maxPages` / `maxRetries` | Defaults 50 / 8 / 3; hard caps 200 / 64 / 8. |
|
|
21
|
+
|
|
22
|
+
`authorization` on retrieve remains host-verified RAG ACL from Task 11. Empty grants withhold. Missing ACL capability on the store fails closed.
|
|
23
|
+
|
|
24
|
+
## Outputs / response / events
|
|
25
|
+
|
|
26
|
+
- `{ pages, upserted, deleted, skipped, withheld, cursor?, exhausted }`
|
|
27
|
+
- Cursor value `{ v: 1, cursor }` under the host checkpoint namespace/key. Crash before CAS replays the same page; `contentHash` skip makes replay embed-free.
|
|
28
|
+
- Ingestion status `freshness` is `current` after a granted upsert, `stale`/`unavailable` after withhold. Deletes remove status.
|
|
29
|
+
|
|
30
|
+
No tools, no watch-channel authorization, no events.
|
|
31
|
+
|
|
32
|
+
## Request/response example
|
|
33
|
+
|
|
34
|
+
```json
|
|
35
|
+
{
|
|
36
|
+
"scope": { "tenantId": "t1", "resourceId": "docs", "corpusId": "drive" },
|
|
37
|
+
"result": { "pages": 1, "upserted": 2, "deleted": 0, "skipped": 0, "withheld": 1, "exhausted": false }
|
|
38
|
+
}
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Implementation example
|
|
42
|
+
|
|
43
|
+
```ts
|
|
44
|
+
import { createMemoryCheckpointStore } from "@arnilo/prism";
|
|
45
|
+
import { createHashEmbedder, createMemoryVectorStore } from "@arnilo/prism-memory";
|
|
46
|
+
import { createGoogleDriveConnector, syncKnowledge } from "@arnilo/prism-memory/rag";
|
|
47
|
+
|
|
48
|
+
const store = createMemoryVectorStore();
|
|
49
|
+
await syncKnowledge({
|
|
50
|
+
connector: createGoogleDriveConnector({
|
|
51
|
+
tokenProvider: () => process.env.DRIVE_TOKEN!, // resolved at the HTTP edge only
|
|
52
|
+
resolveAccess: (permission) =>
|
|
53
|
+
permission.type === "user" && permission.emailAddress ? { principalId: permission.emailAddress } : undefined,
|
|
54
|
+
}),
|
|
55
|
+
checkpoints: createMemoryCheckpointStore(),
|
|
56
|
+
checkpoint: { namespace: "prism.rag.sync", key: "handbook", tenantId: "t1" },
|
|
57
|
+
store,
|
|
58
|
+
embedder: createHashEmbedder(),
|
|
59
|
+
scope: { tenantId: "t1", resourceId: "docs", corpusId: "handbook" },
|
|
60
|
+
});
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Drive `changes.watch` payloads are wake-ups only — call `syncKnowledge` again; never treat a notification body as ACL or content.
|
|
64
|
+
|
|
65
|
+
## Extension and configuration notes
|
|
66
|
+
|
|
67
|
+
- Any `KnowledgeConnector` can be injected; only Google Drive ships. Hosts own OAuth (`drive.readonly` via `createGoogleWorkspaceOAuthProvider({ capabilities: ["files"] })`), checkpoint durability, and `resolveAccess`.
|
|
68
|
+
- Shared drives: pass `driveId`. Folder-scoped bootstrap: pass `folderId` (Drive identifier charset only).
|
|
69
|
+
- Unsupported Google-native types (Sheets, Slides, folders) are skipped. Google Docs export as `text/plain`. Public `anyone`/`domain` permissions withhold the file unless `resolveAccess` maps them.
|
|
70
|
+
- Task 14 can schedule `syncKnowledge` as an ordinary workload; this package does not add a second scheduler.
|
|
71
|
+
|
|
72
|
+
## Security and performance notes
|
|
73
|
+
|
|
74
|
+
- Credentials never enter argv, cursors, status, or logs. Token header only.
|
|
75
|
+
- Model-supplied metadata `filter` is still not authorization. Notification payloads are not authorization. Unmapped groups are omitted, not inferred. Unmapped `anyone`/`domain` withholds the source.
|
|
76
|
+
- 403 on content → `unavailable` withhold (empty grants). 404/`removed`/`trashed` → delete. 400/410 page tokens → `RagSyncCursorError`. 429 → `RagSyncThrottleError` with bounded retries.
|
|
77
|
+
- Pages are bounded. Unchanged hashes perform no embedding. Only committed pages CAS the cursor.
|
|
78
|
+
|
|
79
|
+
## Related APIs
|
|
80
|
+
|
|
81
|
+
- [Retrieval-augmented generation](rag.md): `replaceSource`, document ACL, retrieval.
|
|
82
|
+
- [Work connectors](work-connectors.md): GWS CLI file tools are a different surface.
|
|
83
|
+
- [Credentials and redaction](credentials-and-redaction.md): OAuth token providers.
|
|
84
|
+
- [Live and end-to-end testing](live-testing.md): `PRISM_TEST_DRIVE_ACCESS_TOKEN` skip-not-fail probe.
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
|
|
19
19
|
Use when a host wants IDE-like language intelligence without embedding a parser framework or trusting model-chosen server commands. Wire host-pinned server binaries (for example `typescript-language-server --stdio`) and gate renames with the same `ExecutionPolicy` used for write/edit tools.
|
|
20
20
|
|
|
21
|
-
Do not use this as a sandbox, tool registry, or process session manager. Optional process-session registration of LSP children can use `createProcessSessions` ([Process sessions](process-sessions.md)).
|
|
21
|
+
Do not use this as a sandbox, tool registry, or process session manager. Optional process-session registration of LSP children can use `createProcessSessions` ([Process sessions](process-sessions.md)). When paired with a container sandbox, language server processes, shell operations, and filesystem tools must agree on the single declared workspace root (typically `/workspace` inside Docker) to maintain workspace coherence and prevent split-brain states.
|
|
22
22
|
|
|
23
23
|
```ts
|
|
24
24
|
import { createLanguageIntelligence } from "@arnilo/prism-coding-tools/agent";
|
package/docs/live-testing.md
CHANGED
|
@@ -71,22 +71,25 @@ Set only the rows you want to run; everything else skips. Least-privilege scope
|
|
|
71
71
|
| `web-tools/obscura-live` | active | `PRISM_LIVE_OBSCURA` + `PRISM_OBSCURA_BIN` | — | Local obscura CLI binary; suite fails closed if flag set without binary. | Local process, no API spend. |
|
|
72
72
|
| `memory/observational-live` | active | `PRISM_LIVE_OBSERVATIONAL_MEMORY_TESTS` + `OPENAI_API_KEY` | `PRISM_LIVE_OPENAI_MODEL` (not wired yet) | Reuses the OpenAI key as the compaction worker provider. | A few small summarization requests. |
|
|
73
73
|
| `memory/compaction-llm-live` | active | `PRISM_LIVE_COMPACTION_TESTS` | — | Stub leg today: live summary-provider checks are wired by plans/064 Task 6 (provider key + model env TBD there). | n/a until wired. |
|
|
74
|
-
| `
|
|
75
|
-
| `
|
|
74
|
+
| `work/libreoffice-golden` | active | `PRISM_TEST_LIBREOFFICE` | — | Local LibreOffice binary renders golden documents; no secret. | Local process, no API spend. |
|
|
75
|
+
| `work/drawio-live` | active | any of: `PRISM_LIVE_DRAWIO_URL` / `PRISM_TEST_DRAWIO_URL` | — | Operator-hosted drawio export service URL (not a secret). | 1-2 export requests to your own service. |
|
|
76
76
|
| `core/postgres` | active | `PRISM_TEST_POSTGRES_URL` | — | Throwaway PostgreSQL database URL (sessions + enterprise + event-source + memory vector legs). | Local/container DB, no API spend. |
|
|
77
77
|
| `core/nats` | active | `PRISM_TEST_NATS_URL` | — | NATS server URL with JetStream enabled. | Local/container server, no API spend. |
|
|
78
78
|
| `coding-tools/docker-sandbox` | active | `PRISM_TEST_DOCKER_SANDBOX` + `PRISM_TEST_DOCKER_BIN` + `PRISM_TEST_DOCKER_IMAGE` + `PRISM_TEST_DOCKER_USER` | — | Local Docker daemon + pinned minimal sandbox image; no secret. | Local containers, no API spend. |
|
|
79
|
+
| `coding-tools/e2b-sandbox-live` | active | `PRISM_TEST_E2B_API_KEY` | — | E2B API key; least privilege: one throwaway sandbox, no production templates. | 1 create + 2 exec + filesystem-only pause + connect/resume + kill. |
|
|
80
|
+
| `work/mistral-ocr-live` | active | `PRISM_TEST_MISTRAL_API_KEY` | — | Mistral OCR API key; least privilege: one throwaway 2-page sample PDF, no Files API upload. | 1 OCR request against mistral-ocr-latest. |
|
|
79
81
|
| `core/keychain` | active | `PRISM_TEST_KEYCHAIN` | — | Real OS keychain; writes throwaway test entries only. | Local, no API spend. |
|
|
80
82
|
| `acp/client-smoke` | active | `PRISM_TEST_ACP_CLIENT` | — | Real ACP SDK client over stdio in a subprocess; sandboxed, policy never disabled. | Local process, no API spend. |
|
|
81
83
|
| `canaries/deployed` | active | `PRISM_LIVE_CANARIES`; optional: `PRISM_CANARY_TIMEOUT_MS` `PRISM_CANARY_REPORT` | — | Deployed prism provider/MCP/A2A endpoints; script itself validates all PRISM_CANARY_* URL/token vars and credential-free HTTPS. | 1-4 bounded requests (64 KiB JSON cap) against your deployments. |
|
|
82
84
|
| `providers/azure` | active | `PRISM_LIVE_PROVIDER_TESTS` + `AZURE_OPENAI_ENDPOINT` + `AZURE_OPENAI_API_KEY` + `PRISM_LIVE_AZURE_MODEL` | `PRISM_LIVE_AZURE_MODEL` (default `gpt-5.1`) | Azure OpenAI resource key. | 3-4 requests on the configured deployment. |
|
|
83
|
-
| `providers/bedrock` | active | `PRISM_LIVE_PROVIDER_TESTS` + `AWS_ACCESS_KEY_ID` + `AWS_SECRET_ACCESS_KEY` + `AWS_REGION` | `PRISM_LIVE_BEDROCK_MODEL` (default `us.anthropic.claude-haiku-4-5-20251001-v1:0`) | AWS access key + secret (SigV4). |
|
|
85
|
+
| `providers/bedrock` | active | `PRISM_LIVE_PROVIDER_TESTS` + `AWS_ACCESS_KEY_ID` + `AWS_SECRET_ACCESS_KEY` + `AWS_REGION` | `PRISM_LIVE_BEDROCK_MODEL` (default `us.anthropic.claude-haiku-4-5-20251001-v1:0`) | AWS access key + secret (SigV4). | 6-8 requests on haiku-class model (compatible SSE + native Converse/ConverseStream). |
|
|
84
86
|
| `providers/vertex` | active | `PRISM_LIVE_PROVIDER_TESTS` + `GOOGLE_VERTEX_PROJECT` + `PRISM_VERTEX_ACCESS_TOKEN` | `PRISM_LIVE_VERTEX_MODEL` (default `gemini-2.5-flash`) | Pre-minted Vertex bearer token (e.g. gcloud auth print-access-token). | 3-4 requests on flash-class model. |
|
|
85
87
|
| `providers/ollama` | active | `PRISM_LIVE_PROVIDER_TESTS` + `OLLAMA_BASE_URL` | `PRISM_LIVE_OLLAMA_MODEL` (default `(first model served by OLLAMA_BASE_URL)`) | No credential for local ollama serve; Ollama Cloud key optional via provider options. | 3-4 requests on the first locally pulled model. |
|
|
86
88
|
| `providers/ai-sdk` | active | `PRISM_LIVE_PROVIDER_TESTS` + `OPENAI_API_KEY` | `PRISM_LIVE_AISDK_MODEL` (default `gpt-5.1`) | Reuses OPENAI_API_KEY through the real @ai-sdk/openai provider. | 3 requests on gpt-5.1. |
|
|
87
89
|
| `providers/model-discovery` | active | `PRISM_LIVE_PROVIDER_TESTS`; any of: `OPENAI_API_KEY` / `GEMINI_API_KEY` | — | Reuses OPENAI_API_KEY or GEMINI_API_KEY for a real listing request. | 2 GET /models requests (second cached in TTL). |
|
|
88
90
|
| `cli/live-journey` | planned | `PRISM_LIVE_PROVIDER_TESTS` + `OPENAI_API_KEY` | — | Packed CLI: init/provider-add/print/json/rpc over a real provider; transcript secret-scanned. | 1 pack + install, offline scaffold tests, <=3 wire prompts on the selected provider model (wire legs skip on 401/403). |
|
|
89
91
|
| `memory/rag-rerankers-live` | active | any of: `PRISM_TEST_TEI_RERANKER_URL` / `PRISM_TEST_HOSTED_RERANK_URL`; optional: `PRISM_TEST_HOSTED_RERANK_URL` | `PRISM_LIVE_TEI_RERANKER_MODEL` (default `(endpoint default model)`), `PRISM_LIVE_HOSTED_RERANK_MODEL` (default `(endpoint default model)`) | Real TEI / OpenAI-compatible rerank endpoints; each leg self-skips when its endpoint env is unset. | 1 rerank request per configured endpoint (≤2 total). |
|
|
92
|
+
| `memory/drive-sync-live` | active | `PRISM_TEST_DRIVE_ACCESS_TOKEN`; optional: `PRISM_TEST_DRIVE_FOLDER_ID` `PRISM_TEST_DRIVE_SHARED_DRIVE_ID` | — | Delegated Drive readonly token; least privilege: one throwaway folder. Folder/shared-drive ids optional. | <=2 Drive list/changes pages plus file media for that page; replay must not re-embed. |
|
|
90
93
|
| `coding-tools/openapi-live` | active | `PRISM_LIVE_OPENAPI_TOOLS` | — | Real public OpenAPI 3.1 spec (warnely.com) + real GET tool calls; no credential. | 3 HTTP requests against example.com-class public hosts (plan budget ≤5). |
|
|
91
94
|
| `coding-tools/computer-use-live` | active | `PRISM_TEST_COMPUTER_USE` + `PRISM_COMPUTER_USE_BIN` | — | Real host computer-use-linux MCP binary over stdio; real tool inventory + one bounded read-only screenshot. | Local desktop only; ≤30s ceiling. |
|
|
92
95
|
| `mcp/client-smoke` | active | `PRISM_TEST_MCP_CLIENT` | — | Real @modelcontextprotocol/client SDK over a real stdio subprocess serving createPrismMcpServer. | Local only; ≤30s. |
|
|
@@ -101,6 +104,8 @@ Set only the rows you want to run; everything else skips. Least-privilege scope
|
|
|
101
104
|
| `coding-tools/lsp-forge` | active | — (hermetic leg) | — | LSP/language-intelligence + forge suites: real child-process spawns over the real LSP/forge wire protocols against fixture binaries. | Hermetic; no network. |
|
|
102
105
|
| `ag-ui/conformance` | active | — (hermetic leg) | — | AG-UI + ACP conformance suites: real-event replay over the acp/a2a/ag-ui protocol surfaces (fixture agents, real event-source wire semantics). | Hermetic; no network. |
|
|
103
106
|
| `prism-providers/conformance` | active | — (hermetic leg) | — | Plan-065 machine-checked thinking coverage: every first-party reasoning catalog model declares capabilities.thinkingLevels + a compat.thinkingFamily stamp and emits a legal effort field on the wire (14 catalogs walked hermetically). | free |
|
|
107
|
+
| `channels/telegram-live` | active | `PRISM_LIVE_TELEGRAM` + `TELEGRAM_BOT_TOKEN`; optional: `PRISM_LIVE_TELEGRAM_CHAT_ID` | — | Operator-owned Telegram bot token. getMe + 1s poll; send only if PRISM_LIVE_TELEGRAM_CHAT_ID names a chat you operate. Never creates contacts. | 1 getMe + 1 getWebhookInfo (+ optional 1 sendMessage). |
|
|
108
|
+
| `channels/signal-live` | active | `PRISM_LIVE_SIGNAL` + `PRISM_LIVE_SIGNAL_SOCKET` + `PRISM_LIVE_SIGNAL_ACCOUNT` + `PRISM_LIVE_SIGNAL_TERMS_VERSION`; optional: `PRISM_LIVE_SIGNAL_RECIPIENT_UUID` | — | Existing private signal-cli v0.14.8 Unix socket and account you already operate. Never registers or links. Send only to PRISM_LIVE_SIGNAL_RECIPIENT_UUID. | 1 subscribeReceive + health (+ optional 1 send). |
|
|
104
109
|
<!-- generated:live-matrix:end -->
|
|
105
110
|
|
|
106
111
|
## Strict CI workflow
|
package/docs/mcp-tools.md
CHANGED
|
@@ -48,7 +48,7 @@ await bridge.listResources();
|
|
|
48
48
|
await bridge.getPrompt("review", { topic: "security" });
|
|
49
49
|
```
|
|
50
50
|
|
|
51
|
-
Roots and sampling callbacks are **deprecated with MCP 2026-07-28 (SEP-2577)** and kept only for existing legacy callers;
|
|
51
|
+
Roots and sampling callbacks are **deprecated with MCP 2026-07-28 (SEP-2577)** and kept only for existing legacy callers; Prism MCP adds nothing on those surfaces and they may be removed when the protocol revision does (earliest per spec: a revision released on or after 2027-07-28). Elicitation is the active capability: on the modern era a server answering a tool call with `input_required` is fulfilled by the SDK's MRTR driver against the same `elicitation` callback, capped by `maxMrtrRounds` (default 10, the SDK's own bound — the option can only tighten it) with the call timeout as the outer ceiling; on the legacy era the same handler serves direct `elicitation/create` requests. `humanInteraction: true` remains mandatory for accepted elicitation, and no hand-written retry/state machinery exists on either path.
|
|
52
52
|
|
|
53
53
|
Server capability matrix for the modular TypeScript SDK v2 (`@modelcontextprotocol/client` + `@modelcontextprotocol/server` 2.0.0): tools/resources/prompts and their list-change notifications are supported through official registrations; roots/sampling/form+URL elicitation are supported as explicit client callbacks. Missing server resources/prompts throw `McpUnsupportedCapabilityError` with `ERR_PRISM_MCP_UNSUPPORTED_CAPABILITY`. Resource/prompt results and sampling/elicitation inputs/results are bounded JSON. Accepted form/URL elicitation requires host-only `humanInteraction: true`; bridge strips marker before protocol output and fails closed when absent. Automatic root discovery/consent, model selection, credential resolution, URL navigation, generic command proxying, and custom JSON-RPC are unsupported.
|
|
54
54
|
|
|
@@ -221,6 +221,7 @@ Remote MCP tools default to `external_mutation`/`unsupported` unless the host `e
|
|
|
221
221
|
| Untrusted subprocess (stdio) | Explicit `command` / `args` / `env` / `cwd`; review before deploy |
|
|
222
222
|
| SSRF / DNS rebinding / redirects (HTTP) | Exact HTTPS origins; credentials/fragments/redirects denied; every DNS answer public; one address pinned per request; explicit loopback-only HTTP escape hatch. Since 0.2.1 the client transport re-routes through the shared core `pinnedFetch` primitive (DNS-pinned fetch) with byte-identical `McpBridgeError`/`McpOAuthError` wrapping |
|
|
223
223
|
| Hostile discovery / schema compilation | Raw SDK `tools/list` requests avoid SDK Ajv output-schema compilation; finite pages/tools/cursors/metadata/schema totals; failed refresh leaves previous tools unchanged |
|
|
224
|
+
| Stale or revoked tool after refresh | Loaded `execute` compares the listing digest (schema + effect) to the current remote tool; mismatch or deletion fails closed before `tools/call` |
|
|
224
225
|
| Tool-name shadowing | Prefixed names + `createToolRegistry({ duplicate: "error" })` |
|
|
225
226
|
| MCP Apps metadata/HTML | Explicit extension acknowledgement; bounded nested metadata; app-only tools absent from model list; only linked `ui://` HTML/MIME resource body reaches the host renderer |
|
|
226
227
|
| Oversized/deep/wide server output | One aggregate byte/depth/property walk covers content, structured content, compatibility `toolResult`, and bounded remote errors before `ToolResult` |
|
|
@@ -304,6 +305,7 @@ The scenario stays sandboxed (read-only echo tool, authorize-gated denial, no po
|
|
|
304
305
|
|
|
305
306
|
## Related APIs
|
|
306
307
|
|
|
308
|
+
- [Connected apps](connected-apps.md): identity-bound host admission and lifecycle for a small set of MCP bridges.
|
|
307
309
|
- [Agent identity](agent-identity.md): optional verified identity on MCP authorize results
|
|
308
310
|
- [Tools](tools.md): registry, dispatch, validation
|
|
309
311
|
- [Web search, fetch, and extraction](web-tools.md): preferred direct bounded Brave/Exa/Firecrawl production path
|
|
@@ -0,0 +1,416 @@
|
|
|
1
|
+
# Memory fabric
|
|
2
|
+
|
|
3
|
+
## What it does
|
|
4
|
+
|
|
5
|
+
Typed notes over the memory stores a host already configured. `createMemoryFabric()` does not add a
|
|
6
|
+
store, a database, or a second memory engine: it writes notes through `createMemory()` and reads them
|
|
7
|
+
back with the same consent, redaction, and lineage rules.
|
|
8
|
+
|
|
9
|
+
| Kind | Backing store | Notes |
|
|
10
|
+
| --- | --- | --- |
|
|
11
|
+
| `fact` | vector store | `metadata.fabric` carries the note fields. |
|
|
12
|
+
| `procedure` | vector store | Never returned by an untyped recall; request `kinds: ["procedure"]`. |
|
|
13
|
+
| `file` | vector store | `path` is required; `content` defaults to the path. The fabric does not read the file. |
|
|
14
|
+
| `working` | working store | Labeled block under the `_fabric.blocks` key of the scope's working value. |
|
|
15
|
+
| `episode` | observational memory | A **view** over an existing observation id. Nothing is written. |
|
|
16
|
+
|
|
17
|
+
`recall` returns ranked notes plus a parallel `explain` row for each hit, and `searchConversation`
|
|
18
|
+
lexically searches the current branch and returns the observational-memory page around each match.
|
|
19
|
+
`tools()` builds the five governed tools a host may hand to an agent, and `forget` tombstones one note
|
|
20
|
+
or working block.
|
|
21
|
+
|
|
22
|
+
### Relationship to observational memory
|
|
23
|
+
|
|
24
|
+
Observational memory stays **episodic**: its ledger is still the source-backed record of what happened
|
|
25
|
+
in a session, its observer/reflector/dropper workers are still the only writers of observations and
|
|
26
|
+
reflections, and nothing here re-observes, re-folds, or re-scores a transcript. The `episode` kind is a
|
|
27
|
+
**view**: it reads an existing 12-hex observation id through the attached session's ledger and stores
|
|
28
|
+
nothing, so an episode note cannot disagree with the observation it names, and a valid id that survives
|
|
29
|
+
folding keeps working without a copy. Semantic (`fact`, `procedure`, `file`) and working notes are the
|
|
30
|
+
fabric's own rows in the stores `createMemory()` already owns; they never become observations. Hosts
|
|
31
|
+
that want a concluded case promoted out of the ledger write the generalizable part as a `procedure` and
|
|
32
|
+
keep the specifics as a `fact` or an `episode` view — promotion is an explicit host decision, and one
|
|
33
|
+
that a case conclusion filed as a `procedure` fails an invariant on (see the eval fixture below). The
|
|
34
|
+
fabric can make that decision cheap without turning it into an implicit one: for `fact`/`procedure`,
|
|
35
|
+
`remember({ reflectionId })` derives the note content from a reflection that carries an explicit
|
|
36
|
+
`om.scope.bound` entry on a **closed** work scope ([work-scope index](compaction-observational-memory.md)).
|
|
37
|
+
An unknown, unbound, or open-scope-only reflection is rejected, the note records the bind it came from
|
|
38
|
+
as `promotedFrom: { reflectionId, scopeId }`, and no other field changes — the fold, consent, and
|
|
39
|
+
worker rules of a normal `fact`/`procedure` write all still apply.
|
|
40
|
+
|
|
41
|
+
An imported fabric starts nothing: no workers, no timers, no tools, no context provider until a host
|
|
42
|
+
calls one of its methods. Calling `tools()` registers nothing anywhere — it returns definitions.
|
|
43
|
+
|
|
44
|
+
### Write path (consolidation, default on)
|
|
45
|
+
|
|
46
|
+
A `fact`, `procedure`, or `file` write first recalls the nearest same-kind note (one bounded
|
|
47
|
+
`memory.recall` batch using the same oversample factor recall uses) and then folds:
|
|
48
|
+
|
|
49
|
+
| Nearest note | Write |
|
|
50
|
+
| --- | --- |
|
|
51
|
+
| below `consolidate.threshold` (default 0.85) | insert a new row. |
|
|
52
|
+
| the same note — same tokens, or the same `path` for `file` | rewrite that row in place: same id, annotations union, the new note's claims win. |
|
|
53
|
+
| the same subject with changed content | set `validTo` on the old row, then insert a new row carrying `supersedes`. |
|
|
54
|
+
|
|
55
|
+
A `file` note never folds across paths (two paths are two documents), `working` notes are already
|
|
56
|
+
keyed by block label, and `episode` views write nothing. A rewrite keeps the row's `createdAt`,
|
|
57
|
+
consent, importance, and every non-fabric metadata key; an explicit `id` or `supersedes` from the
|
|
58
|
+
caller disables auto-folding for that write.
|
|
59
|
+
|
|
60
|
+
### Workers (opt-in)
|
|
61
|
+
|
|
62
|
+
Both workers run inline (awaited) after the write, see redacted text only, call no model and no
|
|
63
|
+
tools, and are skipped entirely by `passive: true`:
|
|
64
|
+
|
|
65
|
+
| Worker | Option | Does |
|
|
66
|
+
| --- | --- | --- |
|
|
67
|
+
| Linker | `linker: { enabled, topK }` (off by default, `topK` 3) | stores `related` edges to the closest neighbors, weight = embedding similarity. |
|
|
68
|
+
| Evolution | `evolution: { enabled, maxPatches }` (off by default) | unions the new note's keywords into neighbor notes and fills a missing neighbor `context` — never content, validity, consent, or `sourceEntryIds`. |
|
|
69
|
+
|
|
70
|
+
Workers run only while a session is attached (`attach`), so an unattached fabric enriches nothing even
|
|
71
|
+
when the options ask for it — the plain `remember` path writes notes and stops there.
|
|
72
|
+
|
|
73
|
+
## When to use it
|
|
74
|
+
Use it when a host wants durable, time-bounded notes (facts, procedures, file references, promoted
|
|
75
|
+
episodes) alongside the plain working/semantic memory it already has, or wants one call that returns
|
|
76
|
+
both the matching notes and why they matched. Skip it when plain `createMemory()` recall is enough —
|
|
77
|
+
the fabric is an opt-in layer, not a replacement.
|
|
78
|
+
|
|
79
|
+
## Inputs / request
|
|
80
|
+
|
|
81
|
+
**Option surfaces** — `createMemoryFabric` takes `CreateMemoryFabricOptions`; `attach` takes `MemoryFabricAttachOptions`; `tools()` returns definitions configured by `MemoryFabricToolsOptions`; the reflection paths use `MemoryFabricWriteOptions` and `MemoryFabricRecallOptions`; and the opt-in workers are configured by `MemoryFabricConsolidationOptions`, `MemoryFabricLinkerOptions`, `MemoryFabricEvolutionOptions`, `MemoryFabricConversationSearchOptions`, and `CreateFabricFileJailOptions`.
|
|
82
|
+
|
|
83
|
+
`createMemoryFabric(options)`
|
|
84
|
+
|
|
85
|
+
| Field | Type | Required | Meaning |
|
|
86
|
+
| --- | --- | --- | --- |
|
|
87
|
+
| `memory` | `Memory` | yes | An existing `createMemory()` instance; supplies scope, embedder, stores, consent, and limits. |
|
|
88
|
+
| `observational` | `{ session: { entries() } }` | only for `episode` | An attached observational-memory session (`om.attach(session)` result satisfies it). |
|
|
89
|
+
| `consolidate` | `boolean \| { threshold? }` | no | Default `true`; `false` writes unconditionally, `threshold` (default 0.85) is the cosine floor for folding. |
|
|
90
|
+
| `linker` | `boolean \| { enabled?, topK? }` | no | Default off; `topK` defaults to 3 and is capped at 32. |
|
|
91
|
+
| `evolution` | `boolean \| { enabled?, maxPatches? }` | no | Default off; `maxPatches` defaults to the worker per-turn budget and is capped at 32. |
|
|
92
|
+
| `passive` | `boolean` | no | `true` skips both opt-in workers; consolidation is part of the write path and still runs. |
|
|
93
|
+
| `workerLimits` | `MemoryWorkerLimitOptions` | no | Observational-memory worker caps resolved by `resolveMemoryWorkerLimits` (patches per write, patch payload bytes). |
|
|
94
|
+
|
|
95
|
+
`remember(input)` fields:
|
|
96
|
+
|
|
97
|
+
| Field | Type | Required | Meaning |
|
|
98
|
+
| --- | --- | --- | --- |
|
|
99
|
+
| `kind` | `"working" \| "episode" \| "fact" \| "procedure" \| "file"` | yes | Note kind; unknown kinds throw. |
|
|
100
|
+
| `id` | 12-hex string | `episode` yes | Note id (the observation id for `episode`). Derived when omitted for `fact`/`procedure`/`file`; rejected for `working`. |
|
|
101
|
+
| `content` | non-empty string | except `episode`, or `fact`/`procedure` with `reflectionId` | Note text, capped by `limits.maxEntryTextChars`. |
|
|
102
|
+
| `reflectionId` | 12-hex string | no | Derives content from an observational-memory reflection; `fact`/`procedure` only, rejected with `content`, and the reflection must be bound to a closed work scope. |
|
|
103
|
+
| `block` | `[a-z0-9][a-z0-9._-]{0,63}` | `working` yes | Block label; the working note id is derived from it. |
|
|
104
|
+
| `path` | non-empty string | `file` yes | Workspace path; stored as metadata. |
|
|
105
|
+
| `tRef` / `validFrom` / `validTo` | ISO timestamp | no | Reference time and validity window: `validFrom` inclusive, `validTo` exclusive. |
|
|
106
|
+
| `keywords` / `tags` / `sourceEntryIds` | non-empty strings | no | Annotations; `sourceEntryIds` are session entry ids. |
|
|
107
|
+
| `context` | non-empty string | no | Short context line. |
|
|
108
|
+
| `links` | `{ id, relation?, weight? }[]` | no | Edges to other note ids (12-hex). |
|
|
109
|
+
| `supersedes` | 12-hex string | no | Id of the note this one replaces. |
|
|
110
|
+
| `importance` | number in [0,1] | no | Stored recall weight. |
|
|
111
|
+
| `consent` | `MemoryConsentInput` | no | Forwarded to `createMemory()`; the fabric never widens visibility. |
|
|
112
|
+
|
|
113
|
+
`recall(query, options)`
|
|
114
|
+
|
|
115
|
+
| Field | Type | Default | Meaning |
|
|
116
|
+
| --- | --- | --- | --- |
|
|
117
|
+
| `kinds` | `MemoryNoteKind[]` | `["fact", "file"]` | Kinds to return; `procedure` and `working` are opt-in. Empty or unknown kinds throw. |
|
|
118
|
+
| `asOf` | ISO string or `Date` | now | Evaluation point for `validFrom` / `validTo`. |
|
|
119
|
+
| `topK` | number | `memory.limits.topK` | Maximum hits, clamped by the memory hard cap. |
|
|
120
|
+
| `budget` | positive integer | none | Ceiling on the summed `tokenCount` of the returned hits. The best hit is always returned; the ranking stops at the first note that would not fit. |
|
|
121
|
+
| `scoring` | `RecallScoringOptions` | none | Passed through to `memory.recall()`. |
|
|
122
|
+
|
|
123
|
+
`searchConversation(query, options)` — lexical search over the current branch, each hit carrying the
|
|
124
|
+
observational-memory page around it. Requires the `observational` source; without it the call fails
|
|
125
|
+
closed.
|
|
126
|
+
|
|
127
|
+
| Field | Type | Default | Meaning |
|
|
128
|
+
| --- | --- | --- | --- |
|
|
129
|
+
| `limit` | page limit | 20 | Messages per page around a hit; the observational-memory limit (hard cap 100, out of range throws). |
|
|
130
|
+
| `direction` | `"backward" \| "forward"` | `"backward"` | Page direction, same semantics as the branch page. |
|
|
131
|
+
| `detail` | `"summary" \| "full"` | `"summary"` | Page rendering detail. |
|
|
132
|
+
| `topK` | number | 5 | Matching messages returned; hard cap 100. |
|
|
133
|
+
| `signal` | `AbortSignal` | none | Aborts the scan or the page loop. |
|
|
134
|
+
|
|
135
|
+
`tools(options)` — five inert tool definitions for the host to register. `root` is the directory
|
|
136
|
+
the file tools are jailed to; without it those tools fail closed and only block operations work.
|
|
137
|
+
|
|
138
|
+
| Tool | Kind | Arguments | Does |
|
|
139
|
+
| --- | --- | --- | --- |
|
|
140
|
+
| `memory.view` | read | `{ path? }` | lists a directory inside `root` (default: the root), names and byte sizes, at most 200 entries. |
|
|
141
|
+
| `memory.read` | read | `{ path }` | reads one file inside `root`, cut at the byte cap and flagged when truncated. |
|
|
142
|
+
| `memory.insert` | edit | `{ text, block? \| path? }` | appends to a labeled working block, or to a file inside `root` and refreshes that file's note. |
|
|
143
|
+
| `memory.recall` | search | `{ query, kinds?, topK?, asOf?, budget? }` | `recall()` with the same kinds, validity, and budget rules. |
|
|
144
|
+
| `memory.forget` | delete | `{ id? \| block?, hold? }` | `forget()` with the same tombstone rules. |
|
|
145
|
+
|
|
146
|
+
`forget(input)` — `{ id?, block?, hold? }`, exactly one of `id`/`block`:
|
|
147
|
+
|
|
148
|
+
| Target | Effect |
|
|
149
|
+
| --- | --- |
|
|
150
|
+
| `id` | `memory.forget()` in this memory's own thread scope: rows are marked and deleted, or kept as a `legal_hold` when `hold: true`. |
|
|
151
|
+
| `block` | the block's content is cleared and stamped with `forgottenAt`, freeing the label for a later insert. |
|
|
152
|
+
|
|
153
|
+
The result is `{ id?, block?, deleted, held }` — `deleted` counts removed rows (1 for a tombstoned
|
|
154
|
+
block, 0 when the note was held or was not in this scope).
|
|
155
|
+
|
|
156
|
+
`attach(session, options?)` — the one way to authorize a session, and the only way the workers run.
|
|
157
|
+
It returns `{ session, contextProvider, settings, detach() }`; `detach()` (or aborting
|
|
158
|
+
`options.signal`) takes the session back. Attaching fails closed on anything that is not a session
|
|
159
|
+
(no id, or a non-function `entries`) and on a session that is not the fabric's `observational`
|
|
160
|
+
session — an attached session may not read another branch. Nothing happens per turn: no timers, no
|
|
161
|
+
loop, no session proxy; `attach` only flips the gate that `tools()` and the workers read.
|
|
162
|
+
|
|
163
|
+
`createContextProvider(options?)` — the context seam, with the same options as
|
|
164
|
+
`memory.createContextProvider` (`includeWorking`, `includeSemantic`, `query`, `topK`,
|
|
165
|
+
`messageRange`, `name`). Blocks are tagged exactly like `createMemory`'s: working memory as
|
|
166
|
+
`working-memory`, recall as `semantic-memory` — the fabric is semantic memory, so it adds no block
|
|
167
|
+
type and no layer id. With the attention compiler off, these blocks still reach the provider input
|
|
168
|
+
through the normal context assembly.
|
|
169
|
+
|
|
170
|
+
### Attach and the definition recipe
|
|
171
|
+
|
|
172
|
+
A host registers the provider under any name it likes and lists that name in the definition; core
|
|
173
|
+
`AgentDefinition` has no fabric field and takes on no memory dependency:
|
|
174
|
+
|
|
175
|
+
```ts
|
|
176
|
+
registries.contextProviders.register("memory-fabric", fabric.createContextProvider());
|
|
177
|
+
|
|
178
|
+
const agent = await resolveAgentDefinition(
|
|
179
|
+
{ name: "assistant", model, context: ["memory-fabric"], tools: ["memory.recall"] },
|
|
180
|
+
{ registries, providerSource },
|
|
181
|
+
);
|
|
182
|
+
|
|
183
|
+
const session = createAgentSession({ agent });
|
|
184
|
+
fabric.attach(session); // tools run and workers enrich only from here
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
`context: ["memory-fabric"]` resolves through `registries.contextProviders`, so it works only when
|
|
188
|
+
the host registered that name; omitting it is the default (an agent with no fabric injection).
|
|
189
|
+
|
|
190
|
+
A runnable end-to-end demo — notes, folding, supersession, the attach gate, the provider, and
|
|
191
|
+
`forget` — lives in `examples/memory-fabric.ts` (network-free, `node examples/memory-fabric.ts`).
|
|
192
|
+
|
|
193
|
+
### Tools and the file jail
|
|
194
|
+
|
|
195
|
+
Every tool call is refused unless the fabric was attached to the calling session. `view`, `read`, and
|
|
196
|
+
the `path` form of `insert` resolve against `root` and fail closed on anything that leaves it — `..`
|
|
197
|
+
segments, absolute paths, and symlinks are all rejected with a `MemoryValidationError`, and no file is
|
|
198
|
+
touched when the check fails. A refused insert leaves the file and the working-memory version exactly
|
|
199
|
+
as they were.
|
|
200
|
+
|
|
201
|
+
`insert` appends: a working block grows to `limits.maxEntryTextChars` (over that it throws and the
|
|
202
|
+
stored version is untouched, with the write versioned against concurrent writers), and a file grows to
|
|
203
|
+
`maxFileBytes` (default 50 KiB, hard cap 1 MiB) with a newline added when the file already has content
|
|
204
|
+
and the text does not start with one. A file insert also refreshes the `kind: "file"` note for that
|
|
205
|
+
path with the file's text, so recall can find what was written; the indexed text is truncated at
|
|
206
|
+
`limits.maxEntryTextChars` and the tool reports `noteTruncated` when it was. Parent directories are
|
|
207
|
+
created inside the jail as needed — `view` never creates anything.
|
|
208
|
+
|
|
209
|
+
## Outputs / response / events
|
|
210
|
+
|
|
211
|
+
`remember` resolves to one `MemoryNote`: `{ id, kind, content, ingestedAt, tokenCount, ... }` plus the
|
|
212
|
+
optional fields above, `scope`, and (for `working`) `block` or (for `file`) `path`. A reflection-derived
|
|
213
|
+
note also carries `promotedFrom: { reflectionId, scopeId }`. A folded write
|
|
214
|
+
returns the surviving id: the rewritten row for a same-note fold, and a new id carrying `supersedes`
|
|
215
|
+
for a superseded one.
|
|
216
|
+
|
|
217
|
+
`recall` resolves to `{ hits, explain }`: `hits` are notes plus `score` and, when scoring wasrequested, `similarity` and `recency`; `explain[i]` is the parallel provenance row for `hits[i]`
|
|
218
|
+
(`{ id, score, similarity?, recency?, importance?, link, valid }`). A hit whose `explain.link` is
|
|
219
|
+
true arrived through a `links` edge from a seed hit rather than the query ranking. `working` notes are
|
|
220
|
+
not recall hits — they belong to the working value — and a note recorded with `validFrom`/`validTo`
|
|
221
|
+
outside `asOf` is omitted.
|
|
222
|
+
|
|
223
|
+
`searchConversation` resolves to `{ hits, scanned }`: each hit is
|
|
224
|
+
`{ entryId, score, timestamp, page }`, where `score` is the share of query terms the message contains
|
|
225
|
+
and `page` is the `recallObservationalMemoryBranchPage` result for that message (entries, cursors, and
|
|
226
|
+
rendered text). Events are whatever `createMemory()` already emits; the fabric adds none.
|
|
227
|
+
|
|
228
|
+
## Request/response example
|
|
229
|
+
|
|
230
|
+
```json
|
|
231
|
+
{
|
|
232
|
+
"kind": "fact",
|
|
233
|
+
"id": "2f9c1a0b7d3e",
|
|
234
|
+
"content": "User prefers metric units",
|
|
235
|
+
"validFrom": "2026-01-01T00:00:00.000Z",
|
|
236
|
+
"tags": ["prefs"],
|
|
237
|
+
"metadata": { "fabric": { "v": 1, "kind": "fact", "validFrom": "2026-01-01T00:00:00.000Z", "tags": ["prefs"] } }
|
|
238
|
+
}
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
`recall` response shape: `hits[i]` and `explain[i]` describe the same note.
|
|
242
|
+
|
|
243
|
+
```json
|
|
244
|
+
{
|
|
245
|
+
"hits": [
|
|
246
|
+
{ "id": "2f9c1a0b7d3e", "kind": "fact", "content": "User prefers metric units", "tokenCount": 7, "score": 0.91, "similarity": 0.91 },
|
|
247
|
+
{ "id": "7c4d2f10ab98", "kind": "fact", "content": "Rollout health lives on the deploy dashboard", "tokenCount": 11, "score": 0.42 }
|
|
248
|
+
],
|
|
249
|
+
"explain": [
|
|
250
|
+
{ "id": "2f9c1a0b7d3e", "score": 0.91, "similarity": 0.91, "link": false, "valid": true },
|
|
251
|
+
{ "id": "7c4d2f10ab98", "score": 0.42, "link": true, "valid": true }
|
|
252
|
+
]
|
|
253
|
+
}
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
`searchConversation` response shape (one hit, abbreviated page):
|
|
257
|
+
|
|
258
|
+
```json
|
|
259
|
+
{
|
|
260
|
+
"hits": [
|
|
261
|
+
{
|
|
262
|
+
"entryId": "m4",
|
|
263
|
+
"score": 1,
|
|
264
|
+
"timestamp": "2026-01-01T00:03:00.000Z",
|
|
265
|
+
"page": { "found": true, "cursor": "m4", "direction": "backward", "limit": 2, "entries": [], "nextCursor": "m2", "text": "..." }
|
|
266
|
+
}
|
|
267
|
+
],
|
|
268
|
+
"scanned": 12
|
|
269
|
+
}
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
## Implementation example
|
|
273
|
+
|
|
274
|
+
```ts
|
|
275
|
+
import { createMemory, createMemoryVectorStore, createHashEmbedder } from "@arnilo/prism-memory";
|
|
276
|
+
import { createMemoryFabric } from "@arnilo/prism-memory/fabric";
|
|
277
|
+
|
|
278
|
+
const memory = createMemory({
|
|
279
|
+
tenantId: "acme",
|
|
280
|
+
resourceId: "user-1",
|
|
281
|
+
threadId: "thread-1",
|
|
282
|
+
embedder: createHashEmbedder(),
|
|
283
|
+
vectorStore: createMemoryVectorStore(),
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
const fabric = createMemoryFabric({ memory, linker: { enabled: true, topK: 3 } });
|
|
287
|
+
|
|
288
|
+
await fabric.remember({ kind: "fact", content: "User prefers metric units", tags: ["prefs"] });
|
|
289
|
+
await fabric.remember({ kind: "procedure", content: "Deploy via canary first" });
|
|
290
|
+
await fabric.remember({ kind: "working", block: "core", content: "Prefers terse output" });
|
|
291
|
+
|
|
292
|
+
// Same note: rewritten in place, same id, annotations unioned.
|
|
293
|
+
const repeated = await fabric.remember({ kind: "fact", content: "User prefers metric units", keywords: ["units"] });
|
|
294
|
+
// Changed fact: the old row gets validTo, this note carries supersedes.
|
|
295
|
+
const changed = await fabric.remember({ kind: "fact", content: "User prefers metric units and Celsius" });
|
|
296
|
+
// changed.supersedes → the id of the note it replaced; changed.links → derived `related` edges.
|
|
297
|
+
|
|
298
|
+
const { hits, explain } = await fabric.recall("preferred units", {
|
|
299
|
+
scoring: { recencyWeight: 0.3, importanceWeight: 0.2, halfLifeMs: 7 * 86_400_000 },
|
|
300
|
+
budget: 400,
|
|
301
|
+
});
|
|
302
|
+
// hits[0].content → "User prefers metric units"; procedures stay out until kinds: ["procedure"].
|
|
303
|
+
// explain[1].link === true → that hit came in through a links edge from a seed hit.
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
Conversation search over an attached session (`om.attach(session)` result):
|
|
307
|
+
|
|
308
|
+
```ts
|
|
309
|
+
const fabric = createMemoryFabric({ memory, observational: om });
|
|
310
|
+
const { hits, scanned } = await fabric.searchConversation("canary rollout", { limit: 4, topK: 3 });
|
|
311
|
+
// hits[0].page.entries → the four branch messages ending at the match; hits[0].page.text → rendered window.
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
Giving the agent the tools, jailed to a notes directory:
|
|
315
|
+
|
|
316
|
+
```ts
|
|
317
|
+
const tools = fabric.tools({ root: "/srv/agent/memories", maxFileBytes: 50 * 1024 });
|
|
318
|
+
// tools.map((tool) => tool.name) →
|
|
319
|
+
// ["memory.view", "memory.read", "memory.insert", "memory.recall", "memory.forget"]
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
Attaching a session and handing its provider to a definition:
|
|
323
|
+
|
|
324
|
+
```ts
|
|
325
|
+
registries.contextProviders.register("memory-fabric", fabric.createContextProvider({ includeWorking: true }));
|
|
326
|
+
const agent = await resolveAgentDefinition(
|
|
327
|
+
{ name: "assistant", model, context: ["memory-fabric"], tools: ["memory.recall"] },
|
|
328
|
+
{ registries, providerSource },
|
|
329
|
+
);
|
|
330
|
+
const attached = fabric.attach(createAgentSession({ agent }));
|
|
331
|
+
// attached.settings.linker.enabled → whether enrichment is live for this fabric
|
|
332
|
+
// attached.contextProvider → the same provider the registry now holds
|
|
333
|
+
attached.detach();
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
## Extension and configuration notes
|
|
337
|
+
|
|
338
|
+
The host owns the embedder, vector store, working store, tenant/resource/thread scope, redactor,
|
|
339
|
+
`requireConsent` mode, and working-memory schema — the fabric only writes through them. `metadata.fabric`
|
|
340
|
+
is the reserved metadata key on vector records, and `_fabric.blocks` is the reserved key inside the
|
|
341
|
+
working value; a host schema must allow both if it validates the working value. A host that validates
|
|
342
|
+
working values still sees every block append, so its schema remains the last word on block content.
|
|
343
|
+
Episode views require an
|
|
344
|
+
observational-memory session passed as `observational`; without it, `kind: "episode"` fails closed as
|
|
345
|
+
does `searchConversation`. `budget` bounds injected note tokens; `topK` bounds both the primary hits
|
|
346
|
+
and the 1-hop expansions.
|
|
347
|
+
Enrichment is opt-in per fabric: `consolidate` (default on, `threshold` in [0,1]), `linker.topK` and
|
|
348
|
+
`evolution.maxPatches` (both capped at 32), `passive` to skip the workers, and `workerLimits` for the
|
|
349
|
+
observational-memory worker caps. Tool schemas are small and fixed, and a host may rename or wrap any
|
|
350
|
+
definition it registers.
|
|
351
|
+
|
|
352
|
+
## Security and performance notes
|
|
353
|
+
|
|
354
|
+
Everything here is opt-in and inert by default: importing the subpath starts no worker, no timer, and
|
|
355
|
+
no context provider, `tools()` registers nothing, and no note reaches a prompt until a host registers a
|
|
356
|
+
provider and attaches a session. No fabric path calls a model — not on write, not on fold, not in the
|
|
357
|
+
linker or evolution workers, and not in `searchConversation` (lexical coverage, no embedder call) — so
|
|
358
|
+
there is no LLM-on-write step an injected document could steer, and no inferred field to audit.
|
|
359
|
+
|
|
360
|
+
Kinds keep the process/case split: `procedure` is the reusable recipe and never comes back from an
|
|
361
|
+
untyped recall, while case-bound specifics belong in `fact`/`file` or in an `episode` view. A case
|
|
362
|
+
conclusion filed as a `procedure` is caught by the package's invariant fixture
|
|
363
|
+
(`caseConclusionsNotProcedures`, wrapped as `defineScorer({ invariant: true })` by a host harness),
|
|
364
|
+
which scores 0 rather than averaging away.
|
|
365
|
+
|
|
366
|
+
Text and metadata are redacted by `createMemory()` before embedding, and recall hits are redacted
|
|
367
|
+
before they reach the caller; the fabric adds no path around either. Consent, visibility, revocation,
|
|
368
|
+
and `requireConsent` filtering are applied inside `memory.recall()`, so a fabric recall can never see
|
|
369
|
+
more than the underlying memory — including for linked neighbors, which are promoted from the same
|
|
370
|
+
recalled batch rather than read back by id. The lineage plane is the only revocation plane: a note
|
|
371
|
+
whose source was corrected, revoked, forgotten, or put on legal hold is excluded from recall **and**
|
|
372
|
+
from injected context before any background cleanup runs, exactly as `createMemory()` excludes it —
|
|
373
|
+
legal-hold rows are retained but never injected, and the fabric does not invent a second tombstone or a
|
|
374
|
+
parallel derivation table (it stores `sourceEntryIds`/`supersedes` and lets that plane decide).
|
|
375
|
+
A malformed or foreign `metadata.fabric` payload is
|
|
376
|
+
skipped rather than injected, and unknown kinds fail closed at write time. The fabric echoes no query
|
|
377
|
+
text into hits, explain rows, or events; explanations carry scores and provenance flags only.
|
|
378
|
+
|
|
379
|
+
One `remember` of a fact is one bounded `memory.recall` candidate batch plus one
|
|
380
|
+
`memory.remember(..., { wait: true })` call — no extra full scan, no extra round trip — and it waits so
|
|
381
|
+
the note (and any fold) is durable when the call resolves. Recall requests an explicit oversample (up
|
|
382
|
+
to `RECALL_OVERSAMPLE` × `topK`, bounded by the hard top-K cap) because kind and validity filtering
|
|
383
|
+
happen after ranking; working-store byte caps and `maxEntryTextChars` are unchanged.
|
|
384
|
+
|
|
385
|
+
Recall costs exactly one store query: 1-hop expansion is served from the oversampled batch the query
|
|
386
|
+
already returned (up to `topK` extra hits), so a linked neighbor outside that window is not fetched —
|
|
387
|
+
`Memory` has no id lookup. `searchConversation` scans the branch entries the session already returned
|
|
388
|
+
(`scanned` reports how many), scores them by query-term coverage, then calls the observational-memory
|
|
389
|
+
page helper per returned hit; the page limit is validated by that helper (1..100), so the scan and the
|
|
390
|
+
result are both bounded.
|
|
391
|
+
|
|
392
|
+
The workers have no model, no tools, and no network: the linker and evolution read the redacted
|
|
393
|
+
candidate batch the write already fetched. Evolution rewrites a neighbor only when it actually gains
|
|
394
|
+
an annotation, and each patched payload is measured against the resolved worker byte budget, so a
|
|
395
|
+
pathological keyword list cannot grow a row past it. Superseded rows stay in the store (auditable) and
|
|
396
|
+
are filtered out of recall by `validTo` until retention or `forget` removes them.
|
|
397
|
+
|
|
398
|
+
`forget` and the tools add no privilege: a note is deleted in this memory instance's own thread scope,
|
|
399
|
+
working blocks by label in the same scope, and every tool refuses to run for a session the fabric was
|
|
400
|
+
not attached to. An attach is a gate read on each call, not a background process: an unattached fabric
|
|
401
|
+
starts nothing and enriches nothing, and `detach` (or aborting the attach signal) closes the gate
|
|
402
|
+
again. The jail re-checks the real path of the longest existing ancestor before any read or
|
|
403
|
+
append, so a symlink placed inside `root` cannot be used to read or grow a file outside it; reads are
|
|
404
|
+
bounded before they are retained (a 1 GiB file costs one capped buffer), and `insert` refuses before
|
|
405
|
+
writing, so an over-cap append is a no-op. Inserted content is data: it never activates tools, skills,
|
|
406
|
+
or configuration, and it is validated by the host's working-memory schema like any other block value.
|
|
407
|
+
|
|
408
|
+
## Related APIs
|
|
409
|
+
|
|
410
|
+
- [Observational memory](compaction-observational-memory.md): observations and reflections whose ids `episode` notes reference — still the episodic ledger and its workers; the fabric views, never re-observes.
|
|
411
|
+
- [`createMemory`](working-and-semantic-memory.md): working store, semantic recall, consent, redaction, lineage — the store this subpath writes through.
|
|
412
|
+
- [Attention compiler](attention-compiler.md): measures injected context cost; it does not attach or configure a fabric.
|
|
413
|
+
- [Session stores](session-stores.md): branch entries and bounded session search used for conversation-level queries.
|
|
414
|
+
- [Agent definitions](agent-definitions.md): `resolveAgentDefinition` resolves `context` names against `registries.contextProviders`; no fabric field is added to the contract.
|
|
415
|
+
- [Tools](tools.md): how a host registers and governs tool definitions, and what the agent sees.
|
|
416
|
+
- [Coding security](coding-security.md): the path-containment rules the file jail follows locally.
|