npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@vantageos/agent-engine

v0.4.0-alpha.0

Published

Reusable Convex component: durable agent engine. Agent = data aggregate (root sheet + child tables), never compiled code. Built on @convex-dev/workflow + @convex-dev/workpool (+ @convex-dev/agent). Eve parity by design.

Readme

@vantageos/agent-engine

A reusable Convex component that provides a durable agent engine: sessions, delegation, tool approvals, scheduled "habits", bounded spending "mandates", and long-task survival — all built on top of @convex-dev/workflow and @convex-dev/workpool.

An agent is a data aggregate, not compiled code. Creating an agent means writing a row (plus child rows for skills, tool approvals, connections, sub-agents, habits, sandbox config) — there is no build step and no redeploy. This mirrors the authoring model pioneered by vercel/eve, whose slot names, contracts and vocabulary this package tracks closely; credit and thanks to that project for the design this component is built in parity with.

Why

Most agent frameworks compile an agent definition into code that has to be deployed to change. This engine keeps the entire agent definition as database rows: skills, tool approvals, delegated sub-agents, sandbox settings, and scheduled work are all mutable at runtime through ordinary Convex mutations. Combined with @convex-dev/workflow's durable execution, a single agent session — or a long-running background job — survives process restarts, Convex's per-action time limits, and redeploys of the consuming app.

Install

npm install @vantageos/agent-engine

What ships in the package

The published tarball is dist/ (compiled JS + .d.ts), README.md, LICENSE. dist/component/testFixtures/** is deliberately excluded (package.json "files" negation "!dist/component/testFixtures") — it is test-only demo workflow code used by this repo's own component-packaging tests, never part of the public exports (./convex.config and ./_generated/component.js), and not a documented consumer API. No file is published without a stated reason.

Quickstart

Register the component in your app's convex/convex.config.ts:

import { defineApp } from "convex/server";
import agentEngine from "@vantageos/agent-engine/convex.config";

const app = defineApp();
app.use(agentEngine);
export default app;

Then call into it from your own Convex functions. Every public function takes an explicit orgId: string argument — Convex does not propagate the calling app's ctx.auth identity across a component boundary, so your app is responsible for resolving the caller's org before calling in:

import { components } from "./_generated/api";

export const createAgent = mutation({
  args: { orgId: v.string(), name: v.string(), instructions: v.string() },
  handler: async (ctx, args) => {
    return ctx.runMutation(components.agentEngine.engine.agents.create, {
      orgId: args.orgId,
      createdBy: "user_123",
      name: args.name,
      instructions: args.instructions,
      model: "gpt-4o",
    });
  },
});

What's inside

The component's data model (root table agents, plus child tables for skills, sub-agents, connections, habits, sandbox config, and per-tool approvals) is deployed under the Convex component boundary your app registers — it does not touch your own app's tables, and every index is scoped by orgId first, deny-by-default.

API surface (component/, current release)

components.agentEngine.engine.agents.create({
  orgId, createdBy, name, description?, instructions, model,
  reasoningEffort?, modelProvider?, maxSubagents?,
  maxInputTokensPerSession?,
}) -> Id<"agents">
components.agentEngine.engine.agents.get({ orgId, agentId }) -> agent | null

components.agentEngine.engine.directory.listByOrg({ orgId })
  -> { _id, name, status, model, createdAt }[]

components.agentEngine.engine.resolvers.resolveManifest({
  orgId, sessionId,
}) -> { agentId, name, instructions, model, toolApprovals[] }

components.agentEngine.engine.sessions.start({
  orgId, agentId, createdBy,
}) -> { sessionId: string /* the world runId */, workflowId: string }
// 0.3.0: every `sessionId` below IS that runId (string); an unknown,
// cross-org or non-session id is refused as "Session not found".
components.agentEngine.engine.sessions.sendTurn({
  orgId, sessionId, input,
}) -> null
components.agentEngine.engine.sessions.appendReasoning({
  orgId, sessionId, chunk,
}) -> null
components.agentEngine.engine.sessions.requestToolCall({
  orgId, sessionId, toolName, argsJson,
}) -> { authorized, approvalPending, denied }
components.agentEngine.engine.sessions.completeToolCall({
  orgId, sessionId, toolName, resultJson,
}) -> null
components.agentEngine.engine.sessions.completeTurn({
  orgId, sessionId,
}) -> null
components.agentEngine.engine.sessions.end({
  orgId, sessionId, outcome: "completed"|"cancelled",
}) -> null
components.agentEngine.engine.sessions.listEvents({
  orgId, sessionId,
}) -> { _id /* journal eventId */, _creationTime /* event createdAt, ms */,
        name, payload?, createdAt }[]   (read from the world journal)

components.agentEngine.engine.toolApprovals.respond({
  orgId, sessionId, toolName,
  decision: "approved"|"denied",
  updatePolicy?: "always"|"once"|"never",
}) -> null

components.agentEngine.engine.wakeup.enqueue({
  orgId, agentId, mandateScope, cost, reason,
  workFunctionHandle, workflowId, awaitedEventName,
}) -> string  (action; the world-queue messageId)
components.agentEngine.engine.wakeup.cancel({ orgId, messageId }) -> null
components.agentEngine.engine.wakeup.getStatus({
  orgId, messageId,
}) -> { status: "pending" | "delivered" | "failed" }

components.agentEngine.engine.durableJob.start({
  orgId, kind, stepFunctionHandle, totalSteps, maxAttemptsPerStep,
}) -> string  (the world runId)
components.agentEngine.engine.durableJob.getStatus({ orgId, jobId /* runId */ })
  -> { status, cursor, totalSteps, lastError?, abandonedAtStep? }
components.agentEngine.engine.durableJob.cancel({ orgId, jobId }) -> null

components.agentEngine.engine.mandates.set({
  orgId, agentId, scope, kind, rate, period, capacity?,
}) -> Id<"mandates">
components.agentEngine.engine.mandates.get({
  orgId, agentId, scope,
}) -> mandate | null
components.agentEngine.engine.mandates.spend({
  orgId, agentId, scope, cost, reason,
}) -> { ok, approvalId? }
components.agentEngine.engine.mandates.listApprovals({
  orgId, agentId,
}) -> approvalRequest[]
components.agentEngine.engine.mandates.resolveApproval({
  orgId, approvalId, resolvedBy, status: "approved"|"denied",
}) -> null
components.agentEngine.engine.mandates.usageCount({
  orgId, agentId, scope,
}) -> number

components.agentEngine.engine.habits.create({
  orgId, agentId, name, cronExpression, prompt,
}) -> Id<"habits">
components.agentEngine.engine.habits.get({ orgId, habitId }) -> habit | null
components.agentEngine.engine.habits.listForAgent({ orgId, agentId }) -> habit[]
components.agentEngine.engine.habits.updateSchedule({
  orgId, habitId, cronExpression,
}) -> null
components.agentEngine.engine.habits.disable({ orgId, habitId }) -> null
components.agentEngine.engine.habits.enable({ orgId, habitId }) -> null
components.agentEngine.engine.habits.listRuns({ orgId, habitId }) -> habitRun[]

components.agentEngine.engine.sandboxCallback.startRun({
  orgId, workflowId, awaitedEventName, timeoutMs,
}) -> Id<"sandboxRuns">
components.agentEngine.engine.sandboxCallback.attachSandboxId({
  orgId, sandboxRunId, sandboxId,
}) -> null
components.agentEngine.engine.sandboxCallback.getStatus({
  orgId, sandboxRunId,
}) -> { status, resultSummary?, ... }
components.agentEngine.engine.sandboxCallback.processCallback({
  orgId, sandboxRunId, deliveryId, status, resultSummary?,
}) -> { outcome: "woke"|"duplicate" }
components.agentEngine.engine.sandboxCallback.deadLetterMalformed({
  orgId?, deliveryId, reason, rawBody,
}) -> null

components.agentEngine.engine.delegation.declareSubagent({
  orgId, parentAgentId, childAgentId, toolName, description?,
}) -> Id<"subagents">
components.agentEngine.engine.delegation.listSubagents({
  orgId, parentAgentId,
}) -> subagent[]
components.agentEngine.engine.delegation.delegateToSubagent({
  orgId, subagentId, ...,
}) -> null   (action)
components.agentEngine.engine.delegation.invokeAgentClone({
  orgId, agentId, ...,
}) -> null   (action; root-only, non-recursive by construction)

components.agentEngine.engine.vpConnection.get({
  orgId, agentId,
}) -> vpConnection | null
components.agentEngine.engine.vpConnection.getToggles({
  orgId, agentId,
}) -> { memoryEnabled, tasksEnabled, messagesEnabled, notesEnabled }
components.agentEngine.engine.vpConnection.setToggles({
  orgId, agentId, memoryEnabled?, tasksEnabled?,
  messagesEnabled?, notesEnabled?,
}) -> Id<"vpConnections">
components.agentEngine.engine.vpConnection.invokeVpTool({
  orgId, agentId, tool, vpFunctionHandle, argsJson,
}) -> null   (action)

Concepts

  • Sessions — a durable conversation thread, driven by a versioned @convex-dev/workflow run. Turns, streamed reasoning, tool-call lifecycle and approval lifecycle are appended as an event log a client replays on reconnect.
  • Delegation — an agent can declare typed sub-agents (strict isolation: a sub-agent's own skills/connections/approvals are never copied onto the parent) or invoke a non-recursive clone of itself as a built-in tool. Both mechanisms reuse the same pause/wake primitive.
  • Mandates — a bounded, weighted spending cap per agent/org/scope. Spending under the cap succeeds; an overrun creates a pending approval request rather than silently failing open or throwing away the audit trail.
  • Habits — a per-agent, runtime-editable cron schedule (create, update, disable, enable — all with zero redeploy).
  • Long-task survival — durableJob lets a consumer's task survive past a single Convex action's execution-time ceiling by expressing it as N independently-bounded, resumable steps.

Trust model

Convex does not propagate the calling app's ctx.auth identity across a component boundary. Every public function in this component takes orgId (and, where relevant, createdBy) as an explicit argument — your app is responsible for authenticating its user and resolving orgId before calling in. Every document lookup by id is checked against the caller's own orgId before being returned or mutated; a cross-org read or write is refused the same way a not-found row would be.

Dependency pins

ai is pinned to v6 (6.0.35) in the root package; any other tree that declares ai must use the same major (world-convex declares none today).

Two sub-packages (first-orchestrator/, scheduling-specialist/) keep ai ^7.0.58 because [email protected] peers ai ^7.0.58 (no eve release peers v6); each runs in its own process with its own install, sharing no dependency tree with root or world-convex. Everywhere else the v6 pin stands; scripts/vitestScope.test.ts enforces it.

Requirements

  • Convex ^1.42
  • A Convex deployment with @convex-dev/workflow, @convex-dev/workpool, @convex-dev/agent, @convex-dev/rate-limiter, @convex-dev/crons, and @convex-dev/sharded-counter resolvable (all installed automatically as dependencies of this package).

License

FSL-1.1-Apache-2.0 — free for production use; converts to Apache 2.0 after two years, per version.

Credit

Slot names, contracts and authoring vocabulary are deliberately kept in parity with vercel/eve. This package is not affiliated with Vercel; parity is a design choice, not a claim of origin.