@mitosislabs/sdk
v0.16.0
Published
Mitosis — persistent AI agents that remember, replicate, and coordinate
Readme
Mitosis SDK
Persistent AI agents that remember, replicate, and coordinate.
@mitosislabs/sdk is the official TypeScript SDK and mi CLI for the
Mitosis platform. It gives you typed access to every
office-manager API endpoint, OAuth API-key authentication (plus per-agent
secp256k1 signing for agents), a managed XMTP session protocol for talking to
agent pods, and a local keystore so you never paste secrets at a prompt.
import { MitosisClient } from '@mitosislabs/sdk';
// Authenticate once with `mi login` (browser OAuth — issues a personal API key),
// then the SDK picks it up from ~/.os1 automatically:
const mi = await MitosisClient.fromConfig();
const offices = await mi.offices.list();
const agents = await mi.employees.list(offices[0].id);
const balance = await mi.credits.balance(offices[0].id);Naming: the client class is
MitosisClient. It was previously calledOS1AdminClient(after the internal "OS-1" codename); that name is still exported as a deprecated alias for backward compatibility and will be removed in a future major version. New code should importMitosisClient.
Table of contents
- Install
- Quickstart
- Authentication
- The
MitosisClient - API surface
- XMTP — talking to agents
- The
miCLI - Keystore
- Errors
- TypeScript types
- Recipes
- FAQ & troubleshooting
- Repo layout
Install
# Library + CLI
npm install @mitosislabs/sdk
# CLI only (one-off use)
npx -p @mitosislabs/sdk mi --help
# Global CLI
npm install -g @mitosislabs/sdk
mi --helpRequirements: Node.js 18+. The package ships ESM ("type": "module").
Public TypeScript types are bundled.
Quickstart
1. Sign in once
mi login --endpoint https://mitosislabs.aiThis opens your browser for OAuth, issues a personal API key, and writes it
to ~/.os1/config.json (chmod 0600). The key is scoped to your account — it
authenticates as you and can't act on anyone else's behalf.
2. Verify
mi whoami
# → logged in as [email protected] (API key mi_…)3. Drive it
mi offices list
mi agents list --office <officeId>
mi agents hire --office <officeId> --name aria --role researcher --model sonnet
mi tasks create --office <officeId> --title "Audit Q1 revenue" --assign aria
mi chat <officeId> ariaUse it as a library
import { MitosisClient } from '@mitosislabs/sdk';
const mi = await MitosisClient.fromConfig(); // reads ~/.os1
const offices = await mi.offices.list();
console.log(offices.map(o => `${o.name} (${o.id})`));Where rows land (CLA-904)
mi offices create and mi agents hire go through the dashboard, not
office-manager directly. This ensures the canonical offices row gets the
dashboard-only columns (office_secret, manager_url, office_type, created_by)
and that the agent appears in the user's /dashboard view.
The transport derives the dashboard host from the configured endpoint:
| endpoint | derived dashboardEndpoint |
|---|---|
| https://m.mitosislabs.ai | https://mitosislabs.ai |
| https://m.dev.mitosislabs.ai | https://dev.mitosislabs.ai |
| http://localhost:8080 | http://localhost:3000 |
Override via dashboardEndpoint in ClientConfig for non-standard envs.
All other API calls (list/get/fire/etc.) target office-manager directly.
Creating a colony when you already own one
The dashboard's POST /api/offices route deduplicates per user: if you already
own a non-archived colony, it returns that one with existing: true and
ignores the requested name. This is intentional — onboarding flows can call
create idempotently without producing duplicates.
$ mi colonies create -n hello-text
note: you already own a colony named "willow-bend-15c6ff0e" (33f5682c-…) — returning that one.
the requested name "hello-text" was ignored.
to create a second colony, re-run with --force.
{
"id": "33f5682c-…",
"name": "willow-bend-15c6ff0e",
"existing": true
}To explicitly create a second colony, pass --force (CLI) or forceCreate: true
(SDK). This mirrors what the dashboard's office-selector UI does for its
"create new colony" button.
mi colonies create -n hello-text --forceawait mi.offices.create({ name: 'hello-text', forceCreate: true });For LLM agents: when existing: true is in the response, the user already
has a colony and the name they asked for was discarded. Do not pretend you
created what they requested. Surface the existing colony, ask whether they
want to keep using it or create a second one, and only retry with forceCreate
after explicit confirmation.
Authentication
API key (OAuth) — this is the one you want
Run mi login once. It does browser OAuth and persists a personal API key
(mi_…) to ~/.os1/config.json. The SDK sends it as Authorization: Bearer
mi_…; office-manager validates it server-side and resolves it to your user.
The key is scoped to your account and cannot impersonate anyone else.
// after `mi login`:
const mi = await MitosisClient.fromConfig();
// or pass the key explicitly (e.g. in CI — store it as a secret):
const mi = new MitosisClient({
endpoint: 'https://m.mitosislabs.ai',
apiKey: process.env.MITOSIS_API_KEY, // a mi_… key from the dashboard
});Generate keys for CI/headless use from the dashboard (user menu → Copy API
Key), or mi login --token mi_… to paste one on a remote host.
secp256k1 (act as a specific agent)
ECDSA over the canonical request payload:
{unix_timestamp}\n{HTTP_METHOD}\n{path_with_query}Hash is SHA-256, signature is 64-byte compact r||s hex. Office-manager
also accepts DER. Timestamps older than 60 seconds are rejected.
const mi = await MitosisClient.asAgent('office-uuid', 'aria');
// or explicitly
import { MitosisClient } from '@mitosislabs/sdk';
const mi = new MitosisClient({
endpoint: 'https://m.mitosislabs.ai',
agent: { agentId: 'aria', signingKey /* Uint8Array(32) */ },
});Which mode to use
| Mode | Sign with | Identity | Who uses it |
|---------------|-----------------------|------------|--------------------------------------|
| API key | mi_… OAuth key | you | everyone — apps, scripts, CI |
| secp256k1 | per-agent private key | the agent | agents calling the API on their own |
⚠️ First-party / internal only — HMAC JWT. The office-manager also accepts tokens signed with its shared
JWT_SECRET(theClientConfig.jwtoption). That secret is a master key: anyone holding it can mint a token for any user. It exists solely for first-party services that already run with the office-manager secret (the dashboard itself, internal CI). Do not use it as an application or end-user — use an API key. If you don't already operate the office-manager, you should never seeJWT_SECRET.
The MitosisClient
class MitosisClient {
constructor(config: ClientConfig);
// Static constructors
static fromConfig(): Promise<MitosisClient>;
static asAgent(officeId: string, agentName: string,
endpoint?: string): Promise<MitosisClient>;
// Convenience
health(): Promise<boolean>;
verifyAuth(): Promise<{ ok: boolean; method: string; error?: string }>;
close(): Promise<void>; // closes XMTP sessions
// Subsystems (each is a typed class — see § API surface)
readonly offices, employees, tasks, files, credits;
readonly xmtpApi, integrations, extensions;
readonly events, callbacks, backups, env, delegates;
readonly messages, workspace, roles, transfer, llmPing;
readonly whatsapp, chromium, capabilities, proxy;
readonly xmtp; // high-level XMTPChannel
readonly transport; // raw HTTP transport
readonly keystore; // local Keystore
}ClientConfig:
interface ClientConfig {
endpoint: string; // e.g. https://m.mitosislabs.ai
apiKey?: string; // mi_… OAuth key — the normal auth path
agent?: { agentId: string; signingKey: Uint8Array }; // act as an agent
jwt?: { jwtSecret: string; userId?: string }; // ⚠️ first-party only (master secret)
timeout?: number; // ms, default 30000
}API surface
Every endpoint on office-manager has a typed wrapper. Group → class on the client → backing route prefix.
| Group | client.<member> | Route prefix |
|-----------------|-----------------------|-------------------------------------------------|
| Offices | offices | /api/v1/offices |
| Employees | employees | /api/v1/offices/:id/employees |
| Tasks | tasks | /api/v1/offices/:id/tasks |
| Files | files | /api/v1/offices/:id/files |
| Credits + usage | credits | /api/v1/offices/:id/{credits,usage,quota,…} |
| XMTP | xmtpApi | /api/v1/offices/:id/xmtp |
| Integrations | integrations | /api/v1/offices/:id/{provider-models,integrations} |
| Extensions | extensions | /api/v1/offices/:id/extensions + /marketplace |
| Events | events | /api/v1/offices/:id/events |
| Callbacks | callbacks | /api/v1/offices/:id/callbacks |
| Backups | backups | /api/v1/offices/:id/backups |
| Env vars | env | /api/v1/offices/:id/env |
| Delegates | delegates | /api/v1/offices/:id/delegates |
| Messages bus | messages | /api/v1/offices/:id/messages |
| Workspace exec | workspace | /api/v1/offices/:id/workspace |
| Roles | roles | /api/v1/offices/:id/roles |
| Agent transfer | transfer | /api/v1/offices/:id/agents/{prepare,install,…} |
| LLM ping | llmPing | /api/v1/offices/:id/llm-ping |
| WhatsApp | whatsapp | /api/v1/offices/:id/whatsapp + per-agent |
| Chromium | chromium | /api/v1/offices/:id/chromium |
| Capabilities | capabilities | /api/v1/offices/:id/capabilities/self |
| Code/Codex pxy | proxy | /api/v1/offices/:id/{code,codex}-proxy |
For exhaustive method-by-method documentation see
llms-full.txt. A few highlights:
// Offices
await mi.offices.create({ name: 'acme', owner_id: userId });
await mi.offices.suspend(officeId);
await mi.offices.resume(officeId);
await mi.offices.kubeconfig(officeId);
// Hire / manage agents
await mi.employees.hire(officeId, { name: 'aria', role: 'researcher', modelTier: 'sonnet' });
await mi.employees.promote(officeId, 'aria', { modelTier: 'opus' });
await mi.employees.setSkills(officeId, 'aria', { add: ['github'] });
await mi.employees.logs(officeId, 'aria', { tail: 200 });
await mi.employees.activity(officeId, 'aria', { limit: 50, category: 'task' });
// Tasks
await mi.tasks.create(officeId, { title: 'Triage inbox', assigned_to: 'aria' });
await mi.tasks.stats(officeId);
// Files (shared drive)
await mi.files.upload(officeId, 'report.md', Buffer.from('...'));
const list = await mi.files.list(officeId);
const resp = await mi.files.download(officeId, 'report.md');
// Credits & usage
await mi.credits.balance(officeId);
await mi.credits.add(officeId, { amount: 100, reason: 'topup' });
await mi.credits.usageSummary(officeId);
await mi.credits.llmUsageSummary(officeId);
// Integrations
await mi.integrations.listProviderModels(officeId);
await mi.integrations.ensureSecret(officeId, 'github', 'ghp_...');
await mi.integrations.toggleAgent(officeId, 'github', 'aria', true);XMTP — talking to agents
The SDK ships a session-aware XMTP layer that bypasses the dashboard and talks directly to the agent's chat-server presence.
const session = await mi.xmtp.negotiateSession(officeId, 'aria');
console.log(session.sessionId, session.capabilities);
const handle = mi.xmtp.getSession(officeId, 'aria')!;
await handle.send('Summarise yesterday and propose three tasks for today.');
for await (const msg of handle.stream(/* pollIntervalMs */ 2000)) {
console.log(`[${msg.from_agent}] ${msg.content}`);
}Session protocol
- SDK fetches existing conversations from office-manager.
- Sends a control message:
{ "type": "__SESSION_START__", "session_id": "<uuid>", "capabilities_request": true } - Waits up to
timeoutMs(default 30s) for a__SESSION_ACK__carryingcapabilities. If none arrives the session falls back to plain messaging. send(content)posts plain content to the conversation;receive()pulls only newer messages from the target agent and filters out control frames;stream()yields them as an async generator.close()posts__SESSION_END__(best-effort) and tears the session down.
XMTPChannel is the per-client manager — it deduplicates by
{officeId, agentName}, so calling openSession twice returns the same
handle.
The mi CLI
The published binary is mi (formerly os1-admin).
mi --helpmi <command>
login sign in via OAuth (persists an API key)
whoami show who you're logged in as
init configure endpoint (~/.os1)
keys generate -o <office> -a <agent>
keys list -o <office>
keys pubkey -o <office> -a <agent>
offices list
offices create -n <name> -u <ownerId>
offices status <officeId>
offices delete <officeId>
agents list -o <office>
agents hire -o <office> -n <name> [-r role] [-m model]
agents get <office> <name>
agents logs <office> <name> [-t tail]
agents activity <office> <name> [-l limit] [-c category]
agents fire <office> <name>
chat <office> <agent> interactive XMTP session
send <office> <agent> "<msg>" one-shot message
tasks list -o <office>
tasks create -o <office> -t <title> [-d desc] [-k kind] [-a agent]
tasks stats -o <office>
files ls -o <office>
files upload <office> <localPath> [-n remoteName]
files download <office> <filename> [-o outputPath]
credits balance -o <office>
credits add -o <office> -a <amount> -r <reason>
api <METHOD> <path> [-d '<json>'] [--agent <name> --office <id>]Every command has --help for full details. The api subcommand is an
escape hatch for endpoints the typed wrappers haven't grown a method for
yet.
Keystore
~/.os1/
├── config.json # SDK config (endpoint, default office)
├── keys/
│ ├── jwt.key # HMAC secret (chmod 0600)
│ └── <officeId>/
│ └── <agent>.key # secp256k1 private key (chmod 0600)
└── sessions/ # active session stateProgrammatic access:
import { Keystore, generateKeyPair } from '@mitosislabs/sdk';
const ks = new Keystore();
const kp = await ks.generateAndStore(officeId, 'aria'); // public + private
const pub = await ks.getPublicKey(officeId, 'aria');
const list = await ks.listAgentKeys(officeId);The keystore creates directories with 0700, files with 0600. Custom
roots are supported via new Keystore({ basePath: '/custom/path' }).
Errors
All non-2xx responses raise OS1Error:
import { OS1Error } from '@mitosislabs/sdk';
try {
await mi.employees.hire(officeId, { name: 'aria' });
} catch (err) {
if (err instanceof OS1Error) {
console.error(err.status, err.code, err.message);
if (err.status === 402) /* office is out of credits */;
} else {
throw err;
}
}Common statuses: 401 invalid auth · 402 no credits · 403 forbidden · 404 missing resource · 409 name conflict · 429 rate limit (e.g. agent-hire is capped at 3 per 10 min per office).
TypeScript types
Every domain object is exported. The most commonly used shapes:
import type {
// Auth
ClientConfig, JWTAuthConfig, AgentAuthConfig, JWTPayload, KeyPair,
// Office + agent
Office, ClusterStatus,
Employee, EmployeeChannels, EmployeeResources, EmployeeStatus,
HireRequest, UpdateEmployeeRequest, PromoteRequest, SkillsRequest,
AgentKitConfig, AgentKitOwner,
// Tasks / files
Task, CreateTaskRequest, TaskStats,
FileInfo, FileChange, FileChangesResponse, FilePermission,
// Money
CreditBalance, AddCreditsRequest, CreditHistoryEntry,
UsageSummary, LLMUsageSummary, Quota, SetQuotaRequest,
// XMTP
XMTPConversation, XMTPGroup, XMTPMessage,
SendXMTPMessageRequest, CreateGroupRequest, SessionNegotiation,
// Activity / events
ActivityEvent, ActivityQuery, ChatSession,
PodCallback, PodEventRequest,
// Misc
Extension, MarketplaceItem, WhatsAppStatus, ChromiumInstance,
Delegate, ExecRequest, ExecResponse, Backup,
TransferStatus, Role, EnvVar, ModelInfo, IntegrationSecret,
PingResult,
} from '@mitosislabs/sdk';Helpers are also exported:
import {
MitosisClient, // main client
Transport, // raw HTTP transport (rare)
Keystore, // local secret storage
XMTPChannel, XMTPSession, // session-aware messaging
generateJWT, verifyJWT, authorizationHeader,
generateKeyPair, publicKeyFromPrivate,
signRequest, verifySignature,
OS1Error,
} from '@mitosislabs/sdk';Recipes
List every agent in every office
const mi = await MitosisClient.fromConfig();
for (const office of await mi.offices.list()) {
const agents = await mi.employees.list(office.id);
console.log(`[${office.name}] ${agents.length} agents`);
for (const a of agents) console.log(` - ${a.name} (${a.status.phase})`);
}
await mi.close();Stream live messages from an agent
const handle = await mi.xmtp.openSession(officeId, 'aria');
await handle.negotiate(15_000);
for await (const m of handle.stream()) {
if (m.content.includes('done')) break;
console.log(m.from_agent, m.content);
}
await handle.close();Act as an agent (secp256k1) for a single call
const agentClient = await MitosisClient.asAgent(officeId, 'aria');
await agentClient.callbacks.podEvent(officeId, { type: 'ready' });Tail logs in real time-ish
let lastSeen = '';
setInterval(async () => {
const { logs } = await mi.employees.logs(officeId, 'aria', { tail: 200 });
if (logs !== lastSeen) {
process.stdout.write(logs.slice(lastSeen.length));
lastSeen = logs;
}
}, 5_000);Raw API call with the typed transport
const result = await mi.transport.request<MyType>(
'POST',
`/api/v1/offices/${officeId}/custom/route`,
{ body: { foo: 'bar' } },
);FAQ & troubleshooting
401 / not logged in
Run mi login to sign in via OAuth and persist your API key, then retry. Check
status with mi whoami.
403 you do not own this office
Your API key authenticates as you, but the office belongs to another account.
You can only act on offices you own.
429 Too Many Requests on employees.hire
Mitosis caps agent creation at ~3 per 10 minutes per office. Wait or use
mi tasks create to queue work for an existing agent.
Session negotiation hangs forever
The agent pod isn't running, or it's an older agent that doesn't speak the
session protocol. The SDK times out after timeoutMs (default 30s) and
returns a session with empty capabilities; messages still send fine, you
just won't get capability discovery.
Can I use this from a browser?
No. The SDK uses node:crypto, node:fs/promises, node:os, and the
commander CLI library. It's a Node-only package.
Repo layout
src/
├── index.ts # public exports
├── client.ts # MitosisClient
├── transport.ts # authenticated fetch + SSE
├── auth/
│ ├── index.ts
│ ├── jwt.ts # HS256 generate/verify (mirrors office-manager)
│ ├── secp256k1.ts # ECDSA sign/verify (mirrors pubkey_auth.go)
│ └── keystore.ts # ~/.os1 secret storage
├── api/
│ ├── offices.ts # OfficesAPI
│ ├── employees.ts # EmployeesAPI
│ ├── tasks.ts # TasksAPI
│ ├── files.ts # FilesAPI
│ ├── credits.ts # CreditsAPI (credits + usage + quota)
│ ├── xmtp.ts # XMTPAPI (DMs, groups, messages)
│ ├── integrations.ts # IntegrationsAPI
│ ├── extensions.ts # ExtensionsAPI + marketplace
│ └── events.ts # Events / Callbacks / Backups / Env /
│ # Delegates / Messages / Workspace /
│ # Roles / Transfer / LLMPing / WhatsApp /
│ # Chromium / Capabilities / Proxy
├── xmtp/
│ ├── channel.ts # XMTPChannel — multi-session manager
│ └── session.ts # XMTPSession — negotiate / send / stream
├── cli/index.ts # `mi` binary
└── types/index.ts # all public TypeScript types
tests/ # vitest — auth, client, session, integration
scripts/list-employees.mjs # standalone usage example
llms-full.txt # exhaustive reference for LLM agents
SDK.md # original architecture writeup
CLAUDE.md # internal contributor notesLicense
MIT © Mitosis Labs
Links
- Website: https://mitosislabs.ai
- Docs: https://mitosislabs.ai/docs
- Issues / questions: open an issue on the monorepo
- Architecture deep-dive:
SDK.md - LLM-friendly reference:
llms-full.txt
