@chamberu/core
The public API of @chamberu/core, taken from packages/core/src/index.ts. Contract interfaces carry an I prefix (IStore, IClock, INotifier, IAgentState); data shapes are unprefixed types.
import { RunEngine, defineAgent } from '@chamberu/core';Test helpers live in @chamberu/test-kit.
Agents
function defineAgent<S extends StateSchema = StateSchema>(def: AgentDefinition<S>): Agent<S>;
const DEFAULT_MAX_STEPS: 20;
const DEFAULT_TIMEOUT_MS: 900_000; // 15 minutesThrows InvalidAgentDefinitionError for an invalid name or duplicate trigger ids. See Agents.
/*
* Copyright (c) 2026.
* Author Peter Placzek (tada5hi)
* For the full copyright and license information,
* view the LICENSE file that was distributed with this source code.
*/
import type { LanguageModel, ToolSet } from 'ai';
import type { Budget, RunContext } from '../run/types.ts';
import type { IAgentState, StateSchema } from '../state/types.ts';
import type { TriggerDefinition } from '../trigger/types.ts';
export type ToolFactoryContext<S extends StateSchema = StateSchema> = {
state: IAgentState<S>;
run: RunContext;
};
export type ApprovalPolicy = 'user' | ((input: unknown, ctx: { runtimeContext: RunContext }) => 'user' | 'allow' | 'deny');
export type AgentDefinition<S extends StateSchema = StateSchema> = {
name: string;
model: LanguageModel;
instructions: string;
triggers?: TriggerDefinition[];
tools?: (ctx: ToolFactoryContext<S>) => ToolSet;
/** Record<collection, StandardSchemaV1>. */
state?: S;
budget?: Partial<Budget>;
/**
* Tool name to policy (maps to AI SDK toolApproval). A call needing approval
* pauses the run in `waiting`; one approval per pause: when a step asks for
* several, the run waits on the first and the other calls are dropped from
* the history, so the model requests them again after the resume.
*/
approvals?: Record<string, ApprovalPolicy>;
/** Default 24h; on timeout the call is denied. */
approvalTimeoutMs?: number;
/** Notifier name registered on the engine. */
notify?: string;
};
export type Agent<S extends StateSchema = StateSchema> = {
readonly name: string;
readonly definition: Required<Pick<AgentDefinition<S>, 'budget'>> & AgentDefinition<S>;
};Triggers
function cron(expression: string, opts?: CronOptions): TriggerDefinition;
function http(opts?: HttpTriggerOptions): TriggerDefinition;
function manual(opts?: ManualTriggerOptions): TriggerDefinition;TriggerSource, TriggerDefinition and TriggerEnvelope:
/*
* Copyright (c) 2026.
* Author Peter Placzek (tada5hi)
* For the full copyright and license information,
* view the LICENSE file that was distributed with this source code.
*/
export type TriggerSource = 'cron' | 'http' | 'github' | 'mcp' | 'manual';
export type TriggerDefinition<P = unknown> = {
source: TriggerSource;
/** Unique within an agent, e.g. 'cron:*\/30 * * * *'. */
id: string;
/** Source-specific (cron: { expression, timezone?, overlap }). */
options: Record<string, unknown>;
// method signatures (bivariant): a TriggerDefinition<P> fits AgentDefinition.triggers
concurrencyKey?(payload: P): string | undefined;
idempotencyKey?(payload: P): string | undefined;
};
export type TriggerEnvelope<P = unknown> = {
id: string;
agent: string;
trigger: string;
source: TriggerSource;
payload: P;
idempotencyKey?: string;
concurrencyKey?: string;
thread?: string;
receivedAt: Date;
};CronOptions and OverlapPolicy, HttpTriggerOptions, ManualTriggerOptions:
/*
* Copyright (c) 2026.
* Author Peter Placzek (tada5hi)
* For the full copyright and license information,
* view the LICENSE file that was distributed with this source code.
*/
export type OverlapPolicy = 'skip' | 'queue-one' | 'allow';
export type CronOptions = {
timezone?: string;
overlap?: OverlapPolicy;
payload?: unknown;
};/*
* Copyright (c) 2026.
* Author Peter Placzek (tada5hi)
* For the full copyright and license information,
* view the LICENSE file that was distributed with this source code.
*/
import type { StandardSchemaV1 } from '@standard-schema/spec';
export type HttpTriggerOptions = {
path?: string;
schema?: StandardSchemaV1;
};/*
* Copyright (c) 2026.
* Author Peter Placzek (tada5hi)
* For the full copyright and license information,
* view the LICENSE file that was distributed with this source code.
*/
import type { StandardSchemaV1 } from '@standard-schema/spec';
export type ManualTriggerOptions = {
schema?: StandardSchemaV1;
};RunEngine
class RunEngine {
constructor(opts: RunEngineOptions);
dispatch(envelope: DispatchEnvelope): Promise<{ runId: string; deduplicated: boolean }>;
getRun(id: string): Promise<Run | undefined>;
on(event: RunEvent['type'], listener: (e: RunEvent) => void): () => void;
start(): Promise<void>;
stop(): void;
drain(opts?: { timeoutMs?: number }): Promise<void>;
idle(): Promise<void>;
readonly isDraining: boolean;
cancel(runId: string): Promise<void>;
resume(token: string, decision: ResumeDecision): Promise<void>;
tick(): Promise<void>;
recover(): Promise<number>;
}| Method | Description | Guide |
|---|---|---|
dispatch | Stores a queued run; deduplicates by idempotencyKey | Triggers |
getRun | Reads a run from the store | Run Lifecycle |
on | Subscribes to run.updated or step.appended; returns an unsubscribe function | Run Lifecycle |
start | Recovers, starts polling and starts queued runs | Recovery |
stop | Stops polling only | Recovery |
drain | Waits for in-flight runs (default 30 s), then marks the rest interrupted | Recovery |
idle | Resolves when nothing runs and nothing more can start | |
cancel | Cancels locally, in the store, or requests it from the owning replica | Recovery |
resume | Answers the approval a waiting run waits on | Approvals |
tick | Denies expired approvals and re-sends undelivered notifications; runs every poll | Approvals |
recover | Requeues abandoned runs; runs on start and every poll | Recovery |
/*
* Copyright (c) 2026.
* Author Peter Placzek (tada5hi)
* For the full copyright and license information,
* view the LICENSE file that was distributed with this source code.
*/
import type { Agent } from '../agent/types.ts';
import type { IClock } from '../clock/index.ts';
import type { INotifier } from '../notify/types.ts';
import type { Run, Step } from '../run/types.ts';
import type { IStore } from '../store/types.ts';
import type { TriggerEnvelope } from '../trigger/types.ts';
export type RunEvent = { type: 'run.updated'; run: Run } | { type: 'step.appended'; step: Step };
export type RunEngineOptions = {
store: IStore;
agents: Agent[];
notifiers?: Record<string, INotifier>;
/** Default `${hostname}-${pid}`. */
owner?: string;
/** Default 4. */
maxConcurrentRuns?: number;
/** Default 60_000, renewed every ttl/3 while running. */
claimTtlMs?: number;
/** HMAC secret for resume tokens; must not be empty. */
resumeSecret: string;
clock?: IClock;
/** Receives errors that have no caller to throw to. Default console.error. */
onError?: (error: unknown, run?: Run) => void;
};
export type DispatchEnvelope = Omit<TriggerEnvelope, 'id' | 'receivedAt'> & Partial<Pick<TriggerEnvelope, 'id' | 'receivedAt'>>;
export type ResumeDecision = {
approved: boolean;
reason?: string;
};| Option | Default |
|---|---|
notifiers | {} |
owner | `${hostname}-${pid}` |
maxConcurrentRuns | 4 |
claimTtlMs | 60_000, renewed and polled every claimTtlMs / 3 |
clock | systemClock |
onError | logs to console.error |
ApprovalRequest ({ approvalId, toolName, input }) is the tool call a waiting run waits on.
Runs
Run, RunStatusLiteral, RunContext, Step, Checkpoint, WaitPoint, Budget and Usage:
/*
* Copyright (c) 2026.
* Author Peter Placzek (tada5hi)
* For the full copyright and license information,
* view the LICENSE file that was distributed with this source code.
*/
import type { ModelMessage } from 'ai';
import type { TriggerEnvelope } from '../trigger/types.ts';
import type { RunStatus } from './constants.ts';
export type RunStatusLiteral = `${RunStatus}`;
export type Usage = {
inputTokens: number;
outputTokens: number;
};
export type Budget = {
maxSteps: number;
maxTokens?: number;
/**
* Wall-clock limit per execution (not cumulative across resumes): each
* execution of the run gets the full timeout again.
*/
timeoutMs?: number;
};
export type RunContext = {
runId: string;
agent: string;
trigger: TriggerEnvelope;
signal: AbortSignal;
};
export type WaitPoint = {
kind: 'approval';
approvalId: string;
toolName: string;
input: unknown;
expiresAt: Date;
tokenId: string;
/** Set once the agent's notifier accepted the approval request; tick() re-sends until then. */
notifiedAt?: Date;
};
export type Run = {
id: string;
agent: string;
status: RunStatusLiteral;
trigger: TriggerEnvelope;
concurrencyKey?: string;
thread?: string;
output?: unknown;
error?: { code: string; message: string };
usage: Usage;
waiting?: WaitPoint;
claim?: { owner: string; expiresAt: Date };
/**
* Set by cancel() on a replica that does not execute the run: the owner
* aborts it on its next poll, and a later execution settles it cancelled.
*/
cancelRequested?: boolean;
createdAt: Date;
updatedAt: Date;
startedAt?: Date;
finishedAt?: Date;
};
export type Step = {
runId: string;
index: number;
kind: 'llm' | 'tool' | 'wait';
name?: string;
input?: unknown;
output?: unknown;
error?: unknown;
startedAt: Date;
finishedAt: Date;
};
export type Checkpoint = {
runId: string;
stepIndex: number;
messages: ModelMessage[];
/** AI SDK finish reason of the step this checkpoint was taken after. */
finishReason?: string;
createdAt: Date;
};The RunStatus enum, whose values RunStatusLiteral is built from:
/*
* Copyright (c) 2026.
* Author Peter Placzek (tada5hi)
* For the full copyright and license information,
* view the LICENSE file that was distributed with this source code.
*/
export enum RunStatus {
QUEUED = 'queued',
RUNNING = 'running',
WAITING = 'waiting',
COMPLETED = 'completed',
FAILED = 'failed',
CANCELLED = 'cancelled',
INTERRUPTED = 'interrupted',
}Stores
class MemoryStore implements IStore {
constructor(opts?: { clock?: IClock });
}IStore and its parts (IRunStore, IStepStore, ICheckpointStore, IIdempotencyStore, ILockStore, IEffectStore, IStateStore) are listed on the Store Contract page.
Effect tools
function defineEffectTool<I, O>(def: Tool<I, O> & {
effectKey?: (input: I, ctx: RunContext) => string;
alreadyApplied?: (effectKey: string, input: I, ctx: RunContext) => Promise<O | undefined>;
}): EffectTool<I, O>;
function effectMarker(effectKey: string): string; // '<!-- chamberu:effect=<key> -->'
function wrapToolsForRun(tools: ToolSet, ctx: RunContext, store: IEffectStore): ToolSet;
const EFFECT: unique symbol; // marks a tool as an effect toolSee Effect Tools & Reconciliation.
Approvals
function createResumeToken(secret: string, p: ResumeTokenClaims & { expiresAt: Date }): string;
function verifyResumeToken(secret: string, token: string, now: Date): ResumeTokenClaims;
type ResumeTokenClaims = { runId: string; approvalId: string; tokenId: string };verifyResumeToken throws InvalidResumeTokenError for every failure. See Approvals & Resume Tokens.
State
function createAgentState<S extends StateSchema = StateSchema>(
agent: string,
schema: S | undefined,
store: IStateStore,
): IAgentState<S>;
function createStateTools<S extends StateSchema>(state: IAgentState<S>, schema: S): ToolSet;/*
* Copyright (c) 2026.
* Author Peter Placzek (tada5hi)
* For the full copyright and license information,
* view the LICENSE file that was distributed with this source code.
*/
import type { StandardSchemaV1 } from '@standard-schema/spec';
export type StateSchema = Record<string, StandardSchemaV1>;
export type Infer<T extends StandardSchemaV1> = StandardSchemaV1.InferOutput<T>;
export interface IAgentState<S extends StateSchema = StateSchema> {
get<T = unknown>(key: string): Promise<T | undefined>;
set(key: string, value: unknown): Promise<void>;
collection<K extends keyof S & string>(name: K): {
list(q?: { where?: Partial<Infer<S[K]>>; limit?: number }): Promise<Array<{ id: string } & Infer<S[K]>>>;
/** Validates `doc` via the collection's Standard Schema; throws ValidationError and stores nothing on failure. */
upsert(id: string, doc: Infer<S[K]>): Promise<void>;
remove(id: string): Promise<void>;
};
}See Agent State.
Notifiers
INotifier and Notification:
/*
* Copyright (c) 2026.
* Author Peter Placzek (tada5hi)
* For the full copyright and license information,
* view the LICENSE file that was distributed with this source code.
*/
export type Notification = {
kind: 'approval' | 'stuck' | 'result' | 'info';
agent: string;
runId: string;
title: string;
body: string;
/** Signed token resuming the run (kind 'approval'). */
resumeToken?: string;
expiresAt?: Date;
};
export interface INotifier {
readonly name: string;
/**
* Delivers a notification. `idempotencyKey` is stable per logical
* notification (`runId:approvalId` for approvals), so transports can dedupe.
*/
notify(n: Notification, opts: { idempotencyKey: string }): Promise<void>;
}See Notifiers.
Clock
const systemClock: IClock;/*
* Copyright (c) 2026.
* Author Peter Placzek (tada5hi)
* For the full copyright and license information,
* view the LICENSE file that was distributed with this source code.
*/
export type ClockTimerOptions = {
/**
* Keep the process alive until the timer fires (default false: engine
* timers never hold the event loop open). Test clocks ignore it.
*/
keepAlive?: boolean;
};
export interface IClock {
now(): Date;
setTimeout(fn: () => void, ms: number, opts?: ClockTimerOptions): { clear(): void };
}systemClock timers never keep the process alive unless keepAlive is set; only the drain deadline sets it.
Errors
ChamberuError, InvalidAgentDefinitionError, InvalidResumeTokenError, InvalidRunStateError, RunNotFoundError, BudgetExceededError, TimeoutError, FatalToolError, LeaseLostError, ValidationError and ErrorCode:
/*
* Copyright (c) 2026.
* Author Peter Placzek (tada5hi)
* For the full copyright and license information,
* view the LICENSE file that was distributed with this source code.
*/
export const ErrorCode = {
BUDGET_EXCEEDED: 'BudgetExceeded',
FATAL_TOOL: 'FatalToolError',
INVALID_AGENT_DEFINITION: 'invalid_agent_definition',
INVALID_RESUME_TOKEN: 'invalid_resume_token',
INVALID_RUN_STATE: 'invalid_run_state',
LEASE_LOST: 'lease_lost',
RUN_NOT_FOUND: 'run_not_found',
TIMEOUT: 'Timeout',
VALIDATION: 'validation_error',
} as const;
export type ErrorCode = typeof ErrorCode[keyof typeof ErrorCode];See Errors for when each is thrown.