Durable Agent Runtime. The public interface consists of AgentRuntime, Durable
Stores, Config Providers, Run handles, and Run Events. An Agent is a persistent
identity, not a process-local Session object.
import {
AgentRuntime,
createCoreTools,
InMemoryStore,
loadPhases,
loadSkills,
} from "@rowan-agent/agent";
const skills = await loadSkills("./.rowan/skills");
const phases = await loadPhases("./.rowan/phases");
const runtime = await AgentRuntime.init({
store: new InMemoryStore(),
});
const agentId = await runtime.createAgent({
identity: "example:v1", // Stable config snapshot identity, not the Agent ID
model: {
provider: "openai",
id: "gpt-4o",
protocol: "openai-responses",
baseUrl: "https://api.openai.com/v1",
apiKey: process.env.OPENAI_API_KEY!,
},
definition: {
name: "workspace-assistant",
description: "Assist with the current workspace.",
prompt: "You are helpful.",
contexts: ["workspace"],
},
resources: {
tools: createCoreTools({ root: process.cwd() }),
skills,
contexts: [{
name: "workspace",
value: { root: process.cwd() },
}],
phases,
},
});
const run = await runtime.start(agentId, "Summarize the workspace.", {
idempotencyKey: "run-example", // One Agent can have multiple independent Runs
});
const observing = (async () => {
for await (const event of run.observe()) {
if (event.kind === "message_delta") process.stdout.write(event.text);
}
})();
const boundary = await run.wait();
await observing;
await runtime.close();AgentRuntime.init({ store })opens a Runtime Owner with an in-memory Config Provider by default. Passconfigswhen configuration must survive process boundaries.createAgent()creates a persistent Agent identity and binds a configuration snapshot.start()creates a queued Run;run(runId)returns a stateless Run handle.observe()follows display-orientedRunEventvalues;wait()waits for an authoritative boundary.respond()continues aninput_requiredRun;cancel()terminates an unfinished Run.close()seals the Owner and releases the Store.
AgentRuntime does not expose process-local Agents, Sessions, Bindings,
Mailboxes, or compatibility factories. The Durable Store is the source of truth;
Run handles do not hold business state.
InMemoryStore: tests and single-process embedding.SqliteStore: local persistence; the database is initialized on the firstopenOwner().InMemoryConfigProvider: tests and embeddings without an external config service.
The Runtime generates an idempotency key for ordinary Agent creation. Callers
that need to retry the same creation after an unknown result pass a stable
idempotencyKey explicitly. Other write commands retain their documented
idempotency identities. The Store provides atomicity for Runs, events, Tool
lifecycles, and Owner fencing.
Tools are supplied as AgentConfig.resources.tools, selected by the
Definition, and persist through:
pending → running → completed | failed | indeterminate
When an external side effect cannot be confirmed, the Tool must become
indeterminate; the Run then fails and is never automatically retried.
While running, a Tool may call context.reportProgress(progress) with a
JSON-safe value. Progress is live-only and may be dropped.
run.observe() delivers RunEvent values for live presentation:
- transient
message_deltaandtool_progressevents are live-only and best-effort; - durable
message_committed,run_state_changed, andtool_state_changedevents are replayable; - a durable
message_committedevent is the authoritative full content if a transient delta was coalesced or dropped.
runtime.consume() delivers only DurableRunEvent values:
message_committedrun_state_changedtool_state_changed
Durable events and their corresponding Run aggregate changes commit in one Store transaction. Reliable consumers persist progress through cursors and checkpoints; transient events never enter the Durable Store.
AgentRuntime owns a Resource Registry. Register Agent Definitions, Tools,
Skills, and Phases under stable sourceId values, then select the sources for
an Agent through resourceView:
const runtime = await AgentRuntime.init({
store: new InMemoryStore(),
bootstrap: async (registry) => {
await registry.loadExtensions({
sourceId: "workspace.extensions",
directory: "./extensions",
});
},
});
await runtime.loadAgents({
sourceId: "workspace",
values: [{
name: "workspace-assistant",
description: "Assist with the current workspace.",
prompt: "You are helpful.",
}],
});
await runtime.loadTools({
sourceId: "workspace",
values: createCoreTools({ root: process.cwd() }),
});
const agentId = await runtime.createAgent({
identity: "workspace:v1",
definition: { name: "workspace-assistant" },
resourceView: {
agents: ["workspace"],
tools: ["workspace"],
skills: [],
phases: [],
},
model: {
provider: "openai",
id: "gpt-4o",
protocol: "openai-responses",
baseUrl: "https://api.openai.com/v1",
apiKey: process.env.OPENAI_API_KEY!,
},
});Each load*() call replaces one source atomically; use directory or inline
values. resourceView controls visibility, so same-name resources can live
in isolated sources but collide when selected together. The built-in route
Tool and default Phase are always available and cannot be overridden.
Extensions are Runtime-global. Load them only during AgentRuntime.init() via
bootstrap; after initialization they are frozen until the Runtime closes.
Definition name lists narrow the selected Tools, Skills, and Phases: omission
inherits all candidates, [] selects none, and missing names are skipped. The
same rule applies to definition.contexts.
resources and definition have different jobs:
resourcessupplies the concrete candidates available to one Agent. Its Tools contain executableexecute()functions; Skills and Phases contain their loaded content; Contexts contain JSON-safe values.definitiondeclares which candidates this Agent uses.tools,skills,contexts, andphasesare name-based selectors; they cannot create a resource that is absent fromresources.
For a single-process embedding, provide concrete resources directly and omit the selectors when the Agent should use everything:
const agentId = await runtime.createAgent({
identity: "workspace:v1",
definition: {
name: "workspace-assistant",
description: "Assist with the current workspace.",
prompt: "You are helpful.",
},
resources: { tools, skills, contexts, phases },
model,
});Use selectors when several Agents share a candidate pool:
definition: {
name: "read-only-assistant",
description: "Inspect the workspace without changing it.",
prompt: "You are helpful.",
tools: ["read"],
contexts: ["workspace"],
phases: { entryPhaseId: "review", phaseIds: ["review"] },
}For process-boundary persistence, use the Resource Registry form shown above:
resourceView stores stable source IDs instead of executable resource
closures, and the Runtime resolves those sources into an immutable
Configuration Snapshot. This lets a restarted Runtime resolve the same
resource revisions while keeping each Run pinned to the snapshot that created
it. Direct resources are simpler for embedding; resourceView is the
declarative form for shared, reloadable, and restart-resolvable resources.