@irogane/kaji 0.2.0-beta.11 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (137) hide show
  1. package/README.md +112 -551
  2. package/dist/index.d.mts +253 -0
  3. package/dist/index.mjs +440 -0
  4. package/package.json +28 -166
  5. package/contracts/README.md +0 -14
  6. package/contracts/beta-core-v1.json +0 -52
  7. package/contracts/cli/init-cases-v1.json +0 -27
  8. package/contracts/errors/error-codes.json +0 -48
  9. package/contracts/errors/integration-recovery-v1.json +0 -127
  10. package/contracts/errors/provider-normalization.json +0 -111
  11. package/contracts/events/conformance-invalid.json +0 -20
  12. package/contracts/events/conformance.json +0 -511
  13. package/contracts/events/new-kaji-event-v1.schema.json +0 -1021
  14. package/contracts/events/stored-kaji-event-v1.schema.json +0 -1025
  15. package/contracts/feature-tiers-v1.json +0 -488
  16. package/contracts/integrations/abi-index-v1.json +0 -8
  17. package/contracts/integrations/conformance-invalid.json +0 -443
  18. package/contracts/integrations/conformance-valid.json +0 -121
  19. package/contracts/integrations/copy-provenance-v1.schema.json +0 -61
  20. package/contracts/integrations/echo-tool-abi-v1.json +0 -37
  21. package/contracts/integrations/github-api-conformance-v1.json +0 -644
  22. package/contracts/integrations/github-tool-abi-typescript-v1.json +0 -369
  23. package/contracts/integrations/github-tool-abi-v1.json +0 -146
  24. package/contracts/integrations/gmail-api-conformance-v1.json +0 -750
  25. package/contracts/integrations/gmail-tool-abi-v1.json +0 -62
  26. package/contracts/integrations/index.schema.json +0 -37
  27. package/contracts/integrations/manifest.schema.json +0 -119
  28. package/contracts/parity/expected-normalized.json +0 -4906
  29. package/contracts/parity/scenarios.json +0 -100
  30. package/contracts/parity/scenarios.schema.json +0 -214
  31. package/contracts/providers/cost-conformance.json +0 -112
  32. package/contracts/release/github-proof-v1.schema.json +0 -138
  33. package/contracts/release/gmail-proof-v1.schema.json +0 -138
  34. package/contracts/release/kaji-ts-consumer-handoff-v1.schema.json +0 -1289
  35. package/contracts/release/publisher-identity-receipt-v1.schema.json +0 -319
  36. package/contracts/release/typescript-onboarding-evidence-v1.schema.json +0 -740
  37. package/contracts/tools/conformance-invalid.json +0 -271
  38. package/contracts/tools/conformance-valid.json +0 -78
  39. package/contracts/tools/tool-schema-v1.schema.json +0 -18
  40. package/dist/anthropic.cjs +0 -1231
  41. package/dist/anthropic.cjs.map +0 -1
  42. package/dist/anthropic.d.cts +0 -26
  43. package/dist/anthropic.d.ts +0 -26
  44. package/dist/anthropic.js +0 -270
  45. package/dist/anthropic.js.map +0 -1
  46. package/dist/auth.cjs +0 -1507
  47. package/dist/auth.cjs.map +0 -1
  48. package/dist/auth.d.cts +0 -129
  49. package/dist/auth.d.ts +0 -129
  50. package/dist/auth.js +0 -1039
  51. package/dist/auth.js.map +0 -1
  52. package/dist/base-B9FRMcP8.d.cts +0 -140
  53. package/dist/base-nHQd1VtS.d.ts +0 -140
  54. package/dist/chunk-AAM33KAO.js +0 -4367
  55. package/dist/chunk-AAM33KAO.js.map +0 -1
  56. package/dist/chunk-KAJ6BM64.js +0 -153
  57. package/dist/chunk-KAJ6BM64.js.map +0 -1
  58. package/dist/chunk-KCAXIOZS.js +0 -308
  59. package/dist/chunk-KCAXIOZS.js.map +0 -1
  60. package/dist/chunk-LSJ4AVO2.js +0 -243
  61. package/dist/chunk-LSJ4AVO2.js.map +0 -1
  62. package/dist/chunk-TM7ZGOJX.js +0 -716
  63. package/dist/chunk-TM7ZGOJX.js.map +0 -1
  64. package/dist/cli/bin.d.ts +0 -2
  65. package/dist/cli/bin.js +0 -13
  66. package/dist/cli/bin.js.map +0 -1
  67. package/dist/cli/chunk-2RCWPRVY.js +0 -6277
  68. package/dist/cli/chunk-2RCWPRVY.js.map +0 -1
  69. package/dist/cli/chunk-SEBX54TR.js +0 -681
  70. package/dist/cli/chunk-SEBX54TR.js.map +0 -1
  71. package/dist/cli/index.d.ts +0 -232
  72. package/dist/cli/index.js +0 -11
  73. package/dist/cli/index.js.map +0 -1
  74. package/dist/cli/init-worker.d.ts +0 -2
  75. package/dist/cli/init-worker.js +0 -18
  76. package/dist/cli/init-worker.js.map +0 -1
  77. package/dist/cli/integration-copy-worker.js +0 -48
  78. package/dist/cli/integration-copy-worker.js.map +0 -1
  79. package/dist/cli/package-entry-cjs.cjs +0 -21
  80. package/dist/cli/package-entry-cjs.cjs.map +0 -1
  81. package/dist/cli/package-entry-cjs.d.cts +0 -2
  82. package/dist/cli/package-entry.d.ts +0 -2
  83. package/dist/cli/package-entry.js +0 -13
  84. package/dist/cli/package-entry.js.map +0 -1
  85. package/dist/context-BaFHrQHv.d.cts +0 -21
  86. package/dist/context-BaFHrQHv.d.ts +0 -21
  87. package/dist/context-C-YPY-GS.d.cts +0 -1538
  88. package/dist/context-C-YPY-GS.d.ts +0 -1538
  89. package/dist/index.cjs +0 -12346
  90. package/dist/index.cjs.map +0 -1
  91. package/dist/index.d.cts +0 -1560
  92. package/dist/index.d.ts +0 -1560
  93. package/dist/index.js +0 -7852
  94. package/dist/index.js.map +0 -1
  95. package/dist/integrations/github.cjs +0 -2092
  96. package/dist/integrations/github.cjs.map +0 -1
  97. package/dist/integrations/github.d.cts +0 -21
  98. package/dist/integrations/github.d.ts +0 -21
  99. package/dist/integrations/github.js +0 -2088
  100. package/dist/integrations/github.js.map +0 -1
  101. package/dist/integrations.cjs +0 -3370
  102. package/dist/integrations.cjs.map +0 -1
  103. package/dist/integrations.d.cts +0 -202
  104. package/dist/integrations.d.ts +0 -202
  105. package/dist/integrations.js +0 -2650
  106. package/dist/integrations.js.map +0 -1
  107. package/dist/observability-Cj--OkME.d.cts +0 -96
  108. package/dist/observability-Cj--OkME.d.ts +0 -96
  109. package/dist/openai.cjs +0 -1235
  110. package/dist/openai.cjs.map +0 -1
  111. package/dist/openai.d.cts +0 -32
  112. package/dist/openai.d.ts +0 -32
  113. package/dist/openai.js +0 -272
  114. package/dist/openai.js.map +0 -1
  115. package/dist/testing.cjs +0 -554
  116. package/dist/testing.cjs.map +0 -1
  117. package/dist/testing.d.cts +0 -43
  118. package/dist/testing.d.ts +0 -43
  119. package/dist/testing.js +0 -118
  120. package/dist/testing.js.map +0 -1
  121. package/registry/echo/index.ts +0 -53
  122. package/registry/echo/manifest.json +0 -53
  123. package/registry/github/LICENSE +0 -105
  124. package/registry/github/client.ts +0 -1727
  125. package/registry/github/index.ts +0 -263
  126. package/registry/github/manifest.json +0 -227
  127. package/registry/github/owner-fixtures.json +0 -10
  128. package/registry/github/tests/github.test.ts +0 -32
  129. package/registry/gmail/LICENSE +0 -105
  130. package/registry/gmail/client.ts +0 -548
  131. package/registry/gmail/index.ts +0 -165
  132. package/registry/gmail/manifest.json +0 -102
  133. package/registry/gmail/owner-fixtures.json +0 -10
  134. package/registry/gmail/tests/gmail.test.ts +0 -32
  135. package/registry/index.json +0 -21
  136. package/registry/index.schema.json +0 -37
  137. package/registry/schema.json +0 -119
package/README.md CHANGED
@@ -1,598 +1,159 @@
1
- # Kaji (TypeScript)
1
+ # Kaji
2
2
 
3
- `kaji` is an embeddable SDK for building agents in TypeScript: import
4
- the pieces you need and compose them. The core is infra-free (no database,
5
- server, or environment configured). It mirrors the runtime core of the Python
6
- `kaji` SDK.
3
+ Kaji is an embedded execution layer for safely turning agent-requested
4
+ application actions into real state changes.
7
5
 
8
- <!-- canonical-status-links:start -->
9
- > Canonical documentation: https://github.com/enkyuan/alloy/blob/main/docs/kaji/README.md
10
- > Release status and evidence: https://github.com/enkyuan/alloy/blob/main/kaji/RELEASE_MATRIX.md
11
- <!-- canonical-status-links:end -->
12
-
13
- OpenAI is the sole beta-supported primary provider. Anthropic, Gemini, Kimi,
14
- and OpenRouter are opt-in experimental/WIP adapters with no beta compatibility
15
- or publication-proof commitment. Use `MockProvider` as the deterministic
16
- local/test default.
17
-
18
- See [**Kaji MVP**](https://github.com/enkyuan/alloy/blob/main/docs/MVP.md) for
19
- the full five-step developer path and scope definition.
20
-
21
- ## Install
22
-
23
- ```bash
24
- npm install @irogane/kaji@0.2.0-beta.11 zod openai # OpenAI
25
- # or
26
- npm install @irogane/kaji@0.2.0-beta.11 zod @anthropic-ai/sdk # Anthropic (experimental/WIP)
27
- # or: bun add @irogane/kaji@0.2.0-beta.11 zod openai
28
- ```
29
-
30
- `zod` is a required peer dependency (Zod 4). `openai` and `@anthropic-ai/sdk`
31
- are optional peers -- install only the one you use. Anthropic is not in the
32
- beta support tier. The package metadata declares Node `22.x || 24.x`, but the
33
- protected beta onboarding evidence is deliberately narrower: it proves npm and
34
- Bun on GitHub-hosted Linux/x64, with Node 22 on `ubuntu-22.04` and Node 24 on
35
- `ubuntu-24.04`. It makes no broader runtime or platform claim, including for
36
- macOS/arm64, Windows, or fully offline dependency installation. The protected
37
- compiler cells use TypeScript 5.7.3 and 6.0.3.
38
-
39
- ## Quick start
40
-
41
- First prove a text-only turn without credentials. No principal is required
42
- because this runtime has no enabled tools. Save this example as
43
- `quickstart.mts` so top-level await runs as ESM, including from npm's default
44
- CommonJS project:
45
-
46
- ```ts
47
- import { AgentBuilder } from "@irogane/kaji";
48
- import { MockProvider } from "@irogane/kaji/testing";
49
-
50
- const runtime = new AgentBuilder().provider(new MockProvider({ reply: "hello" })).build();
51
- const result = await runtime.turn("Say hello.");
52
- console.log(result.text, result.accounting);
6
+ ```text
7
+ agent / framework / direct caller
8
+
9
+ Kaji
10
+
11
+ application capability
12
+
13
+ application / external system
53
14
  ```
54
15
 
55
- ## Privileged event journal and disposal
16
+ Your application already has functions like `refundPayment()` or
17
+ `cancelBooking()`. When something outside your direct control — an
18
+ agent, an automation, an MCP tool call — can trigger one of those
19
+ functions, it needs caller identity, input validation, authorization,
20
+ approval, and protection against duplicate or ambiguous side effects.
21
+ Kaji standardizes that boundary once, inside your application, instead
22
+ of once per caller.
56
23
 
57
- > **Security boundary:** `TurnResult.events` and `AgentRuntime.history()` are a
58
- > privileged full-fidelity journal. They can contain user prompts,
59
- > provider-derived text and deltas, tool arguments, tool results, and arbitrary
60
- > metadata. They are not redaction-safe; never log, attach, or export them
61
- > wholesale.
24
+ Kaji is a TypeScript library with zero runtime dependencies. It runs in
25
+ your process no server, database, or control plane.
62
26
 
63
- `MetricsSink` and `TraceSink` are best-effort timing and correlation surfaces,
64
- not complete business or audit records. Sink failures are swallowed so
65
- observability cannot change turn behavior. Traces still contain access-controlled
66
- correlation identifiers even though they omit full event payloads.
67
-
68
- Failed turns throw: failed turns have no `TurnResult` and therefore no
69
- `TurnResult.events` or successful-turn `TurnAccounting` aggregate. Applications
70
- that need failure evidence must choose a preselected session ID, retain the
71
- caught error separately for live control flow, page history with an
72
- exclusive `afterSequence` cursor until an empty page, and reduce to an allowlist
73
- before export; generic provider failures have no durable recovery code today. Use the
74
- caught typed provider error and `normalizeProviderError()` where applicable.
75
- Always page until an empty page; a short page is not proof of exhaustion.
27
+ ## Install
76
28
 
77
- ```ts
78
- import type { AgentRuntime, StoredKajiEvent } from "@irogane/kaji";
79
-
80
- async function pageHistory(runtime: AgentRuntime, sessionId: string, limit = 128) {
81
- const events: StoredKajiEvent[] = [];
82
- let afterSequence = 0;
83
- for (;;) {
84
- const page = await runtime.history(sessionId, { afterSequence, limit });
85
- if (page.length === 0) return events;
86
- const nextSequence = page.at(-1)!.sequence;
87
- if (nextSequence <= afterSequence) throw new Error("history cursor did not advance");
88
- events.push(...page);
89
- afterSequence = nextSequence;
90
- }
91
- }
92
-
93
- const SAFE_FIELDS = [
94
- "tool_name",
95
- "tool_call_id",
96
- "error_code",
97
- "phase",
98
- "retryable",
99
- "outcome",
100
- "reason_code",
101
- "recovery_code",
102
- "doc_url",
103
- ] as const;
104
-
105
- function safeJournalEvidence(event: StoredKajiEvent): Record<string, unknown> {
106
- const safe: Record<string, unknown> = { sequence: event.sequence, type: event.type };
107
- if (event.turn_id !== undefined) safe.turn_id = event.turn_id;
108
- for (const field of SAFE_FIELDS) {
109
- const value = Reflect.get(event, field);
110
- if (value !== undefined) safe[field] = value;
111
- }
112
- return safe; // never content, delta, tool_args, result, metadata, or raw session_id
113
- }
114
-
115
- const sessionId = crypto.randomUUID();
116
- let failure: { error: unknown } | undefined;
117
- try {
118
- await runtime.turn("Investigate the failure.", { sessionId });
119
- } catch (error) {
120
- failure = { error }; // retain separately; never add this value to safe evidence
121
- }
122
-
123
- if (failure !== undefined) {
124
- stopIngress(sessionId);
125
- await runtime.drainTools(10_000);
126
- await runtime.drainProviders(10_000);
127
- try {
128
- const privilegedHistory = await pageHistory(runtime, sessionId);
129
- const exportableEvidence = privilegedHistory.map(safeJournalEvidence);
130
- sendToYourIncidentStore(exportableEvidence);
131
- } catch (evidenceError) {
132
- handleEvidenceExportError(evidenceError); // report separately; original failure stays authoritative
133
- } finally {
134
- await runtime.purgeSession(sessionId);
135
- }
136
- handleOriginalError(failure.error); // cleanup finished; preserve original control flow
137
- }
138
- ```
29
+ Kaji v0 is not yet published to npm. Publishing an install command now
30
+ would resolve to the previously published agent SDK under this name
31
+ (0.2.0-beta.11), so the command is enabled only when the v0 artifact
32
+ is published.
139
33
 
140
- Before disposal, stop ingress for the named session so another caller cannot
141
- race the drain-to-purge interval. On a live runtime, drain tools and providers,
142
- page and reduce any required evidence, then call `purgeSession(sessionId)` while
143
- leaving other sessions running. For whole-runtime shutdown, `close()` may run
144
- first to reject future turn APIs; history and purge remain callable afterward.
145
- `close()` does not delete retained history and does not cancel already-active
146
- work.
147
-
148
- This lifecycle is identical to Python's supported in-memory path. The bounded
149
- store never evicts a retained session implicitly: a full store raises
150
- `EventStoreCapacityError` until the host explicitly purges one. Runtime purge
151
- closes its old subscribers, removes the event and ID indexes, and clears every
152
- runtime owner sharing the store; a standalone raw listener must be closed by
153
- its caller. Reset cursors to `0` before reuse; the next generation begins at
154
- sequence `1`.
155
-
156
- `PurgeableEventStore.purgeSession(sessionId)` is the public one-argument
157
- store-only capability, detected by `supportsSessionPurge()`. Runtime purge also
158
- requires Kaji's internal opaque coordinated capability, so a custom store that
159
- implements only the public method fails closed at the runtime boundary. Direct
160
- store operations and new owners remain fenced throughout purge. Split delivery
161
- is unsupported because its outbox cannot cross a reused generation.
162
-
163
- TypeScript's embedded defaults are bounded, in-memory, and process-local. This
164
- beta ships no persistent event store or distributed coordinator and does not
165
- release-certify host implementations; durability, deletion, and cross-process
166
- correctness are host responsibilities. `purgeSession()` removes SDK-owned
167
- retained indexes and caches but cannot promise VM string zeroization or erase
168
- copies already sent to logs, sinks, providers, custom stores, crash dumps, or
169
- caller-owned objects.
170
-
171
- A custom `ToolIdempotencyLedger` must implement optional `releaseSettled()` to
172
- participate in purge. The event store and SDK caches are already cleared before
173
- host ledger cleanup is awaited. If that cleanup rejects, deletion cannot be
174
- rolled back; repair the host ledger and retry the named purge. If it never
175
- settles, the strong `cleanup_pending` tombstone keeps turns, direct store
176
- operations, subscriptions, and new owners fenced. A later runtime purge retries
177
- cleanup without repeating physical deletion. Kaji cannot force hostile
178
- in-process host code to settle. `TurnAccounting` remains TypeScript-only and is
179
- separate from the cross-SDK purge contract.
180
-
181
- Then set an API key and add a risk-classified tool with explicit caller
182
- identity, deadline, and cancellation:
183
-
184
- ```bash
185
- export OPENAI_API_KEY=sk-...
186
- # Experimental/WIP alternative: export ANTHROPIC_API_KEY=sk-ant-...
187
- ```
34
+ ## Minimal example
188
35
 
189
36
  ```ts
190
- import { AgentBuilder, OpenAIProvider, Integration, deadlineAfter, tool } from "@irogane/kaji";
37
+ import { capability, createKaji, knownFailure, memoryStore } from "@irogane/kaji";
191
38
  import { z } from "zod";
192
39
 
193
- class WeatherIntegration extends Integration {
194
- readonly namespace = "weather";
195
-
196
- readonly getWeather = tool(
197
- {
198
- description: "Return weather for a city.",
199
- parameters: z.object({ city: z.string() }),
200
- risk: "read",
201
- },
202
- async (args, _context) => ({ city: args.city, tempF: 68 }),
203
- );
204
- }
205
-
206
- const runtime = new AgentBuilder()
207
- .provider(new OpenAIProvider({ apiKey: process.env.OPENAI_API_KEY! }))
208
- .integration(new WeatherIntegration())
209
- .systemPrompt("You are a weather assistant.")
210
- .build();
211
-
212
- const result = await runtime.turn("Weather in Seattle?", {
213
- context: {
214
- principalId: "weather-app",
215
- deadlineAtMs: deadlineAfter(30_000),
216
- },
40
+ const RefundInput = z.object({
41
+ paymentId: z.string().min(1),
42
+ amount: z.number().positive(),
217
43
  });
218
- console.log(result.text, result.accounting);
219
- ```
220
-
221
- Swap `OpenAIProvider` for `AnthropicProvider` (and `OPENAI_API_KEY` for
222
- `ANTHROPIC_API_KEY`) to use the experimental/WIP Anthropic adapter.
223
44
 
224
- `AgentBuilder` wires a scoped `ToolRegistry` into `ToolPlanner` so integration
225
- tools are both visible to the model and executable.
45
+ const refund = capability({
46
+ name: "payments.refund",
47
+ input: RefundInput,
226
48
 
227
- ### GitHub integration
228
-
229
- The experimental TypeScript package subpath exposes a fixed-origin GitHub
230
- integration without copying its source into your application. For an
231
- investigation agent, opt into read-only exposure so the two mutation tools are
232
- not registered or sent to the model.
49
+ authorize: async ({ principalId, input }) => {
50
+ return canRefund(principalId, input.paymentId);
51
+ },
233
52
 
234
- ```ts
235
- import { AgentBuilder, OpenAIProvider, deadlineAfter } from "@irogane/kaji";
236
- import { createGithubIntegration } from "@irogane/kaji/integrations/github";
237
-
238
- const principalId = "github-investigator";
239
- const github = createGithubIntegration({
240
- repositories: ["owner/repo"],
241
- toolExposure: "read-only",
242
- tokenFor: async (context) => {
243
- if (context.signal.aborted) throw context.signal.reason;
244
- if (context.principalId !== principalId) throw new Error("GitHub credential unavailable");
245
- const token = process.env.GITHUB_TOKEN;
246
- if (!token) throw new Error("GITHUB_TOKEN is required");
247
- return token;
53
+ approval: ({ input }) => input.amount >= 500,
54
+
55
+ execute: async (input, context) => {
56
+ try {
57
+ return await payments.refund({
58
+ ...input,
59
+ idempotencyKey: context.idempotencyKey,
60
+ signal: context.signal,
61
+ });
62
+ } catch (cause) {
63
+ if (isDefinitiveProviderRejection(cause)) throw knownFailure(cause);
64
+ throw cause; // anything else stays unknown: the side effect may have committed
65
+ }
248
66
  },
249
67
  });
250
68
 
251
- const runtime = new AgentBuilder()
252
- .provider(new OpenAIProvider({ apiKey: process.env.OPENAI_API_KEY! }))
253
- .integration(github)
254
- .defaultContext({ principalId })
255
- .systemPrompt("Use GitHub evidence from owner/repo and cite immutable refs.")
256
- .build();
257
-
258
- const result = await runtime.turn("Inspect the latest failed checks.", {
259
- context: { deadlineAtMs: deadlineAfter(30_000) },
69
+ const kaji = createKaji({
70
+ store: memoryStore(),
71
+ approve: async () => ({ approved: true }),
260
72
  });
261
- console.log(result.text);
262
- github.close();
263
- ```
264
-
265
- `toolExposure` controls which tools reach the model; it does not reduce token
266
- permissions. Keep the repository allowlist narrow and use a fine-grained token
267
- with only the read permissions needed by the selected tools. The callback is
268
- lazy, receives the tool execution context, and returns the raw token; Kaji
269
- validates it and adds the `Bearer` header. `AgentBuilder` does not own the
270
- integration, so close it only after active tool work has settled. See the
271
- [GitHub integration guide](https://github.com/enkyuan/alloy/blob/main/apps/docs/content/integrations/github.mdx)
272
- for the 15-tool catalog, mutation policy, limits, and unsupported surfaces.
273
-
274
- ### Gmail integration (experimental)
275
-
276
- Gmail ships as an **experimental** catalog entry. Unlike `github`, it has no
277
- `@irogane/kaji/integrations/gmail` package subpath. You copy its source into your
278
- project and own it:
279
73
 
280
- ```bash
281
- npm exec -- kaji add gmail --allow-experimental --out ./integrations/gmail
282
- ```
283
-
284
- The copied `index.ts` exports `createGmailIntegration({ tokenFor })`, where
285
- `tokenFor` is a lazy callback that returns the OAuth access token for the
286
- `gmail.readonly` and `gmail.send` scopes. Wire it into `AgentBuilder` exactly
287
- like the GitHub integration:
288
-
289
- ```ts
290
- import { createGmailIntegration } from "./integrations/gmail";
291
-
292
- const gmail = createGmailIntegration({
293
- tokenFor: async (context) => {
294
- if (context.signal.aborted) throw context.signal.reason;
295
- const token = process.env.GMAIL_ACCESS_TOKEN;
296
- if (!token) throw new Error("GMAIL_ACCESS_TOKEN is required");
297
- return token;
298
- },
74
+ const result = await kaji.execute(refund, {
75
+ input: { paymentId: "pay_123", amount: 750 },
76
+ principalId: "user_123",
77
+ idempotencyKey: "refund_req_123",
299
78
  });
300
- // ...builder.integration(gmail)... then gmail.close() after tool work settles.
301
- ```
302
-
303
- It exposes three tools: `list_messages` (with `page_token`/`next_page_token`
304
- pagination), `get_message` (first `text/plain` body, decoded and bounded), and
305
- `send_message`, an external-effect write whose ambiguous failures surface the
306
- `gmail_mutation_unknown` recovery reason rather than risking a silent double
307
- send. The client is fixed to `https://gmail.googleapis.com`, rejects redirects,
308
- and bounds every response. See the
309
- [Gmail integration guide](https://github.com/enkyuan/alloy/blob/main/apps/docs/content/integrations/index.mdx)
310
- for scopes, the `kaji connect gmail` grant flow, and limits.
311
-
312
- `deadlineAtMs` is an absolute Unix epoch value; use `deadlineAfter()` when the
313
- caller has a duration. An earlier caller deadline can tighten, but never extend,
314
- the configured 120-second whole-turn default covering queue wait, provider open
315
- and streaming, approval, and tool work. Cooperative provider shutdown may use
316
- the additional configured cancellation grace.
317
-
318
- Catch a Kaji `ProviderError`, then call `normalizeProviderError(error)` for the
319
- redaction-safe `type`, `code`, `service`, `action`, `status`, and `retryable`
320
- fields. The normalizer accepts Kaji provider errors, not arbitrary vendor
321
- exceptions.
322
-
323
- See [`docs/kaji/production-beta.md`](https://github.com/enkyuan/alloy/blob/main/docs/kaji/production-beta.md) for
324
- the installed-package version of both first-success examples and exact default
325
- limits. Operating details are in
326
- [`concurrency-and-ordering.md`](https://github.com/enkyuan/alloy/blob/main/docs/kaji/concurrency-and-ordering.md),
327
- [`tool-contracts.md`](https://github.com/enkyuan/alloy/blob/main/docs/kaji/tool-contracts.md), and
328
- [`troubleshooting.md`](https://github.com/enkyuan/alloy/blob/main/docs/kaji/troubleshooting.md).
329
- Call `runtime.effectiveLimits()` to inspect the immutable
330
- `EffectiveRuntimeLimits` resolved for one runtime.
331
-
332
- Runtimes that share the same `EventStore` also share a default per-store turn
333
- coordinator within the current process, so same-session turns serialize even
334
- when separate builders create the runtimes. Different stores do not block one
335
- another. This is not a distributed lock: multi-process deployments must inject
336
- a `SessionTurnCoordinator` backed by shared infrastructure.
337
-
338
- ## Prove it with a model
339
-
340
- OpenAI with `gpt-5.4-mini` is the recommended first live check because it is
341
- cost-effective and exercises the SDK's Chat Completions tool path.
342
-
343
- ```bash
344
- bun --filter kaji test:quickstart
345
- OPENAI_API_KEY=... KAJI_LIVE_OPENAI_MODEL=gpt-5.4-mini \
346
- bun --filter kaji test:integration tests/integration/openai-tools.test.ts
347
79
  ```
348
80
 
349
- The live test registers a read-only probe tool, verifies the model calls it,
350
- and verifies the runtime emits final assistant text using the tool result.
351
- Keyed OpenAI proof requires protected tool loops in both SDKs on one exact
352
- commit. A missing `OPENAI_API_KEY` blocks that release evidence. Anthropic,
353
- Kimi, Gemini, and OpenRouter are experimental/WIP; the latter three are
354
- OpenAI-compatible factories rather than native provider implementations.
81
+ `canRefund()` and `payments.refund()` are your own application code.
82
+ Kaji never sees your domain logic it only governs when `execute` runs.
355
83
 
356
- For the cross-SDK release gate, run from the repository root:
84
+ ## Why Kaji
357
85
 
358
- ```bash
359
- uv run --project kaji/packages/py python kaji/scripts/beta_release_check.py
360
- ```
86
+ - **Explicit identity.** Every execution requires a caller-supplied
87
+ `principalId`. Kaji never infers one from process state.
88
+ - **Validation before anything else.** Invalid input never reaches
89
+ `authorize`, `approval`, or `execute`.
90
+ - **Duplicate suppression.** A caller-supplied `idempotencyKey` claims
91
+ one intended operation; a repeated request with the same key replays
92
+ the recorded result instead of running `execute` again.
93
+ - **Fail-closed approval.** A capability declares when approval is
94
+ required; the host application supplies how a decision is obtained;
95
+ a missing, rejected, or failed decision prevents execution.
96
+ - **Explicit failure semantics.** `succeeded`, `failed`, and `unknown`
97
+ are distinct outcomes — see below.
361
98
 
362
- This wraps Python unit/static checks, Python wheel smoke, TS unit/static/build
363
- checks, TS package smoke, mandatory pinned ast-grep boundary checks, and no-key
364
- live-gate hygiene. The ast-grep step guards the Python SDK/service boundary, core package dependency direction, removed tool-model imports, TypeScript optional provider imports, and cancellation error shape.
99
+ ## Execution outcomes
365
100
 
366
- For the live-gate credential modes specifically:
101
+ `kaji.execute()` resolves with an explicit `status` for every governed
102
+ execution; it does not throw for expected outcomes.
367
103
 
368
- ```bash
369
- uv run --project kaji/packages/py python kaji/scripts/verify_openai_loop.py
370
- KAJI_REQUIRE_LIVE_KEYS=1 uv run --project kaji/packages/py python kaji/scripts/verify_openai_loop.py
371
- ```
104
+ ```text
105
+ execute() resolves
106
+ succeeded
372
107
 
373
- Without `OPENAI_API_KEY`, the first command proves missing-key hygiene only.
374
- It is not provider evidence. The protected release mode requires
375
- `OPENAI_API_KEY` and fails when it is absent.
108
+ execute() throws knownFailure(cause)
109
+ failed
376
110
 
377
- ```bash
378
- OPENAI_API_KEY=... KAJI_LIVE_OPENAI_MODEL=gpt-5.4-mini uv run --project kaji/packages/py python kaji/scripts/verify_openai_loop.py
111
+ execute() throws or rejects with anything else
112
+ unknown
379
113
  ```
380
114
 
381
- `KAJI_RUN_KEYED_LIVE=1` is the fail-closed two-cell OpenAI proof (Python and
382
- TypeScript). It requires the OpenAI key, frozen artifact set, and exact
383
- 40-character release commit:
115
+ `unknown` means the side effect may have committed, but Kaji cannot
116
+ prove the final result a thrown error, a timeout, or a cancellation
117
+ after `execute()` has started all settle `unknown`, never `failed`.
118
+ Kaji never infers a known failure from an error's class, message, or
119
+ status code; only application code that can prove no side effect
120
+ committed may throw `knownFailure()`. See the
121
+ [execution outcomes guide](https://kaji.build/docs/concepts/outcomes)
122
+ for the full model.
384
123
 
385
- ```bash
386
- OPENAI_API_KEY=... \
387
- KAJI_RELEASE_ARTIFACTS_DIR="$PWD/.artifacts/kaji-release" \
388
- KAJI_RELEASE_COMMIT=<40-character-commit> KAJI_RUN_KEYED_LIVE=1 \
389
- uv run --project kaji/packages/py python kaji/scripts/beta_release_check.py
390
- ```
124
+ ## memoryStore()
391
125
 
392
- The protected rehearsal and publish workflows are authoritative release
393
- evidence. Their required-reviewer environments are purpose-specific:
394
- `kaji-onboarding` protects deterministic TypeScript onboarding,
395
- `kaji-release` protects keyed OpenAI proof, and `kaji-publish` protects
396
- publisher identity and the sole npm write. The single-provider command above
397
- is only a local paid smoke test.
398
-
399
- ## Stability tiers
400
-
401
- - **Stable core:** `AgentBuilder`, `AgentRuntime`, `ToolRegistry`,
402
- `ToolPlanner`, session replay, the OpenAI provider, and the in-memory
403
- event store/committer form the supported embedded-agent surface.
404
- - **Experimental providers:** Anthropic, Gemini, Kimi, and OpenRouter remain
405
- opt-in WIP surfaces without a beta compatibility or publication-proof
406
- commitment.
407
- - **Experimental Python-only:** Redis realtime/history, voice/TTS,
408
- `DocumentRAG`, native Gemini/Kimi providers, tool retrieval, and text/voice
409
- modalities exist in Python but are not production-hardened.
410
- - **TS not ported:** Redis realtime, voice/TTS, and RAG are not implemented in
411
- TypeScript. TS Gemini/Kimi remain OpenAI-compatible factories rather than
412
- native provider implementations.
413
-
414
- See https://github.com/enkyuan/alloy/blob/main/kaji/RELEASE_MATRIX.md for the
415
- cross-SDK release matrix and the exact distinction between stable core,
416
- experimental Python-only surfaces, and TypeScript surfaces that are not ported.
417
-
418
- The beta promise is the core agent loop. Redis realtime/history, voice/TTS,
419
- `DocumentRAG`, native Gemini/Kimi, and tool retrieval remain outside the beta
420
- gate until the promotion criteria in `kaji/RELEASE_MATRIX.md` are met.
421
- Gemini and Kimi are OpenAI-compatible factories in TypeScript, not native
422
- provider implementations.
423
-
424
- ## Approval handler
425
-
426
- Tools whose risk exceeds your policy threshold pause for approval before the
427
- runtime executes them. All hosts use `TypedApprovalHandler` and return an
428
- `ApprovalDecision`. `cliApprovalHandler` is a typed dev/REPL implementation
429
- that prints the tool name, risk, and arguments, then reads `y` / `N` on stdin:
126
+ `createKaji()` needs an `ExecutionStore`. `memoryStore()` is the
127
+ built-in reference implementation:
430
128
 
431
129
  ```ts
432
- import { AgentBuilder, cliApprovalHandler, openai } from "@irogane/kaji";
433
-
434
- const agent = new AgentBuilder()
435
- .provider(openai())
436
- .approvalHandler(cliApprovalHandler({ label: "agent-a" }))
437
- .build();
438
- ```
130
+ import { memoryStore } from "@irogane/kaji";
439
131
 
440
- Custom hosts implement `TypedApprovalHandler.request(call, context)` and return
441
- an `ApprovalDecision`, for example
442
- `{ granted: true, code: "approved" }` or a rejected decision with an explicit
443
- code and safe reason. See
444
- [`tool-contracts.md`](https://github.com/enkyuan/alloy/blob/main/docs/kaji/tool-contracts.md) for the lifecycle.
445
-
446
- `EventApprovalHandler` requires a non-empty turn ID and accepts a decision only
447
- when `turn_id`, `tool_call_id`, and `tool_name` all match the pending request.
448
- Unscoped or stale backlog decisions are ignored.
449
-
450
- ## CLI
451
-
452
- ```
453
- kaji --help # list subcommands
454
- kaji add <integration> # copy an integration into your project
455
- kaji add <integration> --allow-experimental # explicitly copy a non-beta template
456
- kaji init [path] --provider mock --yes # no-key TypeScript scaffold
457
- kaji list-integrations # enumerate the registry catalog
458
- kaji replay <session.jsonl> # render a stored JSONL session log
132
+ const kaji = createKaji({ store: memoryStore() });
459
133
  ```
460
134
 
461
- This is the embedded `kaji` CLI. The standalone cross-language
462
- `@irogane/kaji/cli` scaffold has its own `--lang`/`--provider` options; Python's `kaji`
463
- package also exposes additional Python-only maintenance commands.
464
- Generated projects pin dotenvx and load `.env` from their `start` script after
465
- you copy `.env.example` to `.env`.
135
+ `memoryStore()` is process-local and in-memory. It is useful for local
136
+ development, tests, and examples it is not durable and does not
137
+ coordinate across processes or instances. A production deployment needs
138
+ an `ExecutionStore` backed by durable, atomic storage; see
139
+ [custom store](https://kaji.build/docs/guides/custom-store).
466
140
 
467
- `echo` and `github` are beta catalog entries. `--allow-experimental` is
468
- required only for catalog entries still marked experimental.
141
+ ## What Kaji is not
469
142
 
470
- ## Global tool registry (advanced)
471
-
472
- For simple setups you can use the process-level registry:
473
-
474
- ```ts
475
- import { executeTool, registerTool, toolSpecFromSchema } from "@irogane/kaji";
476
- import { z } from "zod";
477
-
478
- registerTool(
479
- toolSpecFromSchema("get_weather", "Look up weather", z.object({ city: z.string() }), "read"),
480
- async (args, context) => ({
481
- principalId: context.principalId,
482
- city: args.city,
483
- tempF: 68,
484
- }),
485
- );
486
-
487
- const result = await executeTool(
488
- "get_weather",
489
- { city: "Seattle" },
490
- {
491
- principalId: "user-1",
492
- sessionId: "session-1",
493
- turnId: "turn-1",
494
- requestId: "request-1",
495
- traceId: "trace-1",
496
- toolCallId: "call-1",
497
- idempotencyKey: "session-1:call-1",
498
- signal: new AbortController().signal,
499
- metadata: {},
500
- },
501
- );
502
- ```
503
-
504
- ## What's exported
505
-
506
- | Export | What it is |
507
- | ------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ |
508
- | `EventType`, `KajiEvent` | Event discriminants and Zod-validated event union |
509
- | `EventStore`, `InMemoryEventStore` | Event log that is append-only while retained; explicit session purge is an optional lifecycle capability |
510
- | `EventBus` | In-memory pub/sub per session |
511
- | `replaySession`, `SessionManager`, session store types | Session projection and management |
512
- | `registerTool`, `ToolRegistry`, `toolSpecFromSchema`, `executeTool`, `listToolSpecs` | Tool registry (global + scoped) |
513
- | `ToolPolicy`, `ToolPlanner` | Allow/deny and approval-gated execution |
514
- | `TypedApprovalHandler`, `cliApprovalHandler`, `EventApprovalHandler`, `AutoApprovalHandler` | Structured approval handlers: stdin, event-driven (publishes `TOOL_APPROVAL_REQUESTED` for a host UI to answer), and auto-decide by policy |
515
- | `OpenAIProvider`, `AnthropicProvider` | LLM providers; Anthropic is experimental/WIP |
516
- | `normalizeProviderError`, `NormalizedProviderError` | Redaction-safe semantic classification for Kaji provider errors |
517
- | `openai`, `anthropic`, `kimi`, `gemini`, `openrouter` | One-line provider factories; only OpenAI is beta-supported |
518
- | `getProvider`, `registerProvider` | Name-keyed provider registry, for host code that resolves a provider by config string |
519
- | `generateText`, `streamText` | One-shot provider calls without a full `AgentRuntime`: a single request/response or stream, no event log |
520
- | `AgentRuntime`, `AgentBuilder`, `CancellationToken` | ReAct loop and fluent builder |
521
- | `EffectiveRuntimeLimits` | Immutable values returned by `AgentRuntime.effectiveLimits()` after overrides |
522
- | `Integration`, `tool` | Integration helper for scoped tools |
523
- | `EnvSecretSource` | Reads a named secret from `process.env`; the default `SecretSource` implementation |
524
-
525
- Events use snake_case field names (`session_id`, `tool_name`) as the wire format
526
- shared with the Python SDK.
527
-
528
- ## Python vs TypeScript parity
529
-
530
- | Feature | Python SDK | TS SDK |
531
- | ------------------------------------- | ---------------------- | --------------------------------------------------- |
532
- | Event-sourced runtime | Yes | Yes |
533
- | Tool registry + planner + policy | Yes | Yes |
534
- | `AgentBuilder` + integrations | Yes | Yes |
535
- | OpenAI provider | Yes | Yes |
536
- | Anthropic provider (experimental/WIP) | Yes | Yes |
537
- | Kimi / Gemini providers | Yes (experimental/WIP) | Yes (experimental/WIP, OpenAI-compatible factories) |
538
- | Document RAG / vector store | Yes (non-MVP) | No |
539
- | Tool retriever | Yes (non-MVP) | No |
540
- | Text modality adapter | Yes (non-MVP) | No |
541
- | Voice / TTS | Yes (non-MVP) | No |
542
- | Redis realtime bus | Yes (non-MVP) | No (in-memory only) |
543
- | CLI scaffold | Yes | Yes |
544
-
545
- ## Testing without API keys
546
-
547
- Unit and integration tests mock the provider HTTP client -- no keys needed for
548
- the default test suite:
549
-
550
- ```bash
551
- bun run test
552
- ```
553
-
554
- Live provider tests are opt-in and skip automatically when keys are absent.
555
- OpenAI is the beta-supported live path; the Anthropic command remains a WIP
556
- adapter check:
557
-
558
- ```bash
559
- OPENAI_API_KEY=... bun run test:integration
560
- OPENAI_API_KEY=... KAJI_LIVE_OPENAI_MODEL=gpt-5.4-mini \
561
- bun run test:integration tests/integration/openai-tools.test.ts
562
- ANTHROPIC_API_KEY=... bun run test:integration
563
- ```
564
-
565
- `MockProvider` is a deterministic stub that exercises the full tool loop. It is
566
- available from `@irogane/kaji/testing` for unit tests, not from the main package
567
- entrypoint used to build real agents.
568
-
569
- ```ts
570
- import { MockProvider } from "@irogane/kaji/testing";
571
- ```
572
-
573
- ## Development
574
-
575
- ```bash
576
- # from the repository root
577
- bun install
578
- bun --filter kaji typecheck
579
- bun --filter kaji format:check
580
- bun --filter kaji test
581
- bun --filter kaji build
582
- ```
143
+ Kaji does not provide an agent framework, model SDK, workflow engine,
144
+ MCP runtime, tool registry, identity provider, queue, scheduler, retry
145
+ engine, or event-sourcing system. Your application supplies identity,
146
+ authorization policy, and domain logic; Kaji only governs how a request
147
+ to run one action is validated, authorized, approved, executed once,
148
+ and recorded.
583
149
 
584
- ## Relation to the Python SDK
150
+ ## Documentation
585
151
 
586
- This package ports the **runtime core** of the Python `kaji` SDK: events,
587
- sessions, tools, providers, and the ReAct loop. Python's Redis realtime bus,
588
- RAG, and text/voice modalities are not yet ported. The Python bus can be
589
- Redis-backed for multi-process deployments; the TS `EventBus` is in-memory only.
152
+ Full documentation, guides, and the API reference are at
153
+ [kaji.build](https://kaji.build). A complete runnable proof example
154
+ lives in [`examples/refund`](https://github.com/enkyuan/kaji/tree/main/examples/refund)
155
+ in the repository.
590
156
 
591
157
  ## License
592
158
 
593
- Kaji is source-available under the
594
- [Functional Source License 1.1, ALv2 Future License](https://spdx.org/licenses/FSL-1.1-ALv2.html).
595
- It permits internal commercial use, modification, and redistribution for
596
- permitted purposes, but excludes competing commercial products and services;
597
- each version becomes Apache-2.0 after two years. FSL is not an OSI-approved
598
- open-source license.
159
+ FSL-1.1-ALv2. See [LICENSE](https://github.com/enkyuan/kaji/blob/main/LICENSE).