@1kbirds/chidori 3.6.0 → 3.7.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.
package/src/agent.ts CHANGED
@@ -15,6 +15,27 @@ export type AgentJson =
15
15
 
16
16
  export type JsonObject = { [key: string]: AgentJson };
17
17
 
18
+ /**
19
+ * Structured fields for `chidori.log`. Values are JSON; `undefined` entries are
20
+ * accepted (and simply dropped on serialization) so optional data can be logged
21
+ * without `?? null` dances.
22
+ */
23
+ export type LogFields = { [key: string]: AgentJson | undefined };
24
+
25
+ /**
26
+ * What an agent handler may return: JSON, except object members may be
27
+ * `undefined` (they are dropped when the output is serialized). This is what
28
+ * lets outcome shapes with optional fields — {@link BranchOutcome},
29
+ * {@link ActorOutcome} — be returned from an agent without `?? null` dances.
30
+ */
31
+ export type AgentOutput =
32
+ | null
33
+ | boolean
34
+ | number
35
+ | string
36
+ | AgentOutput[]
37
+ | { [key: string]: AgentOutput | undefined };
38
+
18
39
  export interface JsonSchema {
19
40
  type?: "object" | "array" | "string" | "number" | "integer" | "boolean" | "null";
20
41
  description?: string;
@@ -33,6 +54,43 @@ export interface ToolDefinition {
33
54
  parameters: JsonSchema & { type: "object" };
34
55
  }
35
56
 
57
+ /**
58
+ * A tool is nothing more than a function with a documented signature: a
59
+ * `name`, a `description`, and a JSON-schema for its `parameters`, wrapped
60
+ * around the `run` function the model actually calls. That's the whole
61
+ * contract — the signature is what the model reads to decide when and how to
62
+ * call it; the function is what runs. {@link defineTool} builds one as a
63
+ * plain object you import (or define inline) like any other code — no
64
+ * `tools/` directory, no registry.
65
+ *
66
+ * Pass handles directly in `prompt({ tools: [...] })`,
67
+ * `conversation({ tools: [...] })`, or `context().tools([...])`, freely
68
+ * mixed with registered tool names.
69
+ *
70
+ * The `run` function executes in the AGENT'S OWN VM, so closures over agent
71
+ * state and ordinary imports all work, and its side effects (fetch,
72
+ * workspace, node:fs, ...) are the captured effects every agent already
73
+ * has — journaled and deterministically replayable for free. Each model
74
+ * turn of the loop is a durable `respond()` call; each invocation is
75
+ * journaled as a `mark("tool:<name>")` record for the trace.
76
+ */
77
+ export interface ToolHandle<Args extends JsonObject = JsonObject> {
78
+ readonly __chidoriTool: true;
79
+ /** The name the model calls the tool by. */
80
+ readonly name: string;
81
+ /** What the tool does — the model reads this to decide when to call it. */
82
+ readonly description: string;
83
+ /** JSON-schema for the arguments: the documented shape of the call. */
84
+ readonly parameters: JsonSchema & { type: "object" };
85
+ /**
86
+ * The function that runs when the tool is called. The `chidori` runtime
87
+ * handle is passed in by the tool loop, but tool bodies usually just close
88
+ * over the imported `chidori`, so it is optional when you call `run`
89
+ * directly (`tool.run(args)`).
90
+ */
91
+ run(args: Args, chidori?: Chidori): AgentOutput | Promise<AgentOutput>;
92
+ }
93
+
36
94
  export type PromptStreamType = "progress" | "draft" | "subagent" | "final" | (string & {});
37
95
 
38
96
  /** Provider prompt-cache lifetime for a cached prefix. */
@@ -43,12 +101,33 @@ export interface PromptOptions {
43
101
  system?: string;
44
102
  model?: string;
45
103
  maxTokens?: number;
46
- max_tokens?: number;
47
104
  maxTurns?: number;
48
- max_turns?: number;
49
105
  temperature?: number;
50
- tools?: string[];
106
+ tools?: Array<string | ToolHandle>;
107
+ /**
108
+ * `"json"` parses the reply as JSON (a single wrapping markdown fence is
109
+ * tolerated) and, by default, THROWS when the reply is not valid JSON — a
110
+ * truncated reply (e.g. a reasoning model spending the whole `maxTokens`
111
+ * budget on hidden reasoning) can never masquerade as a successful
112
+ * structured result. Set `strict: false` for the lenient raw-string
113
+ * fallback.
114
+ */
51
115
  format?: "json" | (string & {});
116
+ /**
117
+ * Applies to `format: "json"`. `true` (the default): unparseable output
118
+ * throws with the parse error and the reply head. `false`: fall back to
119
+ * returning the raw reply string.
120
+ */
121
+ strict?: boolean;
122
+ /**
123
+ * Opt-in guard for plain-text prompts: `true` THROWS a catchable error when
124
+ * the reply is empty after trimming whitespace — a response truncated to
125
+ * nothing (e.g. a reasoning model spending the whole `maxTokens` budget on
126
+ * hidden reasoning) can never flow on as a silently empty result. Has no
127
+ * effect with `format: "json"`, where strict parsing already throws on an
128
+ * empty reply. Off by default: an empty reply resolves to `""`.
129
+ */
130
+ nonEmpty?: boolean;
52
131
  stream?: boolean;
53
132
  /**
54
133
  * Prompt-cache posture. Defaults to on (`"5m"`): the runtime marks the
@@ -64,12 +143,19 @@ export interface PromptOptions {
64
143
  export interface LlmResponseJson {
65
144
  content: string;
66
145
  blocks: AgentJson[];
67
- tool_calls: { id: string; name: string; input: AgentJson }[];
68
- stop_reason: string;
69
- input_tokens: number;
70
- output_tokens: number;
71
- cache_creation_tokens: number;
72
- cache_read_tokens: number;
146
+ /** Tool-use requests from the model. `input` is always a JSON object, so it
147
+ * can be passed straight to `chidori.tool(call.name, call.input)`. */
148
+ toolCalls: { id: string; name: string; input: JsonObject }[];
149
+ stopReason: string;
150
+ inputTokens: number;
151
+ outputTokens: number;
152
+ cacheCreationTokens: number;
153
+ cacheReadTokens: number;
154
+ /** Hidden reasoning text from reasoning models (OpenAI-compatible
155
+ * `reasoning_content`, e.g. DeepSeek). Present only when the provider
156
+ * reported it; never part of the conversation. Useful for inspecting why
157
+ * a model spent output budget before its visible answer. */
158
+ reasoning?: string;
73
159
  }
74
160
 
75
161
  /** Options for `Context.compact()` — explicit, opt-in window compaction. */
@@ -117,7 +203,7 @@ export interface CompactOptions {
117
203
  export interface Context {
118
204
  system(text: string): Context;
119
205
  /** Expose registered tools (by name, resolved like `prompt({ tools })`). */
120
- tools(names: string[]): Context;
206
+ tools(list: Array<string | ToolHandle>): Context;
121
207
  /** A large stable reference block, labelled for the trace. */
122
208
  doc(label: string, text: string): Context;
123
209
  user(text: string): Context;
@@ -150,18 +236,20 @@ export interface Context {
150
236
  estimateTokens(): number;
151
237
  }
152
238
 
153
- /** One recorded exchange in a {@link Conversation} transcript. */
154
- export interface ConversationTurn {
239
+ /** One recorded exchange in a {@link Conversation} transcript. A type alias
240
+ * (not an interface) so transcripts stay assignable to {@link AgentJson} and
241
+ * can be returned as agent output directly. */
242
+ export type ConversationTurn = {
155
243
  role: "user" | "assistant";
156
244
  text: string;
157
- }
245
+ };
158
246
 
159
247
  /** Options for `chidori.conversation()`. */
160
248
  export interface ConversationOptions {
161
249
  /** System prompt — frozen once as the conversation's cacheable prefix. */
162
250
  system?: string;
163
251
  /** Tool names available on every turn (resolved like `prompt({ tools })`). */
164
- tools?: string[];
252
+ tools?: Array<string | ToolHandle>;
165
253
  /** Default stream label for each turn's prompt (default `"final"`). */
166
254
  type?: PromptStreamType;
167
255
  /** Default model for each turn (a per-turn override still wins). */
@@ -246,18 +334,29 @@ export interface InputOptions {
246
334
  type?: string;
247
335
  default?: string;
248
336
  choices?: string[];
337
+ /**
338
+ * The artifact under review — a draft, a diff, a report — shown to the
339
+ * human alongside the question so an approval gate is never blind. The CLI
340
+ * renders it above the prompt; over HTTP it is surfaced as the paused
341
+ * session's `pending_details`. Never part of the durable record, so adding
342
+ * or editing it does not diverge an existing checkpoint.
343
+ */
344
+ details?: string;
249
345
  }
250
346
 
251
347
  /**
252
348
  * Who delivered a signal. `kind` distinguishes a human participant from a peer
253
349
  * agent; `id` is the participant identity; `runId` is set when an agent sends
254
350
  * (its own run id), so agent-to-agent coordination is attributable in the trace.
351
+ *
352
+ * A type alias (not an interface) so it stays assignable to {@link AgentJson} —
353
+ * senders routinely flow into `chidori.log` fields and agent outputs.
255
354
  */
256
- export interface SignalSender {
355
+ export type SignalSender = {
257
356
  kind: "human" | "agent";
258
357
  id: string;
259
358
  runId?: string;
260
- }
359
+ };
261
360
 
262
361
  /**
263
362
  * A named message delivered into a run mid-flight (`docs/signals.md`). The
@@ -265,11 +364,13 @@ export interface SignalSender {
265
364
  * `{ name, payload, from }` at an agent-declared listen point. Every signal is
266
365
  * recorded in the call log, so the multiplayer session replays deterministically.
267
366
  */
268
- export interface Signal<T = AgentJson> {
367
+ export type Signal<T = AgentJson> = {
269
368
  name: string;
270
369
  payload: T;
271
370
  from: SignalSender;
272
- }
371
+ /** Never set on a delivered signal — the discriminant against {@link SignalTimeout}, so `if (result.timedOut)` narrows. */
372
+ timedOut?: undefined;
373
+ };
273
374
 
274
375
  export interface SignalOptions {
275
376
  /**
@@ -285,14 +386,14 @@ export interface SignalOptions {
285
386
  * The sentinel a `timeoutMs` listen point resolves to when the deadline passes
286
387
  * with no matching delivery — a timeout resolves to this sentinel rather than
287
388
  * rejecting (`docs/signals.md`). `name` is the single awaited name, or `null`
288
- * for a multi-name `signalAny`.
389
+ * for a multi-name fan-in listen.
289
390
  */
290
- export interface SignalTimeout {
391
+ export type SignalTimeout = {
291
392
  name: string | null;
292
393
  payload: null;
293
394
  from: null;
294
395
  timedOut: true;
295
- }
396
+ };
296
397
 
297
398
  export interface ParallelOptions {
298
399
  concurrency?: number;
@@ -314,8 +415,9 @@ export interface BranchVariant {
314
415
 
315
416
  export type BranchStatus = "completed" | "paused" | "failed";
316
417
 
317
- /** The result of one branch sub-run, returned for comparison (not merged). */
318
- export interface BranchOutcome<T extends AgentJson = AgentJson> {
418
+ /** The result of one branch sub-run, returned for comparison (not merged).
419
+ * A type alias so outcomes can flow straight into agent output. */
420
+ export type BranchOutcome<T extends AgentJson = AgentJson> = {
319
421
  label: string;
320
422
  /**
321
423
  * `<parent run id>-op<branch seq>-branch-<k>` — identifies the branch
@@ -330,7 +432,7 @@ export interface BranchOutcome<T extends AgentJson = AgentJson> {
330
432
  pendingPrompt?: string;
331
433
  /** The failure message, when `status` is `"failed"`. */
332
434
  error?: string;
333
- }
435
+ };
334
436
 
335
437
  export interface BranchOptions {
336
438
  /**
@@ -347,6 +449,301 @@ export interface RetryOptions {
347
449
  backoff?: "fixed" | "exponential";
348
450
  }
349
451
 
452
+ /**
453
+ * What the runtime does when an actor iteration fails (`docs/actors.md`):
454
+ * - `"never"` (default) — the failure is the actor's final outcome;
455
+ * - `"clean"` — re-run the actor's module from scratch (fresh log, the
456
+ * spawn-time filesystem anchor, the original input);
457
+ * - `"resume"` — replay the actor's accumulated call log with the trailing
458
+ * failed records stripped, so completed work returns from cache and the
459
+ * failing call itself retries live.
460
+ */
461
+ export type ActorRestartStrategy = "never" | "clean" | "resume";
462
+
463
+ export interface SpawnActorOptions {
464
+ /** Register the actor under a name for `actors.lookup`/`actors.send` addressing. */
465
+ name?: string;
466
+ /** Restart strategy applied when an iteration fails. Default `"never"`. */
467
+ restart?: ActorRestartStrategy;
468
+ /** Restart intensity cap (default 3). */
469
+ maxRestarts?: number;
470
+ /** Base delay between restarts, doubling per attempt (default 0). */
471
+ backoffMs?: number;
472
+ /**
473
+ * How long the actor may sit at an empty-mailbox listen point with no
474
+ * explicit `timeoutMs` before the runtime parks it as a `paused` outcome
475
+ * (default 300 000 ms).
476
+ */
477
+ idleTimeoutMs?: number;
478
+ }
479
+
480
+ /** How an actor's supervision loop settled. */
481
+ export type ActorOutcomeStatus = "completed" | "failed" | "paused" | "stopped";
482
+
483
+ /** The settlement `actors.join()`/`actors.stop()` resolve to.
484
+ * A type alias so outcomes can flow straight into agent output. */
485
+ export type ActorOutcome<T extends AgentJson = AgentJson> = {
486
+ pid: string;
487
+ status: ActorOutcomeStatus;
488
+ /** The actor's return value, when `status` is `"completed"`. */
489
+ output?: T;
490
+ /** The final failure, when `status` is `"failed"`. */
491
+ error?: string;
492
+ /** What the actor is parked on, when `status` is `"paused"`. */
493
+ pendingPrompt?: string;
494
+ /** How many supervised restarts the actor consumed. */
495
+ restarts: number;
496
+ };
497
+
498
+ /**
499
+ * What a `join`/`stop` with `timeoutMs` resolves to when the deadline passes
500
+ * before the actor settles: a snapshot, not a settlement — nothing merged,
501
+ * join again later. Discriminate with `outcome.status === "running"`.
502
+ */
503
+ export type ActorStillRunning = {
504
+ pid: string;
505
+ status: "running";
506
+ restarts: number;
507
+ };
508
+
509
+ export type ActorStatus = {
510
+ pid: string;
511
+ status: ActorOutcomeStatus | "running" | "waiting" | "unknown";
512
+ restarts?: number;
513
+ /** Messages queued and not yet consumed. */
514
+ mailbox?: number;
515
+ /** The listen-point names a `"waiting"` actor is parked on. */
516
+ waitingFor?: string[];
517
+ };
518
+
519
+ /** A message consumed from a mailbox. */
520
+ export type ActorMessage<T = AgentJson> = {
521
+ name: string;
522
+ payload: T;
523
+ /** Sender identity: `id` is the sending actor's pid, or `"run"`. */
524
+ from: SignalSender;
525
+ /** Never set on a delivered message — the discriminant against {@link SignalTimeout}. */
526
+ timedOut?: undefined;
527
+ };
528
+
529
+ export interface ReceiveOptions {
530
+ /** Resolve to the `{ timedOut: true }` sentinel after this many ms. */
531
+ timeoutMs?: number;
532
+ }
533
+
534
+ export interface JoinActorOptions {
535
+ /**
536
+ * Give up waiting after this many ms: the join resolves to
537
+ * {@link ActorStillRunning} without settling the actor — join again later.
538
+ */
539
+ timeoutMs?: number;
540
+ }
541
+
542
+ /**
543
+ * A spawned actor's handle: its address plus its lifecycle methods, so code
544
+ * reads `worker.send(...)` / `worker.join()` instead of threading pid strings.
545
+ * Pure sugar — every method is one recorded durable host call, identical to
546
+ * the string-addressed forms on {@link Actors}.
547
+ */
548
+ export interface ActorHandle {
549
+ pid: string;
550
+ /** The registered name, when the spawn options provided one. */
551
+ name: string | null;
552
+ send<T extends AgentJson = AgentJson>(name: string, payload?: T): Promise<{ delivered: boolean }>;
553
+ join<T extends AgentJson = AgentJson>(): Promise<ActorOutcome<T>>;
554
+ join<T extends AgentJson = AgentJson>(
555
+ options: JoinActorOptions,
556
+ ): Promise<ActorOutcome<T> | ActorStillRunning>;
557
+ stop<T extends AgentJson = AgentJson>(): Promise<ActorOutcome<T>>;
558
+ stop<T extends AgentJson = AgentJson>(
559
+ options: JoinActorOptions,
560
+ ): Promise<ActorOutcome<T> | ActorStillRunning>;
561
+ status(): Promise<ActorStatus>;
562
+ }
563
+
564
+ /**
565
+ * `chidori.actors` — supervised, message-passing actor processes
566
+ * (`docs/actors.md`). `spawn` starts another agent module as a concurrent,
567
+ * addressable sub-run with a durable mailbox and a restart policy; actors can
568
+ * spawn their own children, forming a supervision tree (settling is
569
+ * owner-only, `"parent"` addresses the owner, and supervisors reap their
570
+ * children). Every method is a recorded durable call: a replay re-runs
571
+ * nothing.
572
+ */
573
+ export interface Actors {
574
+ spawn<TInput extends AgentJson = JsonObject>(
575
+ source: string,
576
+ input?: TInput,
577
+ options?: SpawnActorOptions,
578
+ ): Promise<ActorHandle>;
579
+ /**
580
+ * Deliver a named message to a mailbox. `to` is a pid, a registered name,
581
+ * or `"parent"` (the sender's spawner: its owning actor, or the run for a
582
+ * top-level actor). Never blocks; `delivered` is false once the target has
583
+ * settled.
584
+ */
585
+ send<T extends AgentJson = AgentJson>(
586
+ to: string,
587
+ name: string,
588
+ payload?: T,
589
+ ): Promise<{ delivered: boolean }>;
590
+ /** String-addressed `join` — see {@link ActorHandle.join}. Owner-only. */
591
+ join<T extends AgentJson = AgentJson>(target: string): Promise<ActorOutcome<T>>;
592
+ join<T extends AgentJson = AgentJson>(
593
+ target: string,
594
+ options: JoinActorOptions,
595
+ ): Promise<ActorOutcome<T> | ActorStillRunning>;
596
+ /**
597
+ * Request a cooperative stop (honored between iterations, at mailbox waits,
598
+ * and during restart backoff — a live LLM/tool call finishes first), then
599
+ * settle and merge exactly like `join`. Owner-only.
600
+ */
601
+ stop<T extends AgentJson = AgentJson>(target: string): Promise<ActorOutcome<T>>;
602
+ stop<T extends AgentJson = AgentJson>(
603
+ target: string,
604
+ options: JoinActorOptions,
605
+ ): Promise<ActorOutcome<T> | ActorStillRunning>;
606
+ /** A durable snapshot of an actor's lifecycle. */
607
+ status(target: string): Promise<ActorStatus>;
608
+ /** Registry lookup: a handle for the actor registered under `name`, or `null`. */
609
+ lookup(name: string): Promise<ActorHandle | null>;
610
+ }
611
+
612
+ export interface SpawnAgentOptions {
613
+ /** Register the agent under a name in the durable registry; generated when omitted. */
614
+ name?: string;
615
+ /** Restart strategy applied when an iteration fails. Default `"resume"`. */
616
+ restart?: ActorRestartStrategy;
617
+ /** Restart intensity cap (default 3). */
618
+ maxRestarts?: number;
619
+ /** Base delay between restarts, doubling per attempt (default 0). */
620
+ backoffMs?: number;
621
+ /**
622
+ * Default model for the agent's prompts. Defaults to the spawner's
623
+ * resolved model and travels with the agent's durable descriptor, so a
624
+ * wake in a differently-configured process (another server, another env)
625
+ * keeps running on the same model.
626
+ */
627
+ model?: string;
628
+ }
629
+
630
+ /**
631
+ * A detached agent's lifecycle status (`docs/detached-agents.md`).
632
+ * `hibernating` means parked at a listen point holding no thread and no VM,
633
+ * waiting on a mailbox delivery or an alarm deadline.
634
+ */
635
+ export type DetachedAgentStatus =
636
+ | "running"
637
+ | "hibernating"
638
+ | "paused"
639
+ | "completed"
640
+ | "failed"
641
+ | "stopped";
642
+
643
+ /** The snapshot `agents.status()`/`agents.join()` resolve to.
644
+ * A type alias so outcomes can flow straight into agent output. */
645
+ export type DetachedAgentOutcome<T extends AgentJson = AgentJson> = {
646
+ name: string;
647
+ runId: string;
648
+ status: DetachedAgentStatus;
649
+ output?: T;
650
+ error?: string;
651
+ restarts: number;
652
+ /** The listen-point names a hibernating agent wakes on. */
653
+ waitingFor?: string[] | null;
654
+ /** The alarm deadline, when the current wait carries one (ISO timestamp). */
655
+ deadline?: string | null;
656
+ };
657
+
658
+ /**
659
+ * A detached agent's handle — same sugar as {@link ActorHandle}, addressed by
660
+ * the agent's registered name. Every method is one recorded durable call.
661
+ */
662
+ export interface DetachedAgentHandle {
663
+ name: string;
664
+ runId: string | null;
665
+ send<T extends AgentJson = AgentJson>(name: string, payload?: T): Promise<{ delivered: boolean }>;
666
+ join<T extends AgentJson = AgentJson>(
667
+ options?: JoinActorOptions,
668
+ ): Promise<DetachedAgentOutcome<T>>;
669
+ stop<T extends AgentJson = AgentJson>(options?: JoinActorOptions): Promise<DetachedAgentOutcome<T>>;
670
+ status<T extends AgentJson = AgentJson>(): Promise<DetachedAgentOutcome<T>>;
671
+ }
672
+
673
+ /**
674
+ * `chidori.agents` — detached, durable, addressable agent processes
675
+ * (`docs/detached-agents.md`). Where an actor lives inside its spawning run,
676
+ * a detached agent is its OWN durable run: it has its own journal, a
677
+ * registered name that outlives the spawner, a durable mailbox, and a
678
+ * hibernate/wake lifecycle that survives process restarts. Requires
679
+ * persistence.
680
+ */
681
+ export interface DetachedAgents {
682
+ spawn<TInput extends AgentJson = JsonObject>(
683
+ source: string,
684
+ input?: TInput,
685
+ options?: SpawnAgentOptions,
686
+ ): Promise<DetachedAgentHandle>;
687
+ /** Durable delivery into the agent's mailbox; wakes a hibernating agent on a matching name. */
688
+ send<T extends AgentJson = AgentJson>(
689
+ to: string,
690
+ name: string,
691
+ payload?: T,
692
+ ): Promise<{ delivered: boolean }>;
693
+ /** Wait for the agent to settle; with `timeoutMs`, resolves to a status snapshot on expiry. */
694
+ join<T extends AgentJson = AgentJson>(
695
+ target: string,
696
+ options?: JoinActorOptions,
697
+ ): Promise<DetachedAgentOutcome<T>>;
698
+ /** Cooperative stop (a live LLM call finishes first). */
699
+ stop<T extends AgentJson = AgentJson>(
700
+ target: string,
701
+ options?: JoinActorOptions,
702
+ ): Promise<DetachedAgentOutcome<T>>;
703
+ /** Point-in-time snapshot; never blocks. */
704
+ status<T extends AgentJson = AgentJson>(target: string): Promise<DetachedAgentOutcome<T>>;
705
+ /** Registry lookup: a handle for the agent registered under `name`, or `null`. */
706
+ lookup(name: string): Promise<DetachedAgentHandle | null>;
707
+ }
708
+
709
+ /**
710
+ * `chidori.memory` — the persistent, namespaced key-value store. Values are
711
+ * JSON; `options.namespace` scopes keys (default `"default"`).
712
+ */
713
+ export interface MemoryStore {
714
+ set<T extends AgentJson = AgentJson>(key: string, value: T, options?: JsonObject): Promise<void>;
715
+ get<T extends AgentJson = AgentJson>(key: string, options?: JsonObject): Promise<T | null>;
716
+ delete(key: string, options?: JsonObject): Promise<void>;
717
+ /** List entries, optionally filtered with `options.prefix`. */
718
+ list(options?: JsonObject): Promise<AgentJson[]>;
719
+ clear(options?: JsonObject): Promise<void>;
720
+ }
721
+
722
+ /**
723
+ * `chidori.util` — in-VM control-flow helpers. Unlike everything else on the
724
+ * host object these are pure JavaScript and record NOTHING: only the durable
725
+ * calls made inside your callbacks appear in the journal.
726
+ */
727
+ export interface ChidoriUtil {
728
+ parallel<TTasks extends readonly (() => Promise<unknown>)[]>(
729
+ tasks: TTasks,
730
+ options?: ParallelOptions,
731
+ ): Promise<{ [Index in keyof TTasks]: Awaited<ReturnType<TTasks[Index]>> }>;
732
+ retry<T>(fn: () => Promise<T>, options?: RetryOptions): Promise<T>;
733
+ tryCall<T>(fn: () => Promise<T>): Promise<TryCallResult<T>>;
734
+ }
735
+
736
+ /**
737
+ * `chidori.appData` — the agent-run application-data store (generative-UI
738
+ * runs). Writes and queries execute through the host boundary (the agent
739
+ * never holds a credential) and are journaled like every other effect;
740
+ * `params` bind server-side (`$1`, `$2`, …), never string-concatenated.
741
+ */
742
+ export interface AppData {
743
+ write(sql: string, params?: AgentJson[]): Promise<AgentJson>;
744
+ query(sql: string, params?: AgentJson[]): Promise<AgentJson>;
745
+ }
746
+
350
747
  export interface TryCallResult<T> {
351
748
  ok: boolean;
352
749
  value?: T;
@@ -357,7 +754,6 @@ export interface TemplateOptions {
357
754
  source?: "file" | "inline";
358
755
  }
359
756
 
360
- export type MemoryAction = "get" | "set" | "delete" | "list" | "clear";
361
757
 
362
758
  export type WorkspaceFileStatus = "complete" | "writing" | "failed";
363
759
 
@@ -409,13 +805,21 @@ export interface RuntimePolicyConfig {
409
805
  export interface Chidori {
410
806
  workspace: WorkspaceHost;
411
807
  /** Start an immutable multi-turn prompt context (optionally seeded). */
412
- context(seed?: { system?: string; tools?: string[] }): Context;
808
+ context(seed?: { system?: string; tools?: Array<string | ToolHandle> }): Context;
413
809
  /**
414
810
  * Start a multi-turn chat assistant — a stateful wrapper over `context()`
415
811
  * that owns the running dialogue. Send turns with `chat.say(message)` or drive
416
812
  * an interactive `input()` loop with `chat.loop()`.
417
813
  */
418
814
  conversation(options?: ConversationOptions): Conversation;
815
+ /**
816
+ * `format: "json"` parses the reply as JSON before returning it, so the
817
+ * resolved value is structured data (`AgentJson`), not a string. Narrow it
818
+ * to your shape with a single cast: `await chidori.prompt(p, { format:
819
+ * "json" }) as Triage`. (With `strict: false` an unparseable reply falls
820
+ * back to the raw string — still assignable to `AgentJson`.)
821
+ */
822
+ prompt(text: string, options: PromptOptions & { format: "json" }): Promise<AgentJson>;
419
823
  prompt(text: string, options?: PromptOptions): Promise<string>;
420
824
  input(message: string, options?: InputOptions): Promise<string>;
421
825
  /**
@@ -423,10 +827,14 @@ export interface Chidori {
423
827
  * is already queued in the durable mailbox), then resolve to
424
828
  * `{ name, payload, from }`. The inverse of `input()`: the run idles cheaply
425
829
  * on disk and an outside party delivers via `POST /sessions/{id}/signal`.
830
+ * Pass an array for fan-in — the resolved signal's `name` says which fired;
831
+ * pre-arrived candidates are consumed in delivery order. Reach for `signal`
832
+ * when the delivery comes from OUTSIDE the process; use `receive` for actor
833
+ * messages.
426
834
  */
427
- signal<T = AgentJson>(name: string): Promise<Signal<T>>;
835
+ signal<T = AgentJson>(name: string | string[]): Promise<Signal<T>>;
428
836
  signal<T = AgentJson>(
429
- name: string,
837
+ name: string | string[],
430
838
  options: SignalOptions,
431
839
  ): Promise<Signal<T> | SignalTimeout>;
432
840
  /**
@@ -435,17 +843,6 @@ export interface Chidori {
435
843
  * deterministic.
436
844
  */
437
845
  pollSignal<T = AgentJson>(name: string): Promise<Signal<T> | null>;
438
- /**
439
- * Fan-in: pause until ANY of the named signals is delivered (or one is
440
- * already queued in the durable mailbox). Resolves to the bare consumed
441
- * signal — its `name` says which fired. Pre-arrived candidates are consumed
442
- * in delivery order (lowest `delivery_seq` across the whole name set).
443
- */
444
- signalAny<T = AgentJson>(names: string[]): Promise<Signal<T>>;
445
- signalAny<T = AgentJson>(
446
- names: string[],
447
- options: SignalOptions,
448
- ): Promise<Signal<T> | SignalTimeout>;
449
846
  /**
450
847
  * Durable value checkpoint: run `fn` once and journal its JSON-serializable
451
848
  * result; on replay/resume the recorded value (or error) is returned without
@@ -453,8 +850,19 @@ export interface Chidori {
453
850
  * resumed run does not re-pay it. The callback must be pure, synchronous
454
851
  * compute — host effects (`chidori.*`), captured randomness, filesystem
455
852
  * writes, timers, and async callbacks are refused inside a step.
853
+ *
854
+ * Type the result with a `type` alias, not an `interface`: interfaces lack
855
+ * the index signature `AgentJson` requires, so `interface Commit {...}`
856
+ * fails to satisfy `T extends AgentJson` while the structurally identical
857
+ * `type Commit = {...}` works.
456
858
  */
457
859
  step<T extends AgentJson = AgentJson>(name: string, fn: () => T): Promise<T>;
860
+ /**
861
+ * Run another agent module inline and return its output — a synchronous
862
+ * child call sharing this run's context and log. Of the three module
863
+ * runners: need the answer inline → `callAgent`; comparing strategies →
864
+ * `branch`; coordinating concurrent processes → `actors.spawn`.
865
+ */
458
866
  callAgent<TInput extends AgentJson = JsonObject, TOutput extends AgentJson = AgentJson>(
459
867
  path: string,
460
868
  input?: TInput,
@@ -470,28 +878,68 @@ export interface Chidori {
470
878
  variants: BranchVariant[],
471
879
  options?: BranchOptions,
472
880
  ): Promise<BranchOutcome<T>[]>;
881
+ /**
882
+ * Supervised actor processes: spawn agent modules as concurrent,
883
+ * addressable sub-runs with durable mailboxes, supervision trees, and
884
+ * restart policies (`docs/actors.md`).
885
+ */
886
+ actors: Actors;
887
+ /**
888
+ * Detached durable agent processes: spawn agent modules as long-lived,
889
+ * named runs that outlive the spawner, hibernate at listen points holding
890
+ * no thread and no VM, and wake on mailbox deliveries or alarm deadlines
891
+ * (`docs/detached-agents.md`).
892
+ */
893
+ agents: DetachedAgents;
894
+ /**
895
+ * A durable timer: hibernate until the deadline, surviving process
896
+ * restarts (the deadline is persisted and re-armed at boot). Resolves to
897
+ * the `{ timedOut: true }` sentinel when the alarm fires.
898
+ */
899
+ alarm(ms: number): Promise<SignalTimeout>;
900
+ /**
901
+ * Blocking, in-place message consumption: inside an actor, drains the
902
+ * actor's own mailbox; in the spawning run, drains messages actors sent to
903
+ * `"parent"` (and any pre-queued external signals). Unlike `signal()` —
904
+ * which pauses the whole run so an external party can resume it — `receive`
905
+ * waits in place and is woken directly by in-process senders. Reach for
906
+ * `receive` for actor messages; use `signal` for external deliveries.
907
+ *
908
+ * Actor death is observable: an actor that settles `failed` (restart
909
+ * budget spent) or `paused` delivers a monitor message named
910
+ * `"__chidori.down__"` to its owner's mailbox with payload
911
+ * `{ pid, name, status, error?, pendingPrompt?, restarts }` — include it in
912
+ * a fan-in (`receive(["result", "__chidori.down__"])`) so a collection
913
+ * loop reacts to worker death instead of waiting out its timeout. As a
914
+ * backstop, a `receive` (even with `timeoutMs`) fails fast once every
915
+ * spawned actor has settled and nothing matching is queued — nothing
916
+ * in-process can deliver anymore, so blocking would be pure starvation.
917
+ */
918
+ receive<T = AgentJson>(names: string | string[]): Promise<ActorMessage<T>>;
919
+ receive<T = AgentJson>(
920
+ names: string | string[],
921
+ options: ReceiveOptions,
922
+ ): Promise<ActorMessage<T> | SignalTimeout>;
473
923
  tool<TArgs extends JsonObject = JsonObject, TResult extends AgentJson = AgentJson>(
474
924
  name: string,
475
925
  args?: TArgs,
476
926
  ): Promise<TResult>;
477
- parallel<TTasks extends readonly (() => Promise<unknown>)[]>(
478
- tasks: TTasks,
479
- options?: ParallelOptions,
480
- ): Promise<{ [Index in keyof TTasks]: Awaited<ReturnType<TTasks[Index]>> }>;
481
- retry<T>(fn: () => Promise<T>, options?: RetryOptions): Promise<T>;
482
- tryCall<T>(fn: () => Promise<T>): Promise<TryCallResult<T>>;
927
+ /** In-VM control-flow helpers (`parallel`, `retry`, `tryCall`) pure JS, never recorded. */
928
+ util: ChidoriUtil;
483
929
  template(pathOrText: string, vars?: JsonObject, options?: TemplateOptions): Promise<string>;
484
- log(message: string, fields?: JsonObject): Promise<void>;
485
- memory<T extends AgentJson = AgentJson>(
486
- action: MemoryAction,
487
- key?: string,
488
- value?: T,
489
- options?: JsonObject,
490
- ): Promise<T | AgentJson[] | null>;
491
- checkpoint(label?: string, data?: AgentJson): Promise<void>;
930
+ log(message: string, fields?: LogFields): Promise<void>;
931
+ /** The persistent, namespaced key-value store. */
932
+ memory: MemoryStore;
933
+ /** The journaled agent-run application-data store (generative-UI runs). */
934
+ appData: AppData;
935
+ /**
936
+ * Record a labelled trace marker in the call log — an annotation, nothing
937
+ * more. (The durable VALUE checkpoint is `chidori.step`.)
938
+ */
939
+ mark(label?: string, data?: AgentJson): Promise<void>;
492
940
  }
493
941
 
494
- export type AgentFunction<TInput extends AgentJson = JsonObject, TOutput extends AgentJson = AgentJson> = (
942
+ export type AgentFunction<TInput extends AgentJson = JsonObject, TOutput extends AgentOutput = AgentOutput> = (
495
943
  input: TInput,
496
944
  ) => TOutput | Promise<TOutput>;
497
945
 
@@ -537,7 +985,7 @@ export const chidori: Chidori = new Proxy({} as Chidori, {
537
985
  * run(async (input) => ({ greeting: `hello ${input.name}` }));
538
986
  * ```
539
987
  */
540
- export function run<TInput extends AgentJson = JsonObject, TOutput extends AgentJson = AgentJson>(
988
+ export function run<TInput extends AgentJson = JsonObject, TOutput extends AgentOutput = AgentOutput>(
541
989
  handler: AgentFunction<TInput, TOutput>,
542
990
  ): void {
543
991
  void handler;
@@ -546,3 +994,39 @@ export function run<TInput extends AgentJson = JsonObject, TOutput extends Agent
546
994
  "replaced when an agent runs under chidori.",
547
995
  );
548
996
  }
997
+
998
+ /**
999
+ * Wrap a function into a {@link ToolHandle} by attaching its documented
1000
+ * signature — a `name`, a `description`, and a JSON-schema for its
1001
+ * `parameters`. That signature is all the model needs to call the function;
1002
+ * the function itself is just code. No `tools/` directory, no registry —
1003
+ * import (or define inline) the handle like any other value. `parameters`
1004
+ * defaults to an empty object schema; `description` to `""`. Inside the
1005
+ * runtime this import is replaced by the live implementation, exactly like
1006
+ * `run` and `chidori`.
1007
+ *
1008
+ * ```ts
1009
+ * import { chidori, run, defineTool } from "chidori:agent";
1010
+ *
1011
+ * const search = defineTool({
1012
+ * name: "search",
1013
+ * description: "Search the corpus.",
1014
+ * parameters: { type: "object", properties: { q: { type: "string" } }, required: ["q"] },
1015
+ * run: async ({ q }) => (await fetch(BASE + q)).json(),
1016
+ * });
1017
+ *
1018
+ * run(async () => chidori.prompt("find chidori", { tools: [search], maxTurns: 6 }));
1019
+ * ```
1020
+ */
1021
+ export function defineTool<Args extends JsonObject = JsonObject>(def: {
1022
+ name: string;
1023
+ description?: string;
1024
+ parameters?: JsonSchema & { type: "object" };
1025
+ run(args: Args, chidori?: Chidori): AgentOutput | Promise<AgentOutput>;
1026
+ }): ToolHandle<Args> {
1027
+ void def;
1028
+ throw new Error(
1029
+ "defineTool() is only available inside the chidori runtime; this import " +
1030
+ "is replaced when an agent runs under chidori.",
1031
+ );
1032
+ }