@rowan-agent/agent 0.4.6 → 0.4.7

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/README.md CHANGED
@@ -1,74 +1,690 @@
1
1
  # @rowan-agent/agent
2
2
 
3
- ## Main Features
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.
4
4
 
5
- `@rowan-agent/agent` is the public programming entry point and Agent core for Rowan. It wraps model execution, event subscriptions, tool registration, loop-owned thread delegation, run cancellation, and idle waiting.
5
+ ## Installation
6
6
 
7
- The package implements a phase-configured Agent loop. The loop executes a configurable sequence of phase definitions through a single base `runPhase()` runner. Built-in phases (route, thread, plan, execute, verify) preserve the default behavior, while custom phase configurations can extend or replace them.
7
+ ```bash
8
+ bun add @rowan-agent/agent
9
+ ```
8
10
 
9
- ## Architecture
11
+ ## Quick Start
10
12
 
11
- `src/agent.ts` provides the `Agent` class and remains the core/facade entrypoint. It calls `src/loop.ts` directly.
13
+ ```ts
14
+ import {
15
+ Agent,
16
+ createMessage,
17
+ createCoreTools,
18
+ resolveModel,
19
+ } from "@rowan-agent/agent";
12
20
 
13
- `src/loop.ts` implements the generic phase-machine loop:
14
- - Creates the loop runtime from input
15
- - Resolves the phase configuration (default built-in or custom)
16
- - Iterates through phases by following transitions (`next`, `stop`, `abort`)
17
- - Completes the run with an `AgentRunResult`
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", name: "gpt-4.1-mini" },
29
+ stream,
30
+ });
18
31
 
19
- Phase definitions live under `src/loop/`:
20
- - `phase-config.ts` — `AgentPhaseDefinition`, `AgentPhaseConfig`, validation, and default config factory
21
- - `built-in-phases.ts` — built-in route, thread, plan, execute, and verify phase definitions
22
- - `phases.ts` — base `runPhase()` and `runConfiguredPhase()` runners
23
- - `routing.ts` — route scheduling helper used by the route phase
24
- - `thread.ts` — thread execution helper used by the thread phase
32
+ agent.subscribe((event) => console.log(event.type));
25
33
 
26
- Mutable live runtime state and lifecycle helpers live in `src/loop.ts` with the generic phase-machine boundary.
34
+ const result = await agent.run();
35
+ console.log(result.outcome?.message);
36
+ ```
27
37
 
28
- `Agent` keeps a small state object:
38
+ ## Agent
29
39
 
30
- - `sessionId` identifies the current live run/session.
31
- - `context` is the current `systemPrompt`, visible messages, tools, and skills snapshot.
32
- - `model` and `stream` describe the model identity and call path.
33
- - `tools` are the tools the runtime may expose to the model.
34
- - `isRunning`, `currentResult`, and `error` describe the current run state.
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`.
35
41
 
36
- `src/task.ts` and `src/types.ts` expose task helpers, typed protocol phase outputs, `AgentState`, `createAgentState`, `createMessage`, and public Agent types. Runtime-owned core tools are exported by `@rowan-agent/runtime`. `src/index.ts` is the package entry point.
42
+ ```ts
43
+ class Agent {
44
+ constructor(options: AgentOptions);
45
+ run(options?: RunOptions): Promise<AgentRunResult>;
46
+ abort(): void;
47
+ subscribe(listener: AgentEventListener): () => void;
48
+ skill(name: string): Promise<void>;
49
+ phase(name: string): Promise<string>;
50
+ waitForIdle(): Promise<void>;
51
+ flushEvents(): Promise<void>;
52
+ readonly status: AgentStatus;
53
+ }
54
+ ```
37
55
 
38
- The loop consumes adapter-normalized `phase_output` events from `@rowan-agent/protocol` while still accepting `structured_output` events for local scripted streams. Default tool calls are executed through the event-neutral runtime primitive; `agent` translates runtime observations into ordered `AgentEvent`s, conversation messages, attempts, verification, and final `AgentRunResult`.
56
+ ### AgentOptions
39
57
 
40
- `Agent` intentionally exposes the stable, application-facing run surface: context, model, stream, limits, session id, tool approval hooks, event subscriptions, and cancellation. Durable Session persistence belongs to composition roots through `@rowan-agent/session` contracts, not to `Agent`.
58
+ ```ts
59
+ type AgentOptions = {
60
+ context: AgentContext;
61
+ model: LlmModelRef;
62
+ stream: StreamFn;
63
+ sessionId?: string;
64
+ phases?: PhaseRegistry;
65
+ extensions?: ExtensionRunnerRef;
66
+ signal?: AbortSignal;
67
+ maxAttempts?: number;
41
68
 
42
- ## Usage Flow
69
+ // Lifecycle hooks
70
+ beforeToolCall?: BeforeToolCall;
71
+ afterToolCall?: AfterToolCall;
72
+ onModelTranscript?: (transcript: ModelTranscript, meta: { phase: string; model: LlmModelRef }) => Promise<void>;
73
+ onMessage?: (message: AgentMessage) => Promise<void>;
74
+ onOutcome?: (outcome: Outcome) => Promise<void>;
75
+ };
76
+ ```
43
77
 
44
- 1. Prepare `model`, `stream`, and `tools`. Optionally provide skills, limits, and tool approval hooks.
45
- 2. Create an `Agent` instance.
46
- 3. Use `subscribe` to listen to events. Logging modules and UIs can both consume this stream.
47
- 4. Call `run()` with an `AgentRunConfig.context` snapshot to start or continue a conversation turn.
48
- 5. Pass a loaded `sessionId` and reconstructed context before continuing an existing session, or call `abort()` to stop the current run.
78
+ ### AgentRunResult
49
79
 
50
80
  ```ts
51
- import { Agent } from "@rowan-agent/agent";
52
- import { createMessage } from "@rowan-agent/agent";
53
- import { createCoreTools } from "@rowan-agent/runtime";
81
+ type AgentRunResult = {
82
+ status: AgentStatus;
83
+ outcome?: Outcome;
84
+ metrics?: LoopMetrics;
85
+ messages: AgentMessage[];
86
+ sessionId: string;
87
+ };
88
+ ```
89
+
90
+ ## AgentContext
91
+
92
+ 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.
93
+
94
+ ```ts
95
+ type AgentContext = {
96
+ systemPrompt: string;
97
+ messages: AgentMessage[];
98
+ tools: Tool[];
99
+ skills: Skill[];
100
+ };
101
+ ```
102
+
103
+ ### AgentMessage
104
+
105
+ ```ts
106
+ type AgentMessage = {
107
+ id: string;
108
+ role: "system" | "user" | "assistant" | "tool";
109
+ content: string | LlmContentPart[];
110
+ createdAt: string;
111
+ metadata?: Record<string, unknown> & { phase?: string };
112
+ };
113
+ ```
114
+
115
+ ### Outcome
116
+
117
+ The terminal result produced when the loop completes — carries the final message and all tool call results from the run.
54
118
 
55
- const tools = createCoreTools({ root: process.cwd() });
119
+ ```ts
120
+ type Outcome = {
121
+ id: string;
122
+ message: string;
123
+ toolResults?: Array<{
124
+ toolCallId: string;
125
+ toolName: string;
126
+ ok: boolean;
127
+ content: unknown;
128
+ error?: string;
129
+ }>;
130
+ };
131
+ ```
132
+
133
+ ## Tools
134
+
135
+ Four built-in tools cover file read/write and shell execution — the minimum needed for code-related agent work.
136
+
137
+ ```ts
138
+ import { createCoreTools } from "@rowan-agent/agent";
139
+
140
+ const tools = createCoreTools({
141
+ root: process.cwd(),
142
+ maxReadBytes?, // default: 64KB
143
+ bashTimeoutMs?, // default: 30s
144
+ maxBashOutputBytes?, // default: 64KB
145
+ });
146
+ // Returns: read, write, edit, bash
147
+ ```
148
+
149
+ ### Built-in Tools
150
+
151
+ | Tool | Description | Parameters |
152
+ |------|-------------|------------|
153
+ | `read` | Reads a text file within the workspace | `path` (required), `maxBytes?`, `type?` ("skill" \| "phase" \| "markdown" \| "code" \| "file") |
154
+ | `write` | Writes content to a file, creating parent directories as needed | `path` (required), `content` (required) |
155
+ | `edit` | Replaces exact `oldText` with `newText` in a file | `path` (required), `oldText` (required), `newText` (required), `replaceAll?` |
156
+ | `bash` | Runs a bash command within the workspace | `command` (required), `cwd?`, `timeoutMs?`, `maxOutputBytes?` |
157
+
158
+ ### Custom Tools
159
+
160
+ ```ts
161
+ import type { Tool, ToolResult } from "@rowan-agent/agent";
162
+
163
+ const myTool: Tool = {
164
+ name: "search",
165
+ description: "Search project docs",
166
+ parameters: Type.Object({ query: Type.String() }),
167
+ executionMode: "parallel", // "parallel" | "sequential"
168
+ async execute(args, context, signal): Promise<ToolResult> {
169
+ return { toolCallId: context.toolCallId, toolName: "search", ok: true, content: "..." };
170
+ },
171
+ };
172
+ ```
173
+
174
+ ### Tool Execution Hooks
175
+
176
+ `beforeToolCall` can intercept or reject tool calls (e.g. for approval flows); `afterToolCall` can modify results before they reach the model.
177
+
178
+ ```ts
56
179
  const agent = new Agent({
57
- context: {
58
- systemPrompt: "You are Rowan.",
59
- messages: [
60
- createMessage("user", "list the package structure in this project"),
61
- ],
62
- tools,
180
+ async beforeToolCall({ tool, args }) {
181
+ return { allow: true }; // or { allow: false, reason: "blocked" }
182
+ },
183
+ async afterToolCall({ tool, result }) {
184
+ return result;
63
185
  },
64
- model: { provider: "openai-compatible", name: "gpt-4.1-mini" },
65
- stream,
66
186
  });
187
+ ```
188
+
189
+ ## Events
67
190
 
68
- agent.subscribe((event) => {
69
- console.error(event.type);
191
+ 13 event types are emitted during execution — useful for logging, UI updates, or external monitoring.
192
+
193
+ ```ts
194
+ agent.subscribe((event: AgentEvent) => {
195
+ switch (event.type) {
196
+ case "agent_start": // { sessionId }
197
+ case "agent_end": // { sessionId, messages }
198
+ case "turn_start": // { content }
199
+ case "turn_end": // { content, outcome? }
200
+ case "model_requested": // { model, usage }
201
+ case "phase_start": // { phase }
202
+ case "phase_end": // { phase }
203
+ case "message_start": // { message }
204
+ case "message_update": // { message, delta }
205
+ case "message_end": // { message }
206
+ case "tool_execution_start": // { toolCallId, toolName, args }
207
+ case "tool_execution_update": // { toolCallId, toolName, args, partialResult }
208
+ case "tool_execution_end": // { toolCallId, toolName, result, isError }
209
+ }
70
210
  });
211
+ ```
71
212
 
72
- const result = await agent.run();
73
- console.log(result.outcome.message);
213
+ ### Parallel Phase Events
214
+
215
+ 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, a merged `<phase_results>` message is injected and `message_start`/`message_end` fire for it.
216
+
217
+ ## Session
218
+
219
+ JSONL-based session persistence — lets multi-turn conversations survive across process restarts. Supports create, resume, branch, and history replay.
220
+
221
+ ```ts
222
+ import { LocalJsonlSessionManager } from "@rowan-agent/agent";
223
+
224
+ const session = await LocalJsonlSessionManager.create(sessionsDir, { workspaceRoot: process.cwd() });
225
+ const session = await LocalJsonlSessionManager.open(sessionsDir, sessionId);
226
+ const sessions = await LocalJsonlSessionManager.list(sessionsDir);
227
+
228
+ await session.appendMessage(message);
229
+ await session.appendOutcome(outcome);
230
+ await session.appendExecutionTurn(turn);
231
+ const context = await session.buildAgentContext({ tools });
232
+ await session.branch(entryId);
233
+ ```
234
+
235
+ ## Skills
236
+
237
+ Skills are `SKILL.md` knowledge bundles that get injected into the agent context, extending its domain knowledge without changing code.
238
+
239
+ ```ts
240
+ import { loadSkill, loadSkills, resolveSkillPath } from "@rowan-agent/agent";
241
+
242
+ const skills = await loadSkills(workspace);
243
+ const skill = await loadSkill(path, workspace);
244
+ const path = resolveSkillPath("example", workspace);
245
+ // → <rowanDir>/skills/example/SKILL.md
246
+ ```
247
+
248
+ ## Phases
249
+
250
+ Phases are the basic units of the execution loop. There are no built-in phases — when none are configured, a `"default"` phase lets the LLM drive execution and routing directly.
251
+
252
+ ### How It Works
253
+
254
+ Each phase's `PHASE.md` content is injected as a system message, giving the LLM phase-specific instructions. A `route` tool is automatically added — the LLM calls it to decide what happens next: continue, stop, or transition to another phase.
255
+
256
+ ```
257
+ Per iteration:
258
+ 1. Hot-reload phase configs from disk (PHASE.md)
259
+ 2. Inject phase instructions as system message
260
+ 3. Execute phase (factory | run | LLM fallback)
261
+ 4. Extract routing decision from route tool call
262
+ 5. Transition, continue, or stop
263
+ ```
264
+
265
+ ### Example Phase Flow
266
+
267
+ ```
268
+ ┌────────────┐
269
+ │ User Input │
270
+ └─────┬──────┘
271
+
272
+ ┌────────────┐ route("plan") ┌────────────┐
273
+ │ default │ ────────────────▶│ plan │
274
+ └────────────┘ └─────┬──────┘
275
+
276
+ route("execute")
277
+
278
+
279
+ ┌────────────┐
280
+ │ execute │◀──────────────┐
281
+ └─────┬──────┘ │
282
+ │ │
283
+ route("review") route("execute")
284
+ │ (loop: fix issues)
285
+
286
+ ┌────────────┐
287
+ │ review │
288
+ └─────┬──────┘
289
+
290
+ route({ decision: [{ phase: "lint" }, { phase: "typecheck" }] })
291
+
292
+ ┌─────────┴─────────┐
293
+ ▼ ▼
294
+ ┌──────────┐ ┌──────────┐
295
+ │ lint │ │typecheck │
296
+ └────┬─────┘ └────┬─────┘
297
+ └─────────┬─────────┘
298
+
299
+ <phase_results> merged
300
+
301
+ route("stop")
302
+
303
+
304
+ ┌──────────┐
305
+ │ Outcome │
306
+ └──────────┘
307
+ ```
308
+
309
+ Each arrow is an LLM routing decision via the `route` tool. Parallel branches run concurrently and merge back before the next transition.
310
+
311
+ ### Providing Phases
312
+
313
+ Two sources, merged by priority:
314
+
315
+ **File-based** — `<workspace>/.rowan/phases/*/PHASE.md`
316
+
317
+ ```
318
+ .rowan/phases/review/
319
+ ├── PHASE.md # YAML frontmatter + markdown body
320
+ └── index.ts # optional: factory or run function
321
+ ```
322
+
323
+ ```yaml
324
+ ---
325
+ name: Code Review
326
+ description: Review code for correctness and style
327
+ tools: [read, bash]
328
+ target: execute
329
+ ---
330
+
331
+ Review the current implementation for bugs and style issues.
332
+ ```
333
+
334
+ **Extension-registered** — via `api.registerPhase()`. Same id overrides file-based phases.
335
+
336
+ ```ts
337
+ import type { ExtensionAPI } from "@rowan-agent/agent";
338
+
339
+ export default function myPlugin(api: ExtensionAPI) {
340
+ api.registerPhase({
341
+ id: "review",
342
+ name: "Code Review",
343
+ description: "Review code for correctness",
344
+ tools: ["read", "bash"],
345
+ async run(context, execution) {
346
+ const result = await execution.invokeModel(context);
347
+ return { message: result.text, route: "stop" };
348
+ },
349
+ });
350
+ }
351
+ ```
352
+
353
+ ### Phase
354
+
355
+ ```ts
356
+ interface Phase {
357
+ id: string;
358
+ name: string;
359
+ description: string;
360
+ tools?: string[]; // restrict tools (undefined = all)
361
+ skills?: string[]; // restrict skills
362
+ target?: string; // forced next phase (overrides route tool)
363
+ isolated?: boolean; // empty context when run in parallel
364
+ content: string; // PHASE.md body
365
+ factory?: (api: ExtensionAPI) => Promise<void>;
366
+ run?: (context: PhaseContext, execution: PhaseExecution) => Promise<PhaseOutput | void>;
367
+ }
74
368
  ```
369
+
370
+ ### PhaseContext / PhaseOutput
371
+
372
+ ```ts
373
+ interface PhaseContext {
374
+ systemPrompt: string;
375
+ messages: AgentMessage[];
376
+ tools: Tool[];
377
+ skills: Skill[];
378
+ state: PhaseState; // { current, available, iterations, payload }
379
+ }
380
+
381
+ type PhaseOutput = {
382
+ message: string;
383
+ route: string; // "continue" | "stop" | <phase-id>
384
+ payload?: unknown; // data passed to the next phase
385
+ };
386
+ ```
387
+
388
+ ### Parallel Execution (Fork/Join)
389
+
390
+ When the route tool returns multiple targets, phases run concurrently:
391
+
392
+ ```ts
393
+ route({ decision: [{ phase: "research" }, { phase: "analyze" }] });
394
+ ```
395
+
396
+ Each target gets a forked copy of the current messages (or empty if `isolated: true`), runs concurrently via `Promise.allSettled()`, and results are merged back into the conversation. See [docs/phases.md](docs/phases.md).
397
+
398
+ ## Extensions
399
+
400
+ The extension system lets plugins register lifecycle hooks, tools, phases, model providers, and cross-plugin events. Plugins are discovered from `<workspace>/.rowan/extensions`.
401
+
402
+ ```ts
403
+ import { createExtensionRunner, discoverAndLoadExtensions } from "@rowan-agent/agent";
404
+
405
+ const { extensions } = await discoverAndLoadExtensions(cwd);
406
+ const runner = createExtensionRunner({ cwd });
407
+ await runner.loadExtensions(extensions);
408
+ ```
409
+
410
+ ### ExtensionRunner
411
+
412
+ ```ts
413
+ class ExtensionRunner {
414
+ readonly hooks: HooksManager; // 19 lifecycle hook types
415
+ readonly events: EventBus; // cross-plugin event channel
416
+
417
+ loadExtensions(extensions: LoadedExtension[]): Promise<void>;
418
+ getAllRegisteredTools(): RegisteredTool[];
419
+ getPhases(): Phase[];
420
+ createPhaseRegistry(): PhaseRegistry;
421
+ signal: AbortSignal;
422
+ abort(): void;
423
+ }
424
+ ```
425
+
426
+ ### Hook Types
427
+
428
+ | Category | Hooks |
429
+ |----------|-------|
430
+ | Agent | `agent_start`, `agent_end` |
431
+ | Turn | `turn_start`, `turn_end` |
432
+ | Phase | `before_phase`, `after_phase` |
433
+ | Prompt | `before_prompt` |
434
+ | Message | `message_start`, `message_update`, `message_end` |
435
+ | Tool | `before_tool_call`, `after_tool_call`, `tool_execution_start`, `tool_execution_update`, `tool_execution_end` |
436
+ | Lifecycle | `queue_update`, `save_point`, `abort`, `settled` |
437
+
438
+ ### Plugin Format
439
+
440
+ ```
441
+ <workspace>/.rowan/extensions/my-plugin/
442
+ ├── package.json # { "rowan": { "extensions": ["./index.ts"] } }
443
+ └── index.ts
444
+ ```
445
+
446
+ ```ts
447
+ import type { ExtensionAPI } from "@rowan-agent/agent";
448
+
449
+ export default function myPlugin(rowan: ExtensionAPI) {
450
+ rowan.on("agent_start", (event) => { ... });
451
+ rowan.registerTool({ name: "my_tool", description: "...", parameters: {...}, execute: async (args) => {...} });
452
+ rowan.registerPhase({ id: "review", description: "...", run: async (ctx) => {...} });
453
+ rowan.events.emit("my-plugin:ready", {});
454
+ }
455
+ ```
456
+
457
+ > **Full reference:** [Extensions Documentation](docs/extensions.md)
458
+
459
+ ## Configuration
460
+
461
+ Multi-provider model configuration via `.rowan/config.yaml`. Supports multiple API providers, per-model settings, environment variable interpolation, and per-phase model overrides.
462
+
463
+ ### Config File
464
+
465
+ Place `config.yaml` in your `.rowan/` directory (alongside `phases/`, `skills/`, etc.):
466
+
467
+ ```
468
+ <workspace>/
469
+ └── .rowan/
470
+ ├── config.yaml # model configuration
471
+ ├── phases/ # phase definitions
472
+ ├── skills/ # skill bundles
473
+ └── extensions/ # plugins
474
+ ```
475
+
476
+ ### Schema
477
+
478
+ ```yaml
479
+ model: # optional: explicit default model override
480
+ provider: <string> # → providers[].id
481
+ id: <string> # → providers[].models[].id
482
+
483
+ logLevel: <string> # optional: run log detail (default: "info")
484
+ # one of: debug, info, warn, error, silent
485
+ # priority: --log-level flag > config > ROWAN_LOG_LEVEL env > "info"
486
+
487
+ providers: # required: at least one provider
488
+ - id: <string> # required: provider identifier
489
+ name: <string> # optional: display name
490
+ baseUrl: <string> # required: API base URL
491
+ apiKey: <string> # required: API key (supports ${VAR} interpolation)
492
+ protocol: <string> # required: API protocol (see table below)
493
+ timeoutMs: <number> # optional: request timeout (default: 60000)
494
+ maxRetries: <number> # optional: retry count (default: 4)
495
+ retryDelayMs: <number># optional: delay between retries (default: 1000)
496
+ headers: # optional: extra HTTP headers
497
+ <string>: <string>
498
+ models: # required: at least one model
499
+ - id: <string> # required: model identifier
500
+ name: <string> # optional: display name (defaults to id)
501
+ primary: <bool> # optional: mark as default agent model
502
+ reasoning: <bool> # optional: reasoning model (default: false)
503
+ input: # optional: supported input types (default: ["text"])
504
+ - "text"
505
+ - "image"
506
+ contextWindow: <number> # optional: max context tokens (default: 128000)
507
+ maxTokens: <number> # optional: max output tokens (default: 16384)
508
+ cost: # optional: per-token costs (default: all 0)
509
+ input: <number>
510
+ output: <number>
511
+ cacheRead: <number>
512
+ cacheWrite: <number>
513
+ ```
514
+
515
+ ### Protocols
516
+
517
+ | Protocol | Description |
518
+ |----------|-------------|
519
+ | `openai-completions` | OpenAI Chat Completions API (`/v1/chat/completions`) |
520
+ | `openai-responses` | OpenAI Responses API (`/v1/responses`) |
521
+ | `anthropic-messages` | Anthropic Messages API (`/v1/messages`) |
522
+
523
+ ### Environment Variable Interpolation
524
+
525
+ Use `${VAR_NAME}` syntax in any string value to reference environment variables:
526
+
527
+ ```yaml
528
+ apiKey: ${OPENAI_API_KEY}
529
+ ```
530
+
531
+ Undefined or empty variables throw an error at config load time.
532
+
533
+ ### Default Model Resolution
534
+
535
+ When no `--model` flag is passed, the default model is resolved in order:
536
+
537
+ 1. **Top-level `model:`** — explicit override in config
538
+ 2. **`primary: true`** — first model marked primary (by file order)
539
+ 3. **First model** — first model in config (by parse order)
540
+
541
+ ### Per-Phase Model Override
542
+
543
+ Override the model for a specific phase via PHASE.md frontmatter:
544
+
545
+ ```yaml
546
+ ---
547
+ name: Review
548
+ description: Deep code review
549
+ model: anthropic/claude-sonnet-4-20250514 # format: provider/id or just id
550
+ ---
551
+
552
+ Review the implementation for correctness...
553
+ ```
554
+
555
+ - `model: gpt-4.1` — wildcard provider, resolved by model ID
556
+ - `model: anthropic/claude-sonnet-4-20250514` — specific provider + model
557
+
558
+ ### Loading Config
559
+
560
+ ```ts
561
+ import {
562
+ loadConfigFile,
563
+ registerConfigModels,
564
+ resolveDefaultModel,
565
+ parseModelRef,
566
+ } from "@rowan-agent/agent";
567
+
568
+ // Load from .rowan/config.yaml (returns undefined if missing)
569
+ const config = await loadConfigFile(workspace);
570
+
571
+ // Register all configured models into the global registry
572
+ if (config) registerConfigModels(config);
573
+
574
+ // Resolve default model
575
+ const defaultModel = config ? resolveDefaultModel(config) : undefined;
576
+
577
+ // Parse a model reference string
578
+ const ref = parseModelRef("anthropic/claude-sonnet-4-20250514");
579
+ // → { provider: "anthropic", id: "claude-sonnet-4-20250514" }
580
+ ```
581
+
582
+ ### Config Types
583
+
584
+ ```ts
585
+ type AgentConfigFile = {
586
+ model?: { provider: string; id: string };
587
+ providers: ProviderConfigFromFile[];
588
+ };
589
+
590
+ type ProviderConfigFromFile = {
591
+ id: string;
592
+ name?: string;
593
+ baseUrl: string;
594
+ apiKey: string;
595
+ protocol: Protocol;
596
+ timeoutMs?: number;
597
+ maxRetries?: number;
598
+ retryDelayMs?: number;
599
+ headers?: Record<string, string>;
600
+ models: ModelConfigFromFile[];
601
+ };
602
+
603
+ type ModelConfigFromFile = {
604
+ id: string;
605
+ name?: string;
606
+ primary?: boolean;
607
+ reasoning?: boolean;
608
+ input?: ("text" | "image")[];
609
+ contextWindow?: number;
610
+ maxTokens?: number;
611
+ cost?: Partial<ModelCost>;
612
+ };
613
+ ```
614
+
615
+ ## Context & Prompt
616
+
617
+ Helpers for assembling system prompts and building model requests.
618
+
619
+ ```ts
620
+ import {
621
+ buildSystemPrompt,
622
+ buildModelRequest,
623
+ conversationMessages,
624
+ latestUserInput,
625
+ serializeSkills,
626
+ } from "@rowan-agent/agent";
627
+
628
+ const prompt = buildSystemPrompt({ systemPrompt, tools, skills, cwd });
629
+ const messages = conversationMessages(agentMessages);
630
+ const request = buildModelRequest({ systemPrompt, messages, tools });
631
+ ```
632
+
633
+ ## Workspace
634
+
635
+ Workspace resolution — dev mode uses the project root, packaged binary uses `~/.rowan`.
636
+
637
+ ```ts
638
+ import { resolveWorkspacePaths, resolveInWorkspace } from "@rowan-agent/agent";
639
+
640
+ const workspace = resolveWorkspacePaths();
641
+ // → { mode: "source" | "binary", cwd: string, rowanDir: string }
642
+ ```
643
+
644
+ ## Loop Metrics
645
+
646
+ ```ts
647
+ type LoopMetrics = {
648
+ iterations: number;
649
+ phaseTransitions: Array<{ from: string; to: string; ts: string }>;
650
+ compactionCount: number;
651
+ retryCount: number;
652
+ startedAt: string;
653
+ durationMs?: number;
654
+ };
655
+ ```
656
+
657
+ ## Key Types
658
+
659
+ | Type | Description |
660
+ |------|-------------|
661
+ | `Agent` | Main agent facade |
662
+ | `AgentContext` | System prompt, messages, tools, skills |
663
+ | `AgentMessage` | Typed message with role, content, metadata |
664
+ | `AgentEvent` | Discriminated union of 13 event types |
665
+ | `Tool` / `ToolResult` | Tool definition and execution result |
666
+ | `Skill` | Loaded skill bundle |
667
+ | `Phase` | Phase definition with content, execution, and routing config |
668
+ | `PhaseContext` / `PhaseOutput` | Phase input and output |
669
+ | `PhaseRegistry` | Map of phase ids to Phase objects plus entry phase id |
670
+ | `Outcome` | Terminal result with message and tool results |
671
+ | `LoopMetrics` | Loop iteration, timing, and phase transition stats |
672
+ | `LocalJsonlSessionManager` | JSONL session manager |
673
+ | `ExtensionRunner` | Extension runtime with hooks and events |
674
+ | `HooksManager` / `EventBus` | Hook registry and cross-plugin event channel |
675
+ | `StreamFn` / `LlmModelRef` | Model stream function and model reference |
676
+ | `AgentConfigFile` | Parsed `.rowan/config.yaml` structure |
677
+ | `ProviderConfigFromFile` / `ModelConfigFromFile` | Provider and model config entries |
678
+ | `loadConfigFile` / `registerConfigModels` / `resolveDefaultModel` | Config loading and model registration |
679
+ | `parseModelRef` | Parse `"provider/id"` or `"id"` strings to `LlmModelRef` |
680
+
681
+ ## Documentation
682
+
683
+ | Doc | Description |
684
+ |-----|-------------|
685
+ | [Phases](docs/phases.md) | Phase lifecycle, PHASE.md format, parallel execution, routing, payload |
686
+ | [Extensions](docs/extensions.md) | Extension API, 19 hooks, custom tools/phases, model providers, event bus |
687
+
688
+ ## Version
689
+
690
+ Current version: **0.4.6**