@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.
- package/README.md +256 -321
- package/dist/index.d.ts +666 -886
- package/dist/index.js +2213 -499
- package/package.json +4 -5
package/README.md
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
# @rowan-agent/agent
|
|
2
2
|
|
|
3
|
-
|
|
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
|
-
|
|
16
|
-
|
|
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
|
|
22
|
-
|
|
23
|
-
|
|
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
|
-
|
|
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
|
-
|
|
35
|
-
|
|
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
|
-
|
|
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
|
-
|
|
45
|
-
|
|
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
|
|
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
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
210
|
+
const second = await agent.send("now focus on the CLI package");
|
|
211
|
+
console.log((await second.result()).message);
|
|
142
212
|
```
|
|
143
213
|
|
|
144
|
-
|
|
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
|
-
|
|
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
|
|
150
|
-
|
|
151
|
-
|
|
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
|
-
|
|
236
|
+
## AgentRun
|
|
155
237
|
|
|
156
|
-
`
|
|
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
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
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 =
|
|
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 {
|
|
439
|
+
import { LocalJsonlSessionProvider } from "@rowan-agent/agent";
|
|
309
440
|
|
|
310
|
-
const
|
|
311
|
-
const session = await
|
|
312
|
-
|
|
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
|
|
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
|
-
//
|
|
492
|
-
const agent =
|
|
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
|
-
###
|
|
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
|
-
|
|
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
|
-
##
|
|
666
|
+
## Model Selection
|
|
547
667
|
|
|
548
|
-
|
|
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
|
-
|
|
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
|
-
| `
|
|
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
|
-
| `
|
|
766
|
-
| `
|
|
767
|
-
| `
|
|
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.
|
|
718
|
+
Current version: **0.6.0**
|