@rowan-agent/agent 0.5.6 → 0.6.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 (4) hide show
  1. package/README.md +256 -321
  2. package/dist/index.d.ts +666 -886
  3. package/dist/index.js +2213 -499
  4. package/package.json +4 -5
package/README.md CHANGED
@@ -1,6 +1,8 @@
1
1
  # @rowan-agent/agent
2
2
 
3
- Core agent runtime for Rowan. Provides a configurable phase-based execution loop, tool calling, session persistence, event streaming, skills, and an extension system for plugins.
3
+ Embedded durable agent runtime for Rowan. It owns Agent lifecycle, scheduling,
4
+ recovery, Tool Calls, and Runtime Events while preserving the configurable
5
+ phase loop, Sessions, skills, and extension system.
4
6
 
5
7
  ## Installation
6
8
 
@@ -10,86 +12,159 @@ bun add @rowan-agent/agent
10
12
 
11
13
  ## Quick Start
12
14
 
15
+ The durable lifecycle is the public entrypoint: start one `AgentRuntime`, create
16
+ or reconstruct an `Agent`, submit input with `send()`, and wait on the returned
17
+ `AgentRun`.
18
+
13
19
  ```ts
14
20
  import {
15
- Agent,
16
- createMessage,
21
+ AgentRuntime,
22
+ InMemoryRuntimeStateStore,
23
+ InMemorySessionProvider,
17
24
  createCoreTools,
18
- createDispatchStream,
19
25
  } from "@rowan-agent/agent";
26
+ import { createModelStream } from "@rowan-agent/models";
20
27
 
21
- const agent = new Agent({
22
- context: {
23
- systemPrompt: "You are a helpful coding assistant.",
24
- messages: [createMessage("user", "list the files in this project")],
25
- tools: createCoreTools({ root: process.cwd() }),
26
- skills: [],
27
- },
28
- model: { provider: "openai", id: "gpt-4.1-mini" },
29
- stream: createDispatchStream(),
28
+ const runtime = await AgentRuntime.start({
29
+ stateStore: new InMemoryRuntimeStateStore(),
30
+ sessionProvider: new InMemorySessionProvider(),
30
31
  });
31
32
 
32
- agent.subscribe((event) => console.log(event.type));
33
+ try {
34
+ const agent = await runtime.createAgent({
35
+ context: {
36
+ systemPrompt: "You are a helpful coding assistant.",
37
+ messages: [],
38
+ tools: createCoreTools({ root: process.cwd() }),
39
+ skills: [],
40
+ phases: {
41
+ phases: new Map(),
42
+ entryPhaseId: default,
43
+ },
44
+ },
45
+ model: { provider: "openai", id: "gpt-4.1-mini" },
46
+ stream: createModelStream(),
47
+ });
48
+
49
+ agent.subscribe((event) => console.log(event.type));
50
+ const run = await agent.send("list the files in this project");
51
+ console.log((await run.result()).message);
52
+ } finally {
53
+ await runtime.stop();
54
+ }
55
+ ```
56
+
57
+ Use `runtime.reconstructAgent(agentId, currentOptions)` to bind an existing
58
+ durable Agent to its Session with current resources. `send()` is non-blocking;
59
+ `AgentRun.result()` waits for its durable terminal Outcome.
60
+
61
+ The example uses in-memory adapters. Use `SqliteRuntimeStateStore` and a
62
+ persistent Session provider when state must survive a process restart.
63
+
64
+ ## AgentRuntime
65
+
66
+ Exactly one `AgentRuntime` may be active in a process. It is the sole owner of
67
+ Agent creation and reconstruction, durable input, scheduling, leases, Runtime
68
+ Events, and Tool Call control. Always stop it during host shutdown.
69
+
70
+ ```ts
71
+ type AgentRuntimeOptions = {
72
+ stateStore: InMemoryRuntimeStateStore | SqliteRuntimeStateStore;
73
+ sessionProvider?: InMemorySessionProvider | LocalJsonlSessionProvider;
74
+ factories?: ReadonlyMap<string, AgentFactory> | Readonly<Record<string, AgentFactory>>;
75
+ toolPolicy?: ToolRuntimePolicy;
76
+ maxConcurrentRuns?: number;
77
+ maxInfrastructureAttempts?: number;
78
+ leaseDurationMs?: number;
79
+ leaseRenewalIntervalMs?: number;
80
+ };
81
+
82
+ class AgentRuntime {
83
+ static start(options: AgentRuntimeOptions): Promise<AgentRuntime>;
84
+ createAgent(options: AgentCreateOptions): Promise<Agent>;
85
+ reconstructAgent(agentId: AgentId, options: AgentOptions): Promise<Agent>;
86
+ pauseAgent(agentId: AgentId): Promise<void>;
87
+ resumeAgent(agentId: AgentId): Promise<void>;
88
+ abortRun(runId: AgentRunId, reason?: string): Promise<void>;
89
+ consumeEvents(
90
+ consumerId: string,
91
+ listener: (event: RuntimeEvent) => void | Promise<void>,
92
+ ): () => void;
93
+ listEvents(cursor?: RuntimeEventCursor): Promise<RuntimeEvent[]>;
94
+ stop(): Promise<void>;
95
+ }
96
+ ```
97
+
98
+ A Session provider is required by `createAgent()` and `reconstructAgent()`.
99
+ Runtime State and conversation history are deliberately separate:
100
+
101
+ | Concern | Durable adapter | In-memory adapter |
102
+ |---------|-----------------|-------------------|
103
+ | Agent records, Messages, Runs, Leases, Runtime Events, Tool Calls | `SqliteRuntimeStateStore` | `InMemoryRuntimeStateStore` |
104
+ | Conversation messages, model transcripts, Outcomes | `LocalJsonlSessionProvider` | `InMemorySessionProvider` |
105
+
106
+ The SQLite Runtime schema has no compatibility migration. Replace an older
107
+ Runtime database when adopting a breaking schema; Session JSONL records remain
108
+ separate.
109
+
110
+ ### Scheduling and Runtime Commands
111
+
112
+ The Scheduler runs at most one Run for each Agent and up to
113
+ `maxConcurrentRuns` across different Agents. `pauseAgent()` gates queued and new
114
+ work without cancelling a Run that is already executing; `resumeAgent()` opens
115
+ that gate. `abortRun()` targets one precise Run.
116
+
117
+ Lease failures and model/provider errors marked `retryable: true` are retried.
118
+ The Runtime renews active leases and retries them up to
119
+ `maxInfrastructureAttempts`; exhausted work fails and its triggering Message is
120
+ dead-lettered.
33
121
 
34
- const result = await agent.run();
35
- console.log(result.outcome?.message);
122
+ ### Factory Recovery
123
+
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.
128
+
129
+ ```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
+ });
36
145
  ```
37
146
 
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.
151
+
38
152
  ## Agent
39
153
 
40
- The `Agent` class is the public facade. It drives the entire execution loop — from receiving context and tools, through phase-based iteration, to producing a terminal `Outcome`.
154
+ `AgentRuntime` is the only lifecycle owner. `Agent` is a bound facade: it cannot
155
+ be directly constructed and has no independent `run()` path.
156
+
157
+ The `Agent` class is the public facade for one Runtime-owned Agent Binding.
41
158
 
42
159
  ```ts
43
160
  class Agent {
44
- constructor(options: AgentOptions);
45
- run(options?: RunOptions): Promise<RunResult>;
46
-
47
- // User input / conversation continuation
48
- appendUserMessage(input: string): void;
49
- appendMessage(message: AgentMessage): void;
50
- appendMessages(messages: AgentMessage[]): void;
51
- runWithUserInput(input: string, options?: RunOptions): Promise<RunResult>;
52
- runWithMessage(message: AgentMessage, options?: RunOptions): Promise<RunResult>;
53
- resetInitialization(): void;
54
-
55
- // Context and transcript
56
- getContext(): AgentContext;
57
- setContext(context: AgentContext): void;
58
- updateContext(updater: (context: AgentContext) => AgentContext): void;
59
- forkContext(overrides?: Partial<AgentContext>): AgentContext;
60
- getMessages(): AgentMessage[];
61
- setMessages(messages: AgentMessage[]): void;
62
- clearMessages(): void;
63
- getTranscript(): AgentMessage[];
64
- replaceTranscript(messages: AgentMessage[]): void;
65
-
66
- // Config access and shortcuts
67
- getConfig(): AgentOptions;
68
- setConfig(config: AgentOptions): void;
69
- updateConfig(updater: (config: AgentOptions) => AgentOptions): void;
70
- setSessionId(sessionId: string): void;
71
- getSessionId(): string | undefined;
72
- setModel(model: LlmModelRef): void;
73
- setTools(tools: Tool[]): void;
74
- setSkills(skills: Skill[]): void;
75
- setPhases(phases: PhaseRegistry): void;
76
- setCwd(cwd: string): void;
77
- setStream(stream: StreamFn): void;
78
- getModel(): LlmModelRef;
79
- getTools(): Tool[];
80
- getSkills(): Skill[];
81
- getPhases(): PhaseRegistry | undefined;
82
- getCwd(): string | undefined;
83
-
84
- abort(): void;
161
+ readonly id: AgentId;
162
+ readonly sessionId: string;
163
+ send(input: string | AgentMessage): Promise<AgentRun>;
85
164
  subscribe(listener: AgentEventListener): () => void;
86
- skill(name: string, additionalInstructions?: string): string;
87
- phase(name: string): Promise<string>;
88
- waitForIdle(): Promise<void>;
89
165
  flushEvents(): Promise<void>;
90
- readonly state: AgentStatus;
91
166
 
92
- // Resource loading — replaces standalone loadSkills/loadPhases/loadExtensions
167
+ // Resource discovery helpers
93
168
  static loadSkills(targetPath: string): Promise<Skill[]>;
94
169
  static loadPhases(targetPath: string): Promise<PhaseRegistry>;
95
170
  static loadExtensions(targetPath: string): Promise<LoadExtensionsResult>;
@@ -105,7 +180,6 @@ type AgentOptions = {
105
180
  stream: StreamFn;
106
181
  cwd?: string;
107
182
  extensions?: LoadedExtension[];
108
- sessionId?: string;
109
183
  maxAttempts?: number;
110
184
 
111
185
  // Lifecycle hooks
@@ -115,61 +189,78 @@ type AgentOptions = {
115
189
  onMessage?: (message: AgentMessage) => Promise<void>;
116
190
  onOutcome?: (outcome: Outcome) => Promise<void>;
117
191
  };
118
- ```
119
192
 
120
- ### RunResult
121
-
122
- ```ts
123
- type RunResult = {
124
- sessionId: string;
125
- messages: AgentMessage[];
126
- outcome: Outcome;
127
- metrics: LoopMetrics;
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;
128
198
  };
129
199
  ```
130
200
 
131
201
  ### Conversation Continuation
132
202
 
133
- For multi-turn use, append input to the agent's current transcript and run with the updated context. `runWithUserInput()` is the main convenience API; the lower-level append methods are useful when a UI or session layer controls message creation.
203
+ Every turn enters through `send()`. The Runtime persists the input before it
204
+ returns an `AgentRun`; Session history is restored during reconstruction.
134
205
 
135
206
  ```ts
136
- const first = await agent.runWithUserInput("summarize this repository");
137
-
138
- agent.appendUserMessage("now focus on the CLI package");
139
- const second = await agent.run();
207
+ const first = await agent.send("summarize this repository");
208
+ console.log((await first.result()).message);
140
209
 
141
- await agent.runWithMessage(createMessage("user", "what changed since last turn?"));
210
+ const second = await agent.send("now focus on the CLI package");
211
+ console.log((await second.result()).message);
142
212
  ```
143
213
 
144
- The agent uses `context.phases.entryPhaseId` only until the first successful run completes. Later turns start from the normalized `default` phase so long-lived agents do not repeat one-time entry work. Call `agent.resetInitialization()` when the next run should use `entryPhaseId` again.
214
+ If a Run is suspended waiting for input, the next `send()` to that Agent resumes
215
+ the same Run instead of creating a second one.
145
216
 
146
- Transcript helpers return snapshots, so callers can inspect or edit history without accidentally mutating the agent until they call a setter.
217
+ ### Updating Runtime Resources
218
+
219
+ Resources are fixed for a live Agent Binding. Apply a new model, prompt, Tool
220
+ set, Skill set, Phase registry, or Extension set during reconstruction. A
221
+ duplicate live Binding is rejected, so explicit reconstruction normally occurs
222
+ after the previous process or Runtime has stopped.
147
223
 
148
224
  ```ts
149
- const messages = agent.getMessages();
150
- agent.replaceTranscript(messages.slice(-6));
151
- agent.clearMessages();
225
+ const agentId = agent.id;
226
+ await runtime.stop();
227
+
228
+ const nextRuntime = await AgentRuntime.start({ stateStore, sessionProvider });
229
+ const reconstructed = await nextRuntime.reconstructAgent(agentId, {
230
+ context: currentContext,
231
+ model: { provider: "openai", id: "gpt-4.1" },
232
+ stream: createModelStream(),
233
+ });
152
234
  ```
153
235
 
154
- ### Updating Config
236
+ ## AgentRun
155
237
 
156
- `AgentOptions` can be replaced wholesale with `setConfig()`, or updated through focused shortcuts for common orchestration flows.
238
+ `send()` returns a Runtime-owned handle immediately after the input and Run are
239
+ durable. The handle exposes cached state for synchronous inspection and can
240
+ refresh from the Runtime Store when needed.
157
241
 
158
242
  ```ts
159
- agent.setSessionId("ses_known");
160
- agent.setModel({ provider: "openai", id: "gpt-4.1" });
161
- agent.setTools(createCoreTools({ root: process.cwd() }));
162
- agent.setSkills(await Agent.loadSkills("./.rowan/skills"));
163
- agent.setPhases(await Agent.loadPhases("./.rowan/phases"));
164
- agent.setCwd(process.cwd());
165
- agent.setStream(createDispatchStream());
166
-
167
- agent.updateContext((context) => ({
168
- ...context,
169
- systemPrompt: `${context.systemPrompt}\n\nPrefer concise answers.`,
170
- }));
243
+ class AgentRun {
244
+ readonly id: AgentRunId;
245
+ readonly messageId: string;
246
+ readonly status: AgentRunState;
247
+ readonly state: AgentRunState; // alias of status
248
+
249
+ getStatus(): Promise<AgentRunState>;
250
+ subscribe(listener: AgentRunListener): () => void;
251
+ consumeRuntimeEvents(
252
+ consumerId: string,
253
+ listener: (event: RuntimeEvent) => void | Promise<void>,
254
+ ): () => void;
255
+ result(): Promise<Outcome>;
256
+ abort(reason?: string): Promise<void>;
257
+ }
171
258
  ```
172
259
 
260
+ `result()` waits through queued, running, and suspended states. Completed,
261
+ failed, and cancelled Runs all resolve to their persisted terminal `Outcome`;
262
+ inspect `status` when the distinction matters. `abort()` affects only this Run.
263
+
173
264
  ## AgentContext
174
265
 
175
266
  The context snapshot that defines what the agent can see and do — the system prompt sets the role, messages form the conversation history, and tools/skills define the capability boundary.
@@ -262,7 +353,10 @@ const myTool: Tool = {
262
353
  `beforeToolCall` can intercept or reject tool calls (e.g. for approval flows); `afterToolCall` can modify results before they reach the model.
263
354
 
264
355
  ```ts
265
- const agent = new Agent({
356
+ const agent = await runtime.createAgent({
357
+ context,
358
+ model,
359
+ stream,
266
360
  async beforeToolCall({ tool, args }) {
267
361
  return { allow: true }; // or { allow: false, reason: "blocked" }
268
362
  },
@@ -272,6 +366,24 @@ const agent = new Agent({
272
366
  });
273
367
  ```
274
368
 
369
+ ### Runtime Tool Policy
370
+
371
+ Every managed Tool Call passes through the Runtime before its adapter executes.
372
+ Runtime policy can narrow the Agent's Tool set and cap concurrency, but it can
373
+ never add a capability that was not supplied in `AgentContext`.
374
+
375
+ ```ts
376
+ const runtime = await AgentRuntime.start({
377
+ stateStore,
378
+ sessionProvider,
379
+ toolPolicy: {
380
+ allowedTools: ["read", "bash"],
381
+ maxConcurrent: 8,
382
+ perToolMaxConcurrent: { bash: 2 },
383
+ },
384
+ });
385
+ ```
386
+
275
387
  ## Events
276
388
 
277
389
  13 event types are emitted during execution — useful for logging, UI updates, or external monitoring.
@@ -296,6 +408,25 @@ agent.subscribe((event: AgentEvent) => {
296
408
  });
297
409
  ```
298
410
 
411
+ ### Durable Runtime Events
412
+
413
+ Runtime State transitions are a separate durable stream. Give each consumer a
414
+ stable ID; its Checkpoint advances only after the listener succeeds. Delivery
415
+ is asynchronous, so a slow or unavailable consumer does not block state
416
+ transitions.
417
+
418
+ ```ts
419
+ const stopConsuming = runtime.consumeEvents("deployment-observer", async (event) => {
420
+ await deliverRuntimeFact(event);
421
+ });
422
+ ```
423
+
424
+ Only one live subscription may use a Consumer ID. If delivery fails, its
425
+ Checkpoint stays put and the Event is delivered again when that Consumer is
426
+ started later. `runtime.listEvents()` inspects the durable stream without
427
+ advancing a Consumer Checkpoint. Use `run.consumeRuntimeEvents()` for the same
428
+ delivery contract filtered to one Run.
429
+
299
430
  ### Parallel Phase Events
300
431
 
301
432
  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.
@@ -305,17 +436,20 @@ When multiple phases run concurrently (via multi-target `route`), each branch em
305
436
  JSONL-based session persistence — lets multi-turn conversations survive across process restarts. Supports create, resume, branch, and history replay.
306
437
 
307
438
  ```ts
308
- import { LocalJsonlSessionManager } from "@rowan-agent/agent";
439
+ import { LocalJsonlSessionProvider } from "@rowan-agent/agent";
309
440
 
310
- const session = await LocalJsonlSessionManager.create(sessionsDir, { workspaceRoot: process.cwd() });
311
- const session = await LocalJsonlSessionManager.open(sessionsDir, sessionId);
312
- const sessions = await LocalJsonlSessionManager.list(sessionsDir);
441
+ const sessions = new LocalJsonlSessionProvider(sessionsDir);
442
+ const session = await sessions.create({
443
+ systemPrompt,
444
+ input: "",
445
+ skills: [],
446
+ });
447
+ const resumed = await sessions.open(sessionId);
448
+ const savedSessions = await sessions.list();
313
449
 
314
450
  await session.appendMessage(message);
315
451
  await session.appendOutcome(outcome);
316
- await session.appendExecutionTurn(turn);
317
452
  const context = await session.buildAgentContext({ tools });
318
- await session.branch(entryId);
319
453
  ```
320
454
 
321
455
  ## Skills
@@ -345,7 +479,7 @@ Per iteration:
345
479
  5. Transition, continue, or stop
346
480
  ```
347
481
 
348
- **`entryPhaseId`** specifies which phase the loop enters for an uninitialized Agent. When phases are loaded from `.rowan/phases/`, the first discovered phase becomes the entry. When none are configured, the Agent normalises to `"default"`. After a successful run, later turns start from `"default"` until `agent.resetInitialization()` is called. This field is an internal routing hint for the phase loop — it is **not** exposed to the LLM, since the agent does not need to know which phase is the entry point to make routing decisions.
482
+ **`entryPhaseId`** specifies which phase the loop enters for a newly bound Agent. When phases are loaded from `.rowan/phases/`, the first discovered phase becomes the entry. When none are configured, the Agent normalises to `"default"`. Later turns start from the normalized default phase. This field is an internal routing hint and is not exposed to the model.
349
483
 
350
484
  ### Example Phase Flow
351
485
 
@@ -488,27 +622,13 @@ The extension system lets plugins register lifecycle hooks, tools, phases, model
488
622
  import { Agent } from "@rowan-agent/agent";
489
623
 
490
624
  const { extensions } = await Agent.loadExtensions(`${cwd}/.rowan/extensions`);
491
- // Pass extensions to the Agent constructor — they are loaded and bound internally
492
- const agent = new Agent({ context, model, stream, extensions });
625
+ // Extensions are fixed for this Runtime-owned Agent Binding.
626
+ const agent = await runtime.createAgent({ context, model, stream, extensions });
493
627
  ```
494
628
 
495
- ### ExtensionRunner
496
-
497
- `ExtensionRunner` is used internally by Agent when extensions are passed via the constructor or `run()`. The Agent manages the runner lifecycle — load, bind, invalidate — automatically.
629
+ ### Extension Runtime
498
630
 
499
- ```ts
500
- class ExtensionRunner {
501
- readonly hooks: HooksManager; // 19 lifecycle hook types
502
- readonly events: EventBus; // cross-plugin event channel
503
-
504
- loadExtensions(extensions: LoadedExtension[]): Promise<void>;
505
- getAllRegisteredTools(): RegisteredTool[];
506
- getPhases(): Phase[];
507
- createPhaseRegistry(): PhaseRegistry;
508
- signal: AbortSignal;
509
- abort(): void;
510
- }
511
- ```
631
+ Extension orchestration is internal. The Runtime-owned Agent Binding loads extensions, binds their hooks and events, invalidates their context, and aborts them with the run.
512
632
 
513
633
  ### Hook Types
514
634
 
@@ -543,196 +663,11 @@ export default function myPlugin(rowan: ExtensionAPI) {
543
663
 
544
664
  > **Full reference:** [Extensions Documentation](docs/extensions.md)
545
665
 
546
- ## Configuration
666
+ ## Model Selection
547
667
 
548
- Multi-provider model configuration via `.rowan/config.yaml`. Supports multiple API providers, per-model settings, environment variable interpolation, and per-phase model overrides.
668
+ `AgentOptions` accepts a model reference and stream implementation from `@rowan-agent/models`. Phase frontmatter may override the model for that phase.
549
669
 
550
- Config is loaded from the runtime Rowan directory, which defaults to `.rowan`.
551
-
552
- ### Config File
553
-
554
- Place `config.yaml` in your `.rowan/` directory (alongside `phases/`, `skills/`, etc.):
555
-
556
- ```
557
- <workspace>/
558
- └── .rowan/
559
- ├── config.yaml # model configuration
560
- ├── phases/ # phase definitions
561
- ├── skills/ # skill bundles
562
- └── extensions/ # plugins
563
- ```
564
-
565
- ### Schema
566
-
567
- ```yaml
568
- model: # optional: explicit default model override
569
- provider: <string> # → providers[].id
570
- id: <string> # → providers[].models[].id
571
-
572
- logLevel: <string> # optional: run log detail (default: "info")
573
- # one of: debug, info, warn, error, silent
574
- # priority: --log-level flag > config > ROWAN_LOG_LEVEL env > "info"
575
-
576
- providers: # required: at least one provider
577
- - id: <string> # required: provider identifier
578
- name: <string> # optional: display name
579
- baseUrl: <string> # required: API base URL
580
- apiKey: <string> # required: API key (supports ${VAR} interpolation)
581
- protocol: <string> # required: API protocol (see table below)
582
- timeoutMs: <number> # optional: streaming idle timeout after first byte (default: 60000)
583
- maxRetries: <number> # optional: retry count (default: 4)
584
- retryDelayMs: <number># optional: delay between retries (default: 1000)
585
- headers: # optional: extra HTTP headers
586
- <string>: <string>
587
- models: # required: at least one model
588
- - id: <string> # required: model identifier
589
- name: <string> # optional: display name (defaults to id)
590
- primary: <bool> # optional: mark as default agent model
591
- reasoning: <bool> # optional: reasoning model (default: false)
592
- input: # optional: supported input types (default: ["text"])
593
- - "text"
594
- - "image"
595
- contextWindow: <number> # optional: max context tokens (default: 128000)
596
- maxTokens: <number> # optional: max output tokens (default: 16384)
597
- cost: # optional: per-token costs (default: all 0)
598
- input: <number>
599
- output: <number>
600
- cacheRead: <number>
601
- cacheWrite: <number>
602
- ```
603
-
604
- ### Protocols
605
-
606
- | Protocol | Description |
607
- |----------|-------------|
608
- | `openai-completions` | OpenAI Chat Completions API (`/v1/chat/completions`) |
609
- | `openai-responses` | OpenAI Responses API (`/v1/responses`) |
610
- | `anthropic-messages` | Anthropic Messages API (`/v1/messages`) |
611
-
612
- ### Environment Variable Interpolation
613
-
614
- Use `${VAR_NAME}` syntax in any string value to reference environment variables:
615
-
616
- ```yaml
617
- apiKey: ${OPENAI_API_KEY}
618
- ```
619
-
620
- Undefined or empty variables throw an error at config load time.
621
-
622
- ### Default Model Resolution
623
-
624
- When no `--model` flag is passed, the default model is resolved in order:
625
-
626
- 1. **Top-level `model:`** — explicit override in config
627
- 2. **`primary: true`** — first model marked primary (by file order)
628
- 3. **First model** — first model in config (by parse order)
629
-
630
- ### Per-Phase Model Override
631
-
632
- Override the model for a specific phase via PHASE.md frontmatter:
633
-
634
- ```yaml
635
- ---
636
- name: Review
637
- description: Deep code review
638
- model: anthropic/claude-sonnet-4-20250514 # format: provider/id or just id
639
- ---
640
-
641
- Review the implementation for correctness...
642
- ```
643
-
644
- - `model: gpt-4.1` — wildcard provider, resolved by model ID
645
- - `model: anthropic/claude-sonnet-4-20250514` — specific provider + model
646
-
647
- ### Loading Config
648
-
649
- ```ts
650
- import {
651
- loadConfigFile,
652
- registerConfigModels,
653
- resolveDefaultModel,
654
- parseModelRef,
655
- } from "@rowan-agent/agent";
656
-
657
- // Load from .rowan/config.yaml (returns undefined if missing)
658
- const config = await loadConfigFile(workspace);
659
-
660
- // Register all configured models into the global registry
661
- if (config) registerConfigModels(config);
662
-
663
- // Resolve default model
664
- const defaultModel = config ? resolveDefaultModel(config) : undefined;
665
-
666
- // Parse a model reference string
667
- const ref = parseModelRef("anthropic/claude-sonnet-4-20250514");
668
- // → { provider: "anthropic", id: "claude-sonnet-4-20250514" }
669
- ```
670
-
671
- ### Config Types
672
-
673
- ```ts
674
- type AgentConfigFile = {
675
- model?: { provider: string; id: string };
676
- providers: ProviderConfigFromFile[];
677
- };
678
-
679
- type ProviderConfigFromFile = {
680
- id: string;
681
- name?: string;
682
- baseUrl: string;
683
- apiKey: string;
684
- protocol: Protocol;
685
- /** Maximum idle gap between response bytes after the first byte. */
686
- timeoutMs?: number;
687
- maxRetries?: number;
688
- retryDelayMs?: number;
689
- headers?: Record<string, string>;
690
- models: ModelConfigFromFile[];
691
- };
692
-
693
- type ModelConfigFromFile = {
694
- id: string;
695
- name?: string;
696
- primary?: boolean;
697
- reasoning?: boolean;
698
- input?: ("text" | "image")[];
699
- contextWindow?: number;
700
- maxTokens?: number;
701
- cost?: Partial<ModelCost>;
702
- };
703
- ```
704
-
705
- ## Context & Prompt
706
-
707
- Helpers for assembling system prompts and building model requests.
708
-
709
- ```ts
710
- import {
711
- buildSystemPrompt,
712
- buildModelRequest,
713
- conversationMessages,
714
- latestUserInput,
715
- serializeSkills,
716
- } from "@rowan-agent/agent";
717
-
718
- const prompt = buildSystemPrompt({ systemPrompt, tools, skills, cwd });
719
- const messages = conversationMessages(agentMessages);
720
- const request = buildModelRequest({ systemPrompt, messages, tools });
721
- ```
722
-
723
- ## Workspace
724
-
725
- Workspace resolution uses the current project root. The project Rowan directory defaults to `<cwd>/.rowan`; pass `rowanDir` to resolve another project-local directory.
726
-
727
- ```ts
728
- import { resolveWorkspacePaths, resolveInWorkspace } from "@rowan-agent/agent";
729
-
730
- const workspace = resolveWorkspacePaths();
731
- // → { cwd: string, rowanDir: string }
732
-
733
- const custom = resolveWorkspacePaths({ rowanDir: ".rowan-project" });
734
- // → custom.rowanDir is <cwd>/.rowan-project
735
- ```
670
+ CLI-specific `.rowan/config.yaml` loading and workspace discovery belong to [`@rowan-agent/cli`](../cli/README.md).
736
671
 
737
672
  ## Loop Metrics
738
673
 
@@ -751,7 +686,11 @@ type LoopMetrics = {
751
686
 
752
687
  | Type | Description |
753
688
  |------|-------------|
754
- | `Agent` | Main agent facade |
689
+ | `AgentRuntime` | Process-wide lifecycle, scheduling, recovery, Event, and Tool owner |
690
+ | `Agent` | Runtime-owned facade for input and transient Stream Events |
691
+ | `AgentRun` | Durable Run handle for state, terminal Outcome, observation, and abort |
692
+ | `SqliteRuntimeStateStore` / `InMemoryRuntimeStateStore` | Durable and test Runtime Store adapters |
693
+ | `RuntimeEvent` / consumer ID string | Durable lifecycle facts and checkpointed consumer identity |
755
694
  | `AgentContext` | System prompt, messages, tools, skills, phases |
756
695
  | `AgentMessage` | Typed message with role, content, metadata |
757
696
  | `AgentEvent` | Discriminated union of 13 event types |
@@ -762,14 +701,10 @@ type LoopMetrics = {
762
701
  | `PhaseRegistry` | Map of phase ids to Phase objects plus entry phase id |
763
702
  | `Outcome` | Terminal result with message and tool results |
764
703
  | `LoopMetrics` | Loop iteration, timing, and phase transition stats |
765
- | `LocalJsonlSessionManager` | JSONL session manager |
766
- | `ExtensionRunner` | Extension runtime with hooks and events |
767
- | `HooksManager` / `EventBus` | Hook registry and cross-plugin event channel |
704
+ | `SessionManagerProvider` | Session lifecycle seam used by the Runtime |
705
+ | `LocalJsonlSessionProvider` / `InMemorySessionProvider` | JSONL and in-memory Session adapters |
706
+ | `ExtensionAPI` / `ExtensionFactory` | Extension developer interface |
768
707
  | `StreamFn` / `LlmModelRef` | Model stream function and model reference |
769
- | `AgentConfigFile` | Parsed `.rowan/config.yaml` structure |
770
- | `ProviderConfigFromFile` / `ModelConfigFromFile` | Provider and model config entries |
771
- | `loadConfigFile` / `registerConfigModels` / `resolveDefaultModel` | Config loading and model registration |
772
- | `parseModelRef` | Parse `"provider/id"` or `"id"` strings to `LlmModelRef` |
773
708
 
774
709
  ## Documentation
775
710
 
@@ -780,4 +715,4 @@ type LoopMetrics = {
780
715
 
781
716
  ## Version
782
717
 
783
- Current version: **0.5.6**
718
+ Current version: **0.6.0**