@arnilo/prism 0.0.96 → 0.1.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 +285 -2
- package/README.md +17 -3
- package/dist/agent-definitions.js +2 -3
- package/dist/agent-event-source.d.ts +11 -0
- package/dist/agent-event-source.js +512 -0
- package/dist/agent-loops.d.ts +5 -0
- package/dist/agent-loops.js +99 -14
- package/dist/agent-run-lifecycle.d.ts +5 -2
- package/dist/agent-run-lifecycle.js +18 -2
- package/dist/agent-run-state.d.ts +27 -1
- package/dist/agent-run-state.js +113 -7
- package/dist/agents.d.ts +3 -1
- package/dist/agents.js +1255 -129
- package/dist/artifacts.d.ts +132 -0
- package/dist/artifacts.js +44 -0
- package/dist/cache-helpers.js +18 -9
- package/dist/checkpoints.d.ts +4 -0
- package/dist/checkpoints.js +17 -9
- package/dist/cli-init.js +3 -7
- package/dist/cli-runner.d.ts +2 -6
- package/dist/cli-runner.js +71 -33
- package/dist/compaction.js +5 -4
- package/dist/config.js +7 -4
- package/dist/content.js +26 -24
- package/dist/context-budget.d.ts +67 -0
- package/dist/context-budget.js +288 -0
- package/dist/contracts.d.ts +590 -8
- package/dist/contracts.js +142 -1
- package/dist/contribution-parsing.js +6 -2
- package/dist/contributions.d.ts +2 -0
- package/dist/contributions.js +3 -0
- package/dist/conversations.d.ts +50 -0
- package/dist/conversations.js +98 -0
- package/dist/credentials.d.ts +22 -2
- package/dist/credentials.js +18 -3
- package/dist/devices.d.ts +94 -0
- package/dist/devices.js +138 -0
- package/dist/event-multiplexer.js +18 -4
- package/dist/extensions.d.ts +18 -1
- package/dist/extensions.js +79 -6
- package/dist/feedback.js +12 -10
- package/dist/guardrails.d.ts +1 -1
- package/dist/guardrails.js +26 -17
- package/dist/identity.d.ts +92 -0
- package/dist/identity.js +265 -0
- package/dist/index.d.ts +94 -72
- package/dist/index.js +48 -36
- package/dist/input.d.ts +10 -1
- package/dist/input.js +152 -52
- package/dist/instruction-injection.d.ts +1 -1
- package/dist/middleware.js +9 -1
- package/dist/models.d.ts +2 -0
- package/dist/models.js +3 -0
- package/dist/node/agent-definitions.js +16 -8
- package/dist/node/contribution-discovery.d.ts +1 -2
- package/dist/node/contribution-discovery.js +3 -3
- package/dist/node/session-store-jsonl.js +13 -7
- package/dist/node/settings.d.ts +1 -1
- package/dist/node/settings.js +1 -1
- package/dist/node/system-project-prompts.js +2 -4
- package/dist/node/trust.js +1 -1
- package/dist/persistence-lifecycle.d.ts +103 -0
- package/dist/persistence-lifecycle.js +202 -0
- package/dist/provider-events.d.ts +1 -0
- package/dist/provider-events.js +6 -1
- package/dist/provider-request-policy.js +3 -4
- package/dist/providers/media.d.ts +1 -1
- package/dist/providers/openai-compatible.d.ts +46 -1
- package/dist/providers/openai-compatible.js +123 -53
- package/dist/providers/openai-primitives.js +10 -7
- package/dist/providers/transport.d.ts +6 -0
- package/dist/providers/transport.js +21 -0
- package/dist/providers.d.ts +2 -0
- package/dist/providers.js +3 -0
- package/dist/redaction.d.ts +1 -0
- package/dist/redaction.js +26 -9
- package/dist/resources.d.ts +2 -2
- package/dist/resources.js +2 -2
- package/dist/retry.d.ts +5 -0
- package/dist/retry.js +8 -1
- package/dist/rpc.js +55 -11
- package/dist/run-ledger.d.ts +6 -0
- package/dist/run-ledger.js +16 -13
- package/dist/run-limits.js +49 -10
- package/dist/secure-agent.js +8 -2
- package/dist/security.js +7 -2
- package/dist/session-stores.d.ts +7 -2
- package/dist/session-stores.js +195 -21
- package/dist/skill-disclosure.d.ts +35 -0
- package/dist/skill-disclosure.js +101 -0
- package/dist/skill-load.d.ts +25 -0
- package/dist/skill-load.js +112 -0
- package/dist/structured-output.d.ts +5 -1
- package/dist/structured-output.js +20 -2
- package/dist/system-prompts.js +7 -2
- package/dist/testing/agent-event-source-conformance.d.ts +4 -0
- package/dist/testing/agent-event-source-conformance.js +54 -0
- package/dist/testing/compaction-conformance.js +5 -1
- package/dist/testing/extension-conformance.js +15 -3
- package/dist/testing/feedback.d.ts +1 -3
- package/dist/testing/feedback.js +1 -1
- package/dist/testing/persistence-schema.d.ts +2 -2
- package/dist/testing/persistence-schema.js +280 -35
- package/dist/testing/provider-conformance.js +3 -3
- package/dist/testing/run-ledger-conformance.js +1 -1
- package/dist/testing/session-store-conformance.d.ts +6 -0
- package/dist/testing/session-store-conformance.js +37 -2
- package/dist/testing/tool-conformance.js +30 -5
- package/dist/testing/tool-effect-store-conformance.d.ts +9 -0
- package/dist/testing/tool-effect-store-conformance.js +85 -0
- package/dist/thinking.js +4 -1
- package/dist/tool-effects.d.ts +15 -0
- package/dist/tool-effects.js +352 -0
- package/dist/tool-result-fold.d.ts +40 -0
- package/dist/tool-result-fold.js +176 -0
- package/dist/tools.d.ts +8 -3
- package/dist/tools.js +248 -13
- package/docs/0.1.0-readiness.md +202 -0
- package/docs/a2a.md +33 -2
- package/docs/acp.md +126 -0
- package/docs/ag-ui-adoption.md +77 -0
- package/docs/ag-ui.md +225 -0
- package/docs/agent-events.md +34 -3
- package/docs/agent-identity.md +144 -0
- package/docs/agent-loops.md +17 -2
- package/docs/agent-session-runtime.md +21 -4
- package/docs/browser-automation.md +5 -0
- package/docs/caveman.md +129 -0
- package/docs/cli-rpc.md +3 -6
- package/docs/coding-agent-tools.md +229 -25
- package/docs/coding-security.md +77 -11
- package/docs/compaction-and-retry.md +5 -2
- package/docs/compaction-llm.md +20 -1
- package/docs/compaction-observational-memory.md +52 -8
- package/docs/context-and-skills.md +94 -7
- package/docs/contribution-registries.md +1 -0
- package/docs/conversations.md +135 -0
- package/docs/credential-storage.md +34 -1
- package/docs/credentials-and-redaction.md +11 -1
- package/docs/database-persistence.md +27 -7
- package/docs/device-adapters.md +97 -0
- package/docs/enterprise-postgres-state.md +178 -0
- package/docs/evaluations.md +14 -1
- package/docs/extensions.md +4 -1
- package/docs/forge-integration.md +113 -0
- package/docs/guardrails.md +16 -2
- package/docs/host-security.md +35 -4
- package/docs/index.md +69 -37
- package/docs/input-and-prompt-assembly.md +8 -7
- package/docs/language-intelligence.md +162 -0
- package/docs/mcp-tools.md +62 -5
- package/docs/middleware-hooks.md +2 -2
- package/docs/migration.md +423 -2
- package/docs/model-routing.md +111 -0
- package/docs/multimodal-content.md +8 -5
- package/docs/node-jsonl-session-store.md +1 -1
- package/docs/observability.md +2 -0
- package/docs/openapi-tools.md +56 -0
- package/docs/performance.md +282 -0
- package/docs/policy-and-audit.md +171 -0
- package/docs/ponytail.md +127 -0
- package/docs/postgres-persistence.md +8 -4
- package/docs/process-sessions.md +147 -0
- package/docs/provider-caching.md +13 -1
- package/docs/provider-conformance.md +29 -5
- package/docs/provider-packages.md +43 -2
- package/docs/provider-request-policies.md +2 -0
- package/docs/providers/ai-sdk.md +24 -7
- package/docs/providers/alibaba.md +179 -0
- package/docs/providers/anthropic.md +93 -0
- package/docs/providers/azure.md +74 -0
- package/docs/providers/bedrock.md +72 -0
- package/docs/providers/google.md +89 -0
- package/docs/providers/ollama.md +166 -0
- package/docs/providers/openai-compatible.md +31 -2
- package/docs/providers/openai.md +24 -5
- package/docs/providers/openrouter.md +2 -0
- package/docs/providers/vertex.md +71 -0
- package/docs/public-contracts.md +61 -4
- package/docs/rag.md +41 -12
- package/docs/release-and-install.md +323 -206
- package/docs/resource-loading.md +3 -0
- package/docs/runs-and-usage.md +3 -0
- package/docs/server.md +44 -6
- package/docs/session-store-conformance.md +2 -0
- package/docs/session-stores.md +41 -2
- package/docs/sqlite-persistence.md +11 -3
- package/docs/structured-output.md +7 -1
- package/docs/supervisors.md +8 -0
- package/docs/tool-effects.md +95 -0
- package/docs/tools.md +5 -0
- package/docs/work-artifacts-and-review.md +102 -0
- package/docs/work-connectors.md +32 -0
- package/docs/work-tools.md +137 -0
- package/docs/workflows.md +6 -0
- package/docs/working-and-semantic-memory.md +40 -7
- package/package.json +30 -8
- package/templates/init/providers.json +22 -0
- package/docs/review-coverage-2026-07-14.md +0 -260
- package/docs/review-coverage-2026-07-15.md +0 -193
- package/docs/review-coverage-2026-07-17-provider-validation.md +0 -192
- package/docs/review-coverage-2026-07-19-phase-3.md +0 -174
- package/docs/review-coverage-2026-07-20-phase-4.md +0 -175
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
# Policy and audit
|
|
2
|
+
|
|
3
|
+
## What it does
|
|
4
|
+
|
|
5
|
+
`@arnilo/prism-policy` records redacted allow/deny/modify/approval decisions with policy version, actor refs from verified `AgentIdentity`, target, reason, expiry, and evidence references. Hosts export cursor-paginated pages to append-only/WORM sinks. The package does not embed a mandatory global policy engine, KMS, or cloud WORM SDK.
|
|
6
|
+
|
|
7
|
+
## When to use it
|
|
8
|
+
|
|
9
|
+
Use it when enterprise hosts need an attributable audit trail alongside existing guardrails, permission checks, and tool-approval interruptions. Skip it for single-tenant apps that only need `RunLedger` / guardrail events.
|
|
10
|
+
|
|
11
|
+
Do not store unrestricted prompts, tool argument bodies, JWTs, or credential secrets on decision records. Do not treat the reference memory/file adapters as production WORM.
|
|
12
|
+
|
|
13
|
+
## Inputs / request
|
|
14
|
+
|
|
15
|
+
| API / field | Meaning |
|
|
16
|
+
| --- | --- |
|
|
17
|
+
| `createPolicyEvaluator({ policyId, policyVersion, evaluate })` | Host rule callback stamped with immutable id/version |
|
|
18
|
+
| `PolicyEvaluateRequest` | Verified `identity`, `action`, `resource`, optional evaluator-only `context` (never persisted) |
|
|
19
|
+
| `AppendPolicyDecisionInput` | Decision fields + verified identity; ownership from identity or explicit scope |
|
|
20
|
+
| `createMemoryPolicyDecisionStore` / `createFilePolicyDecisionStore` | Append-only reference ledgers |
|
|
21
|
+
| `exportPolicyDecisions({ store, ownership, cursor, limit, sink? })` | Cursor pages; optional host WORM sink |
|
|
22
|
+
| `recordGuardrailDecision` / `recordPermissionDecision` / `recordToolApprovalDecision` | Optional bridges from existing decision points |
|
|
23
|
+
|
|
24
|
+
Frozen caps (default / hard): decision `8 KiB / 64 KiB`, reason or evidence ref `1 KiB / 8 KiB`, export page `100 / 500`.
|
|
25
|
+
|
|
26
|
+
## Outputs / response / events
|
|
27
|
+
|
|
28
|
+
- `PolicyDecisionRecord` — frozen redacted row (`actor` refs, `evidenceRefs`, no payload blob).
|
|
29
|
+
- `evaluateAndAppend` — evaluate then append in one call.
|
|
30
|
+
- Policy version mismatch (`requirePolicyVersion`) and unrestricted payload keys fail closed (`ERR_PRISM_POLICY_VERSION` / `ERR_PRISM_POLICY_PAYLOAD`).
|
|
31
|
+
- Missing/expired/unverified identity fails via core `assertIdentityActive` before append.
|
|
32
|
+
|
|
33
|
+
## Request/response example
|
|
34
|
+
|
|
35
|
+
```json
|
|
36
|
+
{
|
|
37
|
+
"id": "dec-1",
|
|
38
|
+
"policyId": "mail",
|
|
39
|
+
"policyVersion": "2026-07-23",
|
|
40
|
+
"outcome": "approval",
|
|
41
|
+
"actor": {
|
|
42
|
+
"tenantId": "tenant-1",
|
|
43
|
+
"userId": "user-1",
|
|
44
|
+
"principalId": "agent-42",
|
|
45
|
+
"principalKind": "agent",
|
|
46
|
+
"sponsorId": "sponsor-7"
|
|
47
|
+
},
|
|
48
|
+
"target": { "kind": "draft", "id": "d1" },
|
|
49
|
+
"reason": "external send",
|
|
50
|
+
"evidenceRefs": ["rule:external"],
|
|
51
|
+
"createdAt": "2026-07-23T12:00:00.000Z",
|
|
52
|
+
"tenantId": "tenant-1",
|
|
53
|
+
"userId": "user-1"
|
|
54
|
+
}
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Implementation example
|
|
58
|
+
|
|
59
|
+
```ts
|
|
60
|
+
import { type AgentIdentity } from "@arnilo/prism";
|
|
61
|
+
import {
|
|
62
|
+
createFilePolicyDecisionStore,
|
|
63
|
+
createPolicyEvaluator,
|
|
64
|
+
evaluateAndAppend,
|
|
65
|
+
exportPolicyDecisions,
|
|
66
|
+
recordToolApprovalDecision,
|
|
67
|
+
} from "@arnilo/prism-policy";
|
|
68
|
+
|
|
69
|
+
const evaluator = createPolicyEvaluator({
|
|
70
|
+
policyId: "mail",
|
|
71
|
+
policyVersion: "2026-07-23",
|
|
72
|
+
evaluate: ({ action }) =>
|
|
73
|
+
action === "mail.send"
|
|
74
|
+
? { outcome: "approval", reason: "external send", evidenceRefs: ["rule:external"] }
|
|
75
|
+
: { outcome: "allow" },
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
const store = createFilePolicyDecisionStore({
|
|
79
|
+
path: "/var/prism/policy-decisions.jsonl",
|
|
80
|
+
requirePolicyVersion: "2026-07-23",
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
await evaluateAndAppend(
|
|
84
|
+
{ identity, action: "mail.send", resource: { kind: "draft", id: "d1" } },
|
|
85
|
+
{ store, evaluator, id: crypto.randomUUID() },
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
await recordToolApprovalDecision({
|
|
89
|
+
store,
|
|
90
|
+
evaluator,
|
|
91
|
+
id: crypto.randomUUID(),
|
|
92
|
+
identity,
|
|
93
|
+
toolName: "mail.send",
|
|
94
|
+
toolCallId: "call-1",
|
|
95
|
+
evidenceRef: "run:abc/tool:call-1",
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
for await (const page of exportPolicyDecisions({
|
|
99
|
+
store,
|
|
100
|
+
tenantId: identity.tenantId,
|
|
101
|
+
userId: identity.userId,
|
|
102
|
+
sink: { async write(records) { await worm.append(records); } },
|
|
103
|
+
})) {
|
|
104
|
+
void page;
|
|
105
|
+
}
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
## Extension and configuration notes
|
|
109
|
+
|
|
110
|
+
Policy is optional. Hosts wire `record*` helpers or `evaluateAndAppend` at permission/guardrail/tool-approval/router/connector boundaries. Model-router and work-connector packages (later Phase 8 tasks) may call the same store when configured. Replace file/memory adapters with host WORM/KMS without changing record shape.
|
|
111
|
+
|
|
112
|
+
## Security and performance notes
|
|
113
|
+
|
|
114
|
+
- Approvals require verified `AgentIdentity`; actor fields are refs only.
|
|
115
|
+
- Policy version pin fails closed on mismatch.
|
|
116
|
+
- Unrestricted payload field names (`prompt`, `body`, `toolArguments`, …) are rejected before append.
|
|
117
|
+
- Evaluate/append are O(fields) and network-free in-package; remote WORM I/O stays in the host sink/adapter.
|
|
118
|
+
- Export never full-scans: page size is capped; raise hard caps only with Phase 8 freeze + tests + docs updates.
|
|
119
|
+
|
|
120
|
+
## OPA external policy adapter (`@arnilo/prism-policy/opa`, 0.0.28)
|
|
121
|
+
|
|
122
|
+
Optional `createOpaPolicyEvaluator` evaluates `PolicyEvaluateRequest`s against a host-pinned OPA REST endpoint (`POST /v1/data/<path>` with `{"input": <document>}`) and returns a core `PolicyEvaluator` for `evaluateAndAppend`. Native `fetch` only; no OPA SDK dependency.
|
|
123
|
+
|
|
124
|
+
| Option | Meaning |
|
|
125
|
+
| --- | --- |
|
|
126
|
+
| `url` / `policyId` / `policyVersion` | Pinned decision URL + immutable ledger attribution |
|
|
127
|
+
| `mapInput` | Input builder (default: redacted actor refs — tenant/account/user/principal/sponsor/scopes + action + resource; never prompts, tool args, JWTs, or credentials; `context` omitted by design) |
|
|
128
|
+
| `mapDecision` | Decision mapper (default: boolean, `{allow}`, or `{outcome, reason?, evidenceRefs?, expiresAt?}`) |
|
|
129
|
+
| `onFailure` | `deny` (default) returns a recorded deny result on OPA failures; `escalate` rethrows the `PolicyError` |
|
|
130
|
+
| `requirePolicyVersion` | Sends `provenance=true` and requires a matching OPA bundle revision (stale/missing fails closed) |
|
|
131
|
+
| `timeoutMs` / `maxInputBytes` / `maxResponseBytes` / `maxRetries` | Bounded caps (2 s/30 s, 16/256 KiB, 64 KiB/1 MiB, 0/2 retries — only timeout/transport/5xx retried) |
|
|
132
|
+
| `redactor` | `SecretRedactor` applied to OPA-provided `reason`/`evidenceRefs` before they leave the adapter |
|
|
133
|
+
| `ssrf` | `SsrfPolicy` for the endpoint; denials surface `MediaContentError` (`ssrf_denied`) |
|
|
134
|
+
|
|
135
|
+
```ts
|
|
136
|
+
import { createOpaPolicyEvaluator } from "@arnilo/prism-policy/opa";
|
|
137
|
+
import { createPostgresEnterpriseState } from "@arnilo/prism-enterprise-postgres";
|
|
138
|
+
import { evaluateAndAppend } from "@arnilo/prism-policy";
|
|
139
|
+
|
|
140
|
+
const evaluator = createOpaPolicyEvaluator({
|
|
141
|
+
url: "https://opa.internal:8181/v1/data/prism/allow",
|
|
142
|
+
policyId: "opa-prism",
|
|
143
|
+
policyVersion: "2026-08-01",
|
|
144
|
+
});
|
|
145
|
+
const state = await createPostgresEnterpriseState({ pool, schema: "prism" });
|
|
146
|
+
await evaluateAndAppend(request, { store: state.policy, evaluator, id: crypto.randomUUID() });
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
Fail-closed codes: `ERR_PRISM_OPA_TIMEOUT`, `ERR_PRISM_OPA_TRANSPORT`, `ERR_PRISM_OPA_RESPONSE_PARSE`, `ERR_PRISM_OPA_RESPONSE_BOUNDS`, `ERR_PRISM_OPA_DECISION_MAPPING`, `ERR_PRISM_OPA_VERSION_MISMATCH`. Redirects are never followed; response bodies are read with a hard cap; caller aborts propagate (never converted to a policy outcome); timeout/parse/bounds/version failures record a deny row through `evaluateAndAppend`, so the durable Phase 6 ledger captures them unchanged.
|
|
150
|
+
|
|
151
|
+
## PostgreSQL enterprise state (0.0.23)
|
|
152
|
+
|
|
153
|
+
For durable multi-replica policy decisions, construct [`createPostgresEnterpriseState`](enterprise-postgres-state.md) and pass its `policy` store to the existing helpers. PostgreSQL keeps the same append/query contract, requires tenant scope and verified identity at append, binds owner data into opaque cursors, validates record bounds on read, and rejects duplicate ids. Memory and JSONL remain development/reference adapters, not production WORM or cross-replica stores.
|
|
154
|
+
|
|
155
|
+
```ts
|
|
156
|
+
const state = await createPostgresEnterpriseState({ pool, schema: "prism" });
|
|
157
|
+
await evaluateAndAppend(request, { store: state.policy, evaluator, id: crypto.randomUUID() });
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
`state.close()` leaves a caller-owned pool open. Run `state.cleanup(...)` from an authorized host schedule only when expiration cleanup is needed; it does not run in the background.
|
|
161
|
+
|
|
162
|
+
## Related APIs
|
|
163
|
+
|
|
164
|
+
- [Model routing](model-routing.md)
|
|
165
|
+
- [Agent identity](agent-identity.md)
|
|
166
|
+
- [Guardrails](guardrails.md)
|
|
167
|
+
- [Runs and usage ledger](runs-and-usage.md)
|
|
168
|
+
- [Workflows](workflows.md): proactive schedule capability enable/revoke events bridge here via `onCapability`.
|
|
169
|
+
- [Host security](host-security.md)
|
|
170
|
+
- [Enterprise PostgreSQL state](enterprise-postgres-state.md): durable policy/evaluation/work/router composition.
|
|
171
|
+
- Package README: [`@arnilo/prism-policy`](../packages/policy/README.md)
|
package/docs/ponytail.md
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
# Ponytail behavior integration
|
|
2
|
+
|
|
3
|
+
## What it does
|
|
4
|
+
|
|
5
|
+
`@arnilo/prism-ponytail` is an optional package that wires [DietrichGebert/ponytail](https://github.com/DietrichGebert/ponytail) into Prism contribution contracts.
|
|
6
|
+
|
|
7
|
+
It registers upstream skills and commands, injects active mode instructions via upstream `getPonytailInstructions` / `filterSkillBodyForMode`, and persists mode as session custom `ponytail-mode` entries. Import is inert; missing upstream fails closed at `setup` with a bounded redacted error.
|
|
8
|
+
|
|
9
|
+
## When to use it
|
|
10
|
+
|
|
11
|
+
Use it when a host wants lazy-minimalism coding behavior (`lite`, `full`, `ultra`) with upstream Ponytail skills (`ponytail-audit`, `ponytail-debt`, `ponytail-gain`, `ponytail-help`, `ponytail-review`) in a Prism extension kernel.
|
|
12
|
+
|
|
13
|
+
Install optional peer `@dietrichgebert/ponytail@^4.8.4` **or** pass `upstreamPath` to a checkout with `skills/` and `hooks/`.
|
|
14
|
+
|
|
15
|
+
Pair with progressive disclosure: mode slices on the `ponytail-mode` injector; full skill bodies via `load_skill` only.
|
|
16
|
+
|
|
17
|
+
## Inputs / request
|
|
18
|
+
|
|
19
|
+
`createPonytailExtension(options)`:
|
|
20
|
+
|
|
21
|
+
| Field | Type | Required | Purpose |
|
|
22
|
+
| --- | --- | --- | --- |
|
|
23
|
+
| `upstreamPath` | `string` | no | Override path to Ponytail root; default resolves optional peer package. |
|
|
24
|
+
| `defaultMode` | `PonytailMode` | no | Initial mode when no session entry exists (default `full`). |
|
|
25
|
+
| `quietStartup` | `boolean` | no | Suppress startup status events. |
|
|
26
|
+
| `appendEntry` | `(entry, opts?) => Promise<void>` | yes | Host session append (OM `attach` pattern). |
|
|
27
|
+
| `getEntries` | `() => readonly SessionEntry[] \| Promise<...>` | yes | Current branch entries for mode restore. |
|
|
28
|
+
| `configPath` | `string` | no | Bounded local config for `defaultMode` / `quietStartup` / `hideStatus`. |
|
|
29
|
+
|
|
30
|
+
`PonytailMode`: `off` \| `lite` \| `full` \| `ultra`.
|
|
31
|
+
|
|
32
|
+
Session custom entry shape:
|
|
33
|
+
|
|
34
|
+
```json
|
|
35
|
+
{ "kind": "custom", "data": { "type": "ponytail-mode", "mode": "full" } }
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Registered skills: `ponytail`, `ponytail-audit`, `ponytail-debt`, `ponytail-gain`, `ponytail-help`, `ponytail-review`.
|
|
39
|
+
|
|
40
|
+
Registered commands: `ponytail`, `ponytail-review`, `ponytail-audit`, `ponytail-gain`, `ponytail-debt`, `ponytail-help`.
|
|
41
|
+
|
|
42
|
+
`ponytail` command actions: `lite|full|ultra|off`, `status`, `default <mode>`.
|
|
43
|
+
|
|
44
|
+
## Outputs / response / events
|
|
45
|
+
|
|
46
|
+
| Export | Purpose |
|
|
47
|
+
| --- | --- |
|
|
48
|
+
| `createPonytailExtension(options)` | Returns an inert `Extension` until `kernel.load([...])`. |
|
|
49
|
+
| `ponytail-mode` injector | `InstructionInjector` calling upstream `getPonytailInstructions(mode)`. |
|
|
50
|
+
| `ponytail` command | Set mode, report status, or persist default mode to config file. |
|
|
51
|
+
| Alias commands | Dispatch `{ skill, dispatch: "load_skill" }` for companion skills. |
|
|
52
|
+
| `ponytail:status` / `ponytail:loaded` events | Optional host metadata (no statusline shell scripts). |
|
|
53
|
+
|
|
54
|
+
Deactivation: exact phrases `stop ponytail` and `normal mode`.
|
|
55
|
+
|
|
56
|
+
## Request/response example
|
|
57
|
+
|
|
58
|
+
```json
|
|
59
|
+
{ "command": "ponytail", "args": { "mode": "lite" }, "sessionId": "s1" }
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
```json
|
|
63
|
+
{ "kind": "custom", "data": { "type": "ponytail-mode", "mode": "lite" } }
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Implementation example
|
|
67
|
+
|
|
68
|
+
```ts
|
|
69
|
+
import { createPonytailExtension } from "@arnilo/prism-ponytail";
|
|
70
|
+
import {
|
|
71
|
+
createExtensionKernel,
|
|
72
|
+
createLoadSkillTool,
|
|
73
|
+
createLoadedSkillSet,
|
|
74
|
+
createMemorySessionStore,
|
|
75
|
+
createSkillRegistry,
|
|
76
|
+
} from "@arnilo/prism";
|
|
77
|
+
|
|
78
|
+
const store = createMemorySessionStore();
|
|
79
|
+
const callbacks = {
|
|
80
|
+
appendEntry: async (entry, options) => store.append(entry, options),
|
|
81
|
+
getEntries: async () => store.list("s1"),
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
const kernel = createExtensionKernel({ errorPolicy: "throw" });
|
|
85
|
+
await kernel.load([
|
|
86
|
+
createPonytailExtension({
|
|
87
|
+
upstreamPath: undefined, // optional peer @dietrichgebert/ponytail
|
|
88
|
+
defaultMode: "full",
|
|
89
|
+
quietStartup: true,
|
|
90
|
+
...callbacks,
|
|
91
|
+
}),
|
|
92
|
+
]);
|
|
93
|
+
|
|
94
|
+
const registry = createSkillRegistry(kernel.registries.skills.list());
|
|
95
|
+
const loaded = createLoadedSkillSet();
|
|
96
|
+
const loadSkill = createLoadSkillTool({ registry, loaded });
|
|
97
|
+
|
|
98
|
+
await kernel.registries.commands.get("ponytail")!.execute({ mode: "lite" }, { sessionId: "s1" });
|
|
99
|
+
// Select instructionInjectors: ["ponytail-mode"] on runs that should receive mode slices.
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
See `examples/caveman-ponytail.ts` for combined Caveman + Ponytail progressive disclosure demo (network-free fixtures).
|
|
103
|
+
|
|
104
|
+
## Extension and configuration notes
|
|
105
|
+
|
|
106
|
+
- Import alone registers nothing (`sideEffects: false`); no timers, watchers, network, or shell scripts.
|
|
107
|
+
- Upstream hook modules load via `createRequire` from resolved root — instruction strings are not forked in Prism.
|
|
108
|
+
- Mode restore scans `getEntries()` for latest `data.type === "ponytail-mode"` (OM attach pattern).
|
|
109
|
+
- `ponytail-subagent` hook is not wired; nested-agent behavior is host responsibility.
|
|
110
|
+
- No TUI statusline scripts; use `ponytail status` command or extension events.
|
|
111
|
+
- Not included in `@arnilo/prism-code` or `@arnilo/prism-sdk` profiles — opt-in install only.
|
|
112
|
+
|
|
113
|
+
## Security and performance notes
|
|
114
|
+
|
|
115
|
+
- Upstream text is untrusted; reads bounded (`MAX_SKILL_FILE_BYTES` 256 KiB, `MAX_INJECTED_INSTRUCTION_BYTES` 32 KiB).
|
|
116
|
+
- Config writes only to host `configPath` with size cap (`MAX_CONFIG_FILE_BYTES` 16 KiB).
|
|
117
|
+
- Errors redact absolute paths and home directories.
|
|
118
|
+
- O(skills) setup scan; O(1) mode tracking per turn; no background workers.
|
|
119
|
+
|
|
120
|
+
## Related APIs
|
|
121
|
+
|
|
122
|
+
- [Caveman behavior integration](caveman.md): complementary terse-communication mode package.
|
|
123
|
+
- [Extension kernel and event bus](extensions.md): explicit `kernel.load`.
|
|
124
|
+
- [Context and skills](context-and-skills.md): progressive catalog + `load_skill`.
|
|
125
|
+
- [Instruction injection](instruction-injection.md): `ponytail-mode` injector.
|
|
126
|
+
- [Observational memory compaction package](compaction-observational-memory.md): session callback attach pattern.
|
|
127
|
+
- [Migration guide](migration.md): `0.0.21 → 0.0.22` notes.
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
The optional `@arnilo/prism-session-store-postgres` package ships a production-oriented PostgreSQL adapter that implements:
|
|
6
6
|
|
|
7
|
-
- `SessionStore` — atomic `append` / `list` / `get` / `readBranchPath`
|
|
7
|
+
- `SessionStore` — atomic `append` / `list` / `get` / `readBranchPath` / bounded `searchSessions` / bounded `searchSessions`
|
|
8
8
|
- `RunLedger` — durable run, event, tool-call, and usage rows
|
|
9
9
|
- `ProductionPersistenceStore` — cursor-paginated `query*` reads plus generic `checkpoints` and atomic `leases` capabilities
|
|
10
10
|
|
|
@@ -24,7 +24,7 @@ Use this package when you need server-backed persistence with pooled connections
|
|
|
24
24
|
- managed cloud databases (RDS, Cloud SQL, Neon, Supabase, etc.)
|
|
25
25
|
- CI integration tests against a real PostgreSQL service
|
|
26
26
|
|
|
27
|
-
Prefer [`@arnilo/prism-session-store-sqlite`](sqlite-persistence.md) for local CLI tools, single-writer workloads, and network-free default tests. This adapter stores sessions/runs, not semantic vectors; use the separate [`@arnilo/prism-memory` pgvector path](working-and-semantic-memory.md), which rejects non-finite vectors before SQL, when vector recall is needed.
|
|
27
|
+
Prefer [`@arnilo/prism-session-store-sqlite`](sqlite-persistence.md) for local CLI tools, single-writer workloads, and network-free default tests. This adapter stores sessions/runs, not semantic vectors; use the separate [`@arnilo/prism-memory` pgvector path](working-and-semantic-memory.md), which rejects non-finite vectors before SQL, when vector recall is needed. For durable policy decisions, evaluations, work mutation idempotency, and model-router state, use the separate [`@arnilo/prism-enterprise-postgres`](enterprise-postgres-state.md) composition; it has its own migration history and does not replace session/run persistence.
|
|
28
28
|
|
|
29
29
|
## Inputs / request
|
|
30
30
|
|
|
@@ -40,6 +40,8 @@ import { createPostgresPersistence } from "@arnilo/prism-session-store-postgres"
|
|
|
40
40
|
| `schema` | `string` | PostgreSQL schema for Prism tables. Defaults to `"prism"`. Validated and double-quoted. |
|
|
41
41
|
| `poolMax` | `number` | Maximum pool size for adapter-owned pools. Defaults to `10`. |
|
|
42
42
|
| `feedbackRedactor` | `SecretRedactor` | Optional redaction for feedback comment/tags/metadata before insert. |
|
|
43
|
+
| `eventSource` | `AgentEventSourceOptions` | Bounds durable event pages, subscribers, polling, reconnects, and cleanup. |
|
|
44
|
+
| `eventCursorSecret` | `string \| Uint8Array` | Stable HMAC secret shared by replicas that resume durable event cursors. |
|
|
43
45
|
| `poolConfig` | `PoolConfig` | Additional `pg` options (TLS, idle timeout, application name, etc.). |
|
|
44
46
|
|
|
45
47
|
Hosts own TLS (`ssl` in `poolConfig`), credentials, connection limits, and backup/retention enforcement.
|
|
@@ -54,12 +56,13 @@ Hosts own TLS (`ssl` in `poolConfig`), credentials, connection limits, and backu
|
|
|
54
56
|
| `SessionStore.list` / `get` | Indexed reads by `session_id` and primary key. |
|
|
55
57
|
| `SessionStore.readBranchPath` | Recursive ancestor query from `leafId` (or latest leaf) in root→leaf order. |
|
|
56
58
|
| `RunLedger.append*` | Inserts run/event/tool/usage rows; events receive monotonic per-run `sequence` values. |
|
|
59
|
+
| `events` | Durable `AgentEventSource`; `LISTEN`/`NOTIFY` only wakes exact owned indexed reads, while polling remains recovery fallback. |
|
|
57
60
|
| `ProductionPersistenceStore.query*` | Parameterized cursor pagination on indexed columns with tenant/account/user filters. |
|
|
58
61
|
| `checkpoints` | Generic versioned `CheckpointStore` backed by `prism_checkpoints`; ownership, CAS/fencing checks, bounded pagination, and workflow suspended/denied/schedule/state/replay values without a schema migration. |
|
|
59
62
|
| `leases` | Atomic `LeaseStore` backed by `prism_leases`; database-clock expiry, opaque renew/release token, monotonic takeover fence. |
|
|
60
63
|
| `close()` | Ends the pool when the adapter created it from `connectionString`. |
|
|
61
64
|
|
|
62
|
-
Migrations run automatically on open and are idempotent across reopen. Concurrent setup uses per-schema advisory transaction locks. While holding that lock, startup verifies ordered contract name/version/SHA-256 rows and full schema-
|
|
65
|
+
Migrations run automatically on open and are idempotent across reopen. Concurrent setup uses per-schema advisory transaction locks. While holding that lock, startup verifies ordered contract name/version/SHA-256 rows and full schema-v7 `information_schema`/catalog shape (all required tables, columns/types/nullability/defaults, PK/unique/FK keys, and named indexes) before any runtime write. A complete legacy 0.0.5 history with all `checksum` values `NULL` is shape-verified then backfilled transactionally once. Unknown, duplicate, out-of-order, partial-legacy, checksum, or shape drift rejects open; restore or apply reviewed DDL rather than editing migration rows.
|
|
63
66
|
|
|
64
67
|
## Request/response example
|
|
65
68
|
|
|
@@ -116,7 +119,7 @@ PRISM_TEST_POSTGRES_URL="$DATABASE_URL" npm run test:postgres --workspace @arnil
|
|
|
116
119
|
- The package is optional and workspace-local; `@arnilo/prism` core has no PostgreSQL dependency.
|
|
117
120
|
- Schema names must match `^[a-zA-Z_][a-zA-Z0-9_]*$`; the adapter quotes them and never interpolates user values into identifier positions.
|
|
118
121
|
- `SessionAppendOptions` idempotency rows are durable in `prism_session_append_idempotency` and survive reopen.
|
|
119
|
-
- Schema version **
|
|
122
|
+
- Schema version **6** applies `001_init`, `002_usage_scope`, `003_run_feedback`, `004_session_search`, `005_lifecycle_hold_quota`, and `006_agent_event_source`. Migration 006 backfills `prism_agent_event_streams`, then replaces the former non-unique run sequence index with unique `(run_id, sequence)` allocation enforced in the event append transaction. `LISTEN` registration commits before initial catch-up; notifications carry only a constant wake token, so dropped/coalesced notifications affect latency rather than delivery. Migration 003 adds immutable `prism_run_feedback` rows with run FK/cascade deletion and owner/run/trace cursor indexes. Migration 004 adds session search FTS (Postgres `tsvector` FTS table dual-written on append) plus `prism_sessions(updated_at, id)` cursor index; existing entries are backfilled once. `persistence.feedback` validates exact run ownership, bounds/redacts through optional `feedbackRedactor`, queries bounded pages, and deletes only exact-owned IDs. Search hits never include credentials; ownership filters apply when present. SQLite shares sequence compatibility only; it does not provide distributed subscriptions.
|
|
120
123
|
- Pass an existing `pg` `Pool` when your host already manages pooling, TLS, and credential rotation.
|
|
121
124
|
|
|
122
125
|
## Security and performance notes
|
|
@@ -141,4 +144,5 @@ PRISM_TEST_POSTGRES_URL="$DATABASE_URL" npm run test:postgres --workspace @arnil
|
|
|
141
144
|
- [Persistence, credentials, and multimodality primitives](persistence-credentials-multimodality-primitives.md): package matrix and threat model.
|
|
142
145
|
- [Workflows](workflows.md): adapt `persistence.checkpoints` and pass `persistence.leases` to `createWorkflowCoordinator()` and `createWorkflowSchedules()` for durable background execution and schedules.
|
|
143
146
|
- [Working and semantic memory](working-and-semantic-memory.md): optional `@arnilo/prism-memory` PostgreSQL/pgvector working + semantic stores (separate from session/run persistence).
|
|
147
|
+
- [Enterprise PostgreSQL state](enterprise-postgres-state.md): separate durable policy/evaluation/work/router stores and cleanup.
|
|
144
148
|
- [Migration guide](migration.md): moving from JSONL/in-memory to database-backed persistence.
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
# Process sessions
|
|
2
|
+
|
|
3
|
+
## What it does
|
|
4
|
+
|
|
5
|
+
`createProcessSessions` is an optional host-activated registry in `@arnilo/prism-coding-agent` for **long-running** child processes: start, cursor-paged output, input, wait, signal/kill, and release (detach). Sessions have bounded lifetime (sweep on registry access — no import-time timers), ownership/identity attribution, durable metadata (command fingerprint without env/secrets), and typed `CodingProcessEvent`s via a host callback. Reuses `ExecutionPolicy`, `killProcessTree`, and `OutputAccumulator` (including spill + `readRaw` cursor paging). Optional duck-typed `sandbox` backend uses `startProcess` when present; one-shot adapters fail closed. Nothing spawns on import or construction.
|
|
6
|
+
|
|
7
|
+
| Export | Purpose |
|
|
8
|
+
| --- | --- |
|
|
9
|
+
| `createProcessSessions(options)` | Build a `ProcessSessions` registry bound to one workspace `cwd`. |
|
|
10
|
+
| `ProcessSessions` | `start`, `get`, `cancelOwned`, `markUnknown`, `reconcile`, `dispose`. |
|
|
11
|
+
| `ProcessSession` | Handle: `output` / `input` / `wait` / `signal` / `kill` / `release` / `metadata`. |
|
|
12
|
+
| `ProcessSessionState` | `starting` \| `running` \| `exited` \| `killed` \| `released` \| `expired` \| `unknown`. |
|
|
13
|
+
| `ProcessSandboxBackend` | Duck-typed optional sandbox (`startProcess?`, `status?`); mirrors coding-security `SandboxProcessHandle`. |
|
|
14
|
+
| `CodingProcessEvent` | Host-sink events (`process_started` / `_exited` / `_killed` / `_released` / `_expired` / `_unknown`). |
|
|
15
|
+
| `ProcessSessionError` | Typed fail-closed errors (`ERR_PRISM_PROCESS_*`). |
|
|
16
|
+
| `resolveProcessSessionLimits` / `DEFAULT_MAX_PROCESS_*` / `HARD_MAX_PROCESS_*` | Session count, input bytes, lifetime, chunk/total output caps. |
|
|
17
|
+
|
|
18
|
+
## When to use it
|
|
19
|
+
|
|
20
|
+
Use when a host needs attachable long-running processes (watch modes, language servers, interactive CLIs) that one-shot `shell` cannot model. Do not use as a job-control language or PTY emulator — `pty: true` fails closed with `ERR_PRISM_PROCESS_PTY_UNSUPPORTED` until a platform capability is wired. Pass a sandbox with `startProcess` for contained long-running work; omit `sandbox` for native spawn.
|
|
21
|
+
|
|
22
|
+
```ts
|
|
23
|
+
import { createProcessSessions } from "@arnilo/prism-coding-agent";
|
|
24
|
+
|
|
25
|
+
const sessions = createProcessSessions({ cwd: workspaceRoot, policy, sandbox, onEvent });
|
|
26
|
+
const p = await sessions.start({ command: "npm", args: ["test", "--", "--watch"] });
|
|
27
|
+
const out = await p.output({ cursor: 0, maxBytes: 8192 });
|
|
28
|
+
await p.input("q\n");
|
|
29
|
+
await p.wait({ timeoutMs: 5000 });
|
|
30
|
+
await sessions.dispose();
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Inputs / request
|
|
34
|
+
|
|
35
|
+
`createProcessSessions` options:
|
|
36
|
+
|
|
37
|
+
| Field | Type | Purpose |
|
|
38
|
+
| --- | --- | --- |
|
|
39
|
+
| `cwd` | `string` | Workspace root; session `cwd` must stay inside. |
|
|
40
|
+
| `policy?` | `ExecutionPolicy` | Gated before spawn and rechecked on input/signal/kill. |
|
|
41
|
+
| `limits?` | `ProcessSessionLimits` | Optional overrides; invalid values fail instead of clamping. |
|
|
42
|
+
| `onEvent?` | `(CodingProcessEvent) => void` | Host-owned sink (core `AgentEvent` unchanged); audit owner + terminal/unknown. |
|
|
43
|
+
| `ownership?` | `OwnershipScope` | Default owner key for sessions. |
|
|
44
|
+
| `identity?` | `AgentIdentity` | When ownership omitted, owner key projects from identity. |
|
|
45
|
+
| `sandbox?` | `ProcessSandboxBackend` | When set: require `startProcess` or fail closed; `status` loss → all running → `unknown`. |
|
|
46
|
+
|
|
47
|
+
`ProcessStartRequest`:
|
|
48
|
+
|
|
49
|
+
| Field | Purpose |
|
|
50
|
+
| --- | --- |
|
|
51
|
+
| `command` / `args?` | Executable + argv (not a shell string). |
|
|
52
|
+
| `cwd?` | Relative/absolute path contained under registry `cwd`. |
|
|
53
|
+
| `env?` | Extra env merged onto `process.env` (never in fingerprint). |
|
|
54
|
+
| `pty?` | Default false; unsupported → `ERR_PRISM_PROCESS_PTY_UNSUPPORTED`. |
|
|
55
|
+
| `lifetimeMs?` | Bounded by `maxLifetimeMs`. |
|
|
56
|
+
| `owner?` | Override owner string. |
|
|
57
|
+
| `releaseOnCancel?` | If true, `cancelOwned` releases instead of killing. |
|
|
58
|
+
|
|
59
|
+
## Outputs / response / events
|
|
60
|
+
|
|
61
|
+
| Method | Result |
|
|
62
|
+
| --- | --- |
|
|
63
|
+
| `start` | `ProcessSession` in `running`; emits `process_started`. |
|
|
64
|
+
| `output({ cursor, maxBytes })` | `{ data, cursor, eof }` UTF-8 page from byte cursor. |
|
|
65
|
+
| `input` | Writes stdin; capped by `maxInputBytes`; fails if not running / stdin closed. |
|
|
66
|
+
| `wait` | `{ exitCode, state }` — `exitCode` is `null` for killed/released/expired/unknown. |
|
|
67
|
+
| `signal` / `kill` / `release` | Soft signal, hard kill, or detach (no re-attach). |
|
|
68
|
+
| `cancelOwned(owner)` | Kill (default) or release owned running sessions. |
|
|
69
|
+
| `markUnknown` | Backend-loss terminal state; never fabricates `exitCode`. |
|
|
70
|
+
| `reconcile()` | Host resume: mark every running/starting session `unknown` (O(sessions)). |
|
|
71
|
+
|
|
72
|
+
Events: `process_started`, `process_exited`, `process_killed`, `process_released`, `process_expired`, `process_unknown`.
|
|
73
|
+
|
|
74
|
+
Errors: `ERR_PRISM_PROCESS_POLICY`, `ERR_PRISM_PROCESS_OWNERSHIP`, `ERR_PRISM_PROCESS_STATE`, `ERR_PRISM_PROCESS_LIMIT`, `ERR_PRISM_PROCESS_PTY_UNSUPPORTED`, `ERR_PRISM_PROCESS_UNSUPPORTED`.
|
|
75
|
+
|
|
76
|
+
## Request/response example
|
|
77
|
+
|
|
78
|
+
```json
|
|
79
|
+
{
|
|
80
|
+
"command": "npm",
|
|
81
|
+
"args": ["test", "--", "--watch"],
|
|
82
|
+
"lifetimeMs": 14400000,
|
|
83
|
+
"releaseOnCancel": false
|
|
84
|
+
}
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## Implementation example
|
|
88
|
+
|
|
89
|
+
```ts
|
|
90
|
+
const sessions = createProcessSessions({
|
|
91
|
+
cwd: workspaceRoot,
|
|
92
|
+
ownership: { tenantId: "t1", userId: "u1" },
|
|
93
|
+
sandbox, // DisposableSandbox with startProcess, or omit for native
|
|
94
|
+
limits: { maxSessions: 8 },
|
|
95
|
+
onEvent: (e) => audit.write(e),
|
|
96
|
+
policy: hostExecutionPolicy,
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
const p = await sessions.start({
|
|
100
|
+
command: process.execPath,
|
|
101
|
+
args: ["server.js"],
|
|
102
|
+
lifetimeMs: 3_600_000,
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
let cursor = 0;
|
|
106
|
+
for (;;) {
|
|
107
|
+
const chunk = await p.output({ cursor, maxBytes: 50_000 });
|
|
108
|
+
cursor = chunk.cursor;
|
|
109
|
+
if (chunk.eof) break;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
await sessions.cancelOwned(p.owner); // run cancellation
|
|
113
|
+
await sessions.dispose();
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
## Extension and configuration notes
|
|
117
|
+
|
|
118
|
+
- Native when `sandbox` omitted; with `sandbox`, capability is `typeof startProcess === "function"` (never assumed).
|
|
119
|
+
- One-shot sandbox (no `startProcess`) → `ERR_PRISM_PROCESS_UNSUPPORTED` (no native fallback).
|
|
120
|
+
- Sandbox `status` not `running` (or throws) → all live sessions → `unknown`; further `start` fails closed.
|
|
121
|
+
- Host restart: call `reconcile()` on a new registry for in-memory orphans, or listen for `process_unknown` and wire Phase 7 `ToolEffectStore.markUnknown` in the host.
|
|
122
|
+
- Expiry sweep runs on registry/handle access — no timers at import.
|
|
123
|
+
- Command fingerprint is SHA-256 of `[command, ...args]` only (no env).
|
|
124
|
+
- Docker reference adapter does not implement `startProcess` yet — fail closed until a capable runtime is wired.
|
|
125
|
+
|
|
126
|
+
## Security and performance notes
|
|
127
|
+
|
|
128
|
+
| Cap | Default | Hard |
|
|
129
|
+
| --- | ---: | ---: |
|
|
130
|
+
| Sessions per registry | 8 | 32 |
|
|
131
|
+
| Input write bytes | 64 KiB | 1 MiB |
|
|
132
|
+
| Session lifetime | 4 h | 24 h |
|
|
133
|
+
| Output chunk | 50 KiB | 1 MiB |
|
|
134
|
+
| Total output / session | 64 MiB | 1 GiB |
|
|
135
|
+
|
|
136
|
+
- Wrong-owner `get(id, owner)` → `ERR_PRISM_PROCESS_OWNERSHIP`.
|
|
137
|
+
- Released sessions reject all further handle ops.
|
|
138
|
+
- `cwd` outside workspace → `ERR_PRISM_PROCESS_POLICY`.
|
|
139
|
+
- Policy denial before spawn / on mutate → `ERR_PRISM_PROCESS_POLICY`.
|
|
140
|
+
- Unknown outcome never invents an exit code.
|
|
141
|
+
|
|
142
|
+
## Related APIs
|
|
143
|
+
|
|
144
|
+
- [Coding agent tools](coding-agent-tools.md): one-shot `shell` vs long-running sessions.
|
|
145
|
+
- [Language intelligence](language-intelligence.md): LSP servers may later register as managed sessions.
|
|
146
|
+
- [Coding security](coding-security.md): `SandboxProcessHandle` / optional `DisposableSandbox.startProcess`.
|
|
147
|
+
- [Tool effects](tool-effects.md): unknown-outcome vocabulary mirrored by `markUnknown` / `process_unknown` / `reconcile`.
|
package/docs/provider-caching.md
CHANGED
|
@@ -66,7 +66,7 @@ Cache helpers return plain data:
|
|
|
66
66
|
|
|
67
67
|
Provider events do not change. Cache accounting stays in normalized `Usage.cacheReadTokens` and `Usage.cacheWriteTokens`.
|
|
68
68
|
|
|
69
|
-
For stable-prefix payloads,
|
|
69
|
+
For stable-prefix payloads, `inputLayout: "cache_aware"` is the default on the default input builder, `assembleProviderInput()`, `AgentConfig`, and `RunOptions`; set `inputLayout: "legacy"` to restore the prior order. The default prompt builder already places context, selected skills, and tool declarations before input messages; cache-aware input ordering then places attachments/resources, summaries, prior history, and pending tool results before the current user suffix. The prefix is byte-stable only when those stable inputs are unchanged; Prism still does not guarantee provider cache hits.
|
|
70
70
|
|
|
71
71
|
## Request/response example
|
|
72
72
|
|
|
@@ -146,23 +146,35 @@ Provider request policies can set `ProviderRequestOptions.cache` or the legacy `
|
|
|
146
146
|
| Provider package | Cache kind | Explicit cache hints | Multi-turn reuse notes | Caveats |
|
|
147
147
|
| --- | --- | --- | --- | --- |
|
|
148
148
|
| `@arnilo/prism-provider-openai` | `openai_key` | Sends sanitized `prompt_cache_key`; `prompt_cache_retention: "24h"` only when the model declares `longRetention`. | Stable cache key + stable prefix can improve reuse. | Best-effort only; `"short"`/`"none"` omit retention. |
|
|
149
|
+
| `@arnilo/prism-provider-anthropic` | `cache_control` | Marks only selected Anthropic message anchors; `"long"` maps to documented `ttl: "1h"`. | Keep selected anchors stable. | Best-effort; never stamp every block. |
|
|
150
|
+
| `@arnilo/prism-provider-google` | none | Sends no Prism cache marker. | Host/model may have upstream behavior. | Gemini cache controls are not mapped in this package. |
|
|
149
151
|
| `@arnilo/prism-provider-openrouter` | `cache_control` | Top-level automatic `cache_control` when enabled without breakpoints; otherwise markers only on caller-selected `cache.breakpoints`; `"long"` may add `ttl: "1h"`. Sticky `session_id` routing. | Breakpoint-stable / automatic prefixes can be reused by upstream providers. | Best-effort only; top-level automatic may exclude some backends from routing. |
|
|
150
152
|
| `@arnilo/prism-provider-opencode-go` | route-specific | Sends sanitized `x-opencode-session`; Anthropic route applies selected `cache_control` breakpoints; OpenAI route sends none. | Session id + unchanged selected anchors can help route-native caches. | Best-effort and route-dependent. |
|
|
151
153
|
| `@arnilo/prism-provider-zai` | `implicit` | No explicit cache payload; GLM context caching is automatic. | Resend unchanged prior history for implicit context-cache reuse. | Best-effort only; cache options do not force hits. |
|
|
152
154
|
| `@arnilo/prism-provider-kimi` | implicit by default, optional `cache_control` | Default catalog models send no `cache_control`; hosts may opt in on Anthropic `/messages` models with `ModelConfig.cache.kind: "cache_control"`. | Keep selected Anthropic anchors and prior history stable. | Best-effort and model/route-dependent. |
|
|
153
155
|
| `@arnilo/prism-provider-neuralwatt` | `implicit` | No `cache_control`, `cacheKey`, `prompt_cache`, or `cacheRetention` payload; NeuralWatt vLLM prefix caching is automatic. | Full prior history must be resent unchanged with only the new turn appended; `inputLayout: "cache_aware"` keeps stable prefixes first. | Best-effort only; does not promise cache hits; `cacheRetention: "none"` disables Prism hints only, not the implicit backend prefix cache. |
|
|
154
156
|
| `@arnilo/prism-provider-ai-sdk` | host-owned | No Prism cache payload; host `LanguageModelV4` owns upstream caching. | Host model/provider decides cache keys, breakpoints, and sticky routing. | Adapter maps `inputTokens.cacheRead`/`cacheWrite` from `finish.usage` only; does not invent cache fields. |
|
|
157
|
+
| `@arnilo/prism-provider-alibaba` | implicit by default, optional `cache_control` | DashScope implicit prefix caching is automatic; opt-in `cache_control: {"type":"ephemeral"}` markers only on caller-selected `cache.breakpoints`, capped at 4. | Keep selected anchors and prior history stable; each cached prefix needs ≥1024 tokens and lives ~5 minutes upstream. | Best-effort and model-dependent; `cached_tokens`→read, `cache_creation_input_tokens`→write. |
|
|
158
|
+
| `@arnilo/prism-provider-ollama` | `implicit` | No `cache_control`, `cacheKey`, `prompt_cache`, or `cacheRetention` payload; Ollama KV/prefix caching is automatic with no request knob. | Resend unchanged prior history for implicit KV reuse. | Best-effort only; Ollama reports no cached-token count, so `Usage.cacheReadTokens` stays `undefined`. |
|
|
159
|
+
| `@arnilo/prism-provider-azure` | none | No Prism cache mapping. | Endpoint/model-specific. | Host owns Azure cache policy. |
|
|
160
|
+
| `@arnilo/prism-provider-bedrock` | none | No Prism cache mapping. | Endpoint/model-specific. | Host owns Bedrock cache policy. |
|
|
161
|
+
| `@arnilo/prism-provider-vertex` | none | No Prism cache mapping. | Endpoint/model-specific. | Host owns Vertex cache policy. |
|
|
155
162
|
|
|
156
163
|
Detailed first-party provider notes:
|
|
157
164
|
|
|
158
165
|
- OpenAI Responses (`@arnilo/prism-provider-openai`): `kind: "openai_key"`. Sanitizes/clamps `prompt_cache_key` to 64 chars; `"long"` retention maps to `prompt_cache_retention: "24h"` only when the model declares `cache.longRetention`; `"short"`/`"none"` omit the field. GPT-5.6+ official docs use `prompt_cache_options` / breakpoints instead of retention — `listOpenAIModels` sets `longRetention: false` for those ids. `input_tokens_details.cached_tokens` maps to `Usage.cacheReadTokens`.
|
|
159
166
|
- OpenAI-compatible Chat Completions adapter: minimal scope, sends no cache payload; see [OpenAI-compatible provider](providers/openai-compatible.md).
|
|
167
|
+
- Anthropic (`@arnilo/prism-provider-anthropic`): `kind: "cache_control"`; selected Anthropic message anchors receive `cache_control` and eligible long retention maps to `ttl: "1h"`. Cache read/create usage maps to normalized cache read/write tokens.
|
|
168
|
+
- Google (`@arnilo/prism-provider-google`): sends no Prism cache-control payload. Do not infer cache hits or cache token counts from absent Gemini fields.
|
|
160
169
|
- OpenRouter (`@arnilo/prism-provider-openrouter`): `kind: "cache_control"`. Sanitizes/clamps `session_id`/`X-Session-Id` to 256 chars for sticky routing; with no breakpoints emits top-level automatic `cache_control: { type: "ephemeral" }`; with breakpoints applies Anthropic-style markers only to caller-selected locations (last content block of each selected message); `"long"` retention adds `ttl: "1h"` when the model allows it. `prompt_tokens_details.cached_tokens`/`cache_write_tokens` map to `Usage.cacheReadTokens`/`cacheWriteTokens`. Optional `listOpenRouterModels()` may populate `ModelConfig.cache`/`cost` from live pricing.
|
|
161
170
|
- OpenCode Go (`@arnilo/prism-provider-opencode-go`): default base `https://opencode.ai/zen/go/v1`; `x-opencode-session` from `cacheKey ?? sessionId`, sanitized to 128 chars; the Anthropic route (MiniMax/Qwen) applies `cache_control` markers only to selected breakpoints (`"long"` → `ttl: "1h"`), the OpenAI route (Grok/GLM/Kimi/MiMo/DeepSeek) sends none and preserves `reasoning_content`. OpenAI route maps `prompt_tokens_details.cached_tokens`/`cache_write_tokens`; Anthropic route maps `cache_read_input_tokens`/`cache_creation_input_tokens`. Caller-gated `listOpenCodeGoModels` against official `GET /zen/go/v1/models`.
|
|
162
171
|
- Z.AI (`@arnilo/prism-provider-zai`): `kind: "implicit"`. GLM context caching is automatic; sends no explicit cache payload regardless of cache options. `prompt_tokens_details.cached_tokens`/`cache_write_tokens` map to `Usage.cacheReadTokens`/`cacheWriteTokens`.
|
|
163
172
|
- NeuralWatt (`@arnilo/prism-provider-neuralwatt`): `kind: "implicit"`. NeuralWatt prefix caching is automatic; sends no explicit cache payload regardless of cache options. `cacheRetention: "none"` disables Prism cache-control hints only (not the implicit backend prefix cache). `prompt_tokens_details.cached_tokens` maps to `Usage.cacheReadTokens`; NeuralWatt does not report a cache-write token, so `Usage.cacheWriteTokens` is never fabricated (stays `undefined`). NeuralWatt's `/v1/models` catalog advertises exact `cached_input_per_million` rates for cache reads and `cached_output_per_million: null`; static curated aliases do not guess those prices.
|
|
164
173
|
- Kimi (`@arnilo/prism-provider-kimi`): default catalog models use implicit caching (no `cache_control`); hosts opt in via `ModelConfig.cache.kind: "cache_control"` on the Anthropic `/messages` route, then `cache_control` markers apply only to selected breakpoints (`"long"` → `ttl: "1h"`); the Moonshot OpenAI route sends none. `cache_read_input_tokens`/`cache_creation_input_tokens` map to `Usage.cacheReadTokens`/`cacheWriteTokens`.
|
|
165
174
|
- AI SDK adapter (`@arnilo/prism-provider-ai-sdk`): **host-owned**. Sends no Prism cache payload; the supplied `LanguageModelV4` and its upstream provider own request caching. Maps AI SDK v4 `finish.usage.inputTokens.cacheRead`/`cacheWrite` to `Usage.cacheReadTokens`/`cacheWriteTokens`. No `list*Models()` export.
|
|
175
|
+
- Alibaba Cloud (`@arnilo/prism-provider-alibaba`): implicit by default, optional `cache_control`. DashScope implicit prefix caching is automatic (no marker); explicit opt-in `cache_control: {"type":"ephemeral"}` markers apply only to selected breakpoints when `ModelConfig.cache.kind: "cache_control"` and the caller supplies breakpoints, capped at 4 (each prefix ≥1024 tokens, ~5 minute TTL). `prompt_tokens_details.cached_tokens`/`cache_creation_input_tokens` map to `Usage.cacheReadTokens`/`cacheWriteTokens`. Caller-gated `listAlibabaModels` against OpenAI-compatible `GET {base}/models`.
|
|
176
|
+
- Ollama (`@arnilo/prism-provider-ollama`): `kind: "implicit"`. Ollama reuses its KV/prompt cache automatically; there is no request knob and no wire marker, so Prism never emits `cache_control`. Ollama reports no cached-token count, so `Usage.cacheReadTokens` is intentionally left `undefined` (not `0`). Caller-gated `listOllamaModels` against OpenAI-compatible `GET {base}/models`.
|
|
177
|
+
- Azure, Bedrock, and Vertex: their OpenAI-compatible packages intentionally emit no Prism cache fields. Endpoint/model-specific cache controls remain host-owned rather than guessed from another provider family.
|
|
166
178
|
|
|
167
179
|
### NeuralWatt cache-aware limiter
|
|
168
180
|
|
|
@@ -21,7 +21,28 @@ Use these helpers in provider package tests to check event order, terminal event
|
|
|
21
21
|
|
|
22
22
|
Do not use them as a live integration runner, provider simulator, retry framework, credential loader, or test framework replacement.
|
|
23
23
|
|
|
24
|
-
|
|
24
|
+
Offline conformance is mandatory for every package; credentialed probes are not uniform. Packages with a checked-in `live.test.ts` use `PRISM_LIVE_PROVIDER_TESTS=1` plus their provider key. Realtime, hosted tools, AI SDK host models, Alibaba/Ollama account or daemon paths, and enterprise workload identities need host-owned protected probes instead of a generic fixture. The default `npm test` never sets these gates and stays network-free; see [Release and install](release-and-install.md#015-protected-live-canary-matrix) for the exact environment/key boundary.
|
|
25
|
+
|
|
26
|
+
## Phase 10 provider conformance matrix
|
|
27
|
+
|
|
28
|
+
| Package | Required offline evidence | Restricted live evidence |
|
|
29
|
+
| --- | --- | --- |
|
|
30
|
+
| OpenAI | Responses serialization/stream ordering, provider-hosted authority, continuation cap/cursor, Realtime fake WebSocket caps | Standard API-key smoke; separate protected hosted-tool/Realtime entitlement probe |
|
|
31
|
+
| AI SDK | Exact 4.0.4/V4 gate (`4.0.3` also listed); every mapped stream part; authority, cache usage, redaction, unsupported mapping | Host-created V4 model only; no Prism credential fixture |
|
|
32
|
+
| Anthropic | Messages serialization, cache/thinking/tools, header/redaction/abort assertions | Protected `ANTHROPIC_API_KEY` smoke |
|
|
33
|
+
| Google | `generateContent` serialization, complete tool calls, media/abort/redaction assertions | Protected `GOOGLE_API_KEY` or `GEMINI_API_KEY` smoke |
|
|
34
|
+
| Kimi | Coding/Moonshot route fixtures, thinking/tool reconstruction, headers/redaction | Protected `KIMI_API_KEY` smoke |
|
|
35
|
+
| Z.AI | GLM thinking/tool-stream fixtures, implicit-cache usage, headers/redaction | Protected `ZAI_API_KEY` smoke |
|
|
36
|
+
| OpenRouter | routing/reasoning/cache-control fixture, stream/tool reconstruction, headers/redaction | Protected `OPENROUTER_API_KEY` smoke |
|
|
37
|
+
| OpenCode Go | OpenAI/Anthropic route fixture, completion proof, PDF/media boundary, headers/redaction | Protected `OPENCODE_API_KEY` smoke |
|
|
38
|
+
| Alibaba | DashScope presets, Qwen thinking, image rejection/mapping, cache/usage fixture | Protected account/region host probe; no generic key fixture |
|
|
39
|
+
| Ollama | cloud/local preset, reasoning/image mapping, implicit-cache fixture | Protected cloud or host-local authenticated daemon probe; no daemon starts in tests |
|
|
40
|
+
| NeuralWatt | stream/retry/quota/telemetry fixtures, implicit-cache usage, headers/redaction | Protected `NEURALWATT_API_KEY` smoke |
|
|
41
|
+
| Azure | endpoint preservation, Entra/resource-key header and OpenAI-compatible stream fixture | Protected host workload-identity probe |
|
|
42
|
+
| Bedrock | SigV4/region/PrivateLink and OpenAI-compatible stream fixture | Protected host IAM/IRSA probe |
|
|
43
|
+
| Vertex | location/endpoint preservation, ADC header and OpenAI-compatible stream fixture | Protected host ADC/WIF probe |
|
|
44
|
+
|
|
45
|
+
All rows must retain bounded request/response fixtures, abort propagation, provider-owned-header precedence, and fake-secret leak assertions where the package surfaces those values. A successful fake transport proves Prism mapping, not account entitlement or vendor availability.
|
|
25
46
|
|
|
26
47
|
## Inputs / request
|
|
27
48
|
|
|
@@ -50,6 +71,7 @@ Helpers accept normal `AIProvider`, `ProviderRequest`, `ProviderEvent`, `Usage`,
|
|
|
50
71
|
- `assertSerializedRequestCoversContent()` scans a serialized provider request body for primitive canaries from each Prism content block and fails if any supported block type is silently dropped. Provider-valid transcripts place assistant `tool_call` messages before matching role `tool` `tool_result` messages; runtime, cache-aware input layout, and observational-memory worker loops preserve that order before serialization.
|
|
51
72
|
- `assertProviderOwnedHeadersWin()` compares captured request headers against the provider's authoritative owned header values and a caller-supplied header bag; it fails if any owned header (`authorization`, `content-type`, session/security headers) was overridden by caller headers, and also fails if a non-owned caller header was dropped. This is the provider-neutral check that caller `ProviderRequest.options.headers` cannot hijack provider credentials or sessions; every first-party provider package exercises it.
|
|
52
73
|
- `assertNoSecretLeak()` stringifies all collected events and fails if any known secret string is present.
|
|
74
|
+
- Provider-hosted calls must surface as `tool_call` with `authority: "provider-hosted"`; loops record them but never dispatch them or append a host `tool_result`. Bounded continuations must emit an opaque cursor event and end in `done` or redacted `error`, never silent truncation.
|
|
53
75
|
|
|
54
76
|
## Request/response example
|
|
55
77
|
|
|
@@ -164,10 +186,12 @@ Canonical contract: [Thinking and reasoning](thinking-and-reasoning.md).
|
|
|
164
186
|
`@arnilo/prism-provider-ai-sdk` is a host-owned `LanguageModelV4` bridge. It does not participate in the discovery or thinking/reasoning checklists above. Cover instead:
|
|
165
187
|
|
|
166
188
|
1. **No catalog / no setup fetch** — package exports no `list*Models()`; `createAiSdkProvider` wraps a host model only.
|
|
167
|
-
2. **
|
|
168
|
-
3. **
|
|
169
|
-
4. **
|
|
170
|
-
5. **
|
|
189
|
+
2. **Version + specification gate** — exact `@ai-sdk/provider` matrix version is verified at setup; rejects version skew, non-v4 models (`specificationVersion !== "v4"`), or missing `doStream`.
|
|
190
|
+
3. **Mapping table** — fixture covers every supported matrix row: response metadata id, text/reasoning/tool deltas, client/provider-hosted tool authority, structured output, cache usage, finish/error/abort; unmappable stream parts and `structuredOutput.strict` fail typed instead of dropping.
|
|
191
|
+
4. **Cache usage mapping** — `finish.usage.inputTokens.cacheRead`/`cacheWrite` map to `Usage.cacheReadTokens`/`cacheWriteTokens`; adapter does not emit cache request fields.
|
|
192
|
+
5. **Reasoning stream mapping** — `reasoning-delta` → thinking deltas; assistant `thinking` blocks replay as AI SDK `reasoning` prompt parts.
|
|
193
|
+
6. **Redaction** — direct adapter errors use its supplied `SecretRedactor`; agent runs use their active redactor; opaque provider metadata is never emitted.
|
|
194
|
+
7. **Host-owned controls** — `options.compat` / `options.extra` forward as `providerOptions.prism`; reasoning effort stays on the host model.
|
|
171
195
|
|
|
172
196
|
Canonical contract: [AI SDK provider adapter](providers/ai-sdk.md).
|
|
173
197
|
|