Stand up an agent
This is the front door for building on the agent. You import a layer, open a
durable session, and drive it. Pick the company agent
(@alphacro/coframe-agent) if you want the Coframe
opinions, or the bare framework (@alphacro/agent) if you
want a clean prompt loop with no tools or planning.
Hello, agent
Section titled “Hello, agent”The smallest possible program. runAgent runs one prompt to completion over
the in-memory backend and streams the events it appends — nothing persists past
the call.
import { runAgent } from "@alphacro/agent";
for await (const event of runAgent({ prompt: "hello", env: {} })) { console.log(event.kind); // run.started ... run.completed}A reusable agent + a durable session
Section titled “A reusable agent + a durable session”defineAgent(config) returns an Agent. Open a session per identity with
agent.session(id) — a plain string is branded as the canonical session id, so
the same name always resolves the same event log and transcript.
import { defineAgent } from "@alphacro/agent";
const agent = defineAgent({ env: {} });const session = agent.session("greet-session");
for await (const event of session.send("hello there")) { console.log(event.kind);}
const transcript = await session.messages(); // ReadonlyArray<UIMessage>await session.dispose();.send() vs .stream()
Section titled “.send() vs .stream()”The session is dual-mode. The same durable handle serves a run-to-completion consumer (CLI, evals) and a streaming UI from one composition.
send(text) runs a user turn to completion and yields the events it appended —
the mode the CLI and eval harness drive.
for await (const event of session.send("Improve the hero CTA")) { // react to run / file / process / tool events as they land}stream(input) streams a turn and returns the raw result for a streaming UI to
serve. The result carries the AI SDK StreamTextResult, so a Worker maps it
straight to a response:
const { result, runId, turnId } = await session.stream({ text: "Improve the hero CTA",});
return result.toUIMessageStreamResponse();stream() throws PromptBlockedError when a UserPromptSubmit hook blocks the
turn.
A session also exposes events() (the full ordered event log), host() (the
underlying headless host — an escape hatch for live subscription and tool
approvals), and dispose() (release backend + host + MCP resources; safe to
call repeatedly).
Pick a backend
Section titled “Pick a backend”A backend supplies the three pieces of infrastructure that differ by where a session runs: the append-only event store, the workspace tools read and write, and the persisted transcript. Choosing one is the only infrastructure decision an app makes.
Ephemeral event log, workspace, and transcript. Nothing survives the process — the zero-config default for tests and one-off runs. Used implicitly when you set no backend.
import { defineAgent, memory } from "@alphacro/agent";
const agent = defineAgent({ backend: memory() });A JSONL event log, a real on-disk workspace, and a file-backed transcript, all
rooted under dir and partitioned by session id. Because the transcript
persists too, reattaching the same session id after a restart resumes the
conversation.
import { defineAgent } from "@alphacro/agent";import { filesystem } from "@alphacro/agent/node";
const agent = defineAgent({ backend: filesystem({ dir: ".agent-sessions" }) });const session = agent.session("durable");for await (const _ of session.send("hello")) void _;await session.dispose();
// later, a fresh process — same id resumes the prior transcript + event logconst resumed = defineAgent({ backend: filesystem({ dir: ".agent-sessions" }) }) .session("durable");const messages = await resumed.messages(); // the earlier conversationSet the model
Section titled “Set the model”The model is a plain catalog id string, resolved through the canonical factory (BYOK / AI Gateway routing and the no-key→mock rule behave identically to the Worker and CLI). Omit it and the env-level fallback applies — the scenario mock when no provider key is present.
const agent = defineAgent({ model: "anthropic:claude-sonnet-4-6" });You can also pass an already-resolved LanguageModelV3 instance (a scripted
mock or scenario model) — the seam deterministic tests and evals drive through.
The company agent — defineCoframeAgent
Section titled “The company agent — defineCoframeAgent”For a real CRO agent, use Layer 2. defineCoframeAgent(options) bundles the
company prompt, common tools, planning + requirements spine, delegation,
evidence wiring, and Jarvis MCP — every field is optional, and the defaults
are the company agent. It returns the same Layer 1 Agent, so sessions,
backends, and dual-mode all work identically.
import { defineCoframeAgent } from "@alphacro/coframe-agent";
const agent = defineCoframeAgent({ env: {} });const session = agent.session("operator-42");
for await (const event of session.send("Improve the hero CTA")) { console.log(event.kind);}await session.dispose();memory, the session types, and Backend are re-exported from
@alphacro/coframe-agent, and the Node filesystem() backend from
@alphacro/coframe-agent/node, so an app needs a single import:
import { defineCoframeAgent, memory } from "@alphacro/coframe-agent";import { filesystem } from "@alphacro/coframe-agent/node";Common options
Section titled “Common options”| Option | Default | What it does |
|---|---|---|
model | env/mock | Catalog id string or a LanguageModelV3 instance. |
backend | memory() | Where the event log / workspace / transcript live. |
instructions | COFRAME_AGENT_BASE_PROMPT | Override the company base prompt. |
extraTools | — | Host tools registered after the common suite (e.g. apply_variant). |
extraPlugins | — | Extra plugins composed after the planning/requirements slice. |
includeRunShell | false | Enable run_shell in the common tool suite. |
morphApiKey | — | Enables the edit_file tool; omit to drop it. |
jarvis | — | { apiKey, apiUrl? } — exposes Jarvis MCP tools. Omit to run without Jarvis. |
delegation | true | Enable sub-agent delegation. |
onCompose | — | Observe the composed session internals once per open. |
Declarative MCP
Section titled “Declarative MCP”The company agent connects Jarvis as a declarative MCP server — pass credentials and its tools are bridged in at session open:
const agent = defineCoframeAgent({ jarvis: { apiKey: process.env.JARVIS_API_KEY!, apiUrl: process.env.JARVIS_API_URL, },});Under the hood this is L1’s declarative mcp config (AgentConfig.mcp); the
company agent fills it in for you from buildJarvisMcpServerConfig.
Worked example — the CLI
Section titled “Worked example — the CLI”apps/agent-cli is the reference: one defineCoframeAgent session, in-memory
for fake mode and the CLI’s filesystem adapters for live.
import { defineCoframeAgent, memory, type AgentSession } from "@alphacro/coframe-agent";
const agent = defineCoframeAgent({ backend: options.mode === "fake" ? memory() : cliFilesystemBackend({ workspaceDir, eventsDir }), source: "cli", callerUserId, includeRunShell: false, onChildEvent: logDelegationEvent,});
const session: AgentSession = agent.session(sessionId);const host = await session.host(); // subscribe to events, drive approvalsThe eval harness (apps/agent-evals) drives the same composition through
defineCoframeAgent, using onCompose to capture the live tool registry and
planning store for its assertions. Both apps prove that one import stands up the
full company agent.
Extending the agent
Section titled “Extending the agent”Tools, planning, requirements, delegation, skills, and permissions are all built
as plugins — the extension surface beneath L1 and L2. When you need a new
capability, you contribute a plugin (via extraPlugins, or by composing your
own) rather than forking the harness. See the
Plugin system guide and
Build your own plugin.