Product

Agent activity streaming

Agents you run yourself report their runs to SuperOrgs with the Node SDK, by posting JSON over HTTPS, or by exporting the OpenTelemetry traces they already produce. You get live feeds, cost per run, event timelines, and Orion summaries.

Get a credential

A credential identifies one agent in one organization. Everything the agent reports lands under that agent, next to its owner, pod, and abilities.

  1. 1

    Open the SuperOrgs SDK connector

    In SuperOrgs, go to Connectors and choose SuperOrgs SDK.
  2. 2

    Connect an agent

    Choose Connect an agent, pick the agent this runtime reports as, and click Mint credential. The agent keeps its name, owner, pod, and abilities, and its runs land under it. Connecting a planned agent for the first time needs permission to create and link agents.
  3. 3

    Put the credential in the agent's environment

    It is shown once. SuperOrgs keeps only a hash and cannot show it again. Set both variables where the agent process runs, never in a browser bundle or in source control. Using OpenTelemetry instead of the SDK? Skip these two and use the exporter settings below.
    SUPERORGS_URL=https://app.superorgs.com
    SUPERORGS_STREAM_TOKEN=sot_...

An agent can hold more than one live credential, so rotation never interrupts delivery: mint a replacement from the agent's card, put it in the agent's environment and restart, wait for a delivery to show against the new credential, then revoke the old one. Revoking is immediate, and deleting the agent revokes everything.

Install the SDK

The SDK is a small Node library that runs inside your agent process. It has no runtime dependencies, ships ESM and CommonJS builds with type declarations, and needs Node 20.3 or newer. It is licensed Apache-2.0.

pnpm add @superorgs/sdk

Releases follow semantic versioning. Below 1.0, a breaking change bumps the minor version and everything else bumps the patch version, and each changelog entry names any compatibility implication. Prereleases publish under the next tag, so a plain install only ever gets a stable version.

Report a run

Create one client per process, start a run for each unit of work, report what it did, and finish it. Nothing is captured automatically: every event on the timeline is one call your code made, and prompts, private reasoning, and credentials never leave your process unless you send them.

import { createSuperOrgsClient } from '@superorgs/sdk'

const superorgs = createSuperOrgsClient({
  baseUrl: process.env.SUPERORGS_URL!,
  token: process.env.SUPERORGS_STREAM_TOKEN!,
})

const run = superorgs.startRun({
  externalId: jobId,            // your own id; a retry lands on the same run
  title: 'Weekly discovery sweep',
  origin: 'schedule',           // schedule | slack | web | api | your own label
})

await run.progress('Sweeping 3 communities')
await run.toolCall({ name: 'fetch_profile', args: { handle: 'octocat' }, callId: 'c1' })
await run.toolResult({ name: 'fetch_profile', callId: 'c1', output: { followers: 10 }, durationMs: 120 })
await run.usage({ model: 'claude-sonnet-4-5', inputTokens: 1200, outputTokens: 300 })
await run.artifact({ kind: 'report', title: 'Weekly brief', url: 'https://example.com/brief.pdf' })
await run.finish({ summary: 'Found 2 candidates worth a look' })

// when something goes wrong
await run.fail({ message: 'Apify quota exhausted' })

// on shutdown, so queued events get their last chance to leave
await superorgs.close({ timeoutMs: 10_000 })

Every call takes an optional second argument, { eventId, occurredAt }. Reuse an eventId on a replay and SuperOrgs treats the redelivery as a duplicate; the same id with different content is rejected. occurredAt is when the thing happened and defaults to now.

Client
createSuperOrgsClient(config)One agent's connection to one organization; the credential decides both. Create one per process. Takes baseUrl and token, and optionally a store, an onDiagnostic callback, and delivery overrides.
superOrgsConfigFromEnv()Reads SUPERORGS_URL and SUPERORGS_STREAM_TOKEN. Returns null when either is unset, so an agent can run without streaming instead of failing.
startRun(options)Records run.started and returns the run. externalId is your own id for the unit of work; the same id on a retry or after a restart lands on the same run. Also title, origin, autonomy, requestedBy, model, metadata, and abilityId.
flush()Sends everything that can be sent right now.
close({ timeoutMs })Flushes within a deadline, then stops. Pending events stay in the store.
updateToken(token)Swaps the credential after a rotation without restarting the process. Paused delivery resumes.
status()The delivery state, the queue counts, and whether the store is durable.
Run
progress(text)A readable line about where the run is. Shows on the run's timeline.
log(text)A free-form note. Use progress for anything a person should read in order.
message({ role, text })A turn of conversation you choose to publish. Nothing is captured for you.
toolCall({ name, args, callId })A tool the agent called. callId pairs it with its result.
toolResult({ name, callId, status, output, durationMs })What the tool returned. status is success or error; durationMs is a whole number of milliseconds.
usage({ model, inputTokens, outputTokens, costMicroUsd, estimated, source })Tokens and money for one call or one component of spend. costMicroUsd is integer micro-USD. Send each component once, never a total and its parts.
artifact({ kind, title, url, description })A link to something the run produced. Only http(s) URLs render as links.
ability(abilityId)The Task Library ability the run worked on. SuperOrgs checks it is assigned to the agent and records it as a declaration, never as proof of completion.
heartbeat()The run is still alive.
finish({ summary })The run ended well. The summary is what people read in the feed until Orion writes its own.
fail({ message })The run ended badly. The message is the line every SuperOrgs surface shows for the run.

How delivery works

The SDK owns one delivery loop. Events are recorded into a store the moment you call them and sent in batches, 100 events or 1 MB by default, within a second. Events leave in the order they were recorded: one waiting on a retry holds the ones behind it, so a run's timeline arrives in order.

Network failure, 5xx, or timeout
Retries with exponential backoff and full jitter, capped at 60 seconds. Events stay queued.
429
Retries no sooner than Retry-After.
401 or 403
Pauses delivery and keeps the queue. Call updateToken() after a rotation to resume, or the loop retries once a minute.
400 that names events
Quarantines the named events and resends the rest at once.
413, or a 400 that names nothing
Halves the batch until one event stands accused, then quarantines it. Nothing loops forever.
Oversized event
Never queued. Reported as event_too_large with the event id.

The receiver answers every batch with accepted, duplicates, rejected, and warnings. The SDK checks that the three counts add up to the batch before it removes anything from the queue. Diagnostics arrive through onDiagnostic with a stable code and never contain the credential or payload content; status(), listQuarantined(), and discardQuarantined() let you inspect the queue and clear events the receiver will never accept.

The default store is in memory: bounded at 10,000 events, with the oldest waiting events dropped and counted, and not durable, so a crash loses whatever is queued. For a durable outbox, @superorgs/sdk/sqlite uses your own SQLite database. Hand it your open better-sqlite3 connection. It owns two tables under its prefix, never changes PRAGMAs, never closes the connection, and never reads other tables. Recording is synchronous, so a run row and its events commit or roll back together inside your own transaction.

import Database from 'better-sqlite3'
import { createSuperOrgsClient } from '@superorgs/sdk'
import { createSqliteEventStore } from '@superorgs/sdk/sqlite'

const db = new Database('data/app.db')
const superorgs = createSuperOrgsClient({
  baseUrl: process.env.SUPERORGS_URL!,
  token: process.env.SUPERORGS_STREAM_TOKEN!,
  store: createSqliteEventStore(db),
})

The HTTP contract

No Node runtime? Post the same contract from any HTTP client. The SDK is a client of this endpoint and nothing more. Send a POST with an Authorization: Bearer header carrying the credential and a JSON body of events. A missing, invalid, or revoked credential returns 401.

Streaming endpoint
https://app.superorgs.com/api/agent-stream/v0/events

Each event has a stable event_id (a redelivered id counts as a duplicate, so retries are safe; the same id with different content is rejected with event_id_conflict), a run_external_id that groups events into one run, a type, an ISO 8601 occurred_at, a payload, and an optional run hint carrying title, origin, autonomy, requested_by, model, metadata, and ability_id. Identity fields keep their first value; metadata shallow-merges across deliveries. Terminal state follows occurrence time: a later finish or error wins, and on a tie the failure wins.

run.startedrun.finishedrun.errormessagetool.calltool.resultcostartifacthandoffheartbeatlog
curl -X POST https://app.superorgs.com/api/agent-stream/v0/events \
  -H "Authorization: Bearer $SUPERORGS_STREAM_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
  "events": [
    {
      "event_id": "job-42:started",
      "run_external_id": "job-42",
      "type": "run.started",
      "occurred_at": "2026-09-17T15:04:00Z",
      "payload": {},
      "run": { "title": "Nightly reconciliation", "origin": "schedule" }
    },
    {
      "event_id": "job-42:cost",
      "run_external_id": "job-42",
      "type": "cost",
      "occurred_at": "2026-09-17T15:05:30Z",
      "payload": {
        "model": "claude-sonnet-4-5",
        "input_tokens": 18422,
        "output_tokens": 3110,
        "cost_micro_usd": 84200,
        "estimated": false
      }
    },
    {
      "event_id": "job-42:finished",
      "run_external_id": "job-42",
      "type": "run.finished",
      "occurred_at": "2026-09-17T15:06:12Z",
      "payload": { "summary": "Reconciled 1,204 rows, no exceptions." }
    }
  ]
}'

A cost event carries input_tokens, output_tokens, cache_read_input_tokens, cost_micro_usd, model, estimated, and source. A figure you send is stored as measured, or as an estimate when estimated is true. Token-only usage on a model SuperOrgs can price is priced server-side and marked as an estimate; on an unknown model it shows as unpriced, never as free.

Limits, all in UTF-8 bytes: 500 events and 8 MB per request, 256 KB per event payload, 16 KB of run metadata, 300 characters per event id, and 500 per run id. The receiver answers 202 with the acknowledgement, 400 with a list of issues for an invalid batch, 413 for a body over the ceiling, and 429 with Retry-After when a credential sends too fast. In the acknowledgement, accepted + duplicates + rejected equals the number of events sent, and warnings carries stable codes keyed by event id for anything stored differently from how it was sent.

Send OpenTelemetry traces

Already exporting OpenTelemetry traces? Point the exporter at SuperOrgs with the same per-agent credential and skip the SDK. OTLP over HTTP only, as protobuf or JSON, gzipped or not. Each trace becomes a run: GenAI steps become the run's timeline, model calls become token usage priced server-side where SuperOrgs has a rate for the model, and tool executions become tool calls and their results. The OpenTelemetry GenAI conventions are read, along with OpenLLMetry, the AI SDK's telemetry, and OpenInference model calls.

OTEL_EXPORTER_OTLP_TRACES_ENDPOINT=https://app.superorgs.com/api/otel/v1/traces
OTEL_EXPORTER_OTLP_TRACES_HEADERS=Authorization=Bearer%20sot_...
OTEL_EXPORTER_OTLP_TRACES_PROTOCOL=http/protobuf
OTEL_EXPORTER_OTLP_TRACES_COMPRESSION=gzip

Prompts, responses, tool payloads, and error text are discarded on receipt and never stored; turn off content capture in your instrumentation if it must not leave your servers. If the agent's traces already go to another backend, add a second exporter, or give a Collector pipeline that carries this agent's traces alone, because every span it forwards lands under this one agent. Use one path per agent, the SDK or OpenTelemetry, not both; the connector page warns when an agent reports through both within a day.

Limits: 2,048 spans and 8 MB per request, with attribute and event nodes budgeted while decoding. The receiver answers 400 for a malformed batch, 401 for a bad credential, 413 for a body over the ceiling, 415 for a media type other than protobuf or JSON, and 429 or 503 with Retry-After when to try again. A resent batch is recognized as a duplicate, so retries are safe.

What you get

  • Live feeds, org-wide and per agent, newest run first. A run shows up as soon as a batch lands.
  • Cost per run in micro-USD, each figure marked measured, estimated, or unpriced.
  • The full event timeline for any run: progress, messages, tool calls, results, and cost.
  • A one-line Orion summary for each run, within 15 minutes or sooner when someone opens it.
  • Follow an agent in Slack and get its runs where you work.
  • Error forensics: open a failed run and see exactly what happened.

Also over MCP

The whole stream is queryable from your assistant. Ask what an agent did, what it cost, or why a run failed, right from Claude, ChatGPT, or Cursor. See Connect your AI assistant.

Agent activity streaming | SuperOrgs