@arnilo/prism 0.0.1 → 0.0.3
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 +19 -2
- package/README.md +17 -7
- package/dist/agent-definitions.d.ts +12 -0
- package/dist/agent-definitions.js +131 -0
- package/dist/agent-loops.d.ts +14 -0
- package/dist/agent-loops.js +161 -0
- package/dist/agents.js +263 -76
- package/dist/cache-helpers.d.ts +28 -0
- package/dist/cache-helpers.js +73 -0
- package/dist/cli-runner.d.ts +38 -2
- package/dist/cli-runner.js +167 -5
- package/dist/compaction.js +2 -0
- package/dist/config.js +47 -12
- package/dist/contracts.d.ts +581 -6
- package/dist/contracts.js +41 -1
- package/dist/contribution-parsing.d.ts +19 -0
- package/dist/contribution-parsing.js +124 -0
- package/dist/contributions.d.ts +13 -3
- package/dist/contributions.js +96 -20
- package/dist/extensions.js +3 -0
- package/dist/index.d.ts +19 -9
- package/dist/index.js +10 -4
- package/dist/input.d.ts +7 -1
- package/dist/input.js +52 -11
- package/dist/instruction-injection.d.ts +28 -0
- package/dist/instruction-injection.js +55 -0
- package/dist/manifests.d.ts +1 -1
- package/dist/manifests.js +3 -3
- package/dist/models.d.ts +4 -1
- package/dist/models.js +5 -2
- package/dist/node/agent-definitions.d.ts +98 -0
- package/dist/node/agent-definitions.js +389 -0
- package/dist/node/contribution-discovery.d.ts +17 -0
- package/dist/node/contribution-discovery.js +163 -0
- package/dist/node/instruction-injectors.d.ts +32 -0
- package/dist/node/instruction-injectors.js +72 -0
- package/dist/node/session-store-jsonl.d.ts +1 -1
- package/dist/node/session-store-jsonl.js +42 -4
- package/dist/node/system-project-prompts.d.ts +30 -0
- package/dist/node/system-project-prompts.js +53 -0
- package/dist/provider-events.d.ts +3 -1
- package/dist/provider-events.js +34 -0
- package/dist/provider-request-policy.js +15 -1
- package/dist/providers/openai-compatible.js +1 -1
- package/dist/providers.d.ts +6 -2
- package/dist/providers.js +15 -1
- package/dist/redaction.d.ts +2 -1
- package/dist/redaction.js +3 -0
- package/dist/registry-options.d.ts +5 -0
- package/dist/registry-options.js +5 -0
- package/dist/rpc.d.ts +6 -2
- package/dist/rpc.js +71 -13
- package/dist/session-stores.d.ts +3 -1
- package/dist/session-stores.js +67 -6
- package/dist/skills.d.ts +4 -1
- package/dist/skills.js +3 -1
- package/dist/system-prompts.js +6 -2
- package/dist/testing/compaction-conformance.d.ts +17 -0
- package/dist/testing/compaction-conformance.js +61 -0
- package/dist/testing/extension-conformance.d.ts +26 -0
- package/dist/testing/extension-conformance.js +55 -0
- package/dist/testing/provider-conformance.d.ts +7 -0
- package/dist/testing/provider-conformance.js +18 -31
- package/dist/testing/session-store-conformance.d.ts +20 -0
- package/dist/testing/session-store-conformance.js +92 -0
- package/dist/testing/tool-conformance.d.ts +39 -0
- package/dist/testing/tool-conformance.js +79 -0
- package/dist/tools.d.ts +7 -2
- package/dist/tools.js +50 -13
- package/docs/agent-definitions.md +251 -0
- package/docs/agent-events.md +199 -0
- package/docs/agent-loops.md +217 -0
- package/docs/agent-session-runtime.md +20 -8
- package/docs/cli-rpc.md +39 -4
- package/docs/coding-agent-tools.md +208 -0
- package/docs/compaction-and-retry.md +2 -2
- package/docs/compaction-conformance.md +76 -0
- package/docs/compaction-llm.md +6 -3
- package/docs/compaction-observational-memory.md +4 -4
- package/docs/configuration-and-manifests.md +6 -1
- package/docs/context-and-skills.md +79 -6
- package/docs/contribution-discovery.md +149 -0
- package/docs/contribution-registries.md +9 -6
- package/docs/credentials-and-redaction.md +2 -0
- package/docs/customization.md +191 -0
- package/docs/database-persistence.md +407 -0
- package/docs/extension-authoring.md +193 -0
- package/docs/extension-conformance.md +80 -0
- package/docs/extensions.md +6 -0
- package/docs/host-security.md +141 -0
- package/docs/index.md +41 -19
- package/docs/input-and-prompt-assembly.md +19 -3
- package/docs/instruction-injection.md +183 -0
- package/docs/migration.md +201 -0
- package/docs/model-registry.md +122 -0
- package/docs/node-jsonl-session-store.md +5 -4
- package/docs/performance.md +127 -0
- package/docs/provider-caching.md +206 -0
- package/docs/provider-conformance.md +32 -5
- package/docs/provider-layer.md +51 -11
- package/docs/provider-packages.md +65 -5
- package/docs/provider-request-policies.md +113 -0
- package/docs/providers/kimi.md +22 -0
- package/docs/providers/neuralwatt.md +388 -0
- package/docs/providers/openai-compatible.md +1 -0
- package/docs/providers/openai.md +21 -0
- package/docs/providers/opencode-go.md +31 -3
- package/docs/providers/openrouter.md +29 -0
- package/docs/providers/zai.md +17 -0
- package/docs/public-contracts.md +87 -12
- package/docs/release-and-install.md +79 -27
- package/docs/runs-and-usage.md +236 -0
- package/docs/session-store-conformance.md +78 -0
- package/docs/session-stores-and-branching.md +10 -6
- package/docs/session-stores.md +126 -0
- package/docs/settings-auth-trust-security.md +18 -4
- package/docs/structured-output.md +247 -0
- package/docs/system-prompts.md +104 -2
- package/docs/tool-conformance.md +87 -0
- package/docs/tools.md +65 -8
- package/package.json +36 -2
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
# Contribution discovery (workspace)
|
|
2
|
+
|
|
3
|
+
## What it does
|
|
4
|
+
|
|
5
|
+
Discovery scans the filesystem for workspace contribution directories and turns their on-disk files into inert `DiscoveredContribution` envelopes the host then registers. It reads text only — it never `import()`s, `require()`s, or otherwise executes contribution code, and it never grants tools, permissions, credentials, or provider slots.
|
|
6
|
+
|
|
7
|
+
APIs (Node subpath `@arnilo/prism/node/contribution-discovery`, parsers and registrar on the main barrel):
|
|
8
|
+
|
|
9
|
+
- `discoverContributions(options)` / `DiscoveryOptions`: directory-walking scanner for the workspace `.agents/` tree. One `readdir` per kind-root.
|
|
10
|
+
- `parseSkillFile(text, path)`: stdlib-only frontmatter parser for `SKILL.md` (no YAML dependency, no `node:*` import). Re-exported from `@arnilo/prism`.
|
|
11
|
+
- `registerDiscoveredContributions(registries, contributions)`: registers realized `Skill` objects for the `skill` kind and descriptor-only stubs for other kinds. Re-exported from `@arnilo/prism`.
|
|
12
|
+
- `ContributionFileKind`, `DiscoveredContribution`: contract types re-exported from `@arnilo/prism`.
|
|
13
|
+
- `createPathTrustPolicy` / `isPathInsideReal` (from `@arnilo/prism/node/trust`): realpath-resolved, fail-closed containment used to gate workspace roots.
|
|
14
|
+
|
|
15
|
+
## When to use it
|
|
16
|
+
|
|
17
|
+
Use discovery when a host or the `prism` CLI wants to honor a project-local contribution layout without wiring every contribution by hand. Typical hosts pass `--discover` on the CLI or call `discoverContributions()` on startup, then feed the result through `registerDiscoveredContributions()` into their existing `ContributionRegistries`.
|
|
18
|
+
|
|
19
|
+
Do not use it to discover providers — provider/model packages stay config/package-driven (Phase 24; see [Provider packages](provider-packages.md)). Do not use it to auto-activate anything: discovery registers, never activates. Skills become selectable only when a run passes `RunOptions.activeSkills`, and `toolNames` is still enforced against the resolved tool set at activation time.
|
|
20
|
+
|
|
21
|
+
## Inputs / request
|
|
22
|
+
|
|
23
|
+
### Directory layout
|
|
24
|
+
|
|
25
|
+
| Origin | Path | Scanned by |
|
|
26
|
+
| --- | --- | --- |
|
|
27
|
+
| Workspace | `<workspace>/.agents/{skills,tools,context,instructions}/<name>/` | `workspaceRoot`, gated by `trust` |
|
|
28
|
+
|
|
29
|
+
### Per-kind entry file
|
|
30
|
+
|
|
31
|
+
| Kind | Entry file | Parsed into |
|
|
32
|
+
| --- | --- | --- |
|
|
33
|
+
| `skill` | `<dir>/SKILL.md` | A realized `Skill` (placed in `DiscoveredContribution.skill`) |
|
|
34
|
+
| `tool` / `context` / `instructions` | `<dir>/manifest.json` | A `ManifestContributionDeclaration` |
|
|
35
|
+
|
|
36
|
+
### Frontmatter keys
|
|
37
|
+
|
|
38
|
+
`SKILL.md` frontmatter (YAML-like, parsed by a stdlib-only parser — no YAML dependency):
|
|
39
|
+
|
|
40
|
+
| Key | Meaning |
|
|
41
|
+
| --- | --- |
|
|
42
|
+
| `name` | Required if you want an explicit name; otherwise the parent directory name is used. |
|
|
43
|
+
| `description` | Optional skill description. |
|
|
44
|
+
| `toolNames` | Optional comma list (`[a, b]`) or YAML block list (`- a`). Enforced at activation, not at discovery. |
|
|
45
|
+
|
|
46
|
+
The markdown body below the front fence becomes `Skill.instructions`. Unknown frontmatter keys are tolerated and collected into `Skill.metadata` (never fatal).
|
|
47
|
+
|
|
48
|
+
`manifest.json` entries follow the [`ManifestContributionDeclaration`](configuration-and-manifests.md) shape: `kind`, `name`, and optional `module`/`exportName`/`resource`/`metadata`.
|
|
49
|
+
|
|
50
|
+
### `DiscoveryOptions`
|
|
51
|
+
|
|
52
|
+
| Field | Meaning |
|
|
53
|
+
| --- | --- |
|
|
54
|
+
| `kinds` | Which `ContributionFileKind` values to scan (`skill`, `tool`, `context`, `instructions`). |
|
|
55
|
+
| `workspaceRoot` | Workspace root. Gated by `trust`; untrusted roots are skipped silently. |
|
|
56
|
+
| `permission?` | `PermissionPolicy` asserting each directory read (`assertPermission`). |
|
|
57
|
+
| `trust?` | `TrustPolicy` (e.g. `createPathTrustPolicy`) fail-closed against the workspace root. |
|
|
58
|
+
|
|
59
|
+
### CLI flags
|
|
60
|
+
|
|
61
|
+
| Flag | Meaning |
|
|
62
|
+
| --- | --- |
|
|
63
|
+
| `--discover` | Enable workspace contribution discovery. Opt-in; default runs never touch the filesystem. |
|
|
64
|
+
| `--discover-kinds <csv>` | Kinds to scan. Defaults to `skill`. Accepts `skill,tool,context,instructions`. |
|
|
65
|
+
| `--no-discovery` | Hard-disable discovery even if `--discover` is set. |
|
|
66
|
+
|
|
67
|
+
## Outputs / response / events
|
|
68
|
+
|
|
69
|
+
`discoverContributions()` returns `readonly DiscoveredContribution[]`. Each envelope has `kind`, `name`, `origin` (`"global"` | `"workspace"`), `path`, and either `skill` (for the `skill` kind) or `declaration` (a `ManifestContributionDeclaration` for other kinds), plus optional `metadata`. The envelope is inert: it contains no executable code, no credential, and no resolved provider/model/tool reference.
|
|
70
|
+
|
|
71
|
+
`registerDiscoveredContributions(registries, contributions)` writes into the existing `ContributionRegistries`: `skills` get full `Skill` objects; `tool`/`context` get descriptor-only stubs whose `execute`/`resolve` throw (executable behavior is host-owned); `instructions` get descriptors with empty `text` (Phase 30 lifts `declaration.resource` into actual text). The host retains the original `DiscoveredContribution[]` for provenance — tool and context descriptors carry no `metadata.discovered` slot because `ToolDefinition` and `ContextProvider` have no metadata field. Phase 30 adds a host-owned `loadInstructionInjector` adapter (see [Instruction injection](instruction-injection.md)) that turns a discovered `kind: "instructions"` contribution into a live `InstructionInjector` — markdown-only → static `every_turn` injector; module-referenced → resolved through a host-supplied `moduleLoader` (core never auto-`import()`s).
|
|
72
|
+
|
|
73
|
+
## Request/response example
|
|
74
|
+
|
|
75
|
+
Discovering a workspace skill:
|
|
76
|
+
|
|
77
|
+
```json
|
|
78
|
+
[
|
|
79
|
+
{ "kind": "skill", "name": "greeter", "origin": "workspace", "path": "/proj/.agents/skills/greeter/SKILL.md", "skill": { "name": "greeter", "description": "g", "instructions": "..." } }
|
|
80
|
+
]
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## Implementation example
|
|
84
|
+
|
|
85
|
+
```ts
|
|
86
|
+
import {
|
|
87
|
+
createContributionRegistries,
|
|
88
|
+
registerDiscoveredContributions,
|
|
89
|
+
createSkillRegistry,
|
|
90
|
+
createAgent,
|
|
91
|
+
createMockProvider,
|
|
92
|
+
providerDone,
|
|
93
|
+
} from "@arnilo/prism";
|
|
94
|
+
import { discoverContributions } from "@arnilo/prism/node/contribution-discovery";
|
|
95
|
+
import { createPathTrustPolicy } from "@arnilo/prism/node/trust";
|
|
96
|
+
|
|
97
|
+
// 1. Discover — workspace gated by trust.
|
|
98
|
+
const trust = createPathTrustPolicy({ trustedRoots: [workspaceRoot] });
|
|
99
|
+
const discovered = await discoverContributions({
|
|
100
|
+
kinds: ["skill"],
|
|
101
|
+
workspaceRoot,
|
|
102
|
+
trust,
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
// 2. Register — skills become full Skill objects; other kinds register as stubs.
|
|
106
|
+
// Discovery never imports or executes contribution code.
|
|
107
|
+
const registries = createContributionRegistries();
|
|
108
|
+
registerDiscoveredContributions(registries, discovered);
|
|
109
|
+
|
|
110
|
+
// 3. Run — discovery did NOT activate anything. The run explicitly selects skills.
|
|
111
|
+
const session = createAgent({
|
|
112
|
+
model: { provider: "mock", model: "demo" },
|
|
113
|
+
provider: createMockProvider([providerDone()]),
|
|
114
|
+
skills: createSkillRegistry(registries.skills.list()),
|
|
115
|
+
}).createSession();
|
|
116
|
+
|
|
117
|
+
await session.run("Hi", { activeSkills: ["greeter"] });
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
A complete runnable example lives at `examples/discover-skills.ts`.
|
|
121
|
+
|
|
122
|
+
## Extension and configuration notes
|
|
123
|
+
|
|
124
|
+
- Discovery is loader-driven, not module-driven. The on-disk contribution is a data file (`SKILL.md`/`manifest.json`); the host decides whether, when, and how to register and activate it. There is no `import()` of untrusted modules.
|
|
125
|
+
- `ContributionFileKind` is the discovery-side kind name (`skill`/`tool`/`context`/`instructions`). The manifest-side kind is `ManifestContributionKind` ([Configuration and manifests](configuration-and-manifests.md)); the node scanner maps between them.
|
|
126
|
+
- The `--discover-kinds` CSV lets a host scan a subset. The CLI default is `skill`; other kinds register descriptor stubs only (host-owned execution).
|
|
127
|
+
- First-party skills are expected to ship as installable packages (separate request); Phase 29 ships discovery infrastructure only and adds no first-party skill package.
|
|
128
|
+
|
|
129
|
+
## Security and performance notes
|
|
130
|
+
|
|
131
|
+
- **Workspace gating**: workspace roots are checked through `createPathTrustPolicy` + `isPathInsideReal`, which resolve symlinks and fail closed (return false) if either root or target cannot be resolved. Untrusted workspace roots are skipped silently, never thrown over. Permission is asserted per directory read via `assertPermission`.
|
|
132
|
+
- **Symlink handling**: symlinked entries that escape the kind root after realpath resolution are excluded. `SKILL.md` and `manifest.json` are also realpath-checked against their contribution directory before read, so an entry-file symlink cannot escape to another path.
|
|
133
|
+
- **Opt-in**: discovery is opt-in — it runs only when the host passes `--discover` or calls `discoverContributions()` explicitly. Default runs perform no filesystem I/O.
|
|
134
|
+
- **No auto-execute**: discovery reads text. It does not `import()`, `require()`, or run contribution code. `registerDiscoveredContributions` registers descriptor stubs whose execution methods throw — the host lifts them into live tools/providers itself.
|
|
135
|
+
- **No auto-activate**: discovery registers skills; it does not select them. Activation requires explicit `RunOptions.activeSkills`, and `toolNames` is still validated against the resolved tool set. Discovery grants no tools, permissions, or provider slots.
|
|
136
|
+
- **No provider scanning**: provider/model discovery stays config/package-driven (Phase 24). See [Provider packages](provider-packages.md).
|
|
137
|
+
- **AGENTS.md / SYSTEM.md are not discovery kinds**: the root-level `AGENTS.md` (workspace) prompt file does not fit the named-subdir scanner and is loaded by a sibling Node loader, `loadSystemPromptFiles` from `@arnilo/prism/node/system-prompts`. See [System prompts](system-prompts.md). The CLI auto-loads `AGENTS.md` in print/json modes; RPC mode does not (host-owned).
|
|
138
|
+
- **Secrets**: the secrets-redaction path is unaffected — discovery reads contribution files, not provider request content. Skill `instructions` flow through normal input assembly and the same redaction pipeline as any system message.
|
|
139
|
+
- **Performance**: one `readdir` per kind-root per origin; missing kind directories are normal (ENOENT-tolerant). Default runs perform no discovery I/O at all. The merged output is inert.
|
|
140
|
+
|
|
141
|
+
## Related APIs
|
|
142
|
+
|
|
143
|
+
- [Contribution registries](contribution-registries.md): where discovered envelopes are registered.
|
|
144
|
+
- [Context and skills](context-and-skills.md): `createSkillRegistry` / `resolveActiveSkills` and the `RunOptions.activeSkills` activation that consumes discovered skills.
|
|
145
|
+
- [Extensions](extensions.md): host-provided extensions still register contributions programmatically; discovery is the filesystem-driven complement.
|
|
146
|
+
- [Configuration and manifests](configuration-and-manifests.md): `ManifestContributionDeclaration` / `ManifestContributionKind` shapes used by non-skill entries.
|
|
147
|
+
- [CLI/RPC](cli-rpc.md): the `--discover`, `--discover-kinds`, and `--no-discovery` flags.
|
|
148
|
+
- [System prompts](system-prompts.md): `loadSystemPromptFiles` loads root-level `AGENTS.md` / `SYSTEM.md` — a sibling loader, not a scanner kind.
|
|
149
|
+
- [Security/auth/trust](settings-auth-trust-security.md): `createPathTrustPolicy`, `assertPermission`, and the trust model.
|
|
@@ -18,15 +18,15 @@ Do not use them as a dependency injection container, manifest loader, settings l
|
|
|
18
18
|
## Inputs / request
|
|
19
19
|
|
|
20
20
|
```ts
|
|
21
|
-
createContributionRegistry<T>(options?: { label?: string }): ContributionRegistry<T>
|
|
22
|
-
createContributionRegistries(): ContributionRegistries
|
|
21
|
+
createContributionRegistry<T>(options?: { label?: string; duplicate?: "replace" | "error" }): ContributionRegistry<T>
|
|
22
|
+
createContributionRegistries(options?: { duplicate?: "replace" | "error" }): ContributionRegistries
|
|
23
23
|
```
|
|
24
24
|
|
|
25
25
|
`ContributionRegistry<T>` methods:
|
|
26
26
|
|
|
27
27
|
| Method | Input | Result |
|
|
28
28
|
| --- | --- | --- |
|
|
29
|
-
| `register(key, contribution)` | string key and contribution | Stores
|
|
29
|
+
| `register(key, contribution)` | string key and contribution | Stores/replaces the contribution for that key; throws `Duplicate <label>: <key>` when `duplicate: "error"`. |
|
|
30
30
|
| `get(key)` | string key | Returns the contribution or `undefined`. |
|
|
31
31
|
| `resolve(key)` | string key | Returns the contribution or throws `Unknown <label>: <key>`. |
|
|
32
32
|
| `list()` | none | Returns contributions in insertion order. |
|
|
@@ -37,7 +37,7 @@ createContributionRegistries(): ContributionRegistries
|
|
|
37
37
|
|
|
38
38
|
Registry calls return plain contribution objects. Unknown `resolve()` calls throw before provider, model, tool, credential, prompt, resource, or session behavior can run. `systemPromptContributions` are inert until a host passes selected values to `AgentConfig.systemPrompt` or `RunOptions.systemPrompt`.
|
|
39
39
|
|
|
40
|
-
Registering the same key replaces the contribution deterministically. Registries do not emit events by themselves; the extension kernel may emit events when it uses them.
|
|
40
|
+
Registering the same key replaces the contribution deterministically by default. Passing `duplicate: "error"` adds one `Map.has()` check before `set()` and throws `Duplicate <label>: <key>` instead of silently shadowing. Registries do not emit events by themselves; the extension kernel may emit events when it uses them.
|
|
41
41
|
|
|
42
42
|
## Request/response example
|
|
43
43
|
|
|
@@ -60,7 +60,7 @@ const tool: ToolDefinition = {
|
|
|
60
60
|
},
|
|
61
61
|
};
|
|
62
62
|
|
|
63
|
-
const registries = createContributionRegistries();
|
|
63
|
+
const registries = createContributionRegistries({ duplicate: "error" });
|
|
64
64
|
registries.tools.register(tool.name, tool);
|
|
65
65
|
registries.agents.register("demo", {
|
|
66
66
|
name: "demo",
|
|
@@ -87,6 +87,8 @@ void skill;
|
|
|
87
87
|
- Hosts can use contribution registries directly and skip extension loading entirely.
|
|
88
88
|
- Extension packages should register contributions through the host-provided extension API once the extension kernel is in use.
|
|
89
89
|
- Registry keys are explicit strings. Prefer stable ids/names such as `provider.id`, `tool.name`, `skill.name`, or package-qualified names when collisions matter.
|
|
90
|
+
- Default duplicate policy is `"replace"` for compatibility and deterministic last-write-wins behavior. External apps that load third-party contributions should prefer `duplicate: "error"` to prevent silent shadowing.
|
|
91
|
+
- Migration safety: when moving from legacy all-in-scope capability activation to named `AgentDefinition.tools` / `skills`, enable strict registries first so duplicate third-party names fail during registration instead of changing which capability a name resolves to.
|
|
90
92
|
- Manifest contribution `kind` values match these registry keys. For example, `authMethods` accepts `authMethod` manifest declarations, `providerPackages` accepts `providerPackage`, `providerRequestPolicies` accepts `providerRequestPolicy`, and `systemPromptContributions` accepts `systemPromptContribution`.
|
|
91
93
|
- Manifest and configuration loading are separate APIs; this page only covers in-memory registration.
|
|
92
94
|
- Tool contributions are inert. They are not executable until the host registers selected definitions in an active tool registry and passes that registry to `dispatchToolCall()`.
|
|
@@ -96,7 +98,7 @@ void skill;
|
|
|
96
98
|
|
|
97
99
|
## Security and performance notes
|
|
98
100
|
|
|
99
|
-
- Generic registries are `Map`-backed with O(1) lookup.
|
|
101
|
+
- Generic registries are `Map`-backed with O(1) lookup; strict duplicate checks add one O(1) `Map.has()` during registration only.
|
|
100
102
|
- Registries are explicit objects returned by factories. Prism does not create hidden global contribution registries.
|
|
101
103
|
- Registries must not store resolved credential values, tokens, headers, or secret-bearing settings.
|
|
102
104
|
- `credentialResolvers` may store resolver objects, but resolved credentials must stay at the edge that needs them.
|
|
@@ -116,3 +118,4 @@ void skill;
|
|
|
116
118
|
- [Compaction and retry policies](compaction-and-retry.md): selected compaction strategy and retry policy behavior.
|
|
117
119
|
- [Public contracts](public-contracts.md): contribution contract types stored in these registries.
|
|
118
120
|
- [Credentials and redaction](credentials-and-redaction.md): credential resolver and secret-redaction rules.
|
|
121
|
+
- [Contribution discovery (workspace)](contribution-discovery.md): opt-in directory scanner that fills these registries from `SKILL.md`/`manifest.json`.
|
|
@@ -95,6 +95,7 @@ console.log(error.message);
|
|
|
95
95
|
## Extension and configuration notes
|
|
96
96
|
|
|
97
97
|
- Hosts and extension packages can implement `CredentialResolver` and pass it explicitly to code that needs credentials.
|
|
98
|
+
- `AgentConfig.credentials` is host-owned metadata for compatibility; `createAgent()` / `session.run()` do not call `credentials.resolve()`. Provider adapters, compaction workers, or request policies should receive and resolve credentials at the provider edge.
|
|
98
99
|
- Use `createExplicitCredentialResolver()` when documenting a fixed order such as runtime override, stored credential, caller-provided env object, then fallback resolver.
|
|
99
100
|
- Use `createEnvCredentialResolver()` only with an object supplied by the host; Prism does not read `process.env` for you.
|
|
100
101
|
- Provider adapters should resolve credentials as late as possible, per request.
|
|
@@ -109,6 +110,7 @@ console.log(error.message);
|
|
|
109
110
|
- Cycle and non-JSON value handling: `redactSecrets()` is cycle-safe via a `WeakSet` visited-set. Self-referential or mutually referenced objects render `"[Circular]"` at the back-reference instead of throwing. `Date` and `RegExp` values are passed through unchanged; `ArrayBuffer` and typed arrays are passed through unchanged; `Map` is normalized to a plain object and `Set` to an array so the output stays JSON-compatible. `errorToErrorInfo()` tolerates a cyclic `error.cause` (rendered via `String()`).
|
|
110
111
|
- Use placeholders in tests and docs. Never commit real tokens.
|
|
111
112
|
- Live provider/worker tests are gated behind explicit environment variables and skipped by default: `PRISM_LIVE_PROVIDER_TESTS`, `PRISM_LIVE_COMPACTION_TESTS`, `PRISM_LIVE_OBSERVATIONAL_MEMORY_TESTS`. Default `npm test` is network-free; do not add ungated network calls to default tests.
|
|
113
|
+
- `AgentConfig.credentials` is not eagerly resolved, serialized into provider requests/events/stores, or passed to loops/compaction by the core runtime.
|
|
112
114
|
- `resolveCredentialValue()` and `createExplicitCredentialResolver()` do not cache values. Add host-side caching only if a real credential source needs it.
|
|
113
115
|
- `refreshOAuthCredential()` only calls the supplied OAuth provider and optional store; it has no built-in persistence or retry loop.
|
|
114
116
|
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
# SDK customization guide
|
|
2
|
+
|
|
3
|
+
## What it does
|
|
4
|
+
|
|
5
|
+
This guide maps every supported Prism customization seam to the existing public API. Use it when an embedding app wants to replace provider resolution, middleware, context, input/prompt builders, instruction injectors, agent loops, compaction, retry, session stores, or skill selection without forking the runtime.
|
|
6
|
+
|
|
7
|
+
Prism customization is explicit wiring. There is no hidden global middleware, package auto-activation, provider discovery, tool grant, or background runtime.
|
|
8
|
+
|
|
9
|
+
## When to use it
|
|
10
|
+
|
|
11
|
+
Use this guide when the default agent/session runtime is close enough, but your host app needs one or more custom policies:
|
|
12
|
+
|
|
13
|
+
- route models to providers dynamically
|
|
14
|
+
- add middleware at documented runtime boundaries
|
|
15
|
+
- resolve host context or selected skills
|
|
16
|
+
- replace input/prompt assembly
|
|
17
|
+
- add inert instruction injectors
|
|
18
|
+
- choose a built-in or custom agent loop
|
|
19
|
+
- configure compaction/retry strategies
|
|
20
|
+
- use a durable session store
|
|
21
|
+
|
|
22
|
+
Do not use customization hooks as a sandbox, permission system, credential manager, package loader, workflow engine, vector memory, or hidden tool activator. Host policy and activation stay outside the hook.
|
|
23
|
+
|
|
24
|
+
## Inputs / request
|
|
25
|
+
|
|
26
|
+
Most seams are fields on `AgentConfig` or per-run `RunOptions`. Per-run options win where both exist.
|
|
27
|
+
|
|
28
|
+
| Customize | Entry point | Detailed page |
|
|
29
|
+
| --- | --- | --- |
|
|
30
|
+
| Provider resolution | `provider`, `providerSource`, `createProviderResolver()` | [Provider layer](provider-layer.md) |
|
|
31
|
+
| Middleware | `middleware`, `createMiddlewareRegistry()` | [Middleware hooks](middleware-hooks.md) |
|
|
32
|
+
| Context | `context`, `resolveContextProviders()` | [Context and skills](context-and-skills.md) |
|
|
33
|
+
| Skills | `skills`, `activeSkills`, `createSkillRegistry()`, `resolveActiveSkills()` | [Context and skills](context-and-skills.md) |
|
|
34
|
+
| Input builder | `inputBuilder`, `createDefaultInputBuilder()` | [Input and prompt assembly](input-and-prompt-assembly.md) |
|
|
35
|
+
| Prompt builder | `promptBuilder`, `createDefaultPromptBuilder()` | [Input and prompt assembly](input-and-prompt-assembly.md) |
|
|
36
|
+
| Instruction injectors | `instructionInjectors`, `resolveInstructionInjectors()` | [Instruction injection](instruction-injection.md) |
|
|
37
|
+
| Agent loops | `loop`, `singleShotLoop`, `generateValidateReviseLoop()` | [Agent loops](agent-loops.md) |
|
|
38
|
+
| Compaction | `compaction`, `createDefaultCompactionStrategy()` | [Compaction and retry](compaction-and-retry.md) |
|
|
39
|
+
| Retry | `retry`, `createDefaultRetryPolicy()` | [Compaction and retry](compaction-and-retry.md) |
|
|
40
|
+
| Session store | `store`, `SessionStore`, `createMemorySessionStore()` | [Session stores](session-stores.md) |
|
|
41
|
+
|
|
42
|
+
## Outputs / response / events
|
|
43
|
+
|
|
44
|
+
Customization changes what the existing runtime calls. It does not create new runtime phases.
|
|
45
|
+
|
|
46
|
+
- provider resolution happens once per run before any provider turn
|
|
47
|
+
- input/prompt builders run during provider-request assembly
|
|
48
|
+
- context providers and skills are resolved only when passed to the agent/run
|
|
49
|
+
- middleware runs only for documented hook call sites when a registry is supplied
|
|
50
|
+
- instruction injectors contribute only instructions/context blocks during assembly
|
|
51
|
+
- loops orchestrate shared runtime primitives and return usage
|
|
52
|
+
- compaction/retry run only when configured and triggered
|
|
53
|
+
- session stores receive redacted `SessionEntry` values when a redactor is active
|
|
54
|
+
|
|
55
|
+
Events remain the normal `AgentEvent` stream for runs, tools, compaction, retry, and artifact validation.
|
|
56
|
+
|
|
57
|
+
## Request/response example
|
|
58
|
+
|
|
59
|
+
```json
|
|
60
|
+
{
|
|
61
|
+
"agent": {
|
|
62
|
+
"providerSource": "host resolver",
|
|
63
|
+
"middleware": "host registry",
|
|
64
|
+
"inputBuilder": "custom input builder",
|
|
65
|
+
"promptBuilder": "custom prompt builder",
|
|
66
|
+
"context": ["project"],
|
|
67
|
+
"skills": ["brief"],
|
|
68
|
+
"instructionInjectors": ["json"],
|
|
69
|
+
"loop": "generate-validate-revise",
|
|
70
|
+
"compaction": "host strategy",
|
|
71
|
+
"retry": "host policy",
|
|
72
|
+
"store": "host session store"
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
The JSON is a map of seams, not a Prism config format. Hosts wire concrete public API objects into `createAgent()` or `session.run()`.
|
|
78
|
+
|
|
79
|
+
## Implementation example
|
|
80
|
+
|
|
81
|
+
```ts
|
|
82
|
+
import {
|
|
83
|
+
createAgent,
|
|
84
|
+
createDefaultCompactionStrategy,
|
|
85
|
+
createDefaultRetryPolicy,
|
|
86
|
+
createMemorySessionStore,
|
|
87
|
+
createMiddlewareRegistry,
|
|
88
|
+
createMockProvider,
|
|
89
|
+
createProviderResolver,
|
|
90
|
+
createProviderRegistry,
|
|
91
|
+
createSkillRegistry,
|
|
92
|
+
createToolRegistry,
|
|
93
|
+
generateValidateReviseLoop,
|
|
94
|
+
providerDone,
|
|
95
|
+
type ArtifactValidator,
|
|
96
|
+
type ContextProvider,
|
|
97
|
+
type InputBuilder,
|
|
98
|
+
type PromptBuilder,
|
|
99
|
+
} from "@arnilo/prism";
|
|
100
|
+
|
|
101
|
+
const provider = createMockProvider([providerDone()]);
|
|
102
|
+
const providerSource = createProviderResolver(createProviderRegistry([provider]));
|
|
103
|
+
const middleware = createMiddlewareRegistry();
|
|
104
|
+
middleware.use("provider_request", (request, next) => next(request));
|
|
105
|
+
|
|
106
|
+
const context: ContextProvider = {
|
|
107
|
+
name: "project",
|
|
108
|
+
resolve: () => [{ title: "Project", content: "Host-selected context." }],
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
const inputBuilder: InputBuilder = {
|
|
112
|
+
name: "custom-input",
|
|
113
|
+
build: async (input) => [{ role: "user", content: [{ type: "text", text: String(input) }] }],
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
const promptBuilder: PromptBuilder = {
|
|
117
|
+
name: "custom-prompt",
|
|
118
|
+
build: async (request) => request.messages,
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
const validator: ArtifactValidator<unknown> = (value) =>
|
|
122
|
+
typeof value === "string" && value.length > 0
|
|
123
|
+
? { ok: true }
|
|
124
|
+
: { ok: false, errors: [{ message: "empty output" }] };
|
|
125
|
+
|
|
126
|
+
const agent = createAgent({
|
|
127
|
+
model: { provider: "mock", model: "demo" },
|
|
128
|
+
providerSource,
|
|
129
|
+
middleware,
|
|
130
|
+
context: [context],
|
|
131
|
+
skills: createSkillRegistry([{ name: "brief", instructions: "Be brief." }]),
|
|
132
|
+
tools: createToolRegistry([], { duplicate: "error" }),
|
|
133
|
+
inputBuilder,
|
|
134
|
+
promptBuilder,
|
|
135
|
+
instructionInjectors: [{ name: "json", apply: () => ({ when: "every_turn", instructions: "Use JSON." }) }],
|
|
136
|
+
loop: generateValidateReviseLoop({ validator }),
|
|
137
|
+
compaction: { strategy: createDefaultCompactionStrategy(), thresholdEntries: 40 },
|
|
138
|
+
retry: { policy: createDefaultRetryPolicy({ maxAttempts: 3 }) },
|
|
139
|
+
store: createMemorySessionStore(),
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
await agent.createSession().run("Hello", { activeSkills: ["brief"] });
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
### Per-run override examples
|
|
146
|
+
|
|
147
|
+
```ts
|
|
148
|
+
await session.run("Use a different provider just this run", { providerSource: otherResolver });
|
|
149
|
+
await session.run("No auto compaction here", { compaction: false });
|
|
150
|
+
await session.run("No retry here", { retry: false });
|
|
151
|
+
await session.run("Use only this skill", { activeSkills: ["brief"] });
|
|
152
|
+
await session.run("Use a different loop", { loop: { strategy: "single-shot" } });
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
## Extension and configuration notes
|
|
156
|
+
|
|
157
|
+
- Direct `AgentConfig.provider` takes first precedence and bypasses `providerSource`. Without a direct provider, `RunOptions.providerSource` wins over `AgentConfig.providerSource`.
|
|
158
|
+
- A custom `ProviderResolver` returns an `AIProvider | undefined`; `undefined` fails closed before the provider turn.
|
|
159
|
+
- Middleware is not global. It runs only when the host passes a `MiddlewareRegistry` to `createAgent()`, `assembleProviderInput()`, `dispatchToolCall()`, compaction, or retry paths that document a hook.
|
|
160
|
+
- Context providers are host-selected arrays. Extension-contributed providers remain inert until resolved from registries and passed into config.
|
|
161
|
+
- Skills are selected by the host. `toolNames` only require active tools; they never register, allow, permit, or execute tools.
|
|
162
|
+
- Input and prompt builders are replaceable objects. Default builders remain available when omitted.
|
|
163
|
+
- Instruction injectors can add instructions and context blocks only. They grant no tools, skills, permissions, validators, credentials, resource access, or provider options.
|
|
164
|
+
- Agent loops should use `LoopContext` primitives instead of reimplementing provider calls, retry, abort, store appends, redaction, or event emission.
|
|
165
|
+
- Compaction and retry strategies are inert until selected on `AgentConfig` or `RunOptions`. `RunOptions.compaction: false` and `RunOptions.retry: false` disable configured defaults for one run.
|
|
166
|
+
- Session stores are explicit. `AgentSessionConfig.store` wins over `AgentConfig.store`; otherwise the session uses a private in-memory store.
|
|
167
|
+
- Extension packages can register builders, strategies, policies, providers, tools, context, skills, and injectors, but host code still chooses which contributions become active.
|
|
168
|
+
|
|
169
|
+
## Security and performance notes
|
|
170
|
+
|
|
171
|
+
- Customization cannot grant tools or permissions unless the host explicitly activates tools and permission policies at the tool-dispatch boundary.
|
|
172
|
+
- Middleware, skills, context providers, prompt builders, and instruction injectors cannot bypass tool lookup, allow/deny filters, object-argument checks, permission checks, or `ToolValidator`.
|
|
173
|
+
- Do not put credentials in models, prompts, context, skills, instruction injectors, middleware payloads, cache keys, session entries, ledgers, or examples.
|
|
174
|
+
- Use `createSecretRedactor()` on agent/run config when custom components may handle known secret values.
|
|
175
|
+
- Replaceable hooks are in-process calls on the active path. Prism adds no hidden global middleware, background workers, watchers, package scans, provider calls, resource loads, tool execution, or credential resolution unless the host wires that operation.
|
|
176
|
+
- Custom providers, stores, tools, middleware, loops, and extensions are host code. Prism does not sandbox them.
|
|
177
|
+
- Strict duplicate registries (`{ duplicate: "error" }`) prevent silent shadowing when loading third-party contributions.
|
|
178
|
+
|
|
179
|
+
## Related APIs
|
|
180
|
+
|
|
181
|
+
- [Provider layer](provider-layer.md): provider registries/resolvers and model routing.
|
|
182
|
+
- [Middleware hooks](middleware-hooks.md): hook names and runtime call sites.
|
|
183
|
+
- [Input and prompt assembly](input-and-prompt-assembly.md): default/custom builders, templates, context, and tools in provider requests.
|
|
184
|
+
- [Instruction injection](instruction-injection.md): inert package instructions/context blocks.
|
|
185
|
+
- [Agent loops](agent-loops.md): `singleShotLoop`, `generateValidateReviseLoop`, custom `AgentLoopStrategy`, and `LoopContext`.
|
|
186
|
+
- [Compaction and retry](compaction-and-retry.md): compaction/retry options, strategies, middleware, and disabling per run.
|
|
187
|
+
- [Context and skills](context-and-skills.md): ordered context providers, skill registries, active-skill selection, and `toolNames` fail-closed behavior.
|
|
188
|
+
- [Session stores](session-stores.md): store selection, branch reads, and conformance.
|
|
189
|
+
- [Tools](tools.md): active tool registry, filtering, permission, validation, and no sandbox.
|
|
190
|
+
- [Extension authoring guide](extension-authoring.md): publishing inert contributions for hosts to select.
|
|
191
|
+
- [Host security guide](host-security.md): fail-closed security checklist for embedding apps.
|