@rowan-agent/agent 0.6.0 → 0.6.1

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 (4) hide show
  1. package/README.md +132 -55
  2. package/dist/index.d.ts +209 -145
  3. package/dist/index.js +455 -175
  4. package/package.json +2 -2
package/README.md CHANGED
@@ -20,14 +20,13 @@ or reconstruct an `Agent`, submit input with `send()`, and wait on the returned
20
20
  import {
21
21
  AgentRuntime,
22
22
  InMemoryRuntimeStateStore,
23
- InMemorySessionProvider,
23
+ InMemorySessionStore,
24
24
  createCoreTools,
25
25
  } from "@rowan-agent/agent";
26
- import { createModelStream } from "@rowan-agent/models";
27
26
 
28
27
  const runtime = await AgentRuntime.start({
29
28
  stateStore: new InMemoryRuntimeStateStore(),
30
- sessionProvider: new InMemorySessionProvider(),
29
+ sessionProvider: new InMemorySessionStore(),
31
30
  });
32
31
 
33
32
  try {
@@ -42,8 +41,13 @@ try {
42
41
  entryPhaseId: default,
43
42
  },
44
43
  },
45
- model: { provider: "openai", id: "gpt-4.1-mini" },
46
- stream: createModelStream(),
44
+ model: {
45
+ provider: "openai",
46
+ id: "gpt-4.1-mini",
47
+ protocol: "openai-completions",
48
+ baseUrl: "https://api.openai.com/v1",
49
+ apiKey: process.env.OPENAI_API_KEY!,
50
+ },
47
51
  });
48
52
 
49
53
  agent.subscribe((event) => console.log(event.type));
@@ -67,11 +71,17 @@ Exactly one `AgentRuntime` may be active in a process. It is the sole owner of
67
71
  Agent creation and reconstruction, durable input, scheduling, leases, Runtime
68
72
  Events, and Tool Call control. Always stop it during host shutdown.
69
73
 
74
+ ```ts
75
+ type RuntimeEventConsumer = {
76
+ caughtUp: Promise<void>;
77
+ stop(): void;
78
+ };
79
+ ```
80
+
70
81
  ```ts
71
82
  type AgentRuntimeOptions = {
72
83
  stateStore: InMemoryRuntimeStateStore | SqliteRuntimeStateStore;
73
- sessionProvider?: InMemorySessionProvider | LocalJsonlSessionProvider;
74
- factories?: ReadonlyMap<string, AgentFactory> | Readonly<Record<string, AgentFactory>>;
84
+ sessionProvider?: InMemorySessionStore | JsonlSessionStore;
75
85
  toolPolicy?: ToolRuntimePolicy;
76
86
  maxConcurrentRuns?: number;
77
87
  maxInfrastructureAttempts?: number;
@@ -81,15 +91,20 @@ type AgentRuntimeOptions = {
81
91
 
82
92
  class AgentRuntime {
83
93
  static start(options: AgentRuntimeOptions): Promise<AgentRuntime>;
84
- createAgent(options: AgentCreateOptions): Promise<Agent>;
94
+ createAgent(options: AgentOptions): Promise<Agent>;
85
95
  reconstructAgent(agentId: AgentId, options: AgentOptions): Promise<Agent>;
86
96
  pauseAgent(agentId: AgentId): Promise<void>;
87
97
  resumeAgent(agentId: AgentId): Promise<void>;
98
+ getMessage(messageId: RuntimeMessageId): Promise<RuntimeMessage | undefined>;
99
+ getToolCall(toolCallId: RuntimeToolCallId): Promise<RuntimeToolCall | undefined>;
100
+ getRun(runId: AgentRunId): Promise<AgentRunRecord | undefined>;
101
+ listRuns(input?: { agentId?: AgentId; states?: AgentRunRecord["state"][] }): Promise<AgentRunRecord[]>;
102
+ listActiveRuns(): Promise<AgentRunRecord[]>;
88
103
  abortRun(runId: AgentRunId, reason?: string): Promise<void>;
89
104
  consumeEvents(
90
105
  consumerId: string,
91
- listener: (event: RuntimeEvent) => void | Promise<void>,
92
- ): () => void;
106
+ listener: RuntimeEventListener,
107
+ ): RuntimeEventConsumer;
93
108
  listEvents(cursor?: RuntimeEventCursor): Promise<RuntimeEvent[]>;
94
109
  stop(): Promise<void>;
95
110
  }
@@ -101,7 +116,7 @@ Runtime State and conversation history are deliberately separate:
101
116
  | Concern | Durable adapter | In-memory adapter |
102
117
  |---------|-----------------|-------------------|
103
118
  | Agent records, Messages, Runs, Leases, Runtime Events, Tool Calls | `SqliteRuntimeStateStore` | `InMemoryRuntimeStateStore` |
104
- | Conversation messages, model transcripts, Outcomes | `LocalJsonlSessionProvider` | `InMemorySessionProvider` |
119
+ | Conversation messages, model transcripts, Outcomes | `JsonlSessionStore` | `InMemorySessionStore` |
105
120
 
106
121
  The SQLite Runtime schema has no compatibility migration. Replace an older
107
122
  Runtime database when adopting a breaking schema; Session JSONL records remain
@@ -119,35 +134,20 @@ The Runtime renews active leases and retries them up to
119
134
  `maxInfrastructureAttempts`; exhausted work fails and its triggering Message is
120
135
  dead-lettered.
121
136
 
122
- ### Factory Recovery
137
+ ### Process Recovery
123
138
 
124
- An optional opaque Factory ID lets the Runtime reconstruct active Agents after
125
- a restart. The Factory supplies current executable resources; Rowan persists
126
- the Agent and Session identities, not model clients, Tools, Phases, or
127
- Extensions.
139
+ Runtime startup recovers abandoned Leases into durable queued work without
140
+ constructing Agent Bindings. The host supplies its current executable resources
141
+ when it reconstructs an Agent:
128
142
 
129
143
  ```ts
130
- const factoryId = "coding-agent";
131
- const factories = new Map([
132
- [factoryId, async () => currentAgentOptions],
133
- ]);
134
-
135
- const runtime = await AgentRuntime.start({
136
- stateStore,
137
- sessionProvider,
138
- factories,
139
- });
140
-
141
- const agent = await runtime.createAgent({
142
- ...currentAgentOptions,
143
- factoryId,
144
- });
144
+ const runtime = await AgentRuntime.start({ stateStore, sessionProvider });
145
+ const agent = await runtime.reconstructAgent(agentId, currentAgentOptions);
145
146
  ```
146
147
 
147
- On the next `AgentRuntime.start()` with the same durable adapters and Factory,
148
- the active Agent is reconstructed with its original Agent ID and Session ID.
149
- Missing or declining Factories leave the Agent unbound and emit a durable
150
- Runtime Event.
148
+ Reconstruction preserves the Agent ID and Session ID. Establishing the Binding
149
+ automatically schedules queued Runs. A suspended Agent may remain unbound until
150
+ the host has new input, then reconstruct before calling `send()`.
151
151
 
152
152
  ## Agent
153
153
 
@@ -174,10 +174,18 @@ class Agent {
174
174
  ### AgentOptions
175
175
 
176
176
  ```ts
177
- type AgentOptions = {
177
+ type ModelConfig = ModelRef & {
178
+ protocol: Protocol;
179
+ baseUrl: string;
180
+ apiKey: string;
181
+ headers?: Record<string, string>;
182
+ timeoutMs?: number;
183
+ maxRetries?: number;
184
+ retryDelayMs?: number;
185
+ };
186
+
187
+ type AgentCommonOptions = {
178
188
  context: AgentContext;
179
- model: LlmModelRef;
180
- stream: StreamFn;
181
189
  cwd?: string;
182
190
  extensions?: LoadedExtension[];
183
191
  maxAttempts?: number;
@@ -185,17 +193,15 @@ type AgentOptions = {
185
193
  // Lifecycle hooks
186
194
  beforeToolCall?: BeforeToolCall;
187
195
  afterToolCall?: AfterToolCall;
188
- onModelTranscript?: (transcript: ModelTranscript, meta: { phase: string; model: LlmModelRef }) => Promise<void>;
196
+ onModelTranscript?: (transcript: ModelTranscript, meta: { phase: string; model: ModelRef }) => Promise<void>;
189
197
  onMessage?: (message: AgentMessage) => Promise<void>;
190
198
  onOutcome?: (outcome: Outcome) => Promise<void>;
191
199
  };
192
200
 
193
- type AgentCreateOptions = AgentOptions & {
194
- // Seeds the Session; it does not schedule a Run.
195
- input?: string;
196
- // Enables automatic reconstruction through a registered Factory.
197
- factoryId?: string;
198
- };
201
+ type AgentOptions = AgentCommonOptions & (
202
+ | { model: ModelConfig; stream?: never }
203
+ | { model: ModelRef; stream: StreamFn }
204
+ );
199
205
  ```
200
206
 
201
207
  ### Conversation Continuation
@@ -214,6 +220,11 @@ console.log((await second.result()).message);
214
220
  If a Run is suspended waiting for input, the next `send()` to that Agent resumes
215
221
  the same Run instead of creating a second one.
216
222
 
223
+ Suspended Runs persist the current input request in `AgentRunRecord.inputRequest`
224
+ so a reconstructed Runtime can show the question without replaying a transient
225
+ Agent Event. The request contains its phase, prompt, and timestamp; it is cleared
226
+ when the Run resumes.
227
+
217
228
  ### Updating Runtime Resources
218
229
 
219
230
  Resources are fixed for a live Agent Binding. Apply a new model, prompt, Tool
@@ -228,8 +239,7 @@ await runtime.stop();
228
239
  const nextRuntime = await AgentRuntime.start({ stateStore, sessionProvider });
229
240
  const reconstructed = await nextRuntime.reconstructAgent(agentId, {
230
241
  context: currentContext,
231
- model: { provider: "openai", id: "gpt-4.1" },
232
- stream: createModelStream(),
242
+ model: currentModelConfig,
233
243
  });
234
244
  ```
235
245
 
@@ -240,18 +250,25 @@ durable. The handle exposes cached state for synchronous inspection and can
240
250
  refresh from the Runtime Store when needed.
241
251
 
242
252
  ```ts
253
+ type AgentInputRequest = {
254
+ phase: string;
255
+ prompt: string;
256
+ requestedAt: string;
257
+ };
258
+
243
259
  class AgentRun {
244
260
  readonly id: AgentRunId;
245
261
  readonly messageId: string;
246
262
  readonly status: AgentRunState;
247
263
  readonly state: AgentRunState; // alias of status
264
+ readonly inputRequest?: AgentInputRequest;
248
265
 
249
266
  getStatus(): Promise<AgentRunState>;
250
267
  subscribe(listener: AgentRunListener): () => void;
251
268
  consumeRuntimeEvents(
252
269
  consumerId: string,
253
270
  listener: (event: RuntimeEvent) => void | Promise<void>,
254
- ): () => void;
271
+ ): RuntimeEventConsumer;
255
272
  result(): Promise<Outcome>;
256
273
  abort(reason?: string): Promise<void>;
257
274
  }
@@ -377,7 +394,7 @@ const runtime = await AgentRuntime.start({
377
394
  stateStore,
378
395
  sessionProvider,
379
396
  toolPolicy: {
380
- allowedTools: ["read", "bash"],
397
+ allowedTools: ["read", "bash", "task_manage", "resource_manage"],
381
398
  maxConcurrent: 8,
382
399
  perToolMaxConcurrent: { bash: 2 },
383
400
  },
@@ -416,9 +433,58 @@ is asynchronous, so a slow or unavailable consumer does not block state
416
433
  transitions.
417
434
 
418
435
  ```ts
419
- const stopConsuming = runtime.consumeEvents("deployment-observer", async (event) => {
436
+ const consumer = runtime.consumeEvents("deployment-observer", async (event) => {
420
437
  await deliverRuntimeFact(event);
421
438
  });
439
+ await consumer.caughtUp;
440
+ ```
441
+
442
+ Runtime Messages and their related Events are committed together. A durable
443
+ consumer can use a `run_enqueued` Event as an outbox signal, read immutable
444
+ business correlation metadata from its Message, and update an external index
445
+ before its Checkpoint advances:
446
+
447
+ ```ts
448
+ const indexConsumer = runtime.consumeEvents("everyield-run-index", async (event) => {
449
+ if (event.kind !== "run_enqueued" || !event.messageId || !event.runId) return;
450
+ const message = await runtime.getMessage(event.messageId);
451
+ if (!message) throw new Error(`Runtime Message not found: ${event.messageId}`);
452
+ await upsertRunIndex(event.runId, message.input.metadata);
453
+ });
454
+ await indexConsumer.caughtUp;
455
+ ```
456
+
457
+ If the listener fails, Rowan leaves the Checkpoint unchanged and redelivers the
458
+ Event after the consumer restarts. `getMessage()` is read-only; Rowan treats the
459
+ Agent Message metadata as opaque host data.
460
+
461
+ Tool Call Events can likewise be resolved to their durable record. This is
462
+ especially useful when a `tool_call_indeterminate` Event requires host recovery
463
+ or human review:
464
+
465
+ ```ts
466
+ if (event.kind === "tool_call_indeterminate" && event.toolCallId) {
467
+ const toolCall = await runtime.getToolCall(event.toolCallId);
468
+ await reviewIndeterminateToolCall(toolCall);
469
+ }
470
+ ```
471
+
472
+ A consumer may instead return an `enqueue` disposition to turn the current
473
+ Event into Agent Input. Rowan enqueues the input and advances the Consumer
474
+ Checkpoint in one Runtime State transaction, then schedules the target Agent.
475
+
476
+ ```ts
477
+ const routingConsumer = runtime.consumeEvents("delegated-results", (event) => {
478
+ if (event.kind !== "run_completed" || !event.agentId) return;
479
+ return {
480
+ type: "enqueue",
481
+ agentId: targetAgentId,
482
+ input: createMessage("user", JSON.stringify(event.payload), {
483
+ sourceEventId: event.id,
484
+ }),
485
+ };
486
+ });
487
+ await routingConsumer.caughtUp;
422
488
  ```
423
489
 
424
490
  Only one live subscription may use a Consumer ID. If delivery fails, its
@@ -427,6 +493,15 @@ started later. `runtime.listEvents()` inspects the durable stream without
427
493
  advancing a Consumer Checkpoint. Use `run.consumeRuntimeEvents()` for the same
428
494
  delivery contract filtered to one Run.
429
495
 
496
+ `AgentRunMetadata` is optional opaque host data on `AgentMessage.metadata`. It
497
+ is persisted with the Run and echoed on `run_enqueued`, suspension, completion,
498
+ and abort Event payloads. `listActiveRuns()` returns queued, running, and
499
+ suspended Runs for host-side Agent reconstruction with current `AgentOptions`.
500
+ Use the returned `consumer.caughtUp` Promise when startup must wait until all
501
+ Events through the durable Consumer checkpoint have been delivered before
502
+ recovery continues. Call `consumer.stop()` during shutdown. `run.consumeRuntimeEvents()`
503
+ returns the same handle shape.
504
+
430
505
  ### Parallel Phase Events
431
506
 
432
507
  When multiple phases run concurrently (via multi-target `route`), each branch emits its own `turn_*`, `message_*`, and `tool_execution_*` events into the shared event stream — they are interleaved, not sequenced. Individual parallel phases do **not** emit `phase_start`/`phase_end`; those only fire for serial phases. After all branches complete, their outputs are stashed and surfaced in the next iteration's phase entry message (under `<prev_phase_outputs>`); the `message_start`/`message_end` you observe for that entry message carry the merged results.
@@ -436,9 +511,9 @@ When multiple phases run concurrently (via multi-target `route`), each branch em
436
511
  JSONL-based session persistence — lets multi-turn conversations survive across process restarts. Supports create, resume, branch, and history replay.
437
512
 
438
513
  ```ts
439
- import { LocalJsonlSessionProvider } from "@rowan-agent/agent";
514
+ import { JsonlSessionStore } from "@rowan-agent/agent";
440
515
 
441
- const sessions = new LocalJsonlSessionProvider(sessionsDir);
516
+ const sessions = new JsonlSessionStore(sessionsDir);
442
517
  const session = await sessions.create({
443
518
  systemPrompt,
444
519
  input: "",
@@ -665,7 +740,7 @@ export default function myPlugin(rowan: ExtensionAPI) {
665
740
 
666
741
  ## Model Selection
667
742
 
668
- `AgentOptions` accepts a model reference and stream implementation from `@rowan-agent/models`. Phase frontmatter may override the model for that phase.
743
+ `AgentOptions` accepts either one complete `ModelConfig`, which Rowan binds to an Agent-local default stream, or a model reference plus a custom `StreamFn`. A phase model override therefore requires a custom stream that can resolve the override.
669
744
 
670
745
  CLI-specific `.rowan/config.yaml` loading and workspace discovery belong to [`@rowan-agent/cli`](../cli/README.md).
671
746
 
@@ -691,6 +766,8 @@ type LoopMetrics = {
691
766
  | `AgentRun` | Durable Run handle for state, terminal Outcome, observation, and abort |
692
767
  | `SqliteRuntimeStateStore` / `InMemoryRuntimeStateStore` | Durable and test Runtime Store adapters |
693
768
  | `RuntimeEvent` / consumer ID string | Durable lifecycle facts and checkpointed consumer identity |
769
+ | `RuntimeMessage` / `RuntimeMessageId` | Durable Agent Input and its stable lookup identity |
770
+ | `RuntimeToolCall` / `RuntimeToolCallId` | Durable Tool Call state and its stable lookup identity |
694
771
  | `AgentContext` | System prompt, messages, tools, skills, phases |
695
772
  | `AgentMessage` | Typed message with role, content, metadata |
696
773
  | `AgentEvent` | Discriminated union of 13 event types |
@@ -702,9 +779,9 @@ type LoopMetrics = {
702
779
  | `Outcome` | Terminal result with message and tool results |
703
780
  | `LoopMetrics` | Loop iteration, timing, and phase transition stats |
704
781
  | `SessionManagerProvider` | Session lifecycle seam used by the Runtime |
705
- | `LocalJsonlSessionProvider` / `InMemorySessionProvider` | JSONL and in-memory Session adapters |
782
+ | `JsonlSessionStore` / `InMemorySessionStore` | JSONL and in-memory Session adapters |
706
783
  | `ExtensionAPI` / `ExtensionFactory` | Extension developer interface |
707
- | `StreamFn` / `LlmModelRef` | Model stream function and model reference |
784
+ | `StreamFn` / `ModelRef` | Model stream function and model reference |
708
785
 
709
786
  ## Documentation
710
787