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.
Files changed (87) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +338 -0
  3. package/docs/AGENT_PROFILE_CONTRACT.md +161 -0
  4. package/docs/CLI_SPEC.md +183 -0
  5. package/docs/SECRETS.md +55 -0
  6. package/esm/_dnt.polyfills.d.ts +11 -0
  7. package/esm/_dnt.polyfills.js +15 -0
  8. package/esm/_dnt.shims.d.ts +5 -0
  9. package/esm/_dnt.shims.js +61 -0
  10. package/esm/mod.d.ts +37 -0
  11. package/esm/mod.js +35 -0
  12. package/esm/package.json +3 -0
  13. package/esm/src/guardrails/error.d.ts +35 -0
  14. package/esm/src/guardrails/error.js +116 -0
  15. package/esm/src/guardrails/injection.d.ts +12 -0
  16. package/esm/src/guardrails/injection.js +220 -0
  17. package/esm/src/guardrails/keys.d.ts +12 -0
  18. package/esm/src/guardrails/keys.js +132 -0
  19. package/esm/src/guardrails/mod.d.ts +14 -0
  20. package/esm/src/guardrails/mod.js +14 -0
  21. package/esm/src/guardrails/sanitize.d.ts +22 -0
  22. package/esm/src/guardrails/sanitize.js +133 -0
  23. package/esm/src/guardrails/sensitive.d.ts +12 -0
  24. package/esm/src/guardrails/sensitive.js +88 -0
  25. package/esm/src/kernel/engine/boundary.d.ts +10 -0
  26. package/esm/src/kernel/engine/boundary.js +55 -0
  27. package/esm/src/kernel/engine/delta.d.ts +8 -0
  28. package/esm/src/kernel/engine/delta.js +362 -0
  29. package/esm/src/kernel/engine/hash.d.ts +1 -0
  30. package/esm/src/kernel/engine/hash.js +9 -0
  31. package/esm/src/kernel/engine/record.d.ts +2 -0
  32. package/esm/src/kernel/engine/record.js +7 -0
  33. package/esm/src/kernel/engine/repair.d.ts +9 -0
  34. package/esm/src/kernel/engine/repair.js +38 -0
  35. package/esm/src/kernel/engine/runner.d.ts +14 -0
  36. package/esm/src/kernel/engine/runner.js +731 -0
  37. package/esm/src/kernel/engine/tree.d.ts +2 -0
  38. package/esm/src/kernel/engine/tree.js +17 -0
  39. package/esm/src/kernel/mod.d.ts +16 -0
  40. package/esm/src/kernel/mod.js +15 -0
  41. package/esm/src/kernel/registry/catalog.d.ts +24 -0
  42. package/esm/src/kernel/registry/catalog.js +213 -0
  43. package/esm/src/kernel/registry/profiles.d.ts +36 -0
  44. package/esm/src/kernel/registry/profiles.js +111 -0
  45. package/esm/src/kernel/registry/resolve.d.ts +20 -0
  46. package/esm/src/kernel/registry/resolve.js +235 -0
  47. package/esm/src/kernel/registry/schemas.d.ts +14 -0
  48. package/esm/src/kernel/registry/schemas.js +23 -0
  49. package/esm/src/kernel/registry/tools.d.ts +12 -0
  50. package/esm/src/kernel/registry/tools.js +36 -0
  51. package/esm/src/kernel/types.d.ts +497 -0
  52. package/esm/src/kernel/types.js +10 -0
  53. package/esm/src/observability/mod.d.ts +12 -0
  54. package/esm/src/observability/mod.js +10 -0
  55. package/esm/src/observability/spans.d.ts +16 -0
  56. package/esm/src/observability/spans.js +56 -0
  57. package/esm/src/observability/trace-attach.d.ts +16 -0
  58. package/esm/src/observability/trace-attach.js +81 -0
  59. package/esm/src/observability/trace-record.d.ts +112 -0
  60. package/esm/src/observability/trace-record.js +140 -0
  61. package/esm/src/observability/trace-usage.d.ts +3 -0
  62. package/esm/src/observability/trace-usage.js +32 -0
  63. package/esm/src/observability/trace.d.ts +23 -0
  64. package/esm/src/observability/trace.js +121 -0
  65. package/esm/src/providers/attachments.d.ts +17 -0
  66. package/esm/src/providers/attachments.js +156 -0
  67. package/esm/src/providers/gemini-tape.d.ts +3 -0
  68. package/esm/src/providers/gemini-tape.js +46 -0
  69. package/esm/src/providers/google-tap.d.ts +3 -0
  70. package/esm/src/providers/google-tap.js +48 -0
  71. package/esm/src/providers/interactions.d.ts +5 -0
  72. package/esm/src/providers/interactions.js +127 -0
  73. package/esm/src/providers/media.d.ts +5 -0
  74. package/esm/src/providers/media.js +125 -0
  75. package/esm/src/providers/mod.d.ts +15 -0
  76. package/esm/src/providers/mod.js +13 -0
  77. package/esm/src/providers/openrouter-payload.d.ts +24 -0
  78. package/esm/src/providers/openrouter-payload.js +177 -0
  79. package/esm/src/providers/openrouter.d.ts +17 -0
  80. package/esm/src/providers/openrouter.js +332 -0
  81. package/esm/src/providers/provider.d.ts +13 -0
  82. package/esm/src/providers/provider.js +123 -0
  83. package/esm/src/providers/sse.d.ts +7 -0
  84. package/esm/src/providers/sse.js +53 -0
  85. package/esm/src/providers/tts.d.ts +24 -0
  86. package/esm/src/providers/tts.js +144 -0
  87. 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.
@@ -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
+ ```