@oncell/sdk
v0.6.2
Published
OnCell SDK — per-user sandboxed environments with persistent storage for AI agents
Downloads
1,134
Maintainers
Readme
@oncell/sdk
TypeScript client for oncell.ai — the cloud where AI agents live.
npm install @oncell/sdkThe model
Two objects, and one you never touch.
| | What it is | |---|---| | Project | Owns model credentials and quota. Many agents share one. | | Agent | The unit you address. Identity + tools + skills. | | Cell | The running instance. Created and recycled by the platform; its ID is internal and you never handle it. |
An agent is a definition; a cell is that definition running. You describe the agent and operate on the agent — the platform decides when an instance needs to exist.
Quick start
import { OnCell } from "@oncell/sdk";
const oncell = new OnCell({ apiKey: process.env.ONCELL_API_KEY });
// A project owns the credentials your agents draw on.
const project = await oncell.projects.create({ name: "prod" });
await oncell.projects.addCredential(project.projectId, {
value: process.env.ANTHROPIC_TOKEN!,
label: "seat-1",
});
// An agent: identity, the tools it can touch, the skills it knows.
const agent = await oncell.agents.create({
name: "builder",
projectId: project.projectId,
identity: { instructions: "You build and ship web apps." },
capabilities: ["workspace", "shell", "git"],
});
// Run it.
await oncell.agents.start({ agentId: agent.agentId });
await oncell.agents.startService(agent.agentId, { cmd: "node server.js" });
const { stdout } = await oncell.agents.exec(agent.agentId, { cmd: "npm test" });Agents
Defining
await oncell.agents.create({ name, projectId, identity, capabilities, skills });
await oncell.agents.list();
await oncell.agents.get(agentId);
await oncell.agents.update(agentId, { identity }); // new version; running instances keep theirs
await oncell.agents.destroy(agentId); // removes the agent AND its stateIdentity is the base prompt, model, and budgets. Capabilities are what the agent may touch — prebuilt names (memory, db, files, shell, secrets, ask_human, agents, cells, schedule) or presets (workspace, git). Skills are a prompt for specific work plus the tools that work uses, loaded when relevant.
Capabilities and skills are fields on an agent, not resources — there is nothing to CRUD.
Running
await oncell.agents.start({ agentId, tier, snapshotKey });
await oncell.agents.status(agentId);
await oncell.agents.pause(agentId);start() is idempotent: if the agent is already running you get it back, and if it is dormant an instance is materialised from its latest snapshot. There is no resume() — a paused agent has no instance to resume, so you simply start it again.
pause() snapshots and ends the instance. It is routine and non-destructive. destroy() is the permanent one — it removes the snapshots too.
Operating
// Shell — no network here; installs belong in a service command
await oncell.agents.exec(agentId, { cmd, timeoutMs, idempotencyKey });
// The one long-lived process, and what the agent's URL serves
await oncell.agents.startService(agentId, { cmd, env });
await oncell.agents.getService(agentId);
await oncell.agents.stopService(agentId);
await oncell.agents.serviceLogs(agentId, 200); // first stop when it won't boot
// Files and key-value
await oncell.agents.writeFile(agentId, path, content);
await oncell.agents.readFile(agentId, path);
await oncell.agents.listFiles(agentId, dir);
await oncell.agents.dbSet(agentId, key, value);
await oncell.agents.dbGet(agentId, key);
// Snapshots — build once, start many
const snapshot = await oncell.agents.snapshot(agentId);
await oncell.agents.snapshots(agentId);
await oncell.agents.fork(agentId, "builder-staging");
// Observability — journal and logs work with no live instance
await oncell.agents.journal(agentId);
await oncell.agents.logs(agentId, 100);
await oncell.agents.metrics(agentId); // live counters; needs a running instance
// Wake later
await oncell.agents.setWake(agentId, new Date(Date.now() + 3_600_000));
await oncell.agents.cancelWake(agentId);Invariants that will bite you
Enforced by the platform, not this SDK. Each fails in a way that looks like something else.
A service must bind $PORT on 0.0.0.0. The port is injected, never chosen. A hardcoded port or a loopback-only bind is unreachable and reads as "never became ready".
It has ~30 seconds to accept a connection. A cold npm install takes minutes, so an install-then-serve command gets killed. Bind a placeholder on $PORT first, install behind it, then hand off.
exec has no network. Only the service context does. An npm install from exec doesn't error — it hangs, then times out.
Environments are bare. node and npm only: no git, curl, wget, or python. Fetch with node's own fetch.
Your app owns every path. Nothing at the top level is reserved.
Preview traffic is not authenticated by the platform. Apps bring their own auth.
Projects and model credentials
Bind an agent to a project and its service starts with gateway credentials in its env. Coding agents read those natively, so an unmodified agent inside the sandbox can call a model — and it never holds a vendor key.
await oncell.projects.create({ name, authMode, provider });
await oncell.projects.addCredential(projectId, { value, label });
await oncell.projects.credentials(projectId); // metadata + window counters
await oncell.projects.deleteCredential(projectId, credentialId);Credentials are write-only — no endpoint returns the secret. To rotate, add the replacement and delete the old one.
Add more than one and the pool earns its keep: the gateway spreads calls across them and routes around whichever is rate-limited. When all are parked you get a retryable 429 with code POOL_EXHAUSTED — queue and retry; do not surface it as a failure.
Errors
import { OnCellError } from "@oncell/sdk";
try {
await oncell.agents.exec(agentId, { cmd: "ls" });
} catch (err) {
if (err instanceof OnCellError && err.status === 429) {
// retryable — back off
}
}Scope failures return 403 with { error: { code: "INSUFFICIENT_SCOPE", required_scope } }.
API keys and scopes
Authenticate with Authorization: Bearer oncell_sk_... (or set ONCELL_API_KEY). Keys created with a scopes array are deny-by-default:
agents:read · agents:write · agents:run · projects:read · projects:write · usage:read · keys:manage · domains:manage · secrets:manage
A key created without a scopes array has full access — always set scopes at creation.
Changelog
0.5.0 — breaking
The client now matches the platform's entity model: project → agent, with the running instance internal.
oncell.cellsis gone. Cell IDs are internal to the platform, so there was no caller-visible subject for acellsresource. Every operation moved tooncell.agents.*and is addressed by agent ID.- Added
oncell.agents—create,list,get,update,destroyfor the definition, plus every run-time operation. - Removed the customer dimension.
customer_idwas a business assumption no caller matched: real consumers passed agent-group slugs and project IDs, not customers. The agent is the address. - Removed
resume(). A paused agent has no instance to resume;start()is idempotent and restores from the latest snapshot. pause()returns void — the instance is gone, so there is nothing to describe.
0.4.0
- Removed six entity-CRUD methods (
dbCreate,dbQuery,dbGetAll,dbGetById,dbUpdate,dbDeleteRecord) that mapped to host RPCs which never existed — every call returned400 METHOD_UNKNOWN. - Removed
image,agent,secretsfrom create; the API silently ignored them, so environments came up bare while looking configured. - Added exec, service control, snapshots, fork, wake, and observability — all shipped in the API but absent from this client.
Links
- Docs: oncell.ai/docs
- Dashboard: oncell.ai/dashboard
License
Apache-2.0
