@vantageos/agent-engine 0.1.0-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,257 @@
1
+ # @vantageos/agent-engine
2
+
3
+ Reusable Convex component (pattern `@convex-dev/*`, house precedent
4
+ `vantage-doc-forge`): durable agent engine. **An agent is a DATA
5
+ AGGREGATE** — root sheet + child tables (skills, recursive subagents,
6
+ connections, habits, sandbox config, per-tool approvals) — **never compiled
7
+ code**. Instant creation, zero deployment. Built on `@convex-dev/workflow`
8
+ + `@convex-dev/workpool` (+ `@convex-dev/agent` for threads/context).
9
+
10
+ Consumers, in order: VantagePeers (Sigma, I1 long-task survival),
11
+ EveVantage (Hestia), VantageCRM (Theta).
12
+
13
+ Bible: `elpiarthera/ElPi-Corp` (private) pinned @ `60b69bfc` —
14
+ `analysis/evevantage/SYNTHESE.md` §4/§7,
15
+ `analysis/evevantage/i0-research-convex-components.md`,
16
+ `analysis/evevantage/mvp-evevantage.md` §2. Upstream reference:
17
+ https://github.com/vercel/eve (reasoning source, never an opaque
18
+ dependency).
19
+
20
+ ## EVE PARITY BY DESIGN
21
+
22
+ Non-negotiable rule (SYNTHESE §4.e-ter): slots, contracts and vocabulary
23
+ mirror `vercel/eve`. Every deliberate divergence is marked **"notre
24
+ ligne"** below, in this table and in the code comments next to the field.
25
+
26
+ | Eve slot / concept | Source (vercel/eve) | Our table / contract | Notes |
27
+ |---|---|---|---|
28
+ | Authored agent = a folder, `compileAgent` | `packages/eve/src/compiler/compile-from-memory.ts:86-87` | `agents` root row | **notre ligne** — we never compile; the row IS the agent |
29
+ | `name` derived from folder path (no `name` field) | `agent-definition.ts:281-284` | `agents.name` | **notre ligne** — required field, since we have no folder |
30
+ | `description` | `agent-definition.ts:291` | `agents.description` | direct parity |
31
+ | `instructions` (`instructions.md`) | `shared/instructions-definition.ts:1-18` | `agents.instructions` | direct parity |
32
+ | `model` (required) | `agent-definition.ts:304` | `agents.model` | direct parity |
33
+ | reasoning effort | `agent-definition.ts:315-319` | `agents.reasoningEffort` | direct parity |
34
+ | model provider / API key | not in the Eve repo (env-only) | `agents.modelProvider` + `modelKeys` table | **notre ligne** — BYOK (mvp §2.3), server-only table split from `agents` so list/get can never leak the key |
35
+ | `limits.maxSubagents`, `maxInputTokensPerSession` | `agent-definition.ts:163-207` | `agents.maxSubagents`, `agents.maxInputTokensPerSession` | direct parity |
36
+ | `dynamicSkills` / skills | `manifest.ts:722-725`, `agent-definition.ts:139` | `skills` child table | direct parity |
37
+ | `dynamicTools` / tools | `manifest.ts:722-725` | resolved at read-time from `connections` + `toolApprovals` | direct parity |
38
+ | Tool `approval: Approval<...>` (`always`/`once`/`never`) | `public/definitions/tool.ts:117-125` | `toolApprovals` child table | direct parity |
39
+ | MCP/OpenAPI connections (`defineMcpClientConnection`) | `public/definitions/connections/mcp.ts:23-48` | `connections` child table | direct parity. Our VantagePeers tools (memory, tasks, messages, notes) are wired here as an MCP connection, on by default — **notre ligne**, mvp §2.2 |
40
+ | Declared subagents, root-only `agent` tool | https://eve.dev/docs/subagents | `subagents` child table (`parentAgentId` -> `childAgentId`, recursive) | direct parity. **Isolation strict, "inherits nothing from the root's authored slots"** — a subagent row only declares the link + exposed tool name; it never inlines the parent's skills/connections/approvals |
41
+ | `sandbox` | `agent-definition.ts:150-160` | `sandboxConfig` child table (Vercel Sandbox, egress policy, allowlist) | direct parity, I0 §8 |
42
+ | `schedules` / hooks -> "habits" | `manifest.ts:143`, `:726` | `habits` child table (dynamic cron per agent/org, `@convex-dev/crons`) | **notre ligne (naming)** — Eve calls these schedules/hooks; we call them habits (product decision) and back them with runtime-registered crons instead of static config |
43
+ | Session/turn resolver boundaries (`ALLOWED_DYNAMIC_INSTRUCTION_EVENTS = {session.started, turn.started}`) | `dynamic-tool-definition.ts:40-46` | `sessionEvents.name` union (`session.started`, `turn.started`, ...) + `engine/resolvers.ts:resolveManifest` | direct parity — same event names, same "reread the aggregate, never cache" contract |
44
+ | `subagents` used for inter-agent collaboration | eve docs | **not used** — collaboration goes through VantagePeers MCP tools instead | **notre ligne** — explicit Laurent decision (mvp-evevantage.md §2.5): "la collaboration entre agents n'arrive pas par les `subagents` d'eve" |
45
+ | `outputSchema`, `compaction`, `modelOptions`, `channels` | `agent-definition.ts:124-146,313-330`, `manifest.ts:56-77` | out of scope this lot | left for a later I-cycle, documented as deliberately deferred (mvp §2.5) |
46
+
47
+ ## Data aggregate — schema (`convex/schema.ts`)
48
+
49
+ Root sheet: `agents`.
50
+
51
+ Child tables (>= 7, every index starts with `orgId`):
52
+
53
+ 1. `skills` — contextual procedures
54
+ 2. `subagents` — recursive declared delegation, strict isolation
55
+ 3. `connections` — MCP/OpenAPI (VantagePeers tools included)
56
+ 4. `habits` — dynamic crons (habits, notre ligne naming)
57
+ 5. `sandboxConfig` — Vercel Sandbox settings
58
+ 6. `toolApprovals` — `always` / `once` / `never` per tool
59
+ 7. `modelKeys` — BYOK, server-only, split from `agents`
60
+
61
+ Engine tables: `sessions`, `sessionEvents`, `awaitEventTimeouts`.
62
+
63
+ **Hard rule**: `orgId` is derived server-side from the authenticated
64
+ identity (`convex/lib/auth.ts:requireOrgId`, reads the Clerk `org_id`
65
+ claim) — it is never accepted as a client argument, and it is the FIRST
66
+ field of every `.index(...)` call. Deny by default: cross-org reads return
67
+ `null` / cross-org writes throw, indistinguishable from "not found"
68
+ (`convex/lib/auth.ts:assertSameOrg`).
69
+
70
+ ## Engine — durable sessions (`convex/engine/`)
71
+
72
+ - `engine/workflows.ts` — `WorkflowManager` from `@convex-dev/workflow`.
73
+ The session loop is exported as `sessionWorkflowV1`. **Versioned
74
+ workflows / drain-before-deploy discipline** (workflow#35): a breaking
75
+ change to the loop ships as `sessionWorkflowV2`, never an in-place edit
76
+ of `V1` while sessions still reference it.
77
+ - `engine/resolvers.ts` — `resolveManifest`, the dynamic resolver. Rereads
78
+ the full agent aggregate (skills, tool approvals, connections) on every
79
+ call; callers invoke it at `session.started` / `turn.started` boundaries
80
+ only, mirroring eve's `ALLOWED_DYNAMIC_INSTRUCTION_EVENTS`.
81
+ - `engine/sessions.ts` — `start` / `recordTurnStarted`, the mutations that
82
+ write the named `sessionEvents` rows Eve-style.
83
+ - `engine/awaitEventTimeout.ts` — **self-built `awaitEvent` timeout**
84
+ (workflow#177: Convex ships no native timeout for `step.awaitEvent`).
85
+ `schedule` writes an `awaitEventTimeouts` row and schedules an
86
+ `internalMutation` (`fire`) via `ctx.scheduler.runAfter`; if the real
87
+ event arrives first, `cancel` cancels the scheduled function. `fire`
88
+ inserts a blank `sandbox.timeout` `sessionEvents` row so the awaiting
89
+ workflow step wakes up regardless.
90
+
91
+ `ai` is pinned to `6.0.35` (peer dependency of `@convex-dev/agent@0.6.4`,
92
+ agent#79 warns the v5->v6 migration broke other consumers — we track
93
+ majors deliberately, never float). Counters/rollups, when needed, go
94
+ through `@convex-dev/sharded-counter` / `@convex-dev/aggregate` — no
95
+ canary/alpha third-party dependency for a guardrail (I0 §7.4).
96
+
97
+ ## Long-task survival (I1) — `engine/durableJob.ts`
98
+
99
+ SYNTHESE.md §5/§7: "le plafond de durée des actions Convex est notre
100
+ plafond aujourd'hui, sur tous nos produits" -- `durableJobs` is the
101
+ GENERIC (shape-agnostic, unlike `sessions`) primitive that lets a
102
+ consumer's task survive past that ceiling by expressing it as N
103
+ independently-bounded steps. It **ENVELOPS** `@convex-dev/workflow` (via
104
+ the same `WorkflowManager` + versioned-workflow discipline as
105
+ `engine/workflows.ts`), it does not reimplement durable execution.
106
+
107
+ Contract (see `convex/engine/durableJob.ts` for the full commentary and
108
+ `examples/vantage-peers-long-task/` for the worked integration a consumer
109
+ reads without needing to know our internals):
110
+
111
+ - `start(kind, stepFunctionHandle, totalSteps, maxAttemptsPerStep)` —
112
+ enqueue a job driven by a step function the CALLER owns (component
113
+ isolation: we invoke it by opaque `FunctionHandle`, we never read the
114
+ caller's own tables).
115
+ - `getStatus(jobId)` — poll `status` / `cursor` / `lastError` /
116
+ `abandonedAtStep`.
117
+ - `cancel(jobId)` — best-effort stop.
118
+ - **Pause at step boundary**: one loop iteration == one workflow step ==
119
+ one durability boundary (`@convex-dev/workflow` persists the journal
120
+ after each step returns).
121
+ - **Deterministic replay**: `runStepInternal` no-ops for any `stepIndex <
122
+ job.cursor`, so a resumed workflow's journal replay never re-runs a
123
+ completed step.
124
+ - **Abandon after N**: the caller's step function is retried up to
125
+ `maxAttemptsPerStep` times inside one workflow step; once exhausted the
126
+ job is marked `abandoned` with `abandonedAtStep` + `lastError`, and no
127
+ further step runs.
128
+
129
+ ## `convex/_generated/` freshness -- two gates, not one
130
+
131
+ `convex/_generated/api.d.ts` carries an "AUTOMATICALLY GENERATED" header
132
+ but nothing stops a hand-edit of its *content* (as opposed to adding or
133
+ removing an entire module) from being committed as-is. That gap was
134
+ measured for real on PR #2 (T2-BIS): a manual edit of a single existing
135
+ line (`FunctionReference<any, "public">` flipped to `"internal">`) passed
136
+ the original structural gate with `OK`, because that gate only diffs the
137
+ *list* of modules present under `convex/` against the modules imported by
138
+ `api.d.ts` -- it never recomputes a byte.
139
+
140
+ Two gates now exist, and they check different things:
141
+
142
+ 1. **Structural, offline, secret-free** --
143
+ `scripts/check-generated-freshness.mjs` (`npm run check:generated`).
144
+ Runs in CI (`.github/workflows/ci.yml`). Catches a function file
145
+ added/renamed/removed without regenerating bindings. **Does NOT catch a
146
+ hand-edit of existing generated content.**
147
+ 2. **Content, requires a live deployment** --
148
+ `scripts/check-generated-content.mjs` (`npm run
149
+ check:generated:content`). Actually runs `npx convex codegen` against
150
+ the dev deployment and diffs `convex/_generated/` byte-for-byte
151
+ (`git diff --exit-code`). Catches everything, including a single hand-
152
+ edited character.
153
+
154
+ **Three-state exit contract** (never two -- a two-state gate cannot
155
+ distinguish "I measured, it's clean" from "I could not measure at
156
+ all", and collapsing those is either a silent fail-open or a red that
157
+ proves nothing; see flotte rule `three-state-verdict.md`):
158
+
159
+ | Exit | Meaning | When |
160
+ | ---- | ------- | ---- |
161
+ | `0` | **PASS** | A real measurement was taken (`npx convex codegen` ran) and `convex/_generated/` is byte-identical to its output. |
162
+ | `1` | **VIOLATION** | A real measurement was taken and it found >=1 byte of divergence -- hand-edited, stale, or generated against a different schema. |
163
+ | `2` | **REFUSAL TO JUDGE** | `npx convex codegen` itself could not run (no live, authenticated Convex deployment reachable -- `CONVEX_DEPLOYMENT` / `.env.local` missing or invalid). No measurement was taken; this is neither a clean bill of health nor a proven violation. |
164
+
165
+ ### CI cannot run the content gate -- explicit limit, not a rounding error
166
+
167
+ `npx convex codegen` has no offline mode: it always needs
168
+ `CONVEX_DEPLOYMENT` / a deploy key against a live deployment. This repo
169
+ refuses to store that as a CI secret (plan B, PR #1). **Consequence,
170
+ stated plainly: CI cannot detect a hand-edited `convex/_generated/`
171
+ content change.** CI only runs the structural gate. The content gate is
172
+ the only thing that catches a content-level tamper, and it can only run
173
+ where the secret lives: **locally, pre-commit**, via `.env.local`.
174
+
175
+ To install the pre-commit gate once per clone:
176
+
177
+ ```
178
+ git config core.hooksPath scripts/git-hooks
179
+ ```
180
+
181
+ This points git at the versioned hook `scripts/git-hooks/pre-commit`
182
+ (plain git hook, not a Claude hook -- Claude hooks under `.claude/hooks/`
183
+ are Pi-owned only). The hook no-ops unless a staged file is under
184
+ `convex/`, and then runs `npm run check:generated:content`, reading its
185
+ exit code explicitly rather than treating it as a boolean:
186
+
187
+ - `0` (PASS) -- commit proceeds.
188
+ - `1` (VIOLATION) -- commit is **blocked**, message names the divergence.
189
+ - `2` (REFUSAL TO JUDGE, e.g. `.env.local` deployment secret missing or
190
+ invalid) -- commit is **also blocked**, with a distinct message that
191
+ says explicitly this is *unverified*, not a proven violation. This is a
192
+ deliberate fail-closed choice: an unverified change to `convex/_generated/`
193
+ content must never slip through silently just because the local
194
+ instrument (a live Convex deployment) was unavailable. The trade-off is
195
+ that a developer without a working `.env.local` cannot commit to
196
+ `convex/**` at all until they fix their local deployment credentials --
197
+ judged the safer failure mode than a fail-open that would let a hand-
198
+ edited, un-regenerated `convex/_generated/` through whenever the secret
199
+ happens to be missing.
200
+
201
+ **If `core.hooksPath` is never configured, this protection does not
202
+ exist on that clone.** Nothing currently makes that configuration
203
+ mandatory or automatic -- that decision (e.g. via a repo bootstrap script
204
+ that all contributors run) is Pi's to make, not this PR's.
205
+
206
+ ## Tests (TDD, RED then GREEN)
207
+
208
+ - `convex/agents.test.ts` — org isolation (family 1)
209
+ - `convex/engineResolvers.test.ts` — dynamic resolver, no-redeploy (family 2)
210
+ - `convex/engineAwaitEventTimeout.test.ts` — awaitEvent timeout wakes the
211
+ workflow when the real event never arrives (family 3)
212
+ - `convex/engineDurableJob.test.ts` — I1 long-task survival contract
213
+ (family 4): a 25-step job survives to `completed` via the component; a
214
+ replayed already-completed step is a proven no-op (resume after forced
215
+ interruption); a step that always fails is retried and the job is
216
+ `abandoned` (never touching the next step); a step that fails once then
217
+ succeeds still reaches `completed`; cross-org `getStatus` is denied.
218
+
219
+ Run: `npx vitest run` (or `npm test`).
220
+
221
+ ## Packaging & publication (T2-TER)
222
+
223
+ `@vantageos/agent-engine` is published to npm under the `alpha` dist-tag
224
+ (`npm view @vantageos/agent-engine version` / `dist-tags`, read from the
225
+ registry after publish, never from `npm publish`'s own exit status). The
226
+ package exposes a resolvable `"./convex.config"` subpath (types + JS,
227
+ built by `npm run build` from `convex/component/convex.config.ts` via
228
+ `tsc -p tsconfig.build.json` into `dist/component/`), so a consumer's
229
+ `convex/convex.config.ts` can do:
230
+
231
+ ```ts
232
+ import { defineApp } from "convex/server";
233
+ import agentEngine from "@vantageos/agent-engine/convex.config";
234
+
235
+ const app = defineApp();
236
+ app.use(agentEngine);
237
+ export default app;
238
+ ```
239
+
240
+ **Known remaining gap, stated here where it is read (not only in a
241
+ message):** `convex/component/convex.config.ts` currently declares only
242
+ the component's identity (`defineComponent("agentEngine")`) and its
243
+ sub-component composition (workflow + 2 workpools + agent). It does not
244
+ yet re-host this repo's schema/functions (`convex/schema.ts`,
245
+ `convex/agents.ts`, `convex/skills.ts`, `convex/engine/**`) inside the
246
+ component boundary, so `components.agentEngine.engine.durableJob.*` etc.
247
+ are not yet callable from a consumer through the published component --
248
+ see `examples/vantage-peers-long-task/README.md` "Packaging note" for the
249
+ full detail and exactly what remains.
250
+
251
+ ## Status
252
+
253
+ T1 (schema + durable session engine, PR #1) merged @
254
+ `9f3401ba63e6e663af95e9e4f5da42bc30b3550b`. I1 (long-task survival
255
+ contract, `engine/durableJob.ts`) — not merged, Eta reviews, merge on Pi
256
+ token. T2-TER (this PR, npm packaging + component identity + publication)
257
+ — see "Packaging & publication" above; **not merged**.
@@ -0,0 +1,2 @@
1
+ declare const component: import("convex/server").ComponentDefinition<any, {}>;
2
+ export default component;
@@ -0,0 +1,42 @@
1
+ import { defineComponent } from "convex/server";
2
+ import workflow from "@convex-dev/workflow/convex.config";
3
+ import workpool from "@convex-dev/workpool/convex.config";
4
+ import agent from "@convex-dev/agent/convex.config";
5
+ // @vantageos/agent-engine — Convex COMPONENT definition (pattern
6
+ // `@convex-dev/*`, verified against the published shape of
7
+ // `@convex-dev/workflow` — `defineComponent(name)` + a `convex.config.ts`
8
+ // at the root of the *package that gets published*, not merely at the
9
+ // root of the git repo).
10
+ //
11
+ // This is what a consumer registers via:
12
+ // import agentEngine from "@vantageos/agent-engine/convex.config";
13
+ // app.use(agentEngine);
14
+ //
15
+ // NOTRE LIGNE / KNOWN GAP (say it here, at the point of read, not only in
16
+ // a message — T2-TER instruction): this file declares agent-engine's
17
+ // COMPONENT IDENTITY and its sub-component composition
18
+ // (workflow + 2 named workpools + agent), which is exactly what makes the
19
+ // `@vantageos/agent-engine/convex.config` import RESOLVE for a consumer
20
+ // and what makes `app.use(agentEngine)` build without throwing.
21
+ //
22
+ // It does NOT yet re-host this repo's schema (convex/schema.ts) and
23
+ // functions (convex/agents.ts, convex/skills.ts, convex/engine/**) inside
24
+ // this component boundary, so a consumer cannot yet call
25
+ // `components.agentEngine.agents.create` / `components.agentEngine.engine
26
+ // .durableJob.start` etc. through the published component -- those
27
+ // functions still only exist in this repo's OWN dev/test deployment
28
+ // (convex/convex.config.ts, defineApp, used by `npx convex dev` and
29
+ // convex-test). That migration (moving schema.ts + agents.ts + skills.ts +
30
+ // engine/** under convex/component/, regenerating this component's OWN
31
+ // convex/component/_generated/ against a real component-hosting
32
+ // deployment, and re-verifying every existing test) is real, non-trivial
33
+ // work that was NOT attempted in this packaging pass -- doing it
34
+ // unverified would have violated Day 128 (never weaken/bypass a test to
35
+ // make it pass) by shipping schema/function code with no working
36
+ // convex-test harness behind it. It is the next, separately-scoped task.
37
+ const component = defineComponent("agentEngine");
38
+ component.use(workflow);
39
+ component.use(workpool, { name: "agentTurnPool" });
40
+ component.use(workpool, { name: "wakeupPool" });
41
+ component.use(agent);
42
+ export default component;
package/package.json ADDED
@@ -0,0 +1,50 @@
1
+ {
2
+ "name": "@vantageos/agent-engine",
3
+ "version": "0.1.0-alpha.0",
4
+ "description": "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.",
5
+ "type": "module",
6
+ "license": "UNLICENSED",
7
+ "publishConfig": {
8
+ "access": "public",
9
+ "tag": "alpha"
10
+ },
11
+ "files": [
12
+ "dist",
13
+ "README.md"
14
+ ],
15
+ "exports": {
16
+ "./package.json": "./package.json",
17
+ "./convex.config": {
18
+ "types": "./dist/component/convex.config.d.ts",
19
+ "default": "./dist/component/convex.config.js"
20
+ },
21
+ "./convex.config.js": {
22
+ "types": "./dist/component/convex.config.d.ts",
23
+ "default": "./dist/component/convex.config.js"
24
+ }
25
+ },
26
+ "scripts": {
27
+ "dev": "convex dev",
28
+ "test": "vitest run",
29
+ "test:watch": "vitest",
30
+ "typecheck": "tsc --noEmit",
31
+ "build": "tsc -p tsconfig.build.json",
32
+ "prepublishOnly": "npm run build",
33
+ "check:generated": "node scripts/check-generated-freshness.mjs",
34
+ "check:generated:content": "node scripts/check-generated-content.mjs"
35
+ },
36
+ "dependencies": {
37
+ "@convex-dev/agent": "0.6.4",
38
+ "@convex-dev/workflow": "0.4.4",
39
+ "@convex-dev/workpool": "0.4.7",
40
+ "ai": "6.0.35",
41
+ "convex": "1.42.1",
42
+ "convex-helpers": "0.1.103"
43
+ },
44
+ "devDependencies": {
45
+ "@edge-runtime/vm": "5.0.0",
46
+ "convex-test": "0.0.54",
47
+ "typescript": "5.7.3",
48
+ "vitest": "2.1.9"
49
+ }
50
+ }