Running Agents
runAgent, chat, chatStream, thread scoping, budgets, structured output, and pause/resume.
The SDK exposes both stateless runners and Maniac chat APIs. All paths share the same runner loop and return an AgentResult.
Stateless runners
import { runAgent, runAgentStream, runAgentResume, runAgentResumeStream } from "@maniac-ai/agents";
// Single turn — caller owns history
const result = await runAgent(spec, "Hello", {
prefixMessages: priorMessages,
signal: abortController.signal
});
// Streaming — yields trace events, then AgentResult as final item
for await (const item of runAgentStream(spec, "Hello")) {
if ("kind" in item) renderEvent(item);
if ("final" in item) console.log(item.final);
}
// Resume after approval pause
const resumed = await runAgentResume(spec, checkpoint, approvalResponses);RunOptions includes prefixMessages, threadId (for trace correlation), tracer, and signal (AbortSignal for cooperative cancel).
StreamEnvelope (resume)
runAgentResumeStream and Maniac.resumeStream emit tagged envelopes:
for await (const env of runAgentResumeStream(spec, checkpoint, responses)) {
if (env.type === "event") render(env.event);
else handleResult(env.result);
}Maniac chat APIs
const result = await app.chat("support", "Hello", {
threadId: "thread-1",
resourceId: "user-42",
memoryAgentId: "support", // optional stable memory namespace
signal: abortController.signal
});
for await (const envelope of app.chatStream("support", "Hello", { threadId: "thread-1" })) {
if (envelope.type === "event") { /* token, tool, step, ... */ }
else console.log(envelope.result.final);
}threadId is required — it scopes ConversationStore load/save. resourceId activates cross-thread observational memory when observationalMemory.scope === "resource".
Partial persistence on error
By default (persistPartialOnError: true), chat / chatStream persist partial transcripts when a run errors or is interrupted. Unanswered tool_call IDs receive synthetic failure observations so the next turn stays well-formed for tool-calling providers. Set persistPartialOnError: false to restore v0.2 drop-on-error behavior.
Threads
Thread identity flows through three layers:
threadIdinChatOptions— conversation store key (agent:<id>/threads/<threadId>/...)thread_idon trace events — propagated fromRunOptions.threadIdfor UI correlationresourceId— optional user identity for cross-thread observational memory
// Same thread — history carries forward
await app.chat("support", "My order is ord_123", { threadId: "sess-a" });
await app.chat("support", "When does it arrive?", { threadId: "sess-a" });
// Different thread — fresh history
await app.chat("support", "New topic", { threadId: "sess-b" });Budgets
Budgets enforce limits during the agent loop. Set on the agent spec or app-level budget:
const spec: Agent = {
id: "bounded",
instructions: "...",
model,
budget: {
max_iterations: 16,
max_tokens: 32_000,
max_cost_usd: 0.50,
max_wall_seconds: 60
}
};On overrun the runner emits a structured error event and returns status: "errored". Token and cost counters accumulate across all LM calls in the run (including sub-agents).
Structured output
Pass a Zod schema or JSON Schema as output_model. The runner validates the assistant's final text after the tool loop completes:
import { z } from "zod";
import { runAgent } from "@maniac-ai/agents";
const OutputSchema = z.object({
answer: z.string(),
count: z.number().int()
});
const result = await runAgent(
{
id: "assistant",
instructions: "Respond with JSON matching the Output schema.",
model,
output_model: OutputSchema,
output_repair_attempts: 1
},
"go"
);
expect(result.status).toBe("completed");
expect(result.output).toEqual({ answer: "ok", count: 3 });Repair loop
When validation fails and output_repair_attempts > 0, the runner emits a step event with phase: "repair" and asks the model to correct its answer. Exhausting repairs yields status: "errored".
Fenced JSON (```json ... ```) is stripped before validation.
Pause and resume
Runs with pending approvals return status: "paused" and a checkpoint. Resume with runAgentResume / Maniac.resume after the operator supplies ApprovalResponse values. See Permissions for HITL flows.