@vantageos/agent-engine 0.1.0-alpha.6 → 0.1.0-alpha.8
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/LICENSE +110 -0
- package/README.md +231 -661
- package/dist/component/convex.config.js +8 -9
- package/dist/component/engine/awaitEventTimeout.js +2 -3
- package/dist/component/engine/delegation.js +14 -16
- package/dist/component/engine/directory.js +1 -1
- package/dist/component/engine/durableJob.js +6 -6
- package/dist/component/engine/habits.js +8 -9
- package/dist/component/engine/mandates.js +6 -7
- package/dist/component/engine/sandboxCallback.js +8 -11
- package/dist/component/engine/sessions.js +8 -9
- package/dist/component/engine/toolApprovals.js +1 -1
- package/dist/component/engine/vpConnection.js +6 -9
- package/dist/component/engine/wakeup.js +18 -20
- package/dist/component/engine/workflows.js +4 -7
- package/dist/component/schema.js +42 -67
- package/dist/component/testFixtures/callerSteps.js +1 -1
- package/dist/component/testFixtures/delegationDemoWorkflow.js +2 -3
- package/dist/component/testFixtures/sandboxDemoWorkflow.js +2 -2
- package/dist/component/testFixtures/wakeupDemoWorkflow.js +2 -2
- package/package.json +16 -5
|
@@ -35,15 +35,14 @@ import crons from "@convex-dev/crons/convex.config";
|
|
|
35
35
|
// awaitEventTimeouts, wakeableWork) is deliberately NOT yet re-hosted
|
|
36
36
|
// here — porting those without a working component-level test harness
|
|
37
37
|
// behind each of them would violate Day 128 (never ship unverified).
|
|
38
|
-
//
|
|
39
|
-
// under this component boundary,
|
|
40
|
-
//
|
|
41
|
-
//
|
|
42
|
-
//
|
|
43
|
-
//
|
|
44
|
-
//
|
|
45
|
-
//
|
|
46
|
-
// the repo-root prototype (convex/habits.ts). `crons` is a sub-component of
|
|
38
|
+
// Mandates (bounded, weighted spending authority per agent/org/scope) are
|
|
39
|
+
// hosted under this component boundary, using official `@convex-dev/*`
|
|
40
|
+
// libraries only -- no canary/alpha dependency for a guardrail:
|
|
41
|
+
// `rate-limiter` for the fails-closed spend gate itself, `sharded-counter`
|
|
42
|
+
// for the usage rollup. See component/engine/mandates.ts.
|
|
43
|
+
// Habits (a dynamic, per-agent/org cron schedule editable at runtime) are
|
|
44
|
+
// also hosted under this component boundary, ported byte-identical from the
|
|
45
|
+
// repo-root prototype (convex/habits.ts). `crons` is a sub-component of
|
|
47
46
|
// THIS component -- create/updateSchedule/enable/disable register or tear
|
|
48
47
|
// down a runtime cron job via `@convex-dev/crons` inside the SAME Convex
|
|
49
48
|
// mutation transaction that writes the `habits` row, so no habit schedule
|
|
@@ -4,15 +4,14 @@ import { internalMutation, mutation } from "../_generated/server.js";
|
|
|
4
4
|
import { assertSameOrg, requireOrgId } from "../lib/auth.js";
|
|
5
5
|
// engine/awaitEventTimeout — self-built timeout for step.awaitEvent
|
|
6
6
|
// (workflow#177: "Convex does not provide a native timeout for
|
|
7
|
-
// awaitEvent").
|
|
8
|
-
// prototype) into the component boundary (C2). We schedule a mutation that
|
|
7
|
+
// awaitEvent"). We schedule a mutation that
|
|
9
8
|
// fires a "blank" `sandbox.timeout` sessionEvent at the deadline; the
|
|
10
9
|
// workflow's `awaitEvent` call is written to also accept this event name so
|
|
11
10
|
// it wakes up even if the awaited event never arrives. Pattern:
|
|
12
11
|
// maintainer's posted code in workflow#177, adapted to our sessionEvents
|
|
13
12
|
// table.
|
|
14
13
|
//
|
|
15
|
-
//
|
|
14
|
+
// NOTE ("intentional divergence from Eve"): orgId is an EXPLICIT ARGUMENT on
|
|
16
15
|
// every public function, never derived from `ctx.auth` -- auth does NOT
|
|
17
16
|
// propagate across a component boundary (component/lib/auth.ts). Also:
|
|
18
17
|
// `sessionEvents.name`'s union in component/schema.ts did not previously
|
|
@@ -2,25 +2,23 @@ import { v } from "convex/values";
|
|
|
2
2
|
import { api, internal } from "../_generated/api.js";
|
|
3
3
|
import { action, internalMutation, internalQuery, mutation, query, } from "../_generated/server.js";
|
|
4
4
|
import { assertSameOrg, requireOrgId } from "../lib/auth.js";
|
|
5
|
-
// engine/delegation —
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
// `engine.wakeup.enqueue` -- the SAME public action C2/C3 already ships,
|
|
12
|
-
// imported here and never reimplemented.
|
|
5
|
+
// engine/delegation — an agent delegating to another agent uses THE SAME
|
|
6
|
+
// pause/wake system (awaitEvent/sendEvent) as the sandbox bridge: one
|
|
7
|
+
// system to maintain, not two. Every delegation path below (declared
|
|
8
|
+
// subagent AND the built-in `agent` clone tool) terminates in exactly ONE
|
|
9
|
+
// call to `engine.wakeup.enqueue` -- the same public action this component
|
|
10
|
+
// already ships, imported here and never reimplemented.
|
|
13
11
|
// grep proof: this file makes zero calls to `wakeupPool.*` / `workflow.*`
|
|
14
12
|
// directly; the only wake-capable call in this module is
|
|
15
13
|
// `api.engine.wakeup.enqueue` at the bottom of `delegateToSubagent` and
|
|
16
14
|
// `invokeAgentClone`.
|
|
17
15
|
//
|
|
18
|
-
//
|
|
16
|
+
// orgId discipline (component/lib/auth.ts): every PUBLIC function takes
|
|
19
17
|
// `orgId` as an explicit ARGUMENT (auth does not propagate across a
|
|
20
|
-
// component boundary), same as engine/mandates.ts
|
|
21
|
-
//
|
|
22
|
-
//
|
|
23
|
-
//
|
|
18
|
+
// component boundary), same as engine/mandates.ts / engine/wakeup.ts.
|
|
19
|
+
// `delegateToSubagent`/`invokeAgentClone` are `action`s because they each
|
|
20
|
+
// call `api.engine.wakeup.enqueue` (itself an action: the two-transaction
|
|
21
|
+
// funding protocol) -- `orgId` is threaded explicitly through
|
|
24
22
|
// every hop, never relying on any identity propagation across the boundary.
|
|
25
23
|
// Reserved tool name for the built-in, root-only, non-recursive clone tool
|
|
26
24
|
// (eve.dev/docs/subagents). A declared subagent may never claim this name --
|
|
@@ -125,10 +123,10 @@ export const listSubagents = query({
|
|
|
125
123
|
// (a.2) DELEGATION -- reuses `engine.wakeup.enqueue` end to end
|
|
126
124
|
// ---------------------------------------------------------------------
|
|
127
125
|
// delegateToSubagent — A delegates to a declared subagent B by the SAME
|
|
128
|
-
// pause/
|
|
126
|
+
// pause/wake mechanism the sandbox bridge uses: it enqueues the
|
|
129
127
|
// caller-supplied `workFunctionHandle` (an action that performs B's turn)
|
|
130
128
|
// through `api.engine.wakeup.enqueue`. That enqueue funds through
|
|
131
|
-
// `mandates.spendInternal` (
|
|
129
|
+
// `mandates.spendInternal` (the funding choke point, untouched), puts the work
|
|
132
130
|
// into `wakeupPool`, and when it resolves, `engine.wakeup.onWorkComplete`
|
|
133
131
|
// calls `workflow.sendEvent` (workflowId, awaitedEventName, ...) -- waking
|
|
134
132
|
// whatever workflow step A's own session parked on `step.awaitEvent`. There
|
|
@@ -167,7 +165,7 @@ export const delegateToSubagent = action({
|
|
|
167
165
|
awaitedEventName: args.awaitedEventName,
|
|
168
166
|
});
|
|
169
167
|
// THE reuse point: no bespoke pause/wake code below this line -- this
|
|
170
|
-
// is a direct call into
|
|
168
|
+
// is a direct call into this component's own PUBLIC `engine.wakeup.enqueue`
|
|
171
169
|
// action. `orgId` is threaded explicitly (no identity propagation
|
|
172
170
|
// across the component boundary).
|
|
173
171
|
await ctx.runAction(api.engine.wakeup.enqueue, {
|
|
@@ -2,7 +2,7 @@ import { v } from "convex/values";
|
|
|
2
2
|
import { query } from "../_generated/server.js";
|
|
3
3
|
import { requireOrgId } from "../lib/auth.js";
|
|
4
4
|
// engine/directory — component-boundary port of the app-level
|
|
5
|
-
// `convex/directory.ts:listByOrg
|
|
5
|
+
// `convex/directory.ts:listByOrg`.
|
|
6
6
|
//
|
|
7
7
|
// Trust-boundary divergence from the app-level prototype (see
|
|
8
8
|
// component/lib/auth.ts): auth does NOT propagate across a component
|
|
@@ -3,9 +3,9 @@ import { WorkflowManager } from "@convex-dev/workflow";
|
|
|
3
3
|
import { components, internal } from "../_generated/api.js";
|
|
4
4
|
import { internalMutation, internalQuery, mutation, query, } from "../_generated/server.js";
|
|
5
5
|
import { assertSameOrg, requireOrgId } from "../lib/auth.js";
|
|
6
|
-
// durableJobs — THE SURVIVAL CONTRACT
|
|
7
|
-
//
|
|
8
|
-
//
|
|
6
|
+
// durableJobs — THE SURVIVAL CONTRACT: pause at the step boundary,
|
|
7
|
+
// deterministic replay, resume, and abandon after N failures. This ENVELOPS
|
|
8
|
+
// `@convex-dev/workflow` -- it does not reimplement
|
|
9
9
|
// durable execution. Every real unit of work (the caller-supplied
|
|
10
10
|
// `stepFunctionHandle`) runs inside its own bounded mutation call; the
|
|
11
11
|
// workflow persists `cursor` (steps [0, cursor) done) between calls, so a
|
|
@@ -13,8 +13,8 @@ import { assertSameOrg, requireOrgId } from "../lib/auth.js";
|
|
|
13
13
|
// still completes -- no single call has to survive past the ceiling, only
|
|
14
14
|
// the aggregate progress does.
|
|
15
15
|
//
|
|
16
|
-
// Contract surface (this is what a consumer
|
|
17
|
-
//
|
|
16
|
+
// Contract surface (this is what a consumer calls; see
|
|
17
|
+
// examples/vantage-peers-long-task for a worked integration):
|
|
18
18
|
// 1. `start` -- enqueue a job of N steps, driven by a step function
|
|
19
19
|
// the CALLER owns (created via `createFunctionHandle`
|
|
20
20
|
// in the caller's own app). We never read the
|
|
@@ -40,7 +40,7 @@ import { assertSameOrg, requireOrgId } from "../lib/auth.js";
|
|
|
40
40
|
// which the workflow handler catches and turns into
|
|
41
41
|
// `status: "abandoned"` with `abandonedAtStep` + `lastError`.
|
|
42
42
|
//
|
|
43
|
-
// Versioned workflow discipline (workflow#35
|
|
43
|
+
// Versioned workflow discipline (workflow#35) -- same as
|
|
44
44
|
// `engine/workflows.ts:sessionWorkflowV1`: never edit `durableJobWorkflowV1`
|
|
45
45
|
// in place once jobs reference it; ship `...V2` and drain v1 first.
|
|
46
46
|
const CURRENT_WORKFLOW_VERSION = "v1";
|
|
@@ -3,17 +3,16 @@ import { Crons } from "@convex-dev/crons";
|
|
|
3
3
|
import { internal, components } from "../_generated/api.js";
|
|
4
4
|
import { internalMutation, mutation, query } from "../_generated/server.js";
|
|
5
5
|
import { assertSameOrg, requireOrgId } from "../lib/auth.js";
|
|
6
|
-
// engine/habits —
|
|
7
|
-
// chat
|
|
8
|
-
//
|
|
9
|
-
// `convex/crons.ts
|
|
10
|
-
//
|
|
11
|
-
//
|
|
12
|
-
// convex/habits.ts (repo-root prototype) into the component boundary (C4).
|
|
6
|
+
// engine/habits — "intentional divergence from Eve": a habit is what turns
|
|
7
|
+
// a chat into an employee. A habit is a DYNAMIC cron per agent/org,
|
|
8
|
+
// registered at runtime via @convex-dev/crons — never a static
|
|
9
|
+
// `convex/crons.ts`. Creating, running, editing or disabling one deploys
|
|
10
|
+
// nothing: the `cronExpression` is DATA carried by the `habits` row, and
|
|
11
|
+
// the runtime cron is merely a reflection of it.
|
|
13
12
|
//
|
|
14
|
-
//
|
|
13
|
+
// orgId discipline (component/lib/auth.ts): every PUBLIC function takes
|
|
15
14
|
// `orgId` as an explicit ARGUMENT (auth does not propagate across a
|
|
16
|
-
// component boundary), same as engine/mandates.ts
|
|
15
|
+
// component boundary), same as engine/mandates.ts. The cron runtime
|
|
17
16
|
// target is `internal.engine.habits.run` -- the component-boundary path
|
|
18
17
|
// for this module, mirroring `internal.habits.run` at the app level.
|
|
19
18
|
const crons = new Crons(components.crons);
|
|
@@ -4,9 +4,8 @@ import { ShardedCounter } from "@convex-dev/sharded-counter";
|
|
|
4
4
|
import { internalMutation, mutation, query } from "../_generated/server.js";
|
|
5
5
|
import { components } from "../_generated/api.js";
|
|
6
6
|
import { assertSameOrg, requireOrgId } from "../lib/auth.js";
|
|
7
|
-
// engine/mandates —
|
|
8
|
-
//
|
|
9
|
-
// component boundary (C3). A mandate is a LINE OF DATA scoped
|
|
7
|
+
// engine/mandates — "intentional divergence from Eve": BOUNDED AUTHORITY.
|
|
8
|
+
// A mandate is a LINE OF DATA scoped
|
|
10
9
|
// orgId+agentId+scope, never a constant read from code (litmus test: a
|
|
11
10
|
// client changes their mind => zero deployment). The rate-limiter has NO
|
|
12
11
|
// static named config in this module -- every call to `spend` builds its
|
|
@@ -15,7 +14,7 @@ import { assertSameOrg, requireOrgId } from "../lib/auth.js";
|
|
|
15
14
|
// dynamically"). Fails-closed: no mandate row => refusal, never a silent
|
|
16
15
|
// bypass.
|
|
17
16
|
//
|
|
18
|
-
//
|
|
17
|
+
// orgId discipline (component/lib/auth.ts): every PUBLIC function takes
|
|
19
18
|
// `orgId` as an explicit ARGUMENT (auth does not propagate across a
|
|
20
19
|
// component boundary), same as the rest of this component (agents,
|
|
21
20
|
// wakeup, sessions). `spendInternal` also takes `orgId` as an argument, but
|
|
@@ -24,7 +23,7 @@ import { assertSameOrg, requireOrgId } from "../lib/auth.js";
|
|
|
24
23
|
// caller-supplied `orgId` argument through after validating it.
|
|
25
24
|
const rateLimiter = new RateLimiter(components.rateLimiter);
|
|
26
25
|
// Usage rollup, keyed identically to the rate-limiter key (org+agent+scope).
|
|
27
|
-
//
|
|
26
|
+
// internal research notes: counters via sharded-counter, never a canary/alpha dependency.
|
|
28
27
|
const usageCounter = new ShardedCounter(components.shardedCounter);
|
|
29
28
|
const mandateDoc = v.object({
|
|
30
29
|
_id: v.id("mandates"),
|
|
@@ -143,7 +142,7 @@ export const spend = mutation({
|
|
|
143
142
|
// spendInternal — SAME choke point as `spend`, INTERNAL ONLY (unreachable
|
|
144
143
|
// from any client). This is the ONE function `engine/wakeup.enqueue` (an
|
|
145
144
|
// `action`) calls, in its OWN transaction, before it may ever call
|
|
146
|
-
// `enqueueInternal` to put work in the pool -- see the
|
|
145
|
+
// `enqueueInternal` to put work in the pool -- see the wiring note in
|
|
147
146
|
// component/engine/wakeup.ts. Trusting an `orgId` argument here is exactly
|
|
148
147
|
// as safe as `engine/wakeup.enqueueInternal` trusting one: the only code
|
|
149
148
|
// that can reach an `internal.*` function is other server code, never a
|
|
@@ -205,7 +204,7 @@ async function spendCore(ctx, orgId, args) {
|
|
|
205
204
|
});
|
|
206
205
|
return { ok: false, approvalId };
|
|
207
206
|
}
|
|
208
|
-
// Rollup via sharded-counter (
|
|
207
|
+
// Rollup via sharded-counter (internal research notes: no canary dependency for a
|
|
209
208
|
// guardrail). One shard bucket per org+agent+scope key.
|
|
210
209
|
await usageCounter.add(ctx, key, args.cost);
|
|
211
210
|
return { ok: true };
|
|
@@ -3,12 +3,10 @@ import { v } from "convex/values";
|
|
|
3
3
|
import { components, internal } from "../_generated/api.js";
|
|
4
4
|
import { internalMutation, mutation, query } from "../_generated/server.js";
|
|
5
5
|
import { assertSameOrg, requireOrgId } from "../lib/auth.js";
|
|
6
|
-
// engine/sandboxCallback —
|
|
7
|
-
// PORTED from convex/engine/sandboxCallback.ts (repo-root prototype, I5,
|
|
8
|
-
// SYNTHESE.md §4.c-d, i0-research §8/§5) into the component boundary.
|
|
6
|
+
// engine/sandboxCallback — THE SIGNED CALLBACK GATE'S DB-SIDE LOGIC.
|
|
9
7
|
//
|
|
10
|
-
//
|
|
11
|
-
//
|
|
8
|
+
// BOUNDARY (see the sandbox-bridge header in component/schema.ts for the
|
|
9
|
+
// measured verdict behind each of these):
|
|
12
10
|
// - The public HTTP route (`/sandbox/callback`) that verifies the
|
|
13
11
|
// `SANDBOX_CALLBACK_SECRET` HMAC signature over the raw body CANNOT
|
|
14
12
|
// live in this component (no official `@convex-dev/*` component ships
|
|
@@ -20,12 +18,11 @@ import { assertSameOrg, requireOrgId } from "../lib/auth.js";
|
|
|
20
18
|
// (`Sandbox.create()` / `sandbox.runCommand()`) CANNOT live in this
|
|
21
19
|
// component either (Convex's own bundler hard-crashes on any "use
|
|
22
20
|
// node" file inside a child component). It stays app-level
|
|
23
|
-
//
|
|
24
|
-
// `
|
|
25
|
-
//
|
|
26
|
-
//
|
|
27
|
-
//
|
|
28
|
-
// `spendInternal` for the same convention already established in C3).
|
|
21
|
+
// and calls INTO `startRun` / `attachSandboxId` below (both PUBLIC
|
|
22
|
+
// `mutation`s, not `internalMutation`s, precisely so a cross-component
|
|
23
|
+
// caller can reach them — a component's `internal*` functions are
|
|
24
|
+
// unreachable from outside the component; engine/mandates.ts's
|
|
25
|
+
// `spendInternal` follows the same convention).
|
|
29
26
|
//
|
|
30
27
|
// Everything below assumes the signature already checked out (verified by
|
|
31
28
|
// the caller, upstream of this module) — this module's job is dedup-by-
|
|
@@ -29,12 +29,11 @@ const CURRENT_SESSION_WORKFLOW_VERSION = "v1";
|
|
|
29
29
|
// manifest immediately so a caller can act on it before the first turn,
|
|
30
30
|
// then STARTS the durable, versioned session workflow
|
|
31
31
|
// (engine/workflows.ts:sessionWorkflowV1) that pilots this session from
|
|
32
|
-
// here on
|
|
33
|
-
// table" divergence).
|
|
32
|
+
// here on.
|
|
34
33
|
//
|
|
35
|
-
// RETURN CONTRACT ("
|
|
34
|
+
// RETURN CONTRACT ("intentional divergence from Eve"): `start` returns
|
|
36
35
|
// `{ sessionId, workflowId }` instead of a bare `sessionId`. A bare ID
|
|
37
|
-
//
|
|
36
|
+
// would be the shape for a session with no workflow to reference;
|
|
38
37
|
// once a session IS piloted by a real, durable workflow run, silently
|
|
39
38
|
// dropping that run's id would make it unobservable/uncancellable to
|
|
40
39
|
// every caller except one that re-reads the session row. Returning both
|
|
@@ -45,7 +44,7 @@ const CURRENT_SESSION_WORKFLOW_VERSION = "v1";
|
|
|
45
44
|
export const start = mutation({
|
|
46
45
|
args: { orgId: v.string(), agentId: v.id("agents"), createdBy: v.string() },
|
|
47
46
|
returns: v.object({ sessionId: v.id("sessions"), workflowId: v.string() }),
|
|
48
|
-
// EXPLICIT return-type annotation ("
|
|
47
|
+
// EXPLICIT return-type annotation ("intentional divergence from Eve" -- Convex pattern for
|
|
49
48
|
// any handler that references `components`/a WorkflowManager built on
|
|
50
49
|
// `components.workflow`): `workflow.start` closes over `components`,
|
|
51
50
|
// which closes back over this component's own generated `api` --
|
|
@@ -55,7 +54,7 @@ export const start = mutation({
|
|
|
55
54
|
// `.d.ts` EMISSION (declaration:true, tsconfig.build.json) fails --
|
|
56
55
|
// TS2502/TS2615/TS7022 -- even though `tsc --noEmit` at the repo root
|
|
57
56
|
// stays green (noEmit skips the exact codepath that breaks). Measured
|
|
58
|
-
// on PR #13 (
|
|
57
|
+
// on PR #13 (build gate review): `npm run build` is the build that SHIPS; a
|
|
59
58
|
// green `tsc --noEmit` is not sufficient proof by itself.
|
|
60
59
|
handler: async (ctx, args) => {
|
|
61
60
|
const orgId = requireOrgId(args.orgId);
|
|
@@ -188,8 +187,8 @@ export const requestToolCall = mutation({
|
|
|
188
187
|
// approval === "once": gate unless this tool's approval was already
|
|
189
188
|
// resolved with `decision: "approved"` in this session. A prior
|
|
190
189
|
// `decision: "denied"` resolution must NOT be treated as
|
|
191
|
-
// authorization (
|
|
192
|
-
//
|
|
190
|
+
// authorization (the tool would be skipped while the turn still
|
|
191
|
+
// completed) -- it is reported back as a terminal refusal
|
|
193
192
|
// instead, and the caller re-requests (a fresh human decision) rather
|
|
194
193
|
// than silently proceeding.
|
|
195
194
|
const events = await ctx.db
|
|
@@ -272,7 +271,7 @@ export const listEvents = query({
|
|
|
272
271
|
_id: v.id("sessionEvents"),
|
|
273
272
|
_creationTime: v.number(),
|
|
274
273
|
name: v.union(v.literal("session.started"), v.literal("turn.started"), v.literal("turn.reasoning"), v.literal("turn.toolCall.requested"), v.literal("turn.toolCall.completed"), v.literal("turn.toolCall.denied"), v.literal("turn.completed"), v.literal("tool.approval.requested"), v.literal("tool.approval.resolved"),
|
|
275
|
-
//
|
|
274
|
+
// Blank event auto-inserted by
|
|
276
275
|
// `engine/awaitEventTimeout.fire` (workflow#177).
|
|
277
276
|
v.literal("sandbox.timeout")),
|
|
278
277
|
payload: v.optional(v.string()),
|
|
@@ -10,7 +10,7 @@ import { assertSameOrg, requireOrgId } from "../lib/auth.js";
|
|
|
10
10
|
// 1. `decision` -- the RESPONSE to THIS ONE pending approval request,
|
|
11
11
|
// for THIS turn only. Vocabulary aligned with Eve's `ApprovalStatus`
|
|
12
12
|
// (packages/eve/src/public/definitions/approval.ts: "approved" |
|
|
13
|
-
// "denied" | ...) -- eve parity, cited verbatim, not "
|
|
13
|
+
// "denied" | ...) -- eve parity, cited verbatim, not "intentional divergence from Eve".
|
|
14
14
|
// `"denied"` means: this turn's tool call is refused, full stop. It
|
|
15
15
|
// does NOT change the agent's standing policy for the tool, and it
|
|
16
16
|
// must NOT be interpreted as "tool skipped, turn completed anyway" --
|
|
@@ -2,22 +2,19 @@ import { v } from "convex/values";
|
|
|
2
2
|
import { action, internalMutation, internalQuery, mutation, query, } from "../_generated/server.js";
|
|
3
3
|
import { assertSameOrg, requireOrgId } from "../lib/auth.js";
|
|
4
4
|
import { internal } from "../_generated/api.js";
|
|
5
|
-
// engine/vpConnection —
|
|
6
|
-
// d'équipe". Ported from convex/engine/vpConnection.ts (repo-root
|
|
7
|
-
// prototype) into the component boundary (C6). The four VantagePeers tools
|
|
5
|
+
// engine/vpConnection — teamwork. The four VantagePeers tools
|
|
8
6
|
// (memory, tasks, messages, notes) are wired as a CONNECTION of the agent
|
|
9
|
-
// aggregate --
|
|
7
|
+
// aggregate -- intentional divergence from Eve, same slot family as `connections` (MCP/OpenAPI).
|
|
10
8
|
// Four toggles, ON by default.
|
|
11
9
|
//
|
|
12
10
|
// DIRECT ACCESS, NO NETWORK IN THE HOT PATH: `invokeVpTool` dispatches via a
|
|
13
11
|
// caller-supplied Convex function handle (`createFunctionHandle`), the same
|
|
14
12
|
// discipline `engine/wakeup.ts` and `engine/durableJob.ts` already use for
|
|
15
13
|
// `workFunctionHandle` / `stepFunctionHandle` -- an in-process function
|
|
16
|
-
// reference resolved by `ctx.runMutation`, never an HTTP request
|
|
17
|
-
//
|
|
18
|
-
// literal fetch call.
|
|
14
|
+
// reference resolved by `ctx.runMutation`, never an HTTP request: this file
|
|
15
|
+
// contains zero occurrences of the literal fetch call.
|
|
19
16
|
//
|
|
20
|
-
//
|
|
17
|
+
// orgId discipline (component/lib/auth.ts): every PUBLIC function takes
|
|
21
18
|
// `orgId` as an explicit ARGUMENT (auth does not propagate across a
|
|
22
19
|
// component boundary). `orgId` is passed EXPLICITLY to the target function
|
|
23
20
|
// handle (never trusting the target's own `ctx.auth`) -- this is the
|
|
@@ -25,7 +22,7 @@ import { internal } from "../_generated/api.js";
|
|
|
25
22
|
// is expected to write scoped to the `orgId` it was HANDED, not to derive
|
|
26
23
|
// one itself.
|
|
27
24
|
const vpTool = v.union(v.literal("memory"), v.literal("tasks"), v.literal("messages"), v.literal("notes"));
|
|
28
|
-
// defaultToggles —
|
|
25
|
+
// defaultToggles — four switches, all ticked by default. A
|
|
29
26
|
// missing `vpConnections` row means "use these defaults", so every agent
|
|
30
27
|
// starts with all four VP tools available with zero extra setup.
|
|
31
28
|
export const defaultToggles = {
|
|
@@ -4,17 +4,16 @@ import { WorkflowManager } from "@convex-dev/workflow";
|
|
|
4
4
|
import { components, internal } from "../_generated/api.js";
|
|
5
5
|
import { action, internalMutation, mutation, query } from "../_generated/server.js";
|
|
6
6
|
import { assertSameOrg, requireOrgId } from "../lib/auth.js";
|
|
7
|
-
// engine/wakeup — THE GENERIC WAKE-UP PRIMITIVE
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
// caller-supplied work (an action, referenced by an opaque handle string
|
|
7
|
+
// engine/wakeup — THE GENERIC WAKE-UP PRIMITIVE. An agent does not ask every
|
|
8
|
+
// minute whether work has arrived: it is woken the instant the work resolves.
|
|
9
|
+
// Mechanism: `enqueue` puts caller-supplied work (an action, referenced by
|
|
10
|
+
// an opaque handle string
|
|
12
11
|
// from `createFunctionHandle` in the CALLER's own app, same
|
|
13
12
|
// component-isolation discipline as `durableJob.stepFunctionHandle`) into a
|
|
14
13
|
// dedicated `@convex-dev/workpool` (`components.wakeupPool`, declared in
|
|
15
14
|
// component/convex.config.ts). That pool's `onComplete` callback -- which
|
|
16
|
-
// fires in its OWN transaction once the work resolves (
|
|
17
|
-
//
|
|
15
|
+
// fires in its OWN transaction once the work resolves (workpool issue #133:
|
|
16
|
+
// onComplete runs in a separate transaction; there is no native pause) --
|
|
18
17
|
// calls `workflow.sendEvent(...)` against the CALLER-supplied
|
|
19
18
|
// `workflowId`. Any workflow step parked on
|
|
20
19
|
// `step.awaitEvent({ name: awaitedEventName })` resumes the INSTANT that
|
|
@@ -28,7 +27,7 @@ import { assertSameOrg, requireOrgId } from "../lib/auth.js";
|
|
|
28
27
|
// of those tables.
|
|
29
28
|
//
|
|
30
29
|
// ---------------------------------------------------------------------
|
|
31
|
-
//
|
|
30
|
+
// "intentional divergence from Eve"
|
|
32
31
|
// ---------------------------------------------------------------------
|
|
33
32
|
//
|
|
34
33
|
// (A) orgId is an EXPLICIT ARGUMENT on every public function, never derived
|
|
@@ -36,7 +35,7 @@ import { assertSameOrg, requireOrgId } from "../lib/auth.js";
|
|
|
36
35
|
// (see component/lib/auth.ts). The consumer app has already authenticated
|
|
37
36
|
// its caller and resolved orgId on its OWN side before calling in.
|
|
38
37
|
//
|
|
39
|
-
// (B)
|
|
38
|
+
// (B) MANDATE FUNDING CHOKE-POINT. `enqueue` is an
|
|
40
39
|
// `action` because funding-then-starting is a TWO-TRANSACTION protocol a
|
|
41
40
|
// single mutation cannot express: `mandates.spendInternal` must be able to
|
|
42
41
|
// COMMIT an `approvalRequests` row on an overrun WITHOUT that commit being
|
|
@@ -54,13 +53,12 @@ import { assertSameOrg, requireOrgId } from "../lib/auth.js";
|
|
|
54
53
|
// 4. only on `result.ok === true` does it call `enqueueInternal` in a
|
|
55
54
|
// SECOND transaction to actually put the work in the pool.
|
|
56
55
|
// There is no code path from "client wants work started" to "work enters
|
|
57
|
-
// the pool" that does not pass through step 2 -- this is
|
|
58
|
-
// cannot fabricate"
|
|
56
|
+
// the pool" that does not pass through step 2 -- this is an "argument you
|
|
57
|
+
// cannot fabricate" pattern: the only way to make
|
|
59
58
|
// `enqueueInternal` run at all is to be `enqueue` itself, and `enqueue`
|
|
60
|
-
// always calls `mandates.spendInternal` first.
|
|
61
|
-
//
|
|
62
|
-
//
|
|
63
|
-
// takes `orgId` as an argument rather than deriving it from `ctx.auth`).
|
|
59
|
+
// always calls `mandates.spendInternal` first. `spendInternal` takes
|
|
60
|
+
// `orgId` as an argument rather than deriving it from `ctx.auth`, per the
|
|
61
|
+
// boundary discipline in note A.
|
|
64
62
|
export const wakeupPool = new Workpool(components.wakeupPool, {
|
|
65
63
|
maxParallelism: 10,
|
|
66
64
|
});
|
|
@@ -72,8 +70,8 @@ const workflow = new WorkflowManager(components.workflow);
|
|
|
72
70
|
// ---------------------------------------------------------------------
|
|
73
71
|
// PUBLIC CONTRACT
|
|
74
72
|
// ---------------------------------------------------------------------
|
|
75
|
-
// enqueue — the ONLY client-facing entry point (
|
|
76
|
-
//
|
|
73
|
+
// enqueue — the ONLY client-facing entry point (an `action`, see note B
|
|
74
|
+
// above). Funds the work through `mandates.spendInternal` in its
|
|
77
75
|
// own transaction FIRST; only calls `enqueueInternal` (a second,
|
|
78
76
|
// independent transaction) once that funding succeeded.
|
|
79
77
|
export const enqueue = action({
|
|
@@ -119,9 +117,9 @@ export const enqueue = action({
|
|
|
119
117
|
});
|
|
120
118
|
},
|
|
121
119
|
});
|
|
122
|
-
// enqueueInternal -- INTERNAL ONLY variant, kept for the
|
|
123
|
-
// protocol (
|
|
124
|
-
// once `mandates.
|
|
120
|
+
// enqueueInternal -- INTERNAL ONLY variant, kept for the two-transaction
|
|
121
|
+
// funding protocol (`enqueue` calls this in its SECOND transaction
|
|
122
|
+
// once `mandates.spendInternal` returned `{ ok: true }`, same split as the
|
|
125
123
|
// app-level prototype). Unreachable from any client: Convex excludes
|
|
126
124
|
// `internal.*` functions from the API surface shipped to clients.
|
|
127
125
|
export const enqueueInternal = internalMutation({
|
|
@@ -2,12 +2,9 @@ import { WorkflowManager } from "@convex-dev/workflow";
|
|
|
2
2
|
import { v } from "convex/values";
|
|
3
3
|
import { components, internal } from "../_generated/api.js";
|
|
4
4
|
import { internalMutation, internalQuery } from "../_generated/server.js";
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
// session PILOTED by `@convex-dev/workflow` instead of the T2-QUINQUIES
|
|
9
|
-
// "sessions are just an event table" divergence (component/schema.ts,
|
|
10
|
-
// sessions table comment retired alongside this file).
|
|
5
|
+
// The durable, VERSIONED session workflow. This is what makes
|
|
6
|
+
// `sessions.start` open a session PILOTED by `@convex-dev/workflow` rather
|
|
7
|
+
// than a bare event table.
|
|
11
8
|
//
|
|
12
9
|
// Trust model at this boundary: the workflow engine runs with NO end-user
|
|
13
10
|
// identity and NO client-supplied orgId (component isolation, same
|
|
@@ -53,7 +50,7 @@ export const recordTurnStartedInternal = internalMutation({
|
|
|
53
50
|
return null;
|
|
54
51
|
},
|
|
55
52
|
});
|
|
56
|
-
// Versioned workflow discipline (workflow#35
|
|
53
|
+
// Versioned workflow discipline (workflow#35): "if you change much
|
|
57
54
|
// of anything in a workflow while there are ongoing workflows, they'll
|
|
58
55
|
// fail." We never edit a workflow function in place once sessions
|
|
59
56
|
// reference it -- a breaking change ships as `sessionWorkflowV2`, and the
|