Drive the whole CommandAGI platform as code: agent threads, memory and integrations, and live embodiments (cloud computers, robots, simulated worlds) that you watch and control.
npm i commandagi # the library
export COMMANDAGI_API_KEY=cagi_... # Settings → API keysThis package is the library only. The commandagi command and the local host (the workbench, the files and devices
of this computer, the MCP host, the 3D design service) are the package @commandagi/host:
npm install -g @commandagi/hostimport { CommandAGI } from "commandagi";
const cagi = new CommandAGI(); // reads COMMANDAGI_API_KEY
// Agents: start one on a goal and follow along.
const { threadId } = (await cagi.threads.create({
intent: "Summarise this week's robotics news",
})) as {
threadId: string;
};
await cagi.threads.send(threadId, "Add a link for each one.");
// Embodiments: launch a world YOU drive (no agent in it), watch it, control it.
await using world = await cagi.launch("simulation/warehouse");
console.log(await world.controls()); // what it accepts right now, with payload schemas
await world.sim.ik({ target: [0.3, 0, 0.4] }); // typed robot/sim control
const jpeg = await world.observe({ fresh: true }); // the next frame, after the move
// Anything else: every platform tool by name.
await cagi.call("list_snapshots");The commandagi command (from @commandagi/host) has this SDK's surface, rendered from the same table
(src/cli.ts), beside the local host's own commands:
commandagi whoami
commandagi threads create --intent "research X"
commandagi embodiments act emb_1 click '{"x":10,"y":20}'
commandagi call <tool> --json '{…}' # any platform tool by name
commandagi help # every command, rendered from the schemacommandagi with no arguments is an interactive chat with an agent (/help lists its commands).
commandagi agents — or /agents inside it — opens the agent command center: every thread you
can reach, grouped by where it lives (personal, each org, shared with you), filtered by what it needs:
| filter | means |
|---|---|
| Needs you | an open error, or an unread notification from the thread's agent |
| Working | its run is open and it acted in the last ten minutes |
| Ready | idle, active within the last day |
| Inactive | idle for longer |
tab/shift+tab filter, ↑/↓ move, enter opens the thread in the chat, n starts a new one,
g toggles grouping, ? help, esc back. It refreshes itself every few seconds.
commandagi daemon start | status | stop | logs | run hosts this computer: the same program the desktop app runs.
@commandagi/host's README says how.
This package, the Python SDK (pip install commandagi) and the CLI all come from one schema,
commandagi-sdk.schema.json, which is included in this package:
- Tools:
cagi.threads,cagi.embodiments,cagi.memory,cagi.integrations,cagi.social(platform, account),whoami,search,runandpost. Each one is a typed wrapper overcall(tool, args). Where the schema names a method's options, they are a typed interface:cagi.postal.send("Letters/Ada.letter.tsx", { provider: "click2mail" })(PostalSendOptions; the provider is"lob", the default, or"click2mail", US only; omitted, the account'spostal.providersorder picks it). - Control vocabularies on a live
Session:session.desktopfor computers,session.robotfor physical robots, andsession.simfor simulated worlds. Each method sends one action. The platform checks it against what the embodiment currently declares, whichsession.controls()lists; for anything else, usesession.act(action, payload).
src/generated.ts is regenerated from the schema in the CommandAGI monorepo. Don't edit it.
Compositional primitives that declare structure as the op graph every CommandAGI editor stores (plain
JSON: { id, nodes: { <id>: { id, type, label?, inputs } }, outputs?, meta? }, where a port holds a literal
or one wire { "wire": { node, port } }). The package holds the declaration only. It has no geometry
kernel, solver, router or renderer; the editor that opens your file evaluates what you declare.
-
CAD:
part,assembly,instance(a part declared by another file),box,cylinder,sphere,cone,sketch(rect,circle,polygon,slot),extrude,revolve,union,subtract,intersect,hole,fillet,chamfer,shell,copy,linearPattern,circularPattern,mirror. Each call is one node of the 3D document's feature graph. Lengths are in mm and angles in degrees. -
EDA:
circuit,board,component(reference, value, footprint, placement; its pins are its output ports),net,connect, andfootprints(chip0402–1206for R, C, L, LED and D, plus pin headers, after KiCad's library footprints). A circuit is the graph a.sch.tsxand its.pcb.tsxhold. Nets say what is meant to connect; nothing is routed. -
A schematic in JSX (a
<name>.sch.tsxthe CommandAGI circuit editor opens and edits): a<group>with<resistor>,<capacitor>,<inductor>,<voltagesource>,<currentsource>,<ground name="#PWR1">, each placed withschX/schY(the sheet's millimetres, Y down) andschRotation,<junction>,<trace from to>and<netlabel net connection>. They declare the circuit's own sheet. The editor writes each edit back into the file; an attribute that is an expression is never replaced with a literal (the edit is refused with its line). -
A company, an RFC or a case in JSX (
<Name>.company.tsx,<name>.rfc.tsx,<name>.case.tsx, which the company app and the contract editor open and edit):<Company name about files dashboard>with<Entity jurisdiction form formed fiscalYearEnd …>,<Registration kind jurisdiction id issued expires file>and the standard's parts, which ref their files (<Books journal>,<CapTable ocf>,<People folder>,<Calendar folder>,<Matters folder>);<Rfc title target body id>with<Option title summary>and<Change op parameter value …>;<Case respondent … id>with<Harm>and<Relief>. Import the tags fromcommandagi/design;documentOfreads them into the document the apps use (the company, the RFC or case draft). -
A video or a song in JSX (a
<name>.vid.tsxthe video editor opens, a<name>.mus.tsxthe music studio opens; both write each edit back):<video>with<track>,<clip src="media/take.mp4" start in out>(media stay files, named by path),<composition src="Intro.vid.tsx" start in out inputs>(another video, drawn live from its own code),<artboard src="Poster.draw.tsx" start duration view fit>(a drawing's page, drawn live),<title>,<transition>,<effect>,<keyframe>and<marker>;<song tempo timeSignature>with<track>,<synth>, the effects (<gain>,<filter>,<delay>,<reverb>,<eq>),<clip>and<note pitch="C4" start duration velocity>. Seconds on a video's timeline, beats in a song. -
A 3D document in JSX (a
<name>.3d.tsxthe CommandAGI 3D editor opens and edits): a<part>(or<assembly>) with<parameter>,<plane>, one element per feature named by its type (<sketch>,<extrude>,<revolve>,<fillet>,<hole>,<linearPattern>,<transform>…), a sketch's<point>,<line>,<circle>,<arc>and<constraint>as its children,<body>for a body's material and name, and<slot name value>for any other field. Each attribute is the field of the same name, as the 3D document holds it (millimetres, radians); the reader declares the document's graph itself. The editor writes each edit back into the file, and refuses (with the line) an edit to an attribute that is an expression or to an element in a loop. -
2D documents in JSX (a
.draw.tsx,.paint.tsx,.img.tsxor.nest.tsxthat the CommandAGI draw, paint, photo and nesting editors open and edit): a<drawing>of<layer>s holding<rect>,<ellipse>,<path d>,<text>,<group>and modifiers (<blur>,<transform>… wrap what they change); a<painting>whose<layer>s hold<stroke points>([x, y, pressure, t]each); a<photo>of<raster src>layers (their filters inside them) and adjustment tags (<exposure ev>,<hsl>…); a<nest>of<sheet>,<stock>,<options>and<part>. An element is one node of the editor's own document and its attributes are that node's inputs. A pixel layer names its image file (src); pixels are never code. The editor writes each edit back into the file. -
Office documents in JSX (a
<name>.sheet.tsx,.page.tsxor.deck.tsxthe sheets, docs and decks editors open and edit): a<workbook>of<sheet>s with<cell at value|formula …format>,<column>and<row>; a<page>of<h1>–<h3>,<p>,<bullet>,<numbered>,<todo>,<quote>,<pre>,<divider>and<image>, whose text is their children (marks<b> <i> <u> <s> <code> <a> <br />); a<deck>of<slide layout>s with<text>,<shape>and<image>boxes (x y w h). They declare the editors' own documents; the editor writes each edit back into the file and refuses an edit to what the code computes. -
A board in JSX (a
<name>.pcb.tsx): a<board schematic="Divider.sch.tsx" width height core copper>with<component name footprint pcbX pcbY pcbRotation layer>(the schematic's parts on the board, the board's millimetres, Y down),<trace layer width points from to>and<via pcbX pcbY drill diameter>. A trace's ends say which pin, via or trace point they land on. The circuit editor joins it onto its schematic and writes each board edit back into the file. -
The ontology's files in JSX (CommandAGI opens them where it opens their JSON, and writes each edit back into the code): a world (
worlds/<name>/world.tsx:<world name kind>with<space>,<unit uid name device position rotation>,<view>,<scene>and its<body>s), a device definition (devices/<name>/definition.tsx:<device>with its<channel>s and their bounds), a dashboard (<name>.dashboard.tsx:<dashboard>with<param>s, one layout of<split axis ratio>and<pane id kind …>, and<region side …>s), a geo project (<name>.geo.tsx:<geoproject>with<dateRange>,<camera>,<reference>) and a node graph (<name>.opgraph.tsx:<opgraph>with<node id type x y …ports>and<wire from="a:out" to="b:in">). A record is an element and its fields are its attributes, verbatim.declarationOfgives{ format, document, sources }(a node graph: the op graph itself). A code form declares; the application that reads it checks and enforces it as it does the JSON. -
Any graph:
graph,node(type, inputs),input(a graph input),code(a node that runs another file),channels(set, values)(the numbered portsset.1 … set.N). -
Importers read other frameworks into the same graph: tscircuit JSX (
<board>,<resistor>,<led>,<trace>…, compiled againstcommandagi/jsx-runtime),@jscad/modeling(primitives, booleans, translations andextrudeLinear) andreplicad(drawings,sketchOnPlane,extrude,cut,fuse). They refuse, by name, what they cannot read, rather than guessing.
A code part is a file that exports what it declares, and optionally its parameters:
import { part, box, hole } from "commandagi/design";
export const params = { width: { default: 60, unit: "mm", min: 30 } };
export default ({ width }: { width: number }) =>
part("Plate", () => {
let plate = box({ size: [width, 40, 4], center: [0, 0, 2] });
for (const x of [-1, 1]) plate = hole(plate, { at: [x * (width / 2 - 8), 0, 4], diameter: 3.2, depth: 4 });
return plate;
});Put its path in a graph's code node (a 3D document's code feature, or a code node in a circuit or
node graph). The editor runs the file in a sandboxed worker, with no network and a time limit, cached by the
file's content hash and the node's inputs. The node's other inputs override the params defaults, and
the node outputs what the file declares. The graph editor shows the node as one block naming its file,
so you change the part by editing the file. A code file may import only commandagi/design,
@jscad/modeling and replicad; to use another file, declare it as a code node.
declarationOf(module, inputs) is what the sandbox calls, and you can call it in your own tests.
The params a file declares are the ports of its block (typed, with their defaults; a wire overrides one).
Without a browser, evaluate a part headless in the local host's sandbox (@commandagi/host: a process of its own with
no network, no file writes and bounded time and memory; Python parts under the Pyodide the host ships):
commandagi code eval bracket.part.ts --input width=80 # prints its op graph and a summary as JSON
commandagi code eval blinker.circuit.ts --no-ir --out blinker.jsonAn agent connected to the host over MCP does the same with the evaluate_code tool.
A 3D document says what a mechanical engineer needs with typed elements, checked when the file is read:
export default () => (
<part name="Deck plate">
<box id="plate" center={[0, 0, 3]} size={[60, 40, 6]} operation="new" />
<hole id="h1" face={{ kind: "face", signature: { point: [0, 0, 6], normal: [0, 0, 1] } }}
size="M4 normal fit" style="counterbore" through consumes={["plate"]} />
<body id="plate" engineering={{ partNumber: "EX-0001", material: "6061-T6", process: "cnc-milling", finish: "anodize clear",
cost: { amount: 18, currency: "USD", source: "quote Q-118", date: "2026-10-13" } }} />
<joint id="j1" kind="revolute" a="plate" anchor={[0, 0, 0]} axis={[0, 0, 1]} limits={[-1.57, 1.57]} />
<sheet id="sheet1" size="A3" scale={2} views={[{ id: "front", view: "front" }, { id: "top", view: "top" }]} />
</part>
);size="M4 normal fit"is the clearance hole of ISO 273 (4.5 mm) and, withstyle="counterbore", the counterbore for an ISO 4762 screw (Ø8 × 4.4)."M4 tap"is the tap drill.holeSize("M4 close fit")gives the same row.engineering.materialnames a row of a materials table:material("6061-T6")is its density, modulus and strengths, and where they were read.Kernel.observethen says the body's mass (mass.grams).Kernel.sheet(doc, { format: "pdf" })draws the sheet from the build: views with hidden edges dashed, hole callouts, the document's dimensions and a title block.Kernel.bom(doc)lists the parts.standardRow("iso-4762", "M4"),standardRow("bearings", "608"): the dimension tables of standard parts.STANDARD_SOURCESnames where each table was read; the rows with their full provenance arecloud://global/standards/<table>.csv.
commandagi/design declares a part and builds nothing. Kernel asks the CommandAGI 3D kernel (OpenCascade) to
build it, on this computer or in the cloud.
import { Kernel } from "commandagi";
const kernel = new Kernel(); // reads COMMANDAGI_THREED
const { observation } = await kernel.observe(graph); // bodies, faces, edges, mass properties; status and failures
if (observation.status === "failed") throw new Error(JSON.stringify(observation.failures));
const step = await kernel.export(graph, "step"); // refused (KernelError.failures) when a feature failed
kernel.lastRan; // { kernel: "3d", build, place: "local" | "cloud" }graph is what a .3d.tsx declares (commandagi code eval "Plate.3d.tsx" prints it), or its JSON text. One setting
places the kernel: the places to try, in order (the CommandAGI setting kernels.3d.place):
COMMANDAGI_THREED |
where it runs | needs |
|---|---|---|
local,cloud (the default) |
this computer when its kernel is installed, else the cloud | one of the two below |
local |
this computer, offline, no account. Kernel starts it and it stops with your process |
once: npm install -g @commandagi/host, then commandagi threed install (it downloads OpenCascade as WebAssembly, about 130 MB) |
cloud |
the platform's kernel door (/kernels/3d/v1/<op>) |
the network and COMMANDAGI_API_KEY |
a URL (or COMMANDAGI_THREED_URL) |
one that runs already: commandagi threed serve --port 8091 (the local place) |
COMMANDAGI_THREED_TOKEN, the bearer you started it with |
A place where the kernel is not installed, or that has no key, is passed over. A refusal of the op (a build that is
not whole) is the answer, and is not sent to the next place. kernel.lastRan.place says where the last answer ran.
The placement sets it from where it sent the request, not from what the service says: a result made on this
computer is local and is never a cloud result. kernel.call(op, doc, params) gives the whole answer
({ outputs, result, ran }).
observe(doc), export(doc, "step" | "iges", { bodies }), measure(doc, "distance" | "angle" | "radius" | "closest", a, b), measureAll(doc, [{ quantity, a, b }, …]) (many on one build), interference(doc, { clearance, bodies }),
drawing(doc, { direction, up, section }), tessellate(doc, deflection). An entity is { body: "body_0", kind: "face" | "edge" | "vertex" | "body", index }, with the index as observe lists it.
- A build in which a feature failed is not whole.
observeanswers it withstatus: "failed"andfailures. Every other call refuses it:KernelErrorwithstatus422 andfailures(each failed feature, with why). interferencecompares every pair of bodies:clashes(shared volume),engagements(shared volume that all lies in a declaredthread: a screw in a tapped hole) and, withclearance,close(nearer than it). Each body has its part's name.- An assembly's files go with the call:
{ parts: linkedParts({ "bolt.3d.tsx": boltGraph, "bracket.step": text }) }, each under the name its<externalPart>gives. - A STEP file names its parts: each body is a product of its part's name, an assembly the product they are components of.
dfm(doc, { process: "print" | "mill", limits, setup })says whether a machine can make the part:limitsare a device definition'sprocess.printorprocess.mill,setup.toolsthe mill's tool diameters ([{ diameter_mm: 3.175 }]). Each finding ispass,failorunknown(neverpasswhen a limit or a tool is missing) with its value, limit andwhere(the body, the B-repfaces, a point);verdictsums them.check(doc, { devices }): a<check kind="printability" | "mill" options={{ device }}>reads its limits fromdevices,{ [ref]: { process } }, the definitions you read; without them the check is skipped and says why.
The local place is the local host's 3D design service (@commandagi/host, which needs no checkout of the CommandAGI
repository). Kernel uses the host installed beside this package, else the commandagi command on PATH.
commandagi threed status says whether the service is installed.
COMMANDAGI_API_KEY (your cagi_… key; its scopes decide what every call may do), COMMANDAGI_BASE_URL
(default https://api.commandagi.com) and COMMANDAGI_THREAD_ID (the default thread for self-thread
methods). The platform sets all three when your code runs inside it.
COMMANDAGI_THREED places the 3D kernel Kernel calls (local,cloud, local, cloud or a URL); COMMANDAGI_THREED_URL and
COMMANDAGI_THREED_TOKEN name a 3D design service that runs already, and its bearer.
- A release is the tag
v<version>in this repository and the same version on npm. Pin an exact version:npm install --save-exact commandagi@0.6.2. A range (^,~=,>=) takes the next release without asking you. - The three SDKs (Python, TypeScript, Rust) carry one version number. A vocabulary at version X in one language is the vocabulary at version X in the others.
- A version promises nothing about the next one. CommandAGI keeps no compatibility layers: a release may rename or
remove a function, an element or an attribute, with no alias and no migration.
CHANGELOG.mdsays what changed. - So a document belongs with the version that reads it. Keep the pin in the project that holds the documents, and change both together. A document you open in a CommandAGI editor is read by the SDK that editor ships.
MIT (LICENSE).