
ComputerAgent
One framework to define, harness, run, and remember. Build computer-using agents that ship — without lock-in.
// architecture
One framework.
Every layer, swappable.
Compose definition, harness, runtime, and memory. Each slot accepts the providers you already trust — or your own.

// architecture
Harness Protocol. One system.
Every orthogonal axis of ComputerAgent, drawn out. Swipe through the full architecture.










// pillars
Eight slots. Infinite stacks.

Every step, traced and replayable
OpenTelemetry-native traces, structured logs, token and cost metrics on every run. Inspect prompts, tool calls, and decisions side-by-side — debug agents like you debug code.
// Ten diagrams
From import to running agent.
No glue code. No vendor adapters. Just composition.
> import { ComputerAgent } from "computeragent"; > import { E2BSubstrate } from "@computeragent/runtime-e2b"; > > const agent = new ComputerAgent({ source: { type: "git", url: "github.com/open-gitagent/trading-agent" }, harness: "claude-agent-sdk", runtime: new E2BSubstrate({ apiKey: process.env.E2B_API_KEY! }), sessionStore: { kind: "mongo", options: { url: MONGO_URL, database: "agentos" } }, envs: { ANTHROPIC_API_KEY: process.env.ANTHROPIC_API_KEY! }, }); > > await agent.chat("book my flight");
// Docs
Reference.
The complete API and protocol reference for computeragent and the @open-gitagent/* / @computeragent/* workspace packages.
Install
The umbrella package gives you ComputerAgent, runTask, LocalSubstrate, and all SDK types — enough for a complete agent in one import.
$ npm install computeragent$ npm install @computeragent/runtime-e2b$ npm install @computeragent/runtime-bwrap$ npm install @computeragent/runtime-vzvm$ npm install @open-gitagent/session-store-mongo$ npm install @computeragent/session-store-sqlite$ npm install @open-gitagent/agent-registry-mongo$ npm install @computeragent/observabilityRequires Node 22+ or Bun 1.1+ (the SDK targets modern async iterators + `using` semantics).
Quickstart
import { ComputerAgent, LocalSubstrate } from "computeragent";
const agent = new ComputerAgent({
source: { type: "git", url: "github.com/open-gitagent/general-agent" },
harness: "claude-agent-sdk",
runtime: new LocalSubstrate(),
envs: { ANTHROPIC_API_KEY: process.env.ANTHROPIC_API_KEY! },
});
const result = await agent.chat("Summarize this repo in two sentences.");
console.log(result.messages.at(-1));
console.log(`Cost: $${result.usage.costUsd}`);Same shape works against any substrate (E2BSubstrate, BwrapSubstrate, …), any engine, and any session store.
Core API — ComputerAgentOptions
Methods
agent.chat(input)ChatHandleagent.dispose()Promise<void>agent.harnessUrl()Promise<string>agent.fetchArtifact(path)Promise<Uint8Array | null>agent.listWorkdir(opts?)Promise<FsTreeEntry[]>Disposal
// Explicit:
const agent = new ComputerAgent({...});
try { /* … */ } finally { await agent.dispose(); }
// Modern (auto-dispose at scope exit):
await using agent = new ComputerAgent({...});
// no try/finally neededagent.chat() and ChatHandle
ChatHandle has a dual interface, inspired by client.messages.stream(...) in the Anthropic SDK.
for await (const ev of handle)Raw HarnessEvents as they arriveawait handle / handle.result()ChatResult — drained to completionhandle.getUsage()Snapshot of running UsageRollup at any timehandle.sessionId()Resolves to the real session id once POST /v1/sessions returnshandle.cancel()POST /v1/sessions/:id/cancel to abort the turnhandle.respondToPermission(callId, d)Manually answer a permission request
ChatResult
interface ChatResult {
readonly sessionId: string;
readonly messages: ReadonlyArray<unknown>;
readonly ended: { kind: "ca_session_ended"; reason: string; errorMessage?: string };
readonly usage: UsageRollup;
}
interface UsageRollup {
readonly inputTokens: number;
readonly outputTokens: number;
readonly cacheCreationInputTokens: number;
readonly cacheReadInputTokens: number;
readonly costUsd: number | undefined;
}Cost semantics: tokens always SUM across ca_usage_snapshot events. Cost depends on the engine's costSemantic: "cumulative" (claude-agent-sdk) takes the MAX; "delta" (gitclaw) SUMs.
ChatInput
type ChatInput =
| string // user text
| UserMessage // {role:"user", content:[...]}
| UserMessage[] // multi-message turn
| AsyncIterable<UserMessage>; // streaming inputrunTask — one-shot
import { runTask, LocalSubstrate } from "computeragent";
const result = await runTask({
source: { type: "git", url: "github.com/<org>/<repo>" },
harness: "claude-agent-sdk",
runtime: new LocalSubstrate(),
envs: { ANTHROPIC_API_KEY: process.env.ANTHROPIC_API_KEY! },
message: "Summarize the code in 3 bullets.",
});IdentitySource — where the agent comes from
{ type: "git" } — clone from a remote repo
source: {
type: "git",
url: "github.com/<org>/<repo>",
ref: "v1.2.3", // optional — branch/tag/SHA
subdir: "agents/triage", // optional — sub-path inside the repo
}The git URL is the canonical agent identity. With MongoTelemetry, the same URL across machines deduplicates to the same agent_registry row. Authentication: set GITHUB_TOKEN in envs — the harness bakes it into the clone URL; your token never reaches the engine subprocess.
{ type: "local" } — use a directory already on disk
source: {
type: "local",
path: "/Users/me/my-agent",
}{ type: "inline" } — pass the manifest in-memory
source: {
type: "inline",
manifest: { name: "hello", version: "0.1.0" },
files: {
"agent.yaml": [
'spec_version: "0.1.0"',
"name: hello",
"version: 0.1.0",
"model:",
" preferred: claude-haiku-4-5-20251001",
].join("\n"),
"SOUL.md": "Respond in one short sentence.",
},
}String shorthand
source: "github.com/<org>/<repo>" // → { type: "git", url: "github.com/<org>/<repo>" }Substrates — where the agent runs
A Substrate hosts the harness server. The SDK calls substrate.bootHarness() on first chat(), gets back a URL, and proxies everything else through it.
Dev, library-mode in your existing worker
Isolation without containers on Linux
Strong isolation, untrusted code
macOS-native VM, full OS, persistent disk
// E2B cloud microVM
import { E2BSubstrate } from "@computeragent/runtime-e2b";
runtime: new E2BSubstrate({
apiKey: process.env.E2B_API_KEY!,
templateId: "computeragent-base",
}),
// Linux bubblewrap
import { BwrapSubstrate } from "@computeragent/runtime-bwrap";
runtime: new BwrapSubstrate({
bind: ["/etc/ssl/certs:/etc/ssl/certs:ro"],
}),Engines (harnesses) — the agent loop
claude-agent-sdkgitagentdeepagentsharness is a string — open to extension. TypeScript narrows the built-ins for autocomplete; any string the running harness server has registered is accepted at runtime. Custom engines: implement EngineDriver and register via createHarnessServer({ engines }).
Session stores — conversation memory
"memory"built-in"file"built-in"mongo"@open-gitagent/session-store-mongo"sqlite"@computeragent/session-store-sqlite// MongoDB — shared across pods, resume from any worker
sessionStore: {
kind: "mongo",
options: { url: process.env.MONGO_URL!, database: "agentos" },
},
// Resume a session
const agent = new ComputerAgent({
...opts,
sessionId: "sess_abc123",
sessionStore: { kind: "mongo", options: { url, database } },
});
await agent.chat("Continue where we left off.");Telemetry — AgentTelemetry + AuditSink
AgentTelemetry (SDK-side, library-mode)
import { ComputerAgent } from "computeragent";
import { MongoTelemetry } from "@open-gitagent/agent-registry-mongo";
const agent = new ComputerAgent({
...opts,
telemetry: new MongoTelemetry({
url: process.env.MONGO_URL!,
database: "agentos",
agent: { name: "triage", source: opts.source, harness: opts.harness },
}),
});Hooks fired (all optional, fire-and-forget — exceptions never propagate):
interface AgentTelemetry {
onAgentConstructed?(info: AgentConstructedInfo): void | Promise<void>;
onChatStart?(info: ChatStartInfo): void | Promise<unknown>;
onChatEnd?(info: ChatEndInfo, context?: unknown): void | Promise<void>;
onClose?(): void | Promise<void>;
}AuditSink (harness-side, server-mode)
import { configureOtel, OtelAuditSink } from "@computeragent/observability";
configureOtel({
serviceName: "computeragent",
exporter: "otlp-http",
endpoint: process.env.OTEL_EXPORTER_OTLP_ENDPOINT!,
});
const auditSink = new OtelAuditSink();
const server = new ComputerAgentServer({ /* ..., */ auditSink });Spans use the OpenTelemetry gen_ai.* semantic conventions — any OTel-compatible APM (Honeycomb, Datadog, Grafana, ClickHouse) renders them natively.
Permissions / Human-in-the-loop
onToolCall callback (per-agent)
const agent = new ComputerAgent({
...opts,
onToolCall: async ({ callId, toolName, input, risk }) => {
if (toolName === "Bash" && (input as { command?: string }).command?.startsWith("rm")) {
return { decision: "deny", reason: "rm denied by policy" };
}
return { decision: "allow" };
},
});PermissionDecision shapes
type PermissionDecision =
| { decision: "allow" }
| { decision: "deny"; reason?: string }
| { decision: "modify"; input: Record<string, unknown> };TTY approval (CLI scripts)
import { ttyApproval } from "computeragent";
const agent = new ComputerAgent({
...opts,
onToolCall: ttyApproval(), // prompts at the terminal for each tool call
});Manual iteration
const handle = agent.chat("...");
for await (const ev of handle) {
if (ev.kind === "ca_permission_request") {
await handle.respondToPermission(ev.callId, { decision: "allow" });
}
}Policy — Cedar + OPA guardrails
const agent = new ComputerAgent({
...opts,
policy: {
kind: "srs",
endpoint: "https://your-policy-service.example.com",
apiKey: process.env.SRS_API_KEY!,
policyId: "policy_abc123",
principalId: "user:alice",
},
});The harness fetches the policy once (cached) and evaluates every tool call against the cedar_guardrail + opa_guardrail subsections. Decisions are emitted as ca_permission_decision events for audit. Fail-closed: on 5xx / timeout, the harness defaults to deny.
Configuration reference
Environment variables (read by the engine subprocess)
ANTHROPIC_API_KEYAnthropic direct pathANTHROPIC_BASE_URLOverride Anthropic endpoint (Helicone, OpenRouter, proxies)CLAUDE_CODE_USE_BEDROCKRoute through AWS Bedrock instead of Anthropic directAWS_REGION / AWS_DEFAULT_REGIONBedrock regionAWS_BEDROCK_MODEL_IDe.g. us.anthropic.claude-haiku-4-5-20251001-v1:0AWS_ROLE_ARN / AWS_WEB_IDENTITY_TOKEN_FILEIRSA-injected on EKSAWS_PROFILE / AWS_SHARED_CREDENTIALS_FILE / AWS_CONFIG_FILEAlternative AWS auth pathsGITHUB_TOKENFor cloning private agent source reposOTEL_EXPORTER_OTLP_ENDPOINTOTLP collector URL (turns on OtelAuditSink)OTEL_SERVICE_NAMEService name on emitted spans (default: computeragent)COMPUTERAGENT_LOGdebug / info / warn / error / silent
Model overrides — resolution order (highest wins)
options.modelconstructor argmodel:constructor argagent.yaml'smodel.preferred- Engine default (claude-agent-sdk →
claude-haiku-4-5-20251001)
HTTP wire protocol
The harness server exposes a small REST + SSE surface — curl can drive every endpoint. Schemas are Zod-validated.
REST
GET/healthPOST/v1/sessionsGET/v1/sessions/:idPOST/v1/sessions/:id/messagesPOST/v1/sessions/:id/permission/:callIdPOST/v1/sessions/:id/cancelDELETE/v1/sessions/:idGET/v1/sessions/:id/fs/tree?depth=NGET/v1/sessions/:id/fs/file?path=...SSE event stream
type HarnessEvent =
| { kind: "ca_session_started"; sessionId; engine; identity; capabilities }
| { kind: "sdk_message"; sessionId; payload }
| { kind: "ca_permission_request"; sessionId; callId; toolName; input; risk? }
| { kind: "ca_permission_decision";sessionId; callId; decision; reason? }
| { kind: "ca_turn_started"; sessionId; userTextLen? }
| { kind: "ca_usage_snapshot"; sessionId;
inputTokens?; outputTokens?;
cacheCreationInputTokens?; cacheReadInputTokens?;
costUsd?; costSemantic?: "cumulative" | "delta" }
| { kind: "ca_session_ended"; sessionId; reason; errorMessage? };Every event has a monotonic id. Reconnect with Last-Event-ID: <last-id> and the harness replays from the per-session ring buffer (default: last 1,000 events / 5 minutes).
One-line curl
curl -N -X POST http://127.0.0.1:7700/v1/sessions \
-H 'content-type: application/json' \
-d '{
"source": "github.com/open-gitagent/general-agent",
"harness": "claude-agent-sdk",
"envs": { "ANTHROPIC_API_KEY": "sk-ant-…" },
"message": "Reply: PING"
}'CLI
npx computeragent run \ --source github.com/open-gitagent/general-agent \ --harness claude-agent-sdk \ --message "Summarize the README in 3 bullets"
Common flags
--sourcesource (git URL or local path)--harnessharness--modelmodel--temperaturetemperature--runtimeruntime (local / e2b / bwrap / vz)--session-idsessionId--session-storesessionStore.kind (memory / file / mongo / sqlite)--debugdebug: true--messageone-shot agent.chat(input) and exit
Errors
HarnessProtocolErrorUnknownEngineErrorUnknownLoaderErrorUnknownStoreErrorimport { HarnessProtocolError, UnknownEngineError } from "computeragent";
try {
await agent.chat("…");
} catch (e) {
if (e instanceof HarnessProtocolError) { /* retry, swap harness url, … */ }
if (e instanceof UnknownEngineError) { /* "did you mean…" */ }
throw e;
}AuditSink / AgentTelemetry errors never propagate. They're caught, logged at debug level, and swallowed. Telemetry must never break an agent run.
Companion packages
@open-gitagent/protocol@open-gitagent/sdk@open-gitagent/runtime-local@computeragent/runtime-bwrap@computeragent/runtime-e2b@computeragent/runtime-vzvm@computeragent/harness-server@computeragent/engine-claude-agent-sdk@computeragent/engine-gitagent@computeragent/engine-deepagents@open-gitagent/agent-registry-mongo@open-gitagent/session-store-mongo@computeragent/session-store-sqlite@computeragent/observability@computeragent/observability-api@computeragent/llm-proxy-openai@computeragent/identity-gitagentprotocol@computeragent/testing@computeragent/clicomputeragentcreate-computeragentVersioning
Public packages follow semver and are independently published. The umbrella computeragent is currently at 0.2.x — minor versions may include breaking changes until 1.0. Pin a major if stability matters.
Wire-protocol changes are versioned separately: events carry a protocolVersion field; the harness server rejects requests from incompatible client versions with HarnessProtocolError rather than malforming.