Skip to content

agora.config reference

The agora CLI and the MCP server resolve an agora.config file in the current working directory to obtain the AgoraClient they operate on (and, for the orch family, an OrchContext). Integrators typically keep one agora.config.mjs in their deploy repo.

On every CLI invocation, the loader looks in the current working directory for, in this exact order:

  1. agora.config.ts
  2. agora.config.js
  3. agora.config.mjs

The first file that exists is dynamically imported. If none exist, the CLI errors with no agora.config.{ts,js,mjs} found in <cwd>.

ExportUsed byRequired
default or named clientAll AgoraClient-backed commands (capabilities, subagent, env, dispatch, deploy)The client surface. Errors if neither is present.
named orchThe agora orch familyOnly when running an orch verb. Errors lazily (clear message) if an orch verb runs without it.

default and client are interchangeable for the client — the loader takes mod.default ?? mod.client. The orch export is an OrchContext:

interface OrchContext {
transport: SubmissionTransport & ControlChannel;
anchor?: AuditAnchor;
storage?: { get(ref: string): Promise<Uint8Array> };
verifySignature?: (root: Uint8Array, sig: Signature) => boolean;
runService?: (signal: AbortSignal) => Promise<void>; // pre-wired serve() for `agora orch serve`
scheduleStore?: ScheduleStore; // config-owned; required for `agora orch schedule` verbs
}

runService is required only for agora orch serve; the client verbs use transport (plus anchor/storage for status/watch/audit).

scheduleStore is required only for the agora orch schedule add|list|rm verbs; omitting it has no effect on any other verb. The default implementation is SqliteScheduleStore from @quarry-systems/agora-orchestrator, which persists schedules in a dedicated schedules table on the same SQLite database used by SqliteRunStateStore. Pass the same dbPath to both so they share one file:

import {
SqliteRunStateStore,
SqliteScheduleStore,
serve,
} from '@quarry-systems/agora-orchestrator';
const dbPath = join(tmpdir(), 'my-run-state.db');
const store = new SqliteRunStateStore(dbPath);
const scheduleStore = new SqliteScheduleStore(dbPath);
export const orch = {
transport,
runService: (signal) => serve({ orchestrator, transport, scheduler, signal }),
scheduleStore,
};

Custom implementations can satisfy the ScheduleStore interface directly.

This is the config from examples/offload-fanout/, which wires both a client and an orch context against the local provider stack. It is import-safe: no throw at load when ANTHROPIC_API_KEY is absent.

import { mkdtemp } from 'node:fs/promises';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import { AgoraClient, NoopCredentialProvider, StdoutResultSink } from '@quarry-systems/agora-client';
import { LocalStorageProvider } from '@quarry-systems/agora-storage-local';
import { LocalDockerProvider } from '@quarry-systems/agora-providers-local-docker';
import { LocalSecretStore } from '@quarry-systems/agora-secret-store';
import {
AgoraOrchestrator,
SqliteRunStateStore,
ManualTrigger,
DispatchExecutor,
AuditLog,
LocalAnchor,
createLocalSigner,
verifyEd25519,
MailboxSubmissionTransport,
LocalDirMailbox,
serve,
} from '@quarry-systems/agora-orchestrator';
const rootDir = join(tmpdir(), 'agora-fanout-storage');
const secretDir = join(tmpdir(), 'agora-fanout-secrets');
const mailboxDir = join(tmpdir(), 'agora-fanout-mailbox');
const dbPath = join(tmpdir(), `agora-fanout-${process.pid}.db`);
const workerImage = 'ghcr.io/quarrysystems/agora-worker:latest';
// AgoraClient — lazy: no Docker/network until dispatch fires.
export const client = new AgoraClient({
namespace: 'offload-fanout',
compute: { 'local-docker': new LocalDockerProvider({ allowUnpinnedImage: true }) },
storage: new LocalStorageProvider({ rootDir }),
secretStores: { local: new LocalSecretStore({ dir: secretDir }) },
credentials: { none: new NoopCredentialProvider() },
targets: { local: { compute: 'local-docker', credentials: 'none', secretStore: 'local' } },
resultSink: new StdoutResultSink(),
});
export default client;
// Audit + orchestrator setup (import-safe: constructors are lazy / in-memory).
const store = new SqliteRunStateStore(dbPath);
process.on('exit', () => { try { store.close(); } catch {} });
const signer = createLocalSigner();
const anchor = new LocalAnchor(store);
const auditLog = new AuditLog({ store, signer, anchor });
const orchestrator = new AgoraOrchestrator({
store,
executors: {
dispatch: new DispatchExecutor({
client,
target: 'local',
workerImage,
secrets: {
ANTHROPIC_API_KEY: { inline: process.env.ANTHROPIC_API_KEY ?? '' },
},
}),
},
triggers: { manual: new ManualTrigger() },
queues: { default: { concurrency: 2 } },
auditLog,
});
const verifySignature = (root, sig) => verifyEd25519(root, sig, signer.publicKey);
const transport = new MailboxSubmissionTransport(new LocalDirMailbox(mailboxDir));
const runService = (signal) => serve({ orchestrator, transport, signal });
export const orch = {
transport,
storage: client.storage,
anchor,
verifySignature,
runService,
};

The AgoraClient constructor options are documented in full on the AgoraClient API page — namespace, compute, credentials, storage, targets, secretStores, telemetry, resultSink, defaultModel, and dispatchRetention. The targets map keys each become a valid --target value for agora dispatch run; each target’s compute, credentials, and secretStore must reference a name present in the corresponding option map.

Targeting a self-hosted / S3-compatible store (MinIO, LocalStack)

Section titled “Targeting a self-hosted / S3-compatible store (MinIO, LocalStack)”

The S3 seams accept a custom endpoint, so the whole stack can run against MinIO, LocalStack, or any S3-compatible store — no AWS account required. The worked example is examples/offload-minio/ (a serve container + MinIO via docker-compose). The relevant options:

OptionWherePurpose
endpoint, forcePathStyle, regionnew S3StorageProvider({ bucket, endpoint, forcePathStyle: true, region })Point content-addressed storage at the custom endpoint (or inject a pre-built client).
S3Mailboxnew MailboxSubmissionTransport(new S3Mailbox(s3MailboxClient))The submission inbox/outbox over S3 (the cross-machine analogue of LocalDirMailbox).
AwsSecretStore + AWS_ENDPOINT_URL_SECRETS_MANAGERsecretStores: { aws: new AwsSecretStore() } + the endpoint env on serve & workersSecrets (e.g. the API key) staged into Secrets Manager — LocalStack for self-host, real SM on AWS. Network-reachable, so it crosses the serve→worker boundary; refs-only in the audit.
AGORA_S3_ENDPOINT (+ AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY / AWS_REGION)worker container envThe worker builds its own S3 client at boot to fetch bundles/upload patches; it reads these to reach the same endpoint.
extraEnvnew LocalDockerProvider({ extraEnv: { AGORA_S3_ENDPOINT, AWS_*, AWS_ENDPOINT_URL_SECRETS_MANAGER } })Delivers the worker-boot env above (S3 bootstrap + the Secrets Manager endpoint) to every launched worker container.

The S3 endpoint/creds must reach the worker as container env (via extraEnv), not via a bundle — the worker needs S3 access before it can resolve anything else. Secrets (the API key) go the proper secret lane — staged into a network-reachable SecretStore (Secrets Manager) and resolved by the worker over the wire, not a bundle value. Non-secret config travels as env bundles (content-addressed storage, reach workers).

On real AWS none of this is needed: the default S3 endpoint + an IAM task role + AWS Secrets Manager all work without custom endpoints or extraEnv. LocalStack just stands in for Secrets Manager (and the S3 opts for MinIO) when self-hosting.