@arnilo/prism 0.0.11 → 0.0.13

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.
Files changed (64) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/dist/agent-run-lifecycle.d.ts +5 -1
  3. package/dist/agent-run-lifecycle.js +17 -1
  4. package/dist/agents.d.ts +3 -1
  5. package/dist/agents.js +98 -20
  6. package/dist/contracts.d.ts +29 -1
  7. package/dist/extensions.d.ts +11 -0
  8. package/dist/extensions.js +15 -0
  9. package/dist/identity.d.ts +92 -0
  10. package/dist/identity.js +257 -0
  11. package/dist/index.d.ts +8 -4
  12. package/dist/index.js +4 -2
  13. package/dist/persistence-lifecycle.d.ts +103 -0
  14. package/dist/persistence-lifecycle.js +204 -0
  15. package/dist/providers/openai-compatible.d.ts +5 -1
  16. package/dist/providers/openai-compatible.js +15 -6
  17. package/dist/secure-agent.js +7 -1
  18. package/dist/testing/persistence-schema.d.ts +2 -2
  19. package/dist/testing/persistence-schema.js +35 -2
  20. package/dist/tools.d.ts +2 -0
  21. package/dist/tools.js +6 -0
  22. package/docs/a2a.md +3 -0
  23. package/docs/ag-ui.md +123 -0
  24. package/docs/agent-events.md +2 -1
  25. package/docs/agent-identity.md +111 -0
  26. package/docs/agent-session-runtime.md +5 -1
  27. package/docs/coding-agent-tools.md +2 -0
  28. package/docs/compaction-and-retry.md +2 -1
  29. package/docs/compaction-llm.md +20 -1
  30. package/docs/credential-storage.md +5 -1
  31. package/docs/credentials-and-redaction.md +8 -0
  32. package/docs/database-persistence.md +18 -7
  33. package/docs/extensions.md +1 -0
  34. package/docs/guardrails.md +3 -0
  35. package/docs/host-security.md +7 -1
  36. package/docs/index.md +25 -14
  37. package/docs/mcp-tools.md +2 -0
  38. package/docs/migration.md +43 -0
  39. package/docs/model-routing.md +102 -0
  40. package/docs/observability.md +2 -0
  41. package/docs/performance.md +38 -0
  42. package/docs/policy-and-audit.md +127 -0
  43. package/docs/postgres-persistence.md +1 -1
  44. package/docs/provider-packages.md +17 -0
  45. package/docs/provider-request-policies.md +2 -0
  46. package/docs/providers/anthropic.md +3 -2
  47. package/docs/providers/azure.md +74 -0
  48. package/docs/providers/bedrock.md +72 -0
  49. package/docs/providers/google.md +4 -2
  50. package/docs/providers/openai-compatible.md +3 -1
  51. package/docs/providers/openai.md +2 -2
  52. package/docs/providers/openrouter.md +2 -0
  53. package/docs/providers/vertex.md +71 -0
  54. package/docs/public-contracts.md +5 -1
  55. package/docs/release-and-install.md +167 -17
  56. package/docs/review-coverage-2026-07-22-phase-7.md +173 -0
  57. package/docs/review-coverage-2026-07-23-phase-8.md +245 -0
  58. package/docs/runs-and-usage.md +3 -0
  59. package/docs/server.md +34 -4
  60. package/docs/sqlite-persistence.md +1 -1
  61. package/docs/supervisors.md +2 -0
  62. package/docs/work-connectors.md +28 -0
  63. package/docs/work-tools.md +114 -0
  64. package/package.json +5 -1
@@ -16,13 +16,22 @@ export function createOpenAICompatibleProvider(options) {
16
16
  const secrets = [apiKey];
17
17
  const tools = new Map();
18
18
  try {
19
- const response = await fetchImpl(`${options.baseUrl.replace(/\/$/, "")}/chat/completions`, {
19
+ const url = typeof options.chatCompletionsUrl === "function"
20
+ ? options.chatCompletionsUrl(request)
21
+ : options.chatCompletionsUrl
22
+ ?? `${options.baseUrl.replace(/\/$/, "")}/chat/completions`;
23
+ const authStyle = options.authStyle ?? "bearer";
24
+ const headers = {
25
+ ...Object.fromEntries(Object.entries(request.options?.headers ?? {}).filter((entry) => typeof entry[1] === "string")),
26
+ "content-type": "application/json",
27
+ };
28
+ if (apiKey && authStyle === "api-key")
29
+ headers["api-key"] = apiKey;
30
+ if (apiKey && authStyle === "bearer")
31
+ headers.authorization = `Bearer ${apiKey}`;
32
+ const response = await fetchImpl(url, {
20
33
  method: "POST",
21
- headers: {
22
- ...request.options?.headers,
23
- "content-type": "application/json",
24
- ...(apiKey ? { authorization: `Bearer ${apiKey}` } : {}),
25
- },
34
+ headers,
26
35
  body: JSON.stringify(toOpenAIRequest(request)),
27
36
  signal: request.signal,
28
37
  });
@@ -1,5 +1,6 @@
1
1
  import { createAgent } from "./agents.js";
2
2
  import { validateRunStateOptions } from "./agent-run-state.js";
3
+ import { assertIdentityActive, assertIdentityMatchesOwnership } from "./identity.js";
3
4
  import { resolveRunLimits } from "./run-limits.js";
4
5
  import { createToolParameterValidator, createToolRegistry } from "./tools.js";
5
6
  /** Build an opt-in agent whose security-critical defaults cannot be replaced per run. */
@@ -27,6 +28,10 @@ export function createSecureAgent(options) {
27
28
  throw new TypeError(`Secure agent tool ${tool.name} requires a non-empty parameters schema`);
28
29
  }
29
30
  resolveRunLimits(options.limits);
31
+ if (options.identity) {
32
+ assertIdentityActive(options.identity);
33
+ assertIdentityMatchesOwnership(options.identity, options.ownership);
34
+ }
30
35
  const runState = Object.freeze({ ...options.runState, definitionRevision: options.definitionRevision, interruptBeforeTool: true });
31
36
  validateRunStateOptions(runState);
32
37
  const config = Object.freeze({
@@ -38,6 +43,7 @@ export function createSecureAgent(options) {
38
43
  permission: options.permission,
39
44
  trust: options.trust,
40
45
  ownership: Object.freeze({ ...options.ownership }),
46
+ ...(options.identity ? { identity: Object.freeze({ ...options.identity, scopes: Object.freeze([...options.identity.scopes]) }) } : {}),
41
47
  limits: Object.freeze({ ...options.limits }),
42
48
  guardrails: freezeGuardrails(options.guardrails),
43
49
  runState,
@@ -46,7 +52,7 @@ export function createSecureAgent(options) {
46
52
  return createAgent(config);
47
53
  }
48
54
  function withoutSecureFields(options) {
49
- const { tools: _tools, toolArgumentValidator: _validator, redactor: _redactor, permission: _permission, trust: _trust, ownership: _ownership, limits: _limits, guardrails: _guardrails, definitionRevision: _revision, runState: _runState, ...config } = options;
55
+ const { tools: _tools, toolArgumentValidator: _validator, redactor: _redactor, permission: _permission, trust: _trust, ownership: _ownership, identity: _identity, limits: _limits, guardrails: _guardrails, definitionRevision: _revision, runState: _runState, ...config } = options;
50
56
  return config;
51
57
  }
52
58
  function freezeGuardrails(guardrails) {
@@ -1,7 +1,7 @@
1
1
  import type { PersistencePage, SessionEntry, SessionEntryQuery } from "../contracts.js";
2
2
  /** Current shared persistence schema version for production database adapters. */
3
- export declare const PERSISTENCE_SCHEMA_VERSION = 4;
4
- export type PersistenceTableName = "prism_tenants" | "prism_accounts" | "prism_users" | "prism_agent_definitions" | "prism_sessions" | "prism_branches" | "prism_session_entries" | "prism_session_append_idempotency" | "prism_runs" | "prism_agent_events" | "prism_tool_calls" | "prism_usage" | "prism_run_feedback" | "prism_retention_policies" | "prism_migrations";
3
+ export declare const PERSISTENCE_SCHEMA_VERSION = 5;
4
+ export type PersistenceTableName = "prism_tenants" | "prism_accounts" | "prism_users" | "prism_agent_definitions" | "prism_sessions" | "prism_branches" | "prism_session_entries" | "prism_session_append_idempotency" | "prism_runs" | "prism_agent_events" | "prism_tool_calls" | "prism_usage" | "prism_run_feedback" | "prism_retention_policies" | "prism_legal_holds" | "prism_tenant_quotas" | "prism_migrations";
5
5
  export type PersistenceColumnType = "text" | "integer" | "number" | "boolean" | "json" | "timestamp";
6
6
  export interface PersistenceColumnDefinition {
7
7
  readonly name: string;
@@ -4,7 +4,7 @@ import { createHash } from "node:crypto";
4
4
  // this module defines the shared table/index/pagination/migration expectations
5
5
  // adapter authors implement and test against before shipping dialect-specific DDL.
6
6
  /** Current shared persistence schema version for production database adapters. */
7
- export const PERSISTENCE_SCHEMA_VERSION = 4;
7
+ export const PERSISTENCE_SCHEMA_VERSION = 5;
8
8
  /** Guidance adapters must follow: values are bound parameters, never interpolated. */
9
9
  export const PARAMETERIZED_QUERY_GUIDANCE = "Bind every user-supplied value (session ids, idempotency keys, tenant ids, timestamps, JSON payloads) as a query parameter. Quote/validate schema and table identifiers only; never interpolate untrusted strings into SQL text.";
10
10
  const TENANT_COLUMNS = [
@@ -258,6 +258,33 @@ export function createPersistenceSchemaModel() {
258
258
  { name: "metadata", type: "json", nullable: true },
259
259
  ],
260
260
  },
261
+ {
262
+ name: "prism_legal_holds",
263
+ primaryKey: ["id"],
264
+ columns: [
265
+ { name: "id", type: "text" },
266
+ ...TENANT_COLUMNS,
267
+ { name: "resource_kind", type: "text" },
268
+ { name: "resource_id", type: "text" },
269
+ { name: "reason", type: "text" },
270
+ { name: "created_at", type: "timestamp" },
271
+ { name: "created_by", type: "text", nullable: true },
272
+ { name: "metadata", type: "json", nullable: true },
273
+ ],
274
+ },
275
+ {
276
+ name: "prism_tenant_quotas",
277
+ primaryKey: ["id"],
278
+ columns: [
279
+ { name: "id", type: "text" },
280
+ ...TENANT_COLUMNS,
281
+ { name: "resource_kind", type: "text" },
282
+ { name: "limit_count", type: "integer" },
283
+ { name: "used_count", type: "integer" },
284
+ { name: "updated_at", type: "timestamp" },
285
+ ],
286
+ uniqueKeys: [["tenant_id", "account_id", "user_id", "resource_kind"]],
287
+ },
261
288
  {
262
289
  name: "prism_migrations",
263
290
  primaryKey: ["id"],
@@ -298,6 +325,9 @@ export function createPersistenceSchemaModel() {
298
325
  { name: "prism_run_feedback_run_created_idx", table: "prism_run_feedback", columns: ["run_id", "created_at", "id"], purpose: "run feedback lookup" },
299
326
  { name: "prism_run_feedback_trace_created_idx", table: "prism_run_feedback", columns: ["trace_id", "created_at", "id"], purpose: "trace feedback lookup" },
300
327
  { name: "prism_agent_definitions_name_version_idx", table: "prism_agent_definitions", columns: ["name", "version"], purpose: "definition lookup" },
328
+ { name: "prism_legal_holds_owner_resource_idx", table: "prism_legal_holds", columns: ["tenant_id", "account_id", "user_id", "resource_kind", "resource_id"], purpose: "hold lookup by owned resource" },
329
+ { name: "prism_legal_holds_created_id_idx", table: "prism_legal_holds", columns: ["created_at", "id"], purpose: "hold export pagination" },
330
+ { name: "prism_tenant_quotas_owner_kind_idx", table: "prism_tenant_quotas", columns: ["tenant_id", "account_id", "user_id", "resource_kind"], purpose: "tenant quota lookup" },
301
331
  { name: "prism_migrations_name_version_idx", table: "prism_migrations", columns: ["name", "version"], purpose: "applied-migration lookup (table constraint enforces uniqueness)" },
302
332
  ],
303
333
  };
@@ -320,7 +350,9 @@ function migrationStep(version, name, description) {
320
350
  : version === 4
321
351
  // Adapter-local FTS objects (SQLite FTS5 / Postgres tsvector) map to this canonical name.
322
352
  ? { search: ["prism_session_search"], indexes: ["prism_sessions_updated_id_idx"] }
323
- : (() => { throw new Error(`Unknown migration version ${version}`); })();
353
+ : version === 5
354
+ ? { tables: ["prism_legal_holds", "prism_tenant_quotas"], indexes: ["prism_legal_holds_owner_resource_idx", "prism_legal_holds_created_id_idx", "prism_tenant_quotas_owner_kind_idx"] }
355
+ : (() => { throw new Error(`Unknown migration version ${version}`); })();
324
356
  return {
325
357
  version,
326
358
  name,
@@ -338,6 +370,7 @@ export function createPersistenceMigrationContract() {
338
370
  migrationStep(2, "002_usage_scope", "Distinguish provider-turn usage from aggregate run totals."),
339
371
  migrationStep(3, "003_run_feedback", "Add immutable ownership-scoped run/trace feedback and evaluation links."),
340
372
  migrationStep(4, "004_session_search", "Add bounded session search indexes and adapter-local FTS objects."),
373
+ migrationStep(5, "005_lifecycle_hold_quota", "Add legal-hold and tenant-quota tables for retention lifecycle."),
341
374
  ],
342
375
  lockGuidance: "Acquire a dialect-specific migration lock before applying steps (PostgreSQL advisory lock; SQLite exclusive transaction). Only one process should migrate at a time.",
343
376
  leastPrivilegeGuidance: "Run migrations with a DDL-capable role; use a separate least-privilege runtime role limited to INSERT/SELECT/UPDATE on adapter tables. Never grant migration credentials to the agent runtime.",
package/dist/tools.d.ts CHANGED
@@ -43,6 +43,8 @@ export interface DispatchToolCallOptions {
43
43
  readonly redactor?: SecretRedactor;
44
44
  readonly ledger?: RunLedger;
45
45
  readonly ownership?: OwnershipScope;
46
+ /** Host-verified identity; asserted active before tool side effects when present. */
47
+ readonly identity?: import("./identity.js").AgentIdentity;
46
48
  /** Tool stages run after middleware normalization and before side effects/exposure. */
47
49
  readonly guardrails?: Guardrails;
48
50
  /** Shared run tracker; direct hosts may supply one for their call scope. */
package/dist/tools.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import { isJsonObject } from "./config.js";
2
2
  import { createId } from "./ids.js";
3
3
  import { GuardrailError, runGuardrails } from "./guardrails.js";
4
+ import { assertIdentityActive, assertIdentityMatchesOwnership } from "./identity.js";
4
5
  import { errorToErrorInfo, redactRunLedgerRecord, redactSecrets } from "./redaction.js";
5
6
  import { assertCanRegister } from "./registry-options.js";
6
7
  import { assertPermission, assertTrusted } from "./security.js";
@@ -89,6 +90,7 @@ export async function dispatchToolCall(options) {
89
90
  const context = {
90
91
  ...options.context,
91
92
  toolCallId: mediatedCall.id,
93
+ identity: options.identity ?? options.context.identity,
92
94
  progress: async (progress, metadata) => {
93
95
  await options.context.progress?.(progress, metadata);
94
96
  await options.emit?.({
@@ -108,6 +110,10 @@ export async function dispatchToolCall(options) {
108
110
  },
109
111
  };
110
112
  try {
113
+ if (context.identity) {
114
+ assertIdentityActive(context.identity);
115
+ assertIdentityMatchesOwnership(context.identity, options.ownership);
116
+ }
111
117
  await assertTrusted(options.trust, { kind: "tool", target: mediatedCall.name, capability: "execute", metadata: options.context.metadata });
112
118
  await assertPermission(options.permission, { kind: "tool", action: "execute", target: mediatedCall.name, metadata: options.context.metadata });
113
119
  }
package/docs/a2a.md CHANGED
@@ -80,6 +80,7 @@ Defaults/hard caps include: request 64 KiB/1 MiB; response 1/8 MiB; event 64 KiB
80
80
  ## Security and performance notes
81
81
 
82
82
  - Authorize every operation; lifecycle/push adapters enforce exact owner again at durable storage boundary. Missing and foreign tasks/configs share `-32001`.
83
+ - Optional `A2AAuthorization.identity` is host-verified; the handler asserts activity/ownership match and forwards identity into `session.run`. Cross-tenant or widened scopes fail closed.
83
84
  - URL policy must reject private, loopback, link-local, rebound, redirected, or otherwise disallowed destinations. Package never fetches file URLs. Host push delivery must repeat equivalent checks for every attempt/redirect and process event IDs idempotently.
84
85
  - Push token/auth credentials are accepted only into host adapter input and removed from protocol reads/responses. Keep them out of task parts, events, telemetry, ledgers, and errors.
85
86
  - Known-secret redaction applies before handler JSON/SSE output. Client redacts mapped text/errors. Raw/data/url content remains explicitly untrusted.
@@ -88,7 +89,9 @@ Defaults/hard caps include: request 64 KiB/1 MiB; response 1/8 MiB; event 64 KiB
88
89
 
89
90
  ## Related APIs
90
91
 
92
+ - [Agent identity](agent-identity.md)
91
93
  - [Supervisor delegation](supervisors.md)
92
94
  - [Agent/session runtime](agent-session-runtime.md)
93
95
  - [Workflows](workflows.md)
94
96
  - [Host security](host-security.md)
97
+ - [Frontend interoperability (AG-UI and ACP)](ag-ui.md): browser/editor protocol adapters over a Prism session; not an A2A card, task lifecycle, or remote-agent transport.
package/docs/ag-ui.md ADDED
@@ -0,0 +1,123 @@
1
+ # Frontend interoperability (AG-UI and ACP)
2
+
3
+ ## What it does
4
+
5
+ `@arnilo/prism-ag-ui` is an optional, framework-free protocol adapter over Prism's existing redacted `AgentEvent`, session, durable-run, and persistence seams.
6
+
7
+ - Root export maps Prism events to AG-UI `@ag-ui/core` **0.0.57** events and offers `createAgUiHandler()` (`Request` → SSE `Response`) plus `createPersistenceAgUiReplay()`.
8
+ - `@arnilo/prism-ag-ui/acp` uses stable `@agentclientprotocol/sdk` **1.3.0** root exports for `createAcpEventMapper()` and `createPrismAcpAgent()`.
9
+ - Core remains protocol-free. `resumeAgentRunStream()` / `AgentRunLifecycle.resumeStream()` are generic durable-resume streams shared by adapters.
10
+
11
+ This is not an app TUI, desktop shell, conversation database, terminal/filesystem bridge, A2A implementation, or frontend tool registry.
12
+
13
+ ## When to use it
14
+
15
+ Use AG-UI when a host already authenticates users, owns sessions and durable run correlation, and needs a bounded Web endpoint for a browser/TUI/desktop client. Use ACP when an editor client already supplies an ACP transport and needs text, safe tool status, usage, and approval updates from a Prism session.
16
+
17
+ Use [A2A interoperability](a2a.md) for remote agent-to-agent JSON-RPC/HTTPS tasks. AG-UI/ACP are frontend/client protocol adapters; neither replaces A2A task lifecycle or storage.
18
+
19
+ ## Inputs / request
20
+
21
+ Install the optional package beside the core runtime (it becomes publishable with the 0.0.12 release graph):
22
+
23
+ ```bash
24
+ npm install @arnilo/prism @arnilo/prism-ag-ui
25
+ ```
26
+
27
+ `createAgUiHandler()` takes host-owned callbacks:
28
+
29
+ | Input | Purpose |
30
+ | --- | --- |
31
+ | `authorize` | Rebinds untrusted AG-UI thread/run selectors to host ownership on every request. `false` returns 403. |
32
+ | `sessionFactory` | Returns an authorized Prism `AgentSession`; client input never selects tools or capabilities. |
33
+ | `lifecycle` + `resolveRun` | Optional durable status/resume path. Required only for a resumed interruption. |
34
+ | `replay` | Optional `createPersistenceAgUiReplay(store, options)` adapter for ownership-scoped durable pages. |
35
+ | `projection` | Explicit safe tool args/results, paths, or state projection. Omit it for default deny. |
36
+ | `redactor`, `limits` | Host redaction and narrowing-only finite caps. |
37
+
38
+ The handler accepts only `POST` JSON validated with AG-UI `RunAgentInputSchema`. IDs are bounded URL-safe values; it uses only the last text user message. Frontend tools and non-empty frontend state are rejected before authorization or session lookup. Start a run with no `resume` and no `?cursor=`; resume has exactly one entry; replay supplies `?cursor=`.
39
+
40
+ ## Outputs / response / events
41
+
42
+ The handler returns `text/event-stream`, one `data: <AG-UI event>\n\n` frame per output. Mapper lifecycle is ordered: Prism `agent_started`/assistant text/tool events map to `RUN_STARTED`, `TEXT_MESSAGE_*`, and `TOOL_CALL_*`; terminal success maps to `RUN_FINISHED`; runtime errors map to `RUN_ERROR`. Active AG-UI message/tool sequences close before an error, interruption, or finish.
43
+
44
+ A Prism durable `agent_suspended` returns `RUN_FINISHED` with interrupt id `${runId}:${version}` and a strict `{ decision: "approve" | "deny" }` schema. A client must address that exact current id. `cancelled` means deny; a resolved resume payload must contain only that decision. The adapter checks host authorization, selected run, suspended status, and checkpoint version, then calls `AgentRunLifecycle.resumeStream()` once. Claimed/dispatched tools are never replayed.
45
+
46
+ `createPersistenceAgUiReplay()` queries only the host-resolved run with ownership and ascending bounded pagination. Every record must already be redacted. Events carry `prismEventId` for at-least-once page-boundary de-duplication; a nonterminal final page may attach a filtered live subscriber. Terminal pages never create a session or rerun a provider/tool.
47
+
48
+ ACP maps assistant text to `agent_message_chunk`, safe tool lifecycle to `tool_call`/`tool_call_update`, provider usage to `usage_update`, and durable suspension to `session/request_permission`. Only `allow_once` approves; reject, cancellation, unknown outcomes, and request failure deny. It advertises only close-session capability—no terminal, filesystem, MCP, editor state, location, diff, or raw input/output capability.
49
+
50
+ ## Request/response example
51
+
52
+ ```json
53
+ {
54
+ "threadId": "thread-1",
55
+ "runId": "run-1",
56
+ "messages": [{ "id": "message-1", "role": "user", "content": "Summarize this" }],
57
+ "tools": [],
58
+ "state": {}
59
+ }
60
+ ```
61
+
62
+ A suspended response includes this resumable interrupt shape:
63
+
64
+ ```json
65
+ {
66
+ "type": "RUN_FINISHED",
67
+ "threadId": "thread-1",
68
+ "runId": "run-1",
69
+ "outcome": {
70
+ "type": "interrupt",
71
+ "interrupts": [{ "id": "run-1:4", "responseSchema": { "required": ["decision"] } }]
72
+ }
73
+ }
74
+ ```
75
+
76
+ Resume the same host thread/run with `resume: [{ "interruptId": "run-1:4", "status": "resolved", "payload": { "decision": "approve" } }]`. Do not send a copied session transcript, tool definitions, or mutable application state.
77
+
78
+ ## Implementation example
79
+
80
+ ```ts
81
+ import { createAgent, createMockProvider, providerDone, providerTextDelta } from "@arnilo/prism";
82
+ import { createAgUiHandler } from "@arnilo/prism-ag-ui";
83
+
84
+ const agent = createAgent({
85
+ model: { provider: "mock", model: "offline" },
86
+ provider: createMockProvider([providerTextDelta("ready"), providerDone()]),
87
+ });
88
+
89
+ const handle = createAgUiHandler({
90
+ authorize: ({ request }) => request.headers.get("authorization") === "Bearer host-checked"
91
+ ? { ownership: { userId: "user-1" } }
92
+ : false,
93
+ sessionFactory: () => agent.createSession({ id: "host-owned-thread" }),
94
+ projection: { toolArguments: () => undefined, toolResult: () => undefined },
95
+ });
96
+
97
+ const response = await handle(request); // adapt this Web Response in host framework
98
+ ```
99
+
100
+ See runnable network-free [`examples/ag-ui-server.ts`](../examples/ag-ui-server.ts). For ACP, construct `createPrismAcpAgent({ authorize, sessionFactory, lifecycle })` and connect the returned stable SDK agent through the host's ACP transport.
101
+
102
+ ## Extension and configuration notes
103
+
104
+ All identity, authorization, session/thread mapping, durable checkpoint lookup, persistence selection, replay cursor persistence, transport adaptation, and optional projection are host-owned. The adapter owns no listener, database, background reconnect loop, credential resolver, or UI state.
105
+
106
+ `AgUiProjection` is an allow-list. Without a callback, raw tool arguments/results/progress, paths, arbitrary state, raw Prism events, ACP locations/diffs/terminals/raw I/O, and frontend-supplied tools remain absent. Use a projector that returns a redacted display value, not a host filesystem path or tool payload.
107
+
108
+ ## Security and performance notes
109
+
110
+ Authorize every start, replay, resume, ACP new/prompt/cancel/close request. Treat thread IDs, run IDs, cursors, client messages, resume payloads, and protocol output as untrusted. Persist run ↔ protocol correlation before exposing an interrupt. Keep `SecretRedactor` active for streaming and ledger writes.
111
+
112
+ Defaults / hard caps: request 64 KiB / 1 MiB; input 128 / 1024 messages and 64 KiB / 1 MiB text; projected event 64 KiB / 1 MiB; error 8 KiB / 64 KiB; cursor 4 / 16 KiB; replay page 100 / 500 records; queue 128 / 4096 events; stream 10,000 / 100,000 events and 10 / 64 MiB; wall time 120 seconds / 30 minutes. Overflow yields a bounded error/closed stream, not an unbounded queue. Reconnect is at-least-once, so clients de-duplicate stable event/message/tool IDs.
113
+
114
+ Benchmark command/result placeholder: Task 8 adds `node scripts/benchmark-0.0.12.mjs` for mapper throughput, replay/handler latency, queue/heap, bytes, and coding-compaction preparation. No 0.0.12 timing result is claimed before that gate.
115
+
116
+ ## Related APIs
117
+
118
+ - [Agent/session runtime](agent-session-runtime.md): `session.stream()`, `resumeAgentRunStream()`, and durable lifecycle.
119
+ - [Agent events](agent-events.md): normalized source events and ledger redaction.
120
+ - [Runs and usage ledger](runs-and-usage.md): durable `AgentEventRecord` query source.
121
+ - [Web-standard server handler](server.md): generic Prism HTTP API, separate from AG-UI.
122
+ - [A2A interoperability](a2a.md): remote agent-to-agent tasks, not frontend protocol mapping.
123
+ - [Host security guide](host-security.md): authorization, ownership, redaction, and credential boundaries.
@@ -191,7 +191,7 @@ for await (const event of session.stream("draft", { loop: { strategy: "generate-
191
191
 
192
192
  - All events flow through `redactAgentEvent(event, activeRedactor)` before subscribers observe them. Configure `AgentConfig.redactor` / `RunOptions.redactor` via `createSecretRedactor([...knownSecretStrings])` so secret values are redacted in `message` content, `errors[].message`, `metadata`, and artifact `result`/`failure` payloads.
193
193
  - The artifact variants are emitted only by `generateValidateReviseLoop`. `singleShotLoop` (the default when no `AgentConfig.loop` / `RunOptions.loop` is set) emits zero artifact events. See [Agent loops](agent-loops.md).
194
- - Subscribers are in-process; the broadcaster is in-memory and live-only. Multiple `subscribe()` calls receive the same stream.
194
+ - Subscribers are in-process; the broadcaster is in-memory and live-only. Multiple `subscribe()` calls receive the same stream. `resumeAgentRunStream()` and `AgentRunLifecycle.resumeStream()` subscribe before resumed execution and yield only their selected durable `runId`; approval emits the normal `agent_started` then `agent_resumed` envelope, denial emits only `agent_denied`.
195
195
  - `session.subscribe(options)` accepts `maxQueuedEvents` (default `1024`, minimum `1`) and `overflow` (default `"close"`). The `close` policy clears queued payload events, queues one `event_subscriber_overflow` notice for that subscriber, then closes it. `drop_oldest` keeps the newest queued events; `drop_newest` ignores new events while full.
196
196
  - The union is additive: new variants are appended without renumbering; subscribers should handle unknown `event.type` gracefully.
197
197
 
@@ -212,3 +212,4 @@ for await (const event of session.stream("draft", { loop: { strategy: "generate-
212
212
  - [Observability](observability.md): `ProviderTurnMetadata`; optional adapter builds one parented GenAI span tree from metadata-only lifecycle events and ignores message/progress deltas.
213
213
  - [Tools](tools.md): `tool_execution_*` variants.
214
214
  - [Compaction and retry policies](compaction-and-retry.md): `compaction_*` and `retry_scheduled` variants.
215
+ - [Frontend interoperability (AG-UI and ACP)](ag-ui.md): optional redacted mapping of this stream; durable replay is ledger-backed and at-least-once, never a live-subscriber substitute.
@@ -0,0 +1,111 @@
1
+ # Agent identity
2
+
3
+ ## What it does
4
+
5
+ Authenticated `Principal` / `AgentIdentity` contracts let hosts attach verified tenant, sponsor/owner, delegated actor, scopes, credential references, issued/expiry, and revocation metadata to runs and tools. Core helpers assert activity, narrow scopes for delegation, project onto `OwnershipScope`, refuse silent widening, and emit redacted telemetry attributes. Prism does not store identities or verify tokens itself — hosts supply an `IdentityVerifier`.
6
+
7
+ ## When to use it
8
+
9
+ Use these APIs when embedding Prism in multi-tenant or enterprise hosts that already authenticate callers (for example Microsoft Entra Agent ID governance). Use them before tools, providers, MCP, A2A, workflows, or persistence that must carry attributable identity.
10
+
11
+ Do not treat optional `ownership` strings as identity provenance. Do not accept caller-asserted identity headers without a host verifier. Do not put JWTs or secret credential material on identity records.
12
+
13
+ ## Inputs / request
14
+
15
+ | Field | Meaning |
16
+ | --- | --- |
17
+ | `Principal` | Actor id/kind (user, service, agent) plus optional display name |
18
+ | `AgentIdentity` | Verified context: required `tenantId`, optional account/user, principal, sponsor/owner, scopes, credential refs, issued/expiry/revocation, `verified: true` |
19
+ | `IdentityVerifier.verify(input)` | Host-owned authentication → `AgentIdentity` |
20
+ | `RunOptions.identity` / `AgentConfig.identity` | Optional verified identity for a run or agent default |
21
+ | Server/MCP/A2A `authorization.identity` | Optional verified identity on authorize results |
22
+
23
+ Frozen caps (defaults / hard): scopes `64 / 256`, scope bytes `128 / 512`, metadata `4 KiB / 16 KiB`, credential ref / principal id `256 B / 2 KiB`.
24
+
25
+ ## Outputs / response / events
26
+
27
+ - `assertIdentityActive` — fail closed on unverified/expired/revoked/wrong-tenant/over-limit shapes (sync, no network).
28
+ - `narrowIdentity` — child scopes ⊆ parent; tenant and ownership ids immutable; expiry cannot extend.
29
+ - `ownershipFromIdentity` — projects tenant/account/user onto existing ownership seams.
30
+ - `assertIdentityMatchesOwnership` / `assertIdentityPropagation` — refuse widen across ownership or boundary hop.
31
+ - `identityTelemetryAttributes` — redacted refs for metadata/OTel (`prism.identity.*`); never includes credential secrets or raw tokens.
32
+ - Tool `ToolExecutionContext.identity` — set when a run carries verified identity.
33
+
34
+ ## Request/response example
35
+
36
+ ```json
37
+ {
38
+ "tenantId": "tenant-1",
39
+ "userId": "user-1",
40
+ "principal": { "kind": "agent", "id": "agent-42" },
41
+ "sponsor": { "kind": "user", "id": "sponsor-7" },
42
+ "scopes": ["mail.read", "mail.draft"],
43
+ "credentialRefs": ["m365:tenant-1:user-1"],
44
+ "issuedAt": "2026-07-23T00:00:00.000Z",
45
+ "expiresAt": "2026-07-23T01:00:00.000Z",
46
+ "verified": true
47
+ }
48
+ ```
49
+
50
+ ## Implementation example
51
+
52
+ ```ts
53
+ import {
54
+ assertIdentityActive,
55
+ createAgent,
56
+ identityTelemetryAttributes,
57
+ narrowIdentity,
58
+ ownershipFromIdentity,
59
+ type AgentIdentity,
60
+ type IdentityVerifier,
61
+ } from "@arnilo/prism";
62
+
63
+ const verifier: IdentityVerifier = {
64
+ async verify(request) {
65
+ // Host validates JWT/session, then returns AgentIdentity with verified: true
66
+ return hostVerifiedIdentityFrom(request);
67
+ },
68
+ };
69
+
70
+ const identity = await verifier.verify(incomingRequest);
71
+ assertIdentityActive(identity);
72
+ const child = narrowIdentity(identity, { scopes: ["mail.read"] });
73
+
74
+ const agent = createAgent({
75
+ model,
76
+ provider,
77
+ ownership: ownershipFromIdentity(identity),
78
+ identity,
79
+ });
80
+
81
+ await agent.createSession().run("Summarize inbox", {
82
+ identity: child,
83
+ ownership: ownershipFromIdentity(child),
84
+ metadata: identityTelemetryAttributes(child),
85
+ });
86
+ ```
87
+
88
+ Server / MCP / A2A authorize callbacks may include the same `identity` beside `ownership`. Handlers assert activity and ownership match before admitting work.
89
+
90
+ ## Extension and configuration notes
91
+
92
+ Identity is optional. Hosts that only set `ownership` keep prior behavior. When identity is present, run start and tool dispatch assert it before side effects. Workflows forward `RunWorkflowOptions.identity` into agent nodes. Credential values stay behind `CredentialResolver` keys listed in `credentialRefs`.
93
+
94
+ ## Security and performance notes
95
+
96
+ - Caller-asserted identity without `IdentityVerifier` is unsupported at trust boundaries.
97
+ - Delegation only narrows scopes; tenant/account/user cannot widen on propagation.
98
+ - Credential refs never expand to secrets in events, ledgers, or telemetry attributes.
99
+ - Checks are O(fields) and network-free in core; remote auth stays in the host verifier.
100
+ - Raising hard caps requires updating `docs/review-coverage-2026-07-23-phase-8.md`, tests, and docs.
101
+
102
+ ## Related APIs
103
+
104
+ - [Policy and audit](policy-and-audit.md)
105
+ - [Host security guide](host-security.md)
106
+ - [Public contracts](public-contracts.md)
107
+ - [Server](server.md)
108
+ - [Supervisors](supervisors.md) / [A2A](a2a.md)
109
+ - [MCP tools](mcp-tools.md)
110
+ - [Observability](observability.md)
111
+ - [Runs and usage ledger](runs-and-usage.md)
@@ -19,6 +19,7 @@ The agent/session runtime adds the minimal shared SDK surface for running provid
19
19
  - `session.fork(options?)`
20
20
  - `session.clone(options?)`
21
21
  - `resumeAgentRun(agent, ref, decision, options)`
22
+ - `resumeAgentRunStream(agent, ref, decision, options)` → owned durable-resume `AsyncIterable<AgentEvent>`
22
23
  - `createAgentRunLifecycle({ checkpoints, resolveAgent })` for host-selected remote status/resume adapters
23
24
 
24
25
  The runtime streams provider text/tool-call content into `AgentEvent` values. Complete `tool_call` events are dispatched through the active host `ToolRegistry`, then returned as tool-result messages on the next provider turn. When a store is supplied, user, assistant, tool-result, and model-change entries are appended under the current branch leaf. Abort propagation and run exclusivity use native `AbortController`.
@@ -56,6 +57,8 @@ string | Message | readonly Message[]
56
57
 
57
58
  `session.stream(input, options?)` subscribes first, starts exactly one run, yields only that run's events, and terminates when the run succeeds, fails, or aborts. Early consumer return aborts the owned run and releases the session. `SubscribeOptions.maxQueuedEvents` / `overflow` may be passed alongside `RunOptions`.
58
59
 
60
+ `resumeAgentRunStream(agent, ref, resume, options?)` does the same for one existing suspended durable run. It validates checkpoint ownership, revision/fingerprint, and `expectedVersion`, then subscribes before emitting `agent_started` / `agent_resumed` and resumed message/tool/terminal events. `AgentRunResumeStreamOptions` combines existing resume options with `signal`, `maxQueuedEvents`, and `overflow`; early return aborts only resumed execution. It does not replay a claimed/dispatched tool, poll a ledger, or retain a worker. `createAgentRunLifecycle().resumeStream(ref, resume, request?)` adds the same behavior after host agent-capability resolution.
61
+
59
62
  `session.subscribe(options?)` remains available for hosts that want a long-lived subscriber across runs. Subscribe before `run()` to observe that run's events. The consumer loop and `session.run()` must run concurrently (e.g. start the `for await` consumer, then `await Promise.all([consumer, session.run("Hi")])`): events are only emitted during a live run, so awaiting the subscribe loop before calling `run()` deadlocks. Prefer `session.stream()` when you only need one run's events. `SubscribeOptions.maxQueuedEvents` defaults to `1024` (minimum `1`) and caps events queued while the consumer is not awaiting `next()`. `SubscribeOptions.overflow` defaults to `"close"`; it clears queued payload events, delivers one `event_subscriber_overflow` notice to that subscriber, then closes it. `"drop_oldest"` keeps newest events; `"drop_newest"` ignores new events while full.
60
63
 
61
64
  For a text-only provider turn, the runtime emits:
@@ -184,7 +187,7 @@ if (result.status === "suspended") {
184
187
  }
185
188
  ```
186
189
 
187
- Resume requires exact checkpoint ownership, version, agent fingerprint, and revision. Prism CAS-claims approval before work, rechecks normal guardrail/permission/validation/limit paths, and marks a pending tool dispatched before its side effect. `createAgentRunLifecycle()` wraps the same core path for server/MCP hosts: adapters pass only authorized ownership, status returns only `{ state, version }`, and `resolveAgent()` supplies current agent/revision. Remote restart requires both checkpoint and session stores to be durable. A crash after that mark is ambiguous and is never replayed automatically; use host tool idempotency keyed by `runId`/`toolCallId` or resolve it manually. Checkpoints contain bounded redacted state plus session/leaf references, never provider objects, callbacks, signals, credentials, or raw secrets. Only built-in loop options are durable; custom `AgentLoopStrategy` rejects before provider work.
190
+ Resume requires exact checkpoint ownership, version, agent fingerprint, and revision. Prism CAS-claims approval before work, rechecks normal guardrail/permission/validation/limit paths, and marks a pending tool dispatched before its side effect. `createAgentRunLifecycle()` wraps the same core path for server/MCP hosts: adapters pass only authorized ownership, status returns only `{ state, version }`, and `resolveAgent()` supplies current agent/revision. `resumeStream()` uses that same claim path and bounded subscriber, so adapters do not poll or duplicate resume logic. Remote restart requires both checkpoint and session stores to be durable. A crash after that mark is ambiguous and is never replayed automatically; use host tool idempotency keyed by `runId`/`toolCallId` or resolve it manually. Checkpoints contain bounded redacted state plus session/leaf references, never provider objects, callbacks, signals, credentials, or raw secrets. Only built-in loop options are durable; custom `AgentLoopStrategy` rejects before provider work.
188
191
 
189
192
  ## Secure composition
190
193
 
@@ -209,6 +212,7 @@ Per-run options may narrow `limits` and append `guardrails`; they cannot replace
209
212
  - [CLI/RPC](cli-rpc.md): terminal and JSONL adapters over this runtime.
210
213
  - [Workflows](workflows.md): optional DAG orchestration that calls `AgentSession.run()` for agent nodes.
211
214
  - [A2A interoperability](a2a.md): direct text exposure calls `AgentSession.run()`; durable/rich/reconnect behavior uses host `A2ATaskLifecycle` over existing checkpoints/persistence, never an in-memory runtime cache.
215
+ - [Frontend interoperability (AG-UI and ACP)](ag-ui.md): optional adapters use `session.stream()` and `resumeAgentRunStream()` / `AgentRunLifecycle.resumeStream()`; protocol/UI state remains outside core.
212
216
 
213
217
  `AgentConfig.loop` and `RunOptions.loop` select a replaceable per-run control loop (`singleShotLoop` default, or `generate-validate-revise` with host callbacks); see [Agent loops](agent-loops.md). `RunOptions.loop` wins over `AgentConfig.loop`. Built-in loops emit the same normal turn/message envelope around provider turns, and both add the first run input to live history once after the first provider turn so later turns see the same transcript shape.
214
218
 
@@ -379,6 +379,7 @@ const remoteWrite = createWriteTool("/repo", {
379
379
 
380
380
  ## Extension and configuration notes
381
381
 
382
+ - **Long coding sessions.** Use `createCodingCompactionStrategy()` from optional `@arnilo/prism-compaction-llm` when history needs a bounded coding handoff. It is selected explicitly through normal `session.compact()` / agent compaction configuration, preserves raw session entries, and prioritizes file paths, patch intent, checks, plan/todo state, blockers, and verification steps. It does not read files, retain full diffs, or create a second coding runtime.
382
383
  - **Pluggable operation backends.** Every tool accepts an `operations` seam. Custom `ReadOperations` must implement bounded `readText` plus `statFile`; custom `EditOperations` must implement `statFile`; read/write methods receive caps/signals. `BashOperations` must stream through `onData` and honor `signal`/`timeout`. Custom `RepositoryOperations` must honor depth/entry/file/match/scan/time caps and abort. A hostile custom backend can still violate its host-owned contract, so isolate it separately.
383
384
  - **Per-tool options.** `ShellToolOptions` adds `timeout` and `maxTotalOutputBytes`; `ReadToolOptions` adds `maxScanBytes`; `WriteToolOptions` adds `maxInputBytes`; `EditToolOptions` adds `maxFileBytes`, `maxInputBytes`, and `maxEdits`; list/search accept `repository` limits and shared aggregator `ToolsOptions.repository`.
384
385
  - **Aggregator options.** `ToolsOptions` (`{ executionPolicy?, shell?, read?, write?, edit?, list?, search?, repository? }`) threads each sub-object to the matching tool. `createCodingTools()`, `createAllTools()`, and `createReadOnlyTools()` apply the shared policy unless that tool has an explicit per-tool override. Read-only membership is deliberately `read` + `repo_list` + `repo_search` (0.0.9 behavior change).
@@ -425,3 +426,4 @@ Every configurable value is a positive safe integer (context may be zero); Prism
425
426
  - [Public contracts](public-contracts.md): `ToolDefinition`, `ToolResult`, `ToolExecutionContext`, `ContentBlock`, and `JsonObject` shapes.
426
427
  - [Host security guide](host-security.md): fail-closed checklist for permission policies, tool validation, and trust boundaries that must gate these tools.
427
428
  - [Tool conformance](tool-conformance.md): assertions for the tool-dispatch blocked-reason matrix these tools participate in.
429
+ - [LLM compaction package](compaction-llm.md): optional `createCodingCompactionStrategy()` retains bounded paths, patch intent, checks, plan/todo state, blockers, and next verification—not complete diffs or raw command output.
@@ -149,7 +149,7 @@ Retry policies are ordinary `RetryPolicy` implementations and can be registered
149
149
 
150
150
  Compaction strategies are ordinary `CompactionStrategy` implementations. Extensions can register strategies through the existing compaction strategy contribution registry, but registration is inert until a host explicitly selects and passes a strategy to runtime code. Extensions can also register `compaction` middleware; the runtime calls it only when the agent/session has that middleware registry configured.
151
151
 
152
- The default strategy does not call a provider. Hosts that need model-generated summaries can use the optional [`@arnilo/prism-compaction-llm` package](compaction-llm.md); its `maxOutputTokens`/`maxSummaryTokens` budget is passed through `model.parameters.maxTokens` and first-party providers serialize that to provider output-token fields. Hosts that need prepared source-backed memory without a compaction-time model call can use [`@arnilo/prism-compaction-observational-memory`](compaction-observational-memory.md).
152
+ The default strategy does not call a provider. Hosts that need model-generated summaries can use the optional [`@arnilo/prism-compaction-llm` package](compaction-llm.md); its `maxOutputTokens`/`maxSummaryTokens` budget is passed through `model.parameters.maxTokens` and first-party providers serialize that to provider output-token fields. Coding sessions can select that package's `createCodingCompactionStrategy()` preset for paths, patch intent, checks, plans/todos, blockers, and next verification steps; it remains an ordinary `CompactionStrategy` and does not retain complete diffs or add a coding runtime. Hosts that need prepared source-backed memory without a compaction-time model call can use [`@arnilo/prism-compaction-observational-memory`](compaction-observational-memory.md).
153
153
 
154
154
  ## Security and performance notes
155
155
 
@@ -173,5 +173,6 @@ The default strategy does not call a provider. Hosts that need model-generated s
173
173
  - [Configuration and manifests](configuration-and-manifests.md): `compactionStrategy` and `retryPolicy` manifest contribution kinds.
174
174
  - [Provider layer](provider-layer.md): safe provider error codes used by retry classification.
175
175
  - [Credentials and redaction](credentials-and-redaction.md): exact secret redaction helper used by default compaction and retry error handling.
176
+ - [LLM compaction package](compaction-llm.md): `createCodingCompactionStrategy()` is the thin coding-focused preset; see `examples/coding-compaction.ts` for a network-free mock.
176
177
 
177
178
  Runtime redaction composes with compaction and retry secret lists: configured redactors apply at session serialization boundaries, while compaction/retry `secrets` still redact their local summaries and errors.
@@ -12,6 +12,7 @@ Key exports:
12
12
  | Export | Purpose |
13
13
  | --- | --- |
14
14
  | `createLlmCompactionStrategy(options)` | Returns a provider-backed `CompactionStrategy`. |
15
+ | `createCodingCompactionStrategy(options)` | Fixed `coding` preset over the LLM strategy: prioritizes file paths, patch intent, commands/checks, plan/todos, blockers, and next verification while retaining normal limits and raw history. |
15
16
  | `createLlmCompactionExtension(options)` | Registers the strategy into an explicit extension kernel compaction registry. |
16
17
  | `prepareLlmCompaction(context, options?)` | Splits branch entries into summary input, kept suffix, optional split-turn prefix, file details, and compaction data. |
17
18
  | `findLlmCompactionCutPoint(entries, options?)` | Finds the last entry covered by a summary using approximate token budgets. |
@@ -76,6 +77,23 @@ const strategy = createLlmCompactionStrategy({
76
77
  await session.compact({ strategy, secrets: [apiKey] });
77
78
  ```
78
79
 
80
+ Coding-session example:
81
+
82
+ ```ts
83
+ import { createCodingCompactionStrategy } from "@arnilo/prism-compaction-llm";
84
+
85
+ const strategy = createCodingCompactionStrategy({
86
+ provider: summaryProvider,
87
+ summaryModel: { provider: "openai", model: "gpt-4.1-mini" },
88
+ keepRecentTokens: 20_000,
89
+ maxSummaryTokens: 800,
90
+ customInstructions: "Keep migration blockers prominent.",
91
+ });
92
+ await session.compact({ strategy });
93
+ ```
94
+
95
+ The preset always uses strategy name `coding` and enables existing read/modified-file retention. It adds no provider call, parser, worker, filesystem access, or complete-diff retention beyond `createLlmCompactionStrategy()`.
96
+
79
97
  Credential factory example:
80
98
 
81
99
  ```ts
@@ -108,7 +126,7 @@ Preparation is O(n) over branch entries and uses only arrays, strings, and JSON
108
126
 
109
127
  Provider deltas are redacted while retained and stop at `maxSummaryTokens * 4` UTF-16 code units without splitting a surrogate pair. Provider iteration is closed/aborted on overflow. A derived finite event ceiling also stops endless empty/non-text deltas. Final history/turn/file composition receives the same cap. Provider error events, generator throws, provider-factory failures, and policy failures expose only bounded redacted detail; host abort remains authoritative.
110
128
 
111
- The strategy makes only the needed provider call(s): one history summary plus one split-turn prefix summary when needed. It does not discover credentials, read files, start background jobs, or add provider SDK dependencies. Redaction is exact-string only; pass every known secret that may appear in history or provider output.
129
+ The strategy makes only the needed provider call(s): one history summary plus one split-turn prefix summary when needed. The coding preset makes the same calls and uses the same bounded file-operation preparation. Neither discovers credentials, reads files, starts background jobs, or adds provider SDK dependencies. Redaction is exact-string only; pass every known secret that may appear in history, paths, instructions, or provider output.
112
130
 
113
131
  ## Related APIs
114
132
 
@@ -119,3 +137,4 @@ The strategy makes only the needed provider call(s): one history summary plus on
119
137
  - [Agent/session runtime](agent-session-runtime.md): `AgentSession.compact()` and opt-in auto-compaction.
120
138
  - [Provider layer](provider-layer.md): mock providers and provider request contracts.
121
139
  - [Credentials and redaction](credentials-and-redaction.md): exact known-secret redaction behavior.
140
+ - [Frontend interoperability (AG-UI and ACP)](ag-ui.md): separate optional frontend transport; coding compaction adds no UI protocol dependency.
@@ -205,7 +205,8 @@ const providers = createOpenAIProviderPackage({ apiKey });
205
205
  - Use distinct `namespace` or vault paths per tenant/environment.
206
206
  - Keychain `list()` / `listOAuth()` are intentionally unsupported — enumerate credentials through host configuration instead of scanning the OS store.
207
207
  - Combine with `createExplicitCredentialResolver()` so runtime overrides still win over stored values.
208
- - Wire `createOAuthCredentialStoreAdapter(store)` into `refreshOAuthCredential()` so refreshed tokens persist durably.
208
+ - Wire `createOAuthCredentialStoreAdapter(store)` into `refreshOAuthCredential()` only for an OAuth flow explicitly selected by the host and authorized by that provider. In 0.0.12 that means OpenAI Codex; Anthropic and Google packages accept API keys only. Never import or migrate Claude Code/Gemini CLI credential files, setup tokens, browser sessions, or CLI OAuth rows into this store.
209
+ - Enterprise cloud providers (`azure` / `bedrock` / `vertex`) expect host workload-identity callbacks (Entra / IAM / ADC), not this local encrypted/keychain store as a cloud token minting service. Store may hold opaque refresh material only when the host already owns the cloud auth flow.
209
210
 
210
211
  ## Security and performance notes
211
212
 
@@ -216,6 +217,8 @@ const providers = createOpenAIProviderPackage({ apiKey });
216
217
  - Keychain operations use `@napi-rs/keyring`'s abort-aware `AsyncEntry`, so native work runs outside the JavaScript event loop. A main-loop timer aborts and rejects at `timeoutMs`; native cancellation remains OS/backend-dependent and may briefly retain one libuv worker after rejection.
217
218
  - Keychain payloads are bytes rather than password strings and are zeroed after parse/write. Unknown native errors are mapped to sanitized typed errors; no native message or secret value is echoed.
218
219
  - Never log passphrases, derived keys, or decrypted credential payloads.
220
+ - Optional host KMS: `encryptWithHostKms` / `decryptWithHostKms` wrap a random AES-256-GCM DEK via host `HostKms.wrapKey`/`unwrapKey` (timeout ≤ 60 s). Envelope sizes reuse vault/file caps. Keys are never logged. `createMemoryHostKms` is for tests only.
221
+ - Storage is not OAuth eligibility. A durable store may persist credentials for a provider only after the host selects a provider-authorized flow; it must not be used to piggyback on a vendor CLI or consumer subscription.
219
222
  - Live keychain tests are opt-in (`PRISM_TEST_KEYCHAIN=1`); default `npm test` stays offline.
220
223
 
221
224
  ## MCP authentication boundary
@@ -229,6 +232,7 @@ MCP credentials remain host inputs: resolve them before constructing client `req
229
232
  ## Related APIs
230
233
 
231
234
  - [Credentials and redaction](credentials-and-redaction.md): core resolver helpers and `refreshOAuthCredential()`
235
+ - [Azure OpenAI / Foundry](providers/azure.md) / [Amazon Bedrock](providers/bedrock.md) / [Google Vertex AI](providers/vertex.md): enterprise workload-identity credential callbacks
232
236
  - [Web search, fetch, and extraction](web-tools.md): late-bound Brave/Exa/Firecrawl credentials
233
237
  - [Security/auth/trust](settings-auth-trust-security.md): host-owned settings/credentials boundaries
234
238
  - [Persistence, credentials, and multimodality primitives](persistence-credentials-multimodality-primitives.md): Plan 056 threat model and conformance matrix rows 7–10