theorum 0.1.2
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/LICENSE +21 -0
- package/README.md +338 -0
- package/docs/AGENT_PROFILE_CONTRACT.md +161 -0
- package/docs/CLI_SPEC.md +183 -0
- package/docs/SECRETS.md +55 -0
- package/esm/_dnt.polyfills.d.ts +11 -0
- package/esm/_dnt.polyfills.js +15 -0
- package/esm/_dnt.shims.d.ts +5 -0
- package/esm/_dnt.shims.js +61 -0
- package/esm/mod.d.ts +37 -0
- package/esm/mod.js +35 -0
- package/esm/package.json +3 -0
- package/esm/src/guardrails/error.d.ts +35 -0
- package/esm/src/guardrails/error.js +116 -0
- package/esm/src/guardrails/injection.d.ts +12 -0
- package/esm/src/guardrails/injection.js +220 -0
- package/esm/src/guardrails/keys.d.ts +12 -0
- package/esm/src/guardrails/keys.js +132 -0
- package/esm/src/guardrails/mod.d.ts +14 -0
- package/esm/src/guardrails/mod.js +14 -0
- package/esm/src/guardrails/sanitize.d.ts +22 -0
- package/esm/src/guardrails/sanitize.js +133 -0
- package/esm/src/guardrails/sensitive.d.ts +12 -0
- package/esm/src/guardrails/sensitive.js +88 -0
- package/esm/src/kernel/engine/boundary.d.ts +10 -0
- package/esm/src/kernel/engine/boundary.js +55 -0
- package/esm/src/kernel/engine/delta.d.ts +8 -0
- package/esm/src/kernel/engine/delta.js +362 -0
- package/esm/src/kernel/engine/hash.d.ts +1 -0
- package/esm/src/kernel/engine/hash.js +9 -0
- package/esm/src/kernel/engine/record.d.ts +2 -0
- package/esm/src/kernel/engine/record.js +7 -0
- package/esm/src/kernel/engine/repair.d.ts +9 -0
- package/esm/src/kernel/engine/repair.js +38 -0
- package/esm/src/kernel/engine/runner.d.ts +14 -0
- package/esm/src/kernel/engine/runner.js +731 -0
- package/esm/src/kernel/engine/tree.d.ts +2 -0
- package/esm/src/kernel/engine/tree.js +17 -0
- package/esm/src/kernel/mod.d.ts +16 -0
- package/esm/src/kernel/mod.js +15 -0
- package/esm/src/kernel/registry/catalog.d.ts +24 -0
- package/esm/src/kernel/registry/catalog.js +213 -0
- package/esm/src/kernel/registry/profiles.d.ts +36 -0
- package/esm/src/kernel/registry/profiles.js +111 -0
- package/esm/src/kernel/registry/resolve.d.ts +20 -0
- package/esm/src/kernel/registry/resolve.js +235 -0
- package/esm/src/kernel/registry/schemas.d.ts +14 -0
- package/esm/src/kernel/registry/schemas.js +23 -0
- package/esm/src/kernel/registry/tools.d.ts +12 -0
- package/esm/src/kernel/registry/tools.js +36 -0
- package/esm/src/kernel/types.d.ts +497 -0
- package/esm/src/kernel/types.js +10 -0
- package/esm/src/observability/mod.d.ts +12 -0
- package/esm/src/observability/mod.js +10 -0
- package/esm/src/observability/spans.d.ts +16 -0
- package/esm/src/observability/spans.js +56 -0
- package/esm/src/observability/trace-attach.d.ts +16 -0
- package/esm/src/observability/trace-attach.js +81 -0
- package/esm/src/observability/trace-record.d.ts +112 -0
- package/esm/src/observability/trace-record.js +140 -0
- package/esm/src/observability/trace-usage.d.ts +3 -0
- package/esm/src/observability/trace-usage.js +32 -0
- package/esm/src/observability/trace.d.ts +23 -0
- package/esm/src/observability/trace.js +121 -0
- package/esm/src/providers/attachments.d.ts +17 -0
- package/esm/src/providers/attachments.js +156 -0
- package/esm/src/providers/gemini-tape.d.ts +3 -0
- package/esm/src/providers/gemini-tape.js +46 -0
- package/esm/src/providers/google-tap.d.ts +3 -0
- package/esm/src/providers/google-tap.js +48 -0
- package/esm/src/providers/interactions.d.ts +5 -0
- package/esm/src/providers/interactions.js +127 -0
- package/esm/src/providers/media.d.ts +5 -0
- package/esm/src/providers/media.js +125 -0
- package/esm/src/providers/mod.d.ts +15 -0
- package/esm/src/providers/mod.js +13 -0
- package/esm/src/providers/openrouter-payload.d.ts +24 -0
- package/esm/src/providers/openrouter-payload.js +177 -0
- package/esm/src/providers/openrouter.d.ts +17 -0
- package/esm/src/providers/openrouter.js +332 -0
- package/esm/src/providers/provider.d.ts +13 -0
- package/esm/src/providers/provider.js +123 -0
- package/esm/src/providers/sse.d.ts +7 -0
- package/esm/src/providers/sse.js +53 -0
- package/esm/src/providers/tts.d.ts +24 -0
- package/esm/src/providers/tts.js +144 -0
- package/package.json +48 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 ORCHID AI LLC
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
```text
|
|
2
|
+
_______ __ __ _______ _______ ______ __ __ __ __
|
|
3
|
+
| || | | || || || _ | | | | || |_| |
|
|
4
|
+
|_ _|| |_| || ___|| _ || | || | | | || |
|
|
5
|
+
| | | || |___ | | | || |_||_ | |_| || |
|
|
6
|
+
| | | || ___|| |_| || __ || || |
|
|
7
|
+
| | | _ || |___ | || | | || || ||_|| |
|
|
8
|
+
|___| |__| |__||_______||_______||___| |_||_______||_| |_|
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
# THEORUM: The Flat Agent Kernel
|
|
12
|
+
|
|
13
|
+
> **"Profiles describe the contract. Providers move bytes. The runner enforces the turn."**
|
|
14
|
+
|
|
15
|
+
THEORUM is a compact TypeScript agent kernel for apps that need deterministic agent execution without embedding product logic inside the runtime. It gives a host application one runner, typed profiles, multimodal input normalization, dynamic tool dispatch, provider adapters, trace sinks, and guardrail hooks.
|
|
16
|
+
|
|
17
|
+
The package is intentionally **not** an agent product. It ships no app profiles, no prompts, no secrets, no database policy, no business rules, and no channel-specific UX. Those belong in the host application.
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## Core Principles
|
|
22
|
+
|
|
23
|
+
```toml
|
|
24
|
+
[kernel_contract]
|
|
25
|
+
profiles = "Host-owned declarations for model, inputs, outputs, tools, and guardrails"
|
|
26
|
+
runner = "Single deterministic execution path for one agent turn"
|
|
27
|
+
providers = "Adapters for OpenRouter-compatible chat and Google Interactions"
|
|
28
|
+
tools = "Profile allowlist ceiling plus per-turn dynamic declarations"
|
|
29
|
+
egress = "Typed host hook for outbound disclosure checks and repair loops"
|
|
30
|
+
traces = "Host-injected sinks; no environment variables or bundled destinations"
|
|
31
|
+
|
|
32
|
+
[non_goals]
|
|
33
|
+
app_profiles = "No bundled assistants, demos, product personas, or business tasks"
|
|
34
|
+
secrets = "No .env files, no ambient key reads in the kernel"
|
|
35
|
+
realtime_voice = "Not included yet; persistent duplex sessions stay host-owned"
|
|
36
|
+
product_copy = "No channel wording, refusal copy, iMessage/Alexa/Web policy, or UX defaults"
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
## Architecture
|
|
42
|
+
|
|
43
|
+
THEORUM is organized around a deliberately small execution boundary.
|
|
44
|
+
|
|
45
|
+
```mermaid
|
|
46
|
+
flowchart TD
|
|
47
|
+
subgraph Host["Host application"]
|
|
48
|
+
Profile["Profiles"]
|
|
49
|
+
Schemas["Structured schemas"]
|
|
50
|
+
Tools["Tool handlers"]
|
|
51
|
+
Keys["Provider keys"]
|
|
52
|
+
TraceSink["Trace sink"]
|
|
53
|
+
Policy["Business rules"]
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
subgraph Kernel["THEORUM"]
|
|
57
|
+
Resolve["resolveTurn"]
|
|
58
|
+
Guard["sanitize + canary + egress"]
|
|
59
|
+
Runner["runTurn"]
|
|
60
|
+
ToolLoop["dynamic tool loop"]
|
|
61
|
+
Repair["repair attempts"]
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
subgraph Providers["Provider adapters"]
|
|
65
|
+
OR["OpenRouter"]
|
|
66
|
+
GI["Google Interactions"]
|
|
67
|
+
TTS["OpenRouter TTS"]
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
Profile --> Resolve
|
|
71
|
+
Schemas --> Resolve
|
|
72
|
+
Tools --> ToolLoop
|
|
73
|
+
Keys --> Providers
|
|
74
|
+
TraceSink --> Runner
|
|
75
|
+
Policy --> Guard
|
|
76
|
+
Resolve --> Runner
|
|
77
|
+
Guard --> Runner
|
|
78
|
+
Runner --> Providers
|
|
79
|
+
Providers --> Runner
|
|
80
|
+
Runner --> TraceSink
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### Turn Lifecycle
|
|
84
|
+
|
|
85
|
+
```mermaid
|
|
86
|
+
stateDiagram-v2
|
|
87
|
+
[*] --> ResolveProfile: host sends TurnRequest
|
|
88
|
+
ResolveProfile --> NormalizeInput: profile input rules
|
|
89
|
+
NormalizeInput --> BindBoundary: canary + user data fencing
|
|
90
|
+
BindBoundary --> ProviderStream: ModelProvider.complete
|
|
91
|
+
ProviderStream --> ToolDispatch: tool event
|
|
92
|
+
ToolDispatch --> ProviderStream: autonomous loop continues
|
|
93
|
+
ProviderStream --> EgressGate: final candidate
|
|
94
|
+
EgressGate --> RepairTurn: blocked + retry budget
|
|
95
|
+
RepairTurn --> ProviderStream
|
|
96
|
+
EgressGate --> ValidateOutput: clear
|
|
97
|
+
ValidateOutput --> EmitEvents: text/media/structured/tokens/done
|
|
98
|
+
EmitEvents --> Trace: host sink receives audit record
|
|
99
|
+
Trace --> [*]
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
---
|
|
103
|
+
|
|
104
|
+
## Install
|
|
105
|
+
|
|
106
|
+
### Deno / JSR
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
deno add jsr:@theorum/core
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
```ts
|
|
113
|
+
import { defineProfile, registerProfile, runTurn } from "jsr:@theorum/core";
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### npm
|
|
117
|
+
|
|
118
|
+
```bash
|
|
119
|
+
npm install theorum
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
```ts
|
|
123
|
+
import { defineProfile, registerProfile, runTurn } from "theorum";
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
---
|
|
127
|
+
|
|
128
|
+
## Minimal Example
|
|
129
|
+
|
|
130
|
+
This example uses a local mock provider so it runs without secrets. Real provider keys should be passed into the provider adapter by the host application.
|
|
131
|
+
|
|
132
|
+
```ts
|
|
133
|
+
import {
|
|
134
|
+
defineProfile,
|
|
135
|
+
registerProfile,
|
|
136
|
+
runTurn,
|
|
137
|
+
type ModelProvider,
|
|
138
|
+
type TurnEvent,
|
|
139
|
+
} from "jsr:@theorum/core";
|
|
140
|
+
|
|
141
|
+
const profile = defineProfile({
|
|
142
|
+
id: "assistant.basic",
|
|
143
|
+
identity: {
|
|
144
|
+
handle: "assistant",
|
|
145
|
+
system: "Answer plainly.",
|
|
146
|
+
},
|
|
147
|
+
model: {
|
|
148
|
+
protocol: "openAi",
|
|
149
|
+
provider: "openrouter",
|
|
150
|
+
allow: ["gemini35FlashLite"],
|
|
151
|
+
thinking: "minimal",
|
|
152
|
+
maxSteps: 1,
|
|
153
|
+
},
|
|
154
|
+
outputs: {
|
|
155
|
+
streaming: { streamThoughts: false },
|
|
156
|
+
},
|
|
157
|
+
guardrails: {
|
|
158
|
+
quota: { perDay: 100 }, // Optional. Omit when the host owns metering.
|
|
159
|
+
},
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
registerProfile(profile);
|
|
163
|
+
|
|
164
|
+
const provider: ModelProvider = {
|
|
165
|
+
async *complete(): AsyncIterable<TurnEvent> {
|
|
166
|
+
yield { type: "text", text: "The turn completed." };
|
|
167
|
+
yield { type: "tokens", tokens: { input: 8, output: 4, total: 12 } };
|
|
168
|
+
yield { type: "done" };
|
|
169
|
+
},
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
for await (const event of runTurn(
|
|
173
|
+
{ profile: "assistant.basic", input: { text: "Ping" } },
|
|
174
|
+
provider,
|
|
175
|
+
)) {
|
|
176
|
+
console.log(event);
|
|
177
|
+
}
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
---
|
|
181
|
+
|
|
182
|
+
## Dynamic Tools
|
|
183
|
+
|
|
184
|
+
THEORUM separates tool concerns into three layers.
|
|
185
|
+
|
|
186
|
+
| Layer | Owner | Purpose |
|
|
187
|
+
| :--- | :--- | :--- |
|
|
188
|
+
| **Access** | Profile | Hard ceiling: the agent cannot use a tool outside `profile.tools.allow`. |
|
|
189
|
+
| **Visibility** | Turn request | Per-turn declarations: T0/T1/T2 schemas can be passed or loaded dynamically. |
|
|
190
|
+
| **Permission** | Host app | `auto`, `session_consent`, and `always_confirm` determine whether execution pauses. |
|
|
191
|
+
|
|
192
|
+
```ts
|
|
193
|
+
const dynamicTools = [
|
|
194
|
+
{
|
|
195
|
+
name: "lookup_order",
|
|
196
|
+
description: "Fetch order state from the host application.",
|
|
197
|
+
loadTier: "T1",
|
|
198
|
+
permissionTier: "session_consent",
|
|
199
|
+
parameters: {
|
|
200
|
+
type: "object",
|
|
201
|
+
properties: { orderId: { type: "string" } },
|
|
202
|
+
required: ["orderId"],
|
|
203
|
+
},
|
|
204
|
+
handler: async (args) => ({
|
|
205
|
+
status: "ok",
|
|
206
|
+
finding: "Order is in transit.",
|
|
207
|
+
data: { orderId: args.orderId, state: "in_transit" },
|
|
208
|
+
}),
|
|
209
|
+
},
|
|
210
|
+
] as const;
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
The host owns the handler and authorization state. The kernel only enforces the declared contract.
|
|
214
|
+
|
|
215
|
+
---
|
|
216
|
+
|
|
217
|
+
## Guardrails and Egress
|
|
218
|
+
|
|
219
|
+
Inbound and outbound safety are generic kernel hooks.
|
|
220
|
+
|
|
221
|
+
```ts
|
|
222
|
+
const guardedProfile = defineProfile({
|
|
223
|
+
id: "assistant.guarded",
|
|
224
|
+
model: { allow: ["gemini35FlashLite"] },
|
|
225
|
+
guardrails: {
|
|
226
|
+
egress: {
|
|
227
|
+
onBlock: "reject_to_agent",
|
|
228
|
+
maxRetries: 2,
|
|
229
|
+
enforce: ({ text, canary }) => {
|
|
230
|
+
if (canary && text.includes(canary)) {
|
|
231
|
+
return {
|
|
232
|
+
blocked: true,
|
|
233
|
+
text: "",
|
|
234
|
+
hits: ["canary_token_leak"],
|
|
235
|
+
rejectionMessage: "Remove private runtime tokens from the reply.",
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
return { blocked: false, text };
|
|
239
|
+
},
|
|
240
|
+
},
|
|
241
|
+
},
|
|
242
|
+
});
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
The egress function is host-owned. One application may block internal tool names, another may block regulated disclosures, and another may disable egress entirely for a trusted development profile.
|
|
246
|
+
|
|
247
|
+
Quota is optional. If a profile omits `guardrails.quota`, the quota helper returns `not_configured` so the host can decide whether that route should be unmetered, rejected, or handled by a separate rate limiter.
|
|
248
|
+
|
|
249
|
+
---
|
|
250
|
+
|
|
251
|
+
## Provider Adapters
|
|
252
|
+
|
|
253
|
+
THEORUM includes provider adapters but does not own credentials.
|
|
254
|
+
|
|
255
|
+
```ts
|
|
256
|
+
import { createOpenRouterProvider } from "jsr:@theorum/core/openrouter";
|
|
257
|
+
|
|
258
|
+
const provider = createOpenRouterProvider({
|
|
259
|
+
apiKey: hostSecrets.openRouterApiKey,
|
|
260
|
+
siteName: "Your app",
|
|
261
|
+
siteUrl: "https://example.com",
|
|
262
|
+
});
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
```ts
|
|
266
|
+
import { createInteractionsProvider } from "jsr:@theorum/core/providers";
|
|
267
|
+
|
|
268
|
+
const provider = createInteractionsProvider({
|
|
269
|
+
keys: hostGeminiKeyVault,
|
|
270
|
+
fetch,
|
|
271
|
+
});
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
Provider support is intentionally split by wire protocol:
|
|
275
|
+
|
|
276
|
+
| Provider | Protocol | Use |
|
|
277
|
+
| :--- | :--- | :--- |
|
|
278
|
+
| OpenRouter | `openAi` | Chat completions, reasoning streams, tool calls, structured output, TTS gateway. |
|
|
279
|
+
| Google Interactions | `geminiInteractions` | Native Google Interactions streaming, image response format, interaction continuity, grounding metadata. |
|
|
280
|
+
|
|
281
|
+
---
|
|
282
|
+
|
|
283
|
+
## Public Entrypoints
|
|
284
|
+
|
|
285
|
+
| Entrypoint | Purpose |
|
|
286
|
+
| :--- | :--- |
|
|
287
|
+
| `jsr:@theorum/core` / `theorum` | Main kernel API: profiles, schemas, runner, core types, provider constructors. |
|
|
288
|
+
| `jsr:@theorum/core/kernel` / `theorum/kernel` | Profile, turn, event, tool, egress, provider, and schema types. |
|
|
289
|
+
| `jsr:@theorum/core/providers` / `theorum/providers` | Provider constructors and provider utility types. |
|
|
290
|
+
| `jsr:@theorum/core/openrouter` / `theorum/openrouter` | OpenRouter payload and streaming adapter. |
|
|
291
|
+
| `jsr:@theorum/core/guardrails` / `theorum/guardrails` | Sanitization, public error mapping, inbound injection/sensitive-data primitives. |
|
|
292
|
+
| `jsr:@theorum/core/observability` / `theorum/observability` | Trace sinks and trace record helpers. |
|
|
293
|
+
|
|
294
|
+
Internal files remain present in source for maintainability, but package consumers should use the public entrypoints above.
|
|
295
|
+
|
|
296
|
+
---
|
|
297
|
+
|
|
298
|
+
## Development
|
|
299
|
+
|
|
300
|
+
```bash
|
|
301
|
+
npm install
|
|
302
|
+
npm run test
|
|
303
|
+
npm run lint
|
|
304
|
+
deno publish --dry-run --allow-dirty
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
Build the npm package from the Deno source:
|
|
308
|
+
|
|
309
|
+
```bash
|
|
310
|
+
npm run build:npm
|
|
311
|
+
cd npm
|
|
312
|
+
npm pack
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
---
|
|
316
|
+
|
|
317
|
+
## Package Boundary
|
|
318
|
+
|
|
319
|
+
THEORUM is ready for host applications when these statements stay true:
|
|
320
|
+
|
|
321
|
+
```toml
|
|
322
|
+
[boundary]
|
|
323
|
+
profiles_in_package = false
|
|
324
|
+
env_files_in_package = false
|
|
325
|
+
ambient_secret_reads = false
|
|
326
|
+
business_logic_in_kernel = false
|
|
327
|
+
provider_keys_host_owned = true
|
|
328
|
+
trace_sinks_host_injected = true
|
|
329
|
+
realtime_duplex_voice = "out of scope"
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
If an app needs domain rules, platform delivery policy, product copy, database access, or session memory, that belongs outside THEORUM.
|
|
333
|
+
|
|
334
|
+
---
|
|
335
|
+
|
|
336
|
+
## License
|
|
337
|
+
|
|
338
|
+
MIT License. Copyright (c) ORCHID AI LLC.
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
# Theorum Profile Specification
|
|
2
|
+
|
|
3
|
+
A **Profile** is the deterministic, typed security and behavioral contract for an agent role in Theorum.
|
|
4
|
+
|
|
5
|
+
Every profile strictly namespaces its capabilities across 6 functional domains:
|
|
6
|
+
1. `identity` — Persona, display handle, and static base system prompts.
|
|
7
|
+
2. `model` — Protocol, provider backend, model whitelist, thinking level, and controls.
|
|
8
|
+
3. `tools` — Tool access ceiling.
|
|
9
|
+
4. `inputs` — Strict ingress constraints, file limits, and routing slots.
|
|
10
|
+
5. `outputs` — Structured schemas, voice, media, streaming, and validation/auto-repair.
|
|
11
|
+
6. `guardrails` — Rate limits, canary leak detection, content safety, and outbound disclosure policies.
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## 1. Authoring Shape
|
|
16
|
+
|
|
17
|
+
Host apps should author profile definitions, not hand-build normalized runtime
|
|
18
|
+
profiles. The minimum useful profile is:
|
|
19
|
+
|
|
20
|
+
```typescript
|
|
21
|
+
defineProfile({
|
|
22
|
+
id: 'host.agent',
|
|
23
|
+
model: { allow: ['your-model-id'] },
|
|
24
|
+
});
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Everything except `id` and `model.allow` is optional at authoring time:
|
|
28
|
+
|
|
29
|
+
```typescript
|
|
30
|
+
export type ProfileDefinition = {
|
|
31
|
+
id: ProfileId;
|
|
32
|
+
identity?: Partial<Profile['identity']>;
|
|
33
|
+
model: Partial<Profile['model']> & Pick<Profile['model'], 'allow'>;
|
|
34
|
+
tools?: Partial<Profile['tools']>;
|
|
35
|
+
inputs?: Partial<Profile['inputs']>;
|
|
36
|
+
outputs?: Partial<Profile['outputs']>;
|
|
37
|
+
guardrails?: Partial<Profile['guardrails']>;
|
|
38
|
+
};
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
`defineProfile()` and `registerProfile()` normalize this shape into a complete
|
|
42
|
+
runtime `Profile`.
|
|
43
|
+
|
|
44
|
+
## 2. Runtime Type Definition
|
|
45
|
+
|
|
46
|
+
```typescript
|
|
47
|
+
export interface Profile {
|
|
48
|
+
/** Unique host-defined profile identifier. */
|
|
49
|
+
id: ProfileId;
|
|
50
|
+
|
|
51
|
+
/** 1. Identity & Persona */
|
|
52
|
+
identity: {
|
|
53
|
+
handle: string;
|
|
54
|
+
chat?: boolean;
|
|
55
|
+
system?: string;
|
|
56
|
+
systemByRole?: Record<string, string>;
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
/** 2. Model & Execution Bounds */
|
|
60
|
+
model: {
|
|
61
|
+
protocol: 'geminiInteractions' | 'openAi';
|
|
62
|
+
provider: 'google' | 'openrouter';
|
|
63
|
+
allow: ModelId[];
|
|
64
|
+
select?: Record<string, ModelId>;
|
|
65
|
+
thinking?: ThinkingLevel | Record<string, ThinkingLevel>;
|
|
66
|
+
controls?: ControlId[];
|
|
67
|
+
maxSteps?: number;
|
|
68
|
+
key?: GeminiFreeBucket;
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
/** 3. Tools Envelope */
|
|
72
|
+
tools: {
|
|
73
|
+
allow: ToolId[];
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
/** 4. Ingress (Input constraints & slots) */
|
|
77
|
+
inputs: {
|
|
78
|
+
text?: boolean;
|
|
79
|
+
attachments?: { accept: string[] };
|
|
80
|
+
voice?: { accept: string[] };
|
|
81
|
+
maxFiles?: number;
|
|
82
|
+
maxBytes?: number;
|
|
83
|
+
maxTurnBytes?: number;
|
|
84
|
+
limitsByMime?: Record<string, number>;
|
|
85
|
+
slots?: Record<string, string[]>;
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
/** 5. Outputs (Structured output, voice, media, validation, streaming) */
|
|
89
|
+
outputs: {
|
|
90
|
+
structured?: StructuredSchemaId | StructuredBySlot | null;
|
|
91
|
+
media?: boolean;
|
|
92
|
+
voice?: ProfileVoiceSpec;
|
|
93
|
+
validation?: ProfileValidationSpec;
|
|
94
|
+
streaming?: ProfileStreamingSpec;
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
/** 6. Guardrails (Policies & safety boundaries) */
|
|
98
|
+
guardrails: {
|
|
99
|
+
quota?: { perDay: number };
|
|
100
|
+
canary?: boolean;
|
|
101
|
+
sanitizeInput?: boolean;
|
|
102
|
+
redactSensitive?: boolean;
|
|
103
|
+
egress?: ProfileEgressSpec;
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
---
|
|
109
|
+
|
|
110
|
+
## 3. Field Reference
|
|
111
|
+
|
|
112
|
+
### `identity`
|
|
113
|
+
- `identity.handle`: Public-facing display handle for the persona.
|
|
114
|
+
- `identity.chat`: Flag marking whether this profile participates in interactive chat.
|
|
115
|
+
- `identity.system`: Base system prompt block. Fenced and bound by Theorum guardrails.
|
|
116
|
+
- `identity.systemByRole`: Role-specialized system prompts (e.g., `{ reviewer: '...', drafter: '...' }`).
|
|
117
|
+
|
|
118
|
+
### `model`
|
|
119
|
+
- `model.protocol`: Wire framing protocol (`'geminiInteractions'` for Google Interactions API, `'openAi'` for OpenAI/OpenRouter compatible chat completions API).
|
|
120
|
+
- `model.provider`: Provider execution backend (`'google'` or `'openrouter'`).
|
|
121
|
+
- `model.allow`: Whitelist of allowable `ModelId`s for this profile.
|
|
122
|
+
- `model.select`: Named model mappings (e.g. `{ fast: 'gemini35FlashLite', deep: 'gemini31ProPreview' }`).
|
|
123
|
+
- `model.thinking`: Pinned thinking level (`'minimal' | 'low' | 'medium' | 'high'`) when not user-controllable.
|
|
124
|
+
- `model.controls`: User-togglable controls on this profile (e.g. `['thinking']`).
|
|
125
|
+
- `model.maxSteps`: Step limit on autonomous tool loops (1 = one-shot; >1 = autonomous tool loop).
|
|
126
|
+
- `model.key`: Gemini free bucket quota pool (`'freeA' | 'freeB' | 'freeC'`).
|
|
127
|
+
|
|
128
|
+
### `tools`
|
|
129
|
+
- `tools.allow`: Whitelist of tool IDs permitted to run under this profile. Calls to unlisted tools are blocked at the kernel boundary.
|
|
130
|
+
|
|
131
|
+
### `inputs`
|
|
132
|
+
- `inputs.text`: Boolean flag accepting user text input.
|
|
133
|
+
- `inputs.attachments`: Allowed mime types for uploaded file attachments.
|
|
134
|
+
- `inputs.voice`: Allowed mime types for recorded audio clips.
|
|
135
|
+
- `inputs.maxFiles`: Maximum number of files permitted per message.
|
|
136
|
+
- `inputs.maxBytes`: Maximum byte size permitted per single file.
|
|
137
|
+
- `inputs.maxTurnBytes`: Maximum total byte size permitted across all files in one turn.
|
|
138
|
+
- `inputs.limitsByMime`: Granular per-MIME byte limits (e.g. `{ 'application/pdf': 50 * 1024 * 1024, 'video/*': 100 * 1024 * 1024 }`).
|
|
139
|
+
- `inputs.slots`: Allowed values for dynamic routing slots.
|
|
140
|
+
|
|
141
|
+
### `outputs`
|
|
142
|
+
- `outputs.structured`: Structured JSON schema specification (or slot-based schema routing).
|
|
143
|
+
- `outputs.media`: Flag enabling native image generation output.
|
|
144
|
+
- `outputs.voice`: Voice specification for TTS (`voice.voice`, `voice.responseFormat: 'pcm' | 'mp3'`).
|
|
145
|
+
- `outputs.validation`: In-harness auto-correction validator (`validate`, optional `extract`, `maxRetries`, `repairGuidance`). If `extract` is omitted, the structured output itself is validated.
|
|
146
|
+
- `outputs.streaming`: SSE streaming behaviors (`streamThoughts`, `gateMedia`).
|
|
147
|
+
|
|
148
|
+
### `guardrails`
|
|
149
|
+
- `guardrails.quota.perDay`: Optional daily turn quota enforced per client IP. If omitted, quota enforcement is explicitly `not_configured`.
|
|
150
|
+
- `guardrails.canary`: Enable unique token canary leak interception (default `true`).
|
|
151
|
+
- `guardrails.sanitizeInput`: Run prompt injection / jailbreak redaction on ingress text (default `true`).
|
|
152
|
+
- `guardrails.redactSensitive`: Redact SSN, credit cards, IP addresses, API keys from inputs (default `true`).
|
|
153
|
+
- `guardrails.egress`: Generic outbound disclosure control engine (`enforce`, `onBlock: 'reject_to_agent' | 'refuse_to_user'`, `maxRetries`, `repairGuidance`). Runs deterministic auto-repair loops for chat or immediate in-character refusal for voice.
|
|
154
|
+
|
|
155
|
+
### Per-turn Interactions state
|
|
156
|
+
- `TurnRequest.input`: Optional turn input object. If omitted, Theorum normalizes it to an empty input and still runs the profile/provider turn.
|
|
157
|
+
- `TurnRequest.previousInteractionId`: Optional Google Interactions server-side conversation pointer. Theorum passes it through as `previous_interaction_id` for profiles using `geminiInteractions`.
|
|
158
|
+
- `TurnRequest.store`: Optional Google Interactions storage override. If omitted, Theorum does not send `store`; provider/project policy remains the authority. If supplied, Theorum serializes the explicit boolean.
|
|
159
|
+
|
|
160
|
+
### Grounding events
|
|
161
|
+
- `TurnEvent.type: 'grounding'`: Provider evidence passthrough for Google Search / Maps grounding. The event carries raw `groundingMetadata`, raw `groundingChunks`, optional search widget HTML, and lightweight `sources` for maps/web URIs. Host apps own domain-specific interpretation, such as store cards or citation display.
|
package/docs/CLI_SPEC.md
ADDED
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
# Theorum CLI & Testing Architecture Specification
|
|
2
|
+
|
|
3
|
+
## 1. Overview & Goals
|
|
4
|
+
|
|
5
|
+
The `theorum` CLI provides developer tooling, profile inspection, and stress-matrix synthesis for applications powered by Theorum.
|
|
6
|
+
|
|
7
|
+
### Core Objectives:
|
|
8
|
+
1. **Zero-Config Profile Stress Testing**: Automatically construct the most demanding valid payload (multimodal, deep reasoning, maximum tools) for any registered profile.
|
|
9
|
+
2. **Capability Matrix Permutations**: Systematically test or isolate specific capabilities (`--lite`, `--attachment`, `--voice`, `--map`, `--search`).
|
|
10
|
+
3. **Conflict Resolution**: Deterministically resolve provider conflicts (e.g. `search XOR maps`) and mutual exclusions without crashing.
|
|
11
|
+
4. **Host-Owned Execution**: The CLI never reads secrets or creates providers. Hosts supply providers when executing live tests programmatically.
|
|
12
|
+
5. **Interactive Turn REPL**: Execute ad-hoc runs only when the host has supplied a `ModelProvider`.
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## 2. Command Surface & UX
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
theorum <command> [subcommand] [flags]
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
### 2.1 `theorum test` (Profile Verification & Stress Runner)
|
|
23
|
+
|
|
24
|
+
Builds validation turns against registered profiles. Live execution requires an explicit host-provided `ModelProvider`; Theorum does not read keys or create providers.
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
# 1. Stress Combo (Default): Auto-constructs the toughest valid test for this host profile
|
|
28
|
+
theorum test --profile your-profile
|
|
29
|
+
|
|
30
|
+
# 2. Lite (Smoke ping): Minimal prompt, fast mode, tools disabled
|
|
31
|
+
theorum test --profile your-profile --lite
|
|
32
|
+
|
|
33
|
+
# 3. Explicit capability flags: Override/test specific modalities
|
|
34
|
+
theorum test --profile your-profile --attachment ./reference.png --map --voice ./audio.wav
|
|
35
|
+
|
|
36
|
+
# 4. Full matrix test: Runs every permutation of supported tools/inputs for a profile
|
|
37
|
+
theorum test --profile your-profile --matrix
|
|
38
|
+
|
|
39
|
+
# 5. Global suite: Test all registered profiles
|
|
40
|
+
theorum test --all [--lite] [--concurrency 4]
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
#### CLI Flag Matrix:
|
|
44
|
+
| Flag | Type | Description |
|
|
45
|
+
| :--- | :--- | :--- |
|
|
46
|
+
| `--profile, -p` | `string` | Target profile ID registered by the host application |
|
|
47
|
+
| `--all, -a` | `boolean` | Run test across all registered profiles |
|
|
48
|
+
| `--lite` | `boolean` | Minimal single-turn connectivity ping (fast mode, tools off, text only) |
|
|
49
|
+
| `--matrix` | `boolean` | Generate and execute all valid permutations for the profile |
|
|
50
|
+
| `--attachment` | `string[]` | Path(s) to custom attachment files (PNG, PDF, CSV, etc.) |
|
|
51
|
+
| `--voice` | `string` | Path to custom voice audio (WAV, PCM) |
|
|
52
|
+
| `--search` | `boolean` | Force Google Search tool on |
|
|
53
|
+
| `--map` | `boolean` | Force Google Maps tool on |
|
|
54
|
+
| `--mode` | `'fast' \| 'smart'` | Force reasoning / model speed tier |
|
|
55
|
+
| `--timeout` | `number` | Timeout in seconds (default: 30) |
|
|
56
|
+
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
### 2.2 `theorum run` (Terminal REPL & Ad-hoc Execution)
|
|
60
|
+
|
|
61
|
+
Interactive execution in the terminal with live streaming when called from a host that supplies a provider.
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
# Interactive REPL
|
|
65
|
+
theorum run --profile your-profile
|
|
66
|
+
|
|
67
|
+
# One-shot CLI run
|
|
68
|
+
theorum run --profile your-profile --prompt "Create a 3-tier architecture plan" --mode smart
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
---
|
|
72
|
+
|
|
73
|
+
### 2.3 `theorum profile` (Registry Inspection & Validation)
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
# List all registered profiles and supported modalities
|
|
77
|
+
theorum profile list
|
|
78
|
+
|
|
79
|
+
# Show detailed profile definition (inputs, tools, schema, model config)
|
|
80
|
+
theorum profile show your-profile
|
|
81
|
+
|
|
82
|
+
# Validate structured output schemas
|
|
83
|
+
theorum profile validate
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
---
|
|
87
|
+
|
|
88
|
+
## 3. Stress Matrix Synthesizer Logic
|
|
89
|
+
|
|
90
|
+
When `theorum test --profile <id>` is invoked without explicit flags, the synthesizer inspects `Profile` metadata:
|
|
91
|
+
|
|
92
|
+
```ts
|
|
93
|
+
interface SynthesizedTurn {
|
|
94
|
+
profile: string;
|
|
95
|
+
select?: 'fast' | 'smart';
|
|
96
|
+
input: {
|
|
97
|
+
text: string;
|
|
98
|
+
voice?: AudioFixture;
|
|
99
|
+
attachments?: AttachmentFixture[];
|
|
100
|
+
};
|
|
101
|
+
tools?: {
|
|
102
|
+
googleSearch?: boolean;
|
|
103
|
+
googleMaps?: boolean;
|
|
104
|
+
codeExecution?: boolean;
|
|
105
|
+
customTools?: string[];
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### Auto-Synthesis Rules:
|
|
111
|
+
1. **Model & Thinking Selection**:
|
|
112
|
+
- If profile supports `smart` mode $\to$ select `smart` (highest pinned thinking tokens).
|
|
113
|
+
- Otherwise $\to$ use default profile model.
|
|
114
|
+
2. **Multimodal Fixtures**:
|
|
115
|
+
- If `inputs.voice === true` $\to$ inject synthetic 16kHz PCM/WAV speech fixture.
|
|
116
|
+
- If `inputs.attachments === true` $\to$ inject synthetic test document/image matching allowed MIME types within byte ceilings (`maxBytes`).
|
|
117
|
+
3. **Tool Resolution & Conflict Handling**:
|
|
118
|
+
- Turn on all allowed tools in `profile.tools.allow`.
|
|
119
|
+
- **Mutual Exclusion Rule**: If both `googleSearch` and `googleMaps` are allowed, default to testing `googleSearch`. When `--matrix` is passed, split into two separate test runs (`[search]` and `[maps]`).
|
|
120
|
+
4. **Structured Schema Validation**:
|
|
121
|
+
- If `profile.outputs.structured` is defined, validate that the final LLM response strictly parses against the JSON schema.
|
|
122
|
+
|
|
123
|
+
---
|
|
124
|
+
|
|
125
|
+
## 4. Fixture Management (`src/cli/matrix/fixtures.ts`)
|
|
126
|
+
|
|
127
|
+
Built-in zero-dependency synthetic fixtures:
|
|
128
|
+
- **`TEST_PNG`**: 1x1 valid PNG image byte buffer (`image/png`).
|
|
129
|
+
- **`TEST_PDF`**: Minimal valid PDF 1.4 document buffer (`application/pdf`).
|
|
130
|
+
- **`TEST_WAV`**: 1-second 16kHz mono PCM/WAV audio tone (`audio/wav`).
|
|
131
|
+
- **`TEST_CSV`**: Minimal CSV table data (`text/csv`).
|
|
132
|
+
|
|
133
|
+
---
|
|
134
|
+
|
|
135
|
+
## 5. File Structure in `theorum` Package
|
|
136
|
+
|
|
137
|
+
```
|
|
138
|
+
theorum/
|
|
139
|
+
├── docs/
|
|
140
|
+
│ ├── AGENT_PROFILE_CONTRACT.md
|
|
141
|
+
│ └── CLI_SPEC.md <-- This specification
|
|
142
|
+
├── src/
|
|
143
|
+
│ ├── cli/
|
|
144
|
+
│ │ ├── index.ts # CLI Entry point & router
|
|
145
|
+
│ │ ├── commands/
|
|
146
|
+
│ │ │ ├── test.ts # 'theorum test' execution & reporting
|
|
147
|
+
│ │ │ ├── run.ts # 'theorum run' REPL & SSE streaming
|
|
148
|
+
│ │ │ └── profile.ts # 'theorum profile' inspector
|
|
149
|
+
│ │ └── matrix/
|
|
150
|
+
│ │ ├── fixtures.ts # Built-in synthetic media fixtures
|
|
151
|
+
│ │ └── synthesizer.ts # Matrix & stress combo generator
|
|
152
|
+
│ ├── guardrails/
|
|
153
|
+
│ ├── kernel/
|
|
154
|
+
│ └── observability/
|
|
155
|
+
├── deno.json # "tasks": { "theorum": "deno run -A src/cli/index.ts" }
|
|
156
|
+
└── mod.ts
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
---
|
|
160
|
+
|
|
161
|
+
## 6. Output & Reporting Contract
|
|
162
|
+
|
|
163
|
+
`theorum test` outputs concise terminal telemetry:
|
|
164
|
+
|
|
165
|
+
```
|
|
166
|
+
[THEORUM TEST] Profile: your-profile (smart mode)
|
|
167
|
+
------------------------------------------------------------
|
|
168
|
+
Inputs: [Text, Voice (1s WAV), Attachment (1 PNG)]
|
|
169
|
+
Tools: [googleSearch, codeExecution]
|
|
170
|
+
Provider: host-supplied ModelProvider
|
|
171
|
+
|
|
172
|
+
⚡ Streaming Turn Execution:
|
|
173
|
+
✓ Thinking tokens: 1,420 tokens (1.12s)
|
|
174
|
+
✓ Tool dispatched: codeExecution (0.34s)
|
|
175
|
+
✓ Schema output: 852 tokens (0.78s)
|
|
176
|
+
|
|
177
|
+
Validation Checks:
|
|
178
|
+
✓ Security Canary: Intact & Redacted
|
|
179
|
+
✓ Schema Conformity: Valid (AST JSON)
|
|
180
|
+
✓ Latency: 2.24s total
|
|
181
|
+
|
|
182
|
+
STATUS: PASSED ✓
|
|
183
|
+
```
|