AgentOSAgentOSv0.125.1

TypeScript SDK

The TypeScript SDK runs hosted AgentOS agents from your code and reports agents that run in your own code, with the same history, alerts and reports. Two jobs in one package:

  • Agents that run in your own code: start a run, emit events and spans, end it. It appears in AgentOS with the same run history, alerts and reports as a hosted agent.
  • Hosted agents: run them from your code with invoke(), stream their output, continue conversations and answer their questions.

Built for production: telemetry is batched and delivered in the background with automatic retries, so instrumentation adds no latency to your agent and never interrupts its work.

Package: @agentos-sdk/core · Node 20+, Edge runtimes and modern browsers. Keep API keys on your server: never ship one to a browser.

Installation

npm install @agentos-sdk/core

AgentOS

import { AgentOS } from "@agentos-sdk/core";

const aos = new AgentOS(); // reads AGENTOS_API_KEY and AGENTOS_AGENT_ID

Every option can be passed explicitly instead:

const aos = new AgentOS({ apiKey: "aos_agent_...", agentId: "your-agent-id" });

Config options

OptionDefaultDescription
apiKeyAGENTOS_API_KEYAn agent key (aos_agent_…) or a workspace key (aos_ws_…). Required.
agentIdAGENTOS_AGENT_IDDefault agent. With a workspace key you can pass agentId per call instead.
baseUrlAGENTOS_BASE_URL, then https://agentos-ai.devFor self-hosted instances
workspaceAGENTOS_WORKSPACEWorkspace slug or id for control-plane calls. Needed with a personal key.
batchInterval500ms between event flushes
batchSize50Max events per request
maxQueueSize1000Events held per run. When full, the oldest debug/info events are dropped first.
maxRetries5Retries for transient failures, with backoff from 250 ms to 4 s
timeoutMs30000Per-request timeout for reporting calls
disabledAGENTOS_DISABLEDNo network and no side effects, for tests
onErrorlogs the first oneCalled with background failures, such as a batch that could not be delivered
fetchglobal fetchA custom fetch, for example behind a proxy

Hosted agents

aos.invoke(input?, options?)

Runs a hosted agent and waits for it (up to the server's 300 s limit). Always check status: a run can finish, or pause for an approval or a question.

const result = await aos.invoke({ ticketId: 4521 });

if (result.status === "completed") {
  console.log(result.output); // e.g. { category: "billing", reply: "..." }
} else if (result.status === "awaiting_approval") {
  // A tool that needs sign-off, such as sending an email, is waiting on the Approvals page.
  console.log("Waiting for approval:", result.approval?.approvalId);
} else if (result.status === "awaiting_input") {
  // The agent asked a question. Answer it with reply(), shown below.
  console.log("The agent asks:", result.inputRequest?.question);
}
OptionDescription
agentIdOverrides the client's agent
promptVariablesValues for {{KEY}} placeholders in the system prompt
startSession / sessionIdStart, then continue, a conversation AgentOS remembers. The result carries sessionId.
historyEarlier turns ({ role, content }, up to 50), if you keep the conversation yourself
externalUserIdYour end user's id, recorded on a new session
parentRunIdLinks the run as a child of another run
testA sandboxed test run: tools that would change something are stubbed, and the run is kept out of metrics

The result is { runId, status, output, durationMs, sessionId, approval, inputRequest }. output is typed with a generic: aos.invoke<{ summary: string }>(…).

An invoke is never retried after a network error, because the agent may already be running. It is retried when the server answers 429.

aos.invokeStream(input?, options?)

Same options, but yields events as the agent works:

for await (const event of aos.invokeStream({ ticketId: 4521 })) {
  if (event.type === "chunk") process.stdout.write(event.text);
  if (event.type === "tool") console.log(`\n[${event.status}] ${event.name}`);
  if (event.type === "done") console.log(`\nFinished in ${event.durationMs} ms`);
}

Event types: start, chunk, tool, then one of done, awaiting_approval or awaiting_input. Types a newer server adds are skipped. If the run fails, the loop throws an AgentOSError.

aos.reply(runId, response)

Answers a hosted run that paused with awaiting_input, and waits for it to continue. Returns the same result as invoke(). Needs a workspace key. Pass { requestId: result.inputRequest.requestId } to answer that exact question; the call then fails with 409 if it was already answered.

if (result.status === "awaiting_input") {
  // result.inputRequest.question: "Refund the full amount or offer a credit?"
  const resumed = await aos.reply(result.runId, "Offer a credit");
  console.log(resumed.status, resumed.output);
}

Agents that run in your own code

aos.withRun(fn, options?)

The simplest way to report a run. The run is completed with fn's return value, or failed with the error it throws (which is rethrown).

const category = await aos.withRun(
  async (run) => {
    const ticket = await loadTicket(4521); // your code
    run.emit("ticket.loaded", { ticketId: ticket.id, words: ticket.body.length });
    return classify(ticket); // your code; its return value becomes the run's output
  },
  { input: { ticketId: 4521 } },
);

aos.startRun(options?)

Starts a run and returns a Run. End it with complete(), fail() or cancel().

const run = await aos.startRun({
  input: { ticketId: 4521 },
  triggerType: "webhook", // "manual" | "scheduled" | "webhook" | "sdk" (default)
  externalRunId: "ticket-4521", // optional: starting again with the same id returns the same run
});

aos.flush()

Sends the queued events of every open run. In serverless functions, call it (or end your runs) before returning, because the platform may freeze the process. In long-running Node processes, queued events are also sent automatically before exit.


Run

run.emit(type, payload?, level?)

Queues a custom event. level is "debug" | "info" | "warn" | "error" (default "info").

run.emit("ticket.classified", { category: "billing", confidence: 0.92 });
run.emit("classification.uncertain", { confidence: 0.41 }, "warn");

Built-in emit helpers

run.emitLLMRequest("claude-sonnet-4-6", prompt);
run.emitLLMResponse("claude-sonnet-4-6", answer, { inputTokens: 512, outputTokens: 128 });

run.emitToolCall("lookup_order", { orderId: 991 });
run.emitToolReturn("lookup_order", { status: "shipped" });
run.emitToolError("lookup_order", "Order service timed out after 5s");

run.startSpan(name, options?)

Records timed, nested steps as a trace tree on the run page. See Tracing.

const step = run.startSpan("classify ticket", { kind: "step" });

const llm = step.child("claude-sonnet-4-6", { kind: "llm" });
const answer = await callModel(prompt); // your code
llm.end({ input_tokens: 1240, output_tokens: 90 }); // shown on the span

step.end(); // or step.fail(error) to record it at error level

run.requestHumanInput(prompt, options?)

Pauses and asks a person in the dashboard, then resolves with their reply. Waits up to timeoutSeconds (default 3600), reconnecting as needed.

const { input } = await run.requestHumanInput("Refund the full amount or offer a credit?", {
  timeoutSeconds: 1800,
});
// input: the reply someone typed in the dashboard, e.g. "Offer a credit"

Throws AgentOSTimeoutError if nobody replies in time.

run.complete(options?)

Sends queued events, then marks the run completed. Token usage is recorded on the run, so cost dashboards include it.

await run.complete({
  output: { category: "billing" },
  usage: { inputTokens: 1240, outputTokens: 90 },
});

run.fail(options) and run.cancel(options?)

await run.fail({ errorMessage: "Order service unavailable", errorDetail: { attempts: 3 } });
await run.cancel({ reason: "Ticket closed by the customer" });

complete, fail and cancel are safe to retry.

run.flush(), run.droppedEvents, run.isEnded

flush() sends queued events now; it never throws. droppedEvents counts events discarded because the queue was full or the server rejected them.

run.startTask(name, input?)

Records a discrete step with its own status. Spans are the richer option for new code.

const task = await run.startTask("fetch order history", { customerId: "cus_812" });
await task.complete({ output: { orders: 3 } }); // or task.fail({ errorMessage }), task.skip()

Control plane

Manage the whole workspace from code: agents, runs and their events, approvals, evals, schedules, resources, alerts, reports, members, keys. Use a workspace key (aos_ws_…), or a personal key with the workspace option.

const aos = new AgentOS({ apiKey: process.env.AGENTOS_WORKSPACE_KEY });

// Create an agent and run it every morning at 07:00 Lisbon time.
const agent = await aos.agents.create<{ agent_id: string }>({
  name: "Ticket triage",
  systemPrompt: "Classify each new support ticket and draft a first reply.",
  tools: ["read_resource"],
});
await aos.schedules.upsert({
  agentId: agent.agent_id,
  frequency: "daily",
  hourOfDay: 7,
  timezone: "Europe/Lisbon",
});

// See what's waiting for sign-off, and why a run failed.
const pending = await aos.approvals.list();
const timeline = await aos.runs.events({ runId: "11111111-1111-4111-8111-111111111111" });

Every method takes camelCase parameters and a second options argument (workspace, timeoutMs), and resolves with the server's JSON. Reads are retried after network errors; writes only after a 429. The full list, generated from the server's schemas, is in the control-plane reference.


LangChain

import { AgentOSCallbackHandler } from "@agentos-sdk/core/langchain";

await aos.withRun(async (run) => {
  return chain.invoke(input, { callbacks: [new AgentOSCallbackHandler(run)] });
});

Chains, models, tools and retrievers are recorded as a span tree with real model and tool names and token counts.


Error types

ClassCodeWhen thrown
AgentOSConfigErrorCONFIG_ERRORNo API key, or no agent id where one is needed
AgentOSAuthErrorUNAUTHENTICATEDInvalid or revoked API key
AgentOSValidationErrorVALIDATION_ERRORBad request payload
AgentOSRateLimitErrorRATE_LIMITEDToo many requests; retryAfter says how long to wait
AgentOSTransientErrorvariesA 5xx, network error or timeout that outlasted the retries
AgentOSTimeoutErrorTIMEOUTNobody answered requestHumanInput() in time
AgentOSErrorvariesEverything else, such as PAYMENT_REQUIRED (402) when the workspace has no plan

Every error has code and status (the HTTP status, or null).