Runs and streaming
Every agent exposes run(). Durable agents (agent and saga) also expose
start(). run() waits for an outcome; start() returns a durable
AgentRun handle that you can stream, cancel, resume, or revisit later.
run() versus start()#
run() starts a run and waits for it to finish. One call, one result.
import { createAgent } from '@coresourceai/sdk';
const agent = createAgent({ type: 'agent', systemPrompt: 'Be exact.' });
const result = await agent.run('Reconcile the March ledger.');
console.log(result.status, result.text);
await agent.close();start() returns an AgentRun handle as soon as the run exists. Nothing is waited on.
import { createAgent } from '@coresourceai/sdk';
const agent = createAgent({ type: 'saga', systemPrompt: 'Be exact.' });
const run = await agent.start('Reconcile every ledger since January.');
console.log(`started ${run.id}`);
await agent.close();Use run() for scripts and request/response handlers where the wait is
acceptable. Use start() when a person is watching, when the work is long, or
when you want to store the run id and come back to it later.
The managed run continues if your process exits. The id returned by
createAgent().start() can be addressed through /v1/runs/{id}, so a
different process can retrieve status, results, retained events, or resume a
pause. Use the HTTP API for detached access. If the agent uses
custom tools or a locally registered MCP server, its SDK process must remain
connected whenever the run needs those local capabilities.
The AgentRun handle#
| Member | Returns | Purpose |
|---|---|---|
id | string | The durable run id |
events(options?) | AsyncIterable<AgentEvent> | Live event stream |
result(options?) | Promise<AgentResult> | Wait for and read the outcome |
cancel(options?) | Promise<void> | Stop the run |
resume(input, options?) | Promise<void> | Answer a pause and continue |
Streaming events#
import { createAgent } from '@coresourceai/sdk';
const agent = createAgent({ type: 'saga' });
const run = await agent.start('Draft the migration plan.');
for await (const event of run.events()) {
switch (event.type) {
case 'run.state_changed':
console.log('state:', event.data);
break;
default:
console.log(event.type);
}
}
const result = await run.result();
await agent.close();The stream can carry state changes (run.created, run.provisioning, run.state_changed, run.input_required, run.completed, run.failed, run.cancelled), progress (run.progress, task.*), messages (message.delta, message.completed), tool activity (tool.call.*), usage (usage.updated), and output updates (artifact.created, result.ready). Event types are strings rather than a closed enum. Filter them server-side when you only care about some of them:
import type { AgentEventOptions } from '@coresourceai/sdk';
const options: AgentEventOptions = {
types: ['run.state_changed', 'artifact.created'],
after: 'run_123:event:42',
timeoutMs: 60_000
};after takes an event id and resumes the stream from just past it. Event ids are optional, so persist the latest one when present and omit after if the stream has not supplied one.
A dropped stream is not a failed run#
This is the single most important operational fact on this page. The event stream is a view onto a run; it is not the run. Long quiet stretches can drop the connection. The run is unaffected.
import { createAgent, type AgentEvent } from '@coresourceai/sdk';
const agent = createAgent({ type: 'saga' });
const run = await agent.start('Audit every invoice from 2024.');
let lastEventId: string | undefined;
async function follow() {
for await (const event of run.events({ after: lastEventId })) {
lastEventId = event.id ?? lastEventId;
report(event);
}
}
// Reconnect a few times, then stop watching and just wait for the outcome.
for (let attempt = 0; attempt < 3; attempt += 1) {
try {
await follow();
break;
} catch {
console.warn('stream dropped; reconnecting from', lastEventId);
}
}
const result = await run.result();
console.log(result.status);
await agent.close();
declare function report(event: AgentEvent): void;If you take one thing from this page: never surface a stream error to your user as a failed run. Fall through to result().
Waiting for a result#
result() polls until the run reaches a terminal state, or until it pauses for input.
import { createAgent } from '@coresourceai/sdk';
const agent = createAgent({ type: 'saga' });
const run = await agent.start('Compile the annual report.');
const result = await run.result({
// Optional. Without it there is no overall timeout and a long-horizon run
// can be waited on for days.
timeoutMs: 30 * 60 * 1000,
pollIntervalMs: 5_000
});
console.log(result.status);
await agent.close();timeoutMs bounds your wait, not the run. When it expires you stop waiting; the run carries on. To actually stop the work, cancel it.
Cancellation#
There are two independent things you might want to abort, and confusing them causes real bugs.
Cancel the run to stop work on the platform:
import { createAgent } from '@coresourceai/sdk';
const agent = createAgent({ type: 'saga' });
const run = await agent.start('Reindex everything.');
await run.cancel();
const result = await run.result();
console.log(result.status); // cancelled
await agent.close();Abort the request. Stops your HTTP call and leaves the run alone:
import { createAgent } from '@coresourceai/sdk';
const agent = createAgent({ type: 'agent' });
const controller = new AbortController();
setTimeout(() => controller.abort(), 5_000);
const run = await agent.start('Summarise the changelog.', {
signal: controller.signal
});
await agent.close();signal on a request option aborts that request. cancel() ends the run. If you want both, do both.
Retries and idempotency#
run() and start() accept start controls, while AgentRun methods accept request controls. Only options relevant to the specific method are available.
| Option | Type | Purpose |
|---|---|---|
signal | AbortSignal | Abort this request |
timeoutMs | number | Per-request timeout |
retry | boolean | number | AgentRetryOptions | Retry policy |
maxRetries | number | Shorthand for retry count |
idempotencyKey | string | Deduplicate the work-starting request; available on run() and start() |
headers | Record<string, string> | Extra request headers |
GET requests are retried up to two times by default when a network error or retriable HTTP status occurs. A POST or other mutation is retried only when you supply idempotencyKey; this prevents the SDK from automatically repeating an unsafe request.
import { createAgent } from '@coresourceai/sdk';
const agent = createAgent({ type: 'agent' });
const run = await agent.start('Process the nightly batch.', {
// Retrying a start without this could launch the batch twice.
idempotencyKey: 'nightly-batch-2026-07-29',
retry: { maxRetries: 4, baseDelayMs: 500, maxDelayMs: 10_000 }
});
await agent.close();Use idempotencyKey on anything that starts work and has a natural unique name. If a start succeeds but its response is lost, manually repeating it without the same key can create a duplicate run.
Passing data with the prompt#
A prompt can be a plain string or an object carrying structured input, files, and metadata.
import { createAgent } from '@coresourceai/sdk';
const agent = createAgent({ type: 'agent', outputs: ['findings.md'] });
const result = await agent.run({
prompt: 'Analyse the supplied invoices and write findings.md.',
input: { country: 'DE', currency: 'EUR' },
files: ['./invoices.csv'],
metadata: { requestedBy: 'finance-ops' }
});
await agent.close();The same fields are also accepted as run options, which is convenient when the prompt itself stays a string:
import { createAgent } from '@coresourceai/sdk';
const agent = createAgent({ type: 'agent' });
const result = await agent.run('Analyse the supplied invoices.', {
input: { country: 'DE' },
files: ['./invoices.csv']
});
await agent.close();