npm.io
0.3.3 • Published 1h ago

@aevr/agents-runtime-local

Licence
MIT
Version
0.3.3
Deps
1
Size
316 kB
Vulns
0
Weekly
0

@aevr/agents-runtime-local

Single-deployment durable task runtime adapter for @aevr/agents.

Status

The package contract, versioned SQLite schema, durable repositories, deterministic worker, action safety, and resumable run coordination are available. Restart and clean-install release gates cover the complete local runtime.

Install

npm install @aevr/agents @aevr/agents-runtime-local

@aevr/agents is a peer dependency so applications control the core contract version shared by their host, runtime, and adapters.

SQLite database

import {
  LOCAL_RUNTIME_SCHEMA_VERSION,
  openLocalRuntimeDatabase,
} from "@aevr/agents-runtime-local/sqlite";

const database = openLocalRuntimeDatabase({
  filePath: "/var/lib/my-app/autonomous-agents.db",
});

console.log(LOCAL_RUNTIME_SCHEMA_VERSION);
database.close();

Opening a writable file-backed database creates parent directories, enables foreign keys, configures a busy timeout, selects WAL journaling with normal synchronization, and applies ordered migrations by default.

The schema includes agent definitions and credentials; tasks, threads, messages, runs, actions, and approvals; append-only events; idempotency records; schedules; inference-usage reconciliation; and runtime heartbeat leases. Query indexes cover task claims, projects, assignees, run/action lookups, approvals, events, schedules, and heartbeat expiry.

Migration guarantees:

  • Ordered contiguous positive versions and SHA-256 checksums.
  • BEGIN IMMEDIATE writer serialization with a migration-lock metadata row.
  • Domain migrations, migration records, and user_version updates commit atomically.
  • Interrupted migrations roll back and can be retried.
  • Applied checksum drift and database versions newer than the package are rejected.
  • Repeated migration at the current version is a no-op.

better-sqlite3 is a native dependency. pnpm workspaces must permit its install build; this repository sets allowBuilds.better-sqlite3: true.

Repositories

import {
  createSqliteRepositorySet,
  openLocalRuntimeDatabase,
} from "@aevr/agents-runtime-local/sqlite";

const database = openLocalRuntimeDatabase({
  filePath: "/var/lib/my-app/autonomous-agents.db",
});
const repositories = createSqliteRepositorySet(database);

const agent = await repositories.agents.get("agent-1");
const credentials = await repositories.credentials.list({ agentId: "agent-1", status: "active" });
const tasks = await repositories.tasks.list({ projectId: "project-1" });

The set implements the public agent, credential, task, message, run/action, approval, event, and presence repository contracts from @aevr/agents. It also provides local idempotency, schedule, and inference-usage reconciliation repositories. Writes enforce unique keys and optimistic versions; task claims, run leases, expiry recovery, and heartbeat sequences use immediate transactions.

Run leases are exclusive to one worker until release or expiry. Task-claim and run-lease recovery clear stale ownership durably so another worker can resume from persisted state. A live runtime heartbeat owns its (agentId, runtimeId) key until its server-stamped lease expires; conflicting workers cannot replace it early.

Pass a custom clock or heartbeatLeaseDurationMs to createSqliteRepositorySet for deterministic tests and deployment-specific presence leases. The caller owns the database handle and must close it during shutdown.

Worker runtime

import {
  LocalTaskRuntime,
  createSqliteRepositorySet,
  openLocalRuntimeDatabase,
} from "@aevr/agents-runtime-local";

const database = openLocalRuntimeDatabase({ filePath: "./agents.db" });
const repositories = createSqliteRepositorySet(database);
const runtime = new LocalTaskRuntime({
  workerId: "worker-1",
  eligibleAgentIds: ["agent-subject-1"],
  repositories,
  executor: {
    async execute({ task, abortSignal }) {
      // Delegate model/run/action behavior to the application integration.
      return {
        status: "completed",
        outcome: { summary: `Completed ${task.title}`, evidence: [] },
      };
    },
  },
});

await runtime.start();
// During application shutdown:
await runtime.stop();
database.close();

start() polls automatically; runOnce() exposes the same iteration for deterministic tests. The runtime dispatches due schedules through an optional application callback, atomically claims eligible tasks, transitions them through durable states, applies bounded exponential retry jitter, aborts work at its deadline, and persists explicit cancellation. stop() stops new claims and waits for active executors to settle.

Executors return completed, blocked, failed, or retry. Thrown executor errors are reported through onError and treated as retryable. Schedule dispatchers return the next due timestamp or null to disable the schedule. Inject clock, scheduler, and random implementations to test all timing behavior without sleeps.

Durable actions

DurableActionCoordinator executes a previously persisted action while preserving crash safety:

  • It compare-and-sets requested -> running and appends tool.started before invoking the host.
  • Supported idempotency uses deriveActionIdempotencyKey(actionId) and rejects changed request hashes.
  • Safe or host-idempotent operations may retry with the same durable identity after response loss.
  • Unsupported operations become unknown after uncertain dispatch or interrupted recovery and are never automatically repeated.
  • reconcile() is the only path from unknown to succeeded or failed.
  • onUnknownOutcome lets the application block the owning task/run or request human intervention after the action state is durable.

Callers must hold the owning run lease while executing or reconciling actions. Action inputs, response summaries, errors, and reconciliation results must already be redacted and bounded at this boundary.

Resumable runs

DurableRunCoordinator binds ordered events to persisted task, run, action, and approval state:

  • checkpoint() stores an application-neutral resume payload with aggregate versions and statuses.
  • loadLatestCheckpoint() and resumeFromCheckpoint() restore that payload after process restart.
  • suspendForApproval() persists the exact approval and moves the action, run, and task to waiting states before checkpointing.
  • resumeApproval() durably resolves the approval, restores executable state, checkpoints again, and emits one wakeup.
  • wakeParentForChild() records and deduplicates terminal child notifications before waking a blocked parent.
  • accumulateUsage() uses durable usage IDs so replay cannot double-count run or task usage.
  • onCheckpoint may return pruneBeforeSequence; pruning must preserve the current checkpoint and never renumbers surviving events.

Resume payloads are owned by the loop adapter or application integration and must be serializable, redacted, and bounded. The coordinator does not interpret provider-specific model state.

Policy-bound tools

DurablePolicyToolExecutor composes registered tools with the existing run and action coordinators:

  • Evaluates policy against exact structured-clone input and persists its canonical SHA-256 hash.
  • Creates one durable action per stable action ID and safely handles concurrent duplicate requests.
  • Persists deny outcomes without dispatching.
  • Suspends run, task, and action state for exact-input approval and checkpoints only IDs/hashes.
  • Resumes approved input only when hashes, expiry, application identity, and current policy still match.
  • Uses DurableActionCoordinator for supported idempotency, safe retries, and unknown unsupported outcomes.
  • Requires host summary callbacks so raw tool input/output is never persisted by default.
  • Writes every audit event to the run event stream as a durable outbox before attempting the host audit sink.

bindDurablePolicyTool() wraps any core RegisteredTool and derives a stable action ID from run, tool call, and capability IDs. Explicit tools use their own descriptor for policy and dispatch. Fallback tools can return a separate exact policyTool from resolvePolicyBinding; action, approval, policy, risk, and idempotency metadata then use the resolved operation while dispatch still calls the constrained fallback executor.

Applications must perform current membership/authorization lookup before invoking the durable wrapper and again before approved resume. The policy engine is reevaluated on resume. A changed policy ID/version requiring approval cannot reuse an old approval.

Design boundaries

  • Intended for one application deployment or one coordinated local worker group.
  • Implements public @aevr/agents repository contracts and TaskRuntimeAdapter.
  • SQLite is the first persistence implementation; PostgreSQL, MongoDB, and Temporal remain separate optional adapters.
  • No host-application or trading-specific behavior.

Development

pnpm test
pnpm run typecheck
pnpm run build
pnpm run verify:exports
pnpm run verify:package
npm pack --dry-run

The test gate includes a real SIGKILL/restart process boundary proving an unsupported accepted side effect is not applied twice, plus file-backed approval restart, exact-input change/expiry/revocation, concurrent action, audit-outbox, and explicit/fallback policy-binding cases. verify:package packs both the core peer and runtime, installs them into a clean offline fixture, opens SQLite, imports runtime APIs, typechecks an external consumer, rejects tarball leakage, and verifies publish workflow routing.

License

MIT

Keywords