Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion .dependency-cruiser.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,12 @@ module.exports = {
from: { path: "^packages/(tools-[^/]+)/" },
to: { path: "^packages/tools-[^/]+/", pathNot: "^packages/$1/" },
},
{
name: "database-must-not-import-billing-policy",
severity: "error",
from: { path: "^packages/db/" },
to: { path: "^packages/billing/" },
},
{
name: "vercel-web-must-not-import-worker-runtime-packages",
severity: "error",
Expand Down Expand Up @@ -53,6 +59,6 @@ module.exports = {
},
],
options: {
doNotFollow: { path: "node_modules" },
doNotFollow: { path: "(^|/)(dist|node_modules)/" },
},
};
2 changes: 0 additions & 2 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,6 @@ POLAR_SERVER=sandbox
POLAR_WEBHOOK_SECRET=
POLAR_PRODUCT_ID_PRO=
POLAR_PRODUCT_ID_PREMIUM=
POLAR_PRODUCT_ID_ULTRA=
POLAR_PRODUCT_ID_MAX=

# Internal local contracts.
DATABASE_CONTEXT_SIGNING_SECRET_AGENT=replace_with_a_distinct_32_byte_secret
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/static-checks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -252,7 +252,7 @@ jobs:
pnpm exec dependency-cruise \
--config .dependency-cruiser.cjs \
--ts-config tsconfig.base.json \
--exclude '(^|/)(dist|\.next|\.turbo|node_modules)/' \
--exclude '(^|/)(\.next|\.turbo|node_modules)/' \
"${directories[@]}"

- name: Check dead code
Expand Down
6 changes: 4 additions & 2 deletions apps/agent-worker/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,8 @@ instructions, source, schemas, templates, and assets in Files. A hidden mirror
manifest avoids rewriting unchanged packages and limits cleanup to files previously
managed by that package, preserving local dependencies and generated output. Curated
default skills are immutable snapshot files under `/home/node/.cheatcode/default-skills/`.
Agent-worker owns the custom-skill capacity decision and applies it inside the database's
per-user locked catalog transaction before inserting a new skill.

Managed processes use required stable IDs and a maximum of 32 live metadata slots per user
sandbox. Reusing an ID atomically replaces that slot. At capacity, ProjectSandbox reconciles the
Expand Down Expand Up @@ -168,7 +170,7 @@ and it does not apply per-run or daily dollar caps. Provider usage remains an
opaque SDK concern.

AgentRun writes Workers Analytics Engine agent-run metrics on terminal statuses and emits
a first-visible-chunk TTFT performance metric for the analytics watchdog. Run
a first-visible-chunk TTFT performance metric. Run
admission events carry the planned logical model, while stream-attempt/completion events carry
the resolved logical model. A failure before any stream attempt keeps planned attribution instead;
provider-local transport IDs remain structured-log context. R2-backed artifact
Expand All @@ -192,7 +194,7 @@ truncation, and there is no transcript-length, step, token, or cost ceiling.
Mastra tool-call chunks also emit `step_started`, `step_completed`,
`tool_invoked`, and `skill_invoked` events when those chunks are present in the
live stream. If the last stream subscriber disconnects while a run is still
running, AgentRun emits `run_abandoned` for the watchdog/funnel trail.
running, AgentRun emits `run_abandoned` for the funnel trail.

Project deletion first fences project/thread mutations, refuses an active run, records a
durable cleanup request, then removes that project's workspace folder. The database marks
Expand Down
200 changes: 99 additions & 101 deletions apps/agent-worker/src/agent-api-run-routes.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,30 @@
import {
type AgentRunHandle,
type AgentRunThreadContext,
createAgentRunForThread,
createDb,
createThreadMessage,
getThread,
type Database,
loadAgentRunThreadContext,
type RunPersonalization,
reconcileAbsentAgentRunStart,
withUserContext,
type UserDatabaseSession,
withUserDb,
} from "@cheatcode/db";
import { APIError, readJsonRequest } from "@cheatcode/observability";
import {
type AgentRunId,
BrowserTakeoverResumeResultSchema,
BrowserTakeoverResumeSchema,
BrowserTakeoverStatusSchema,
type CreateRun,
ThreadId,
UserId as toUserId,
type UIMessagePart,
type UserId,
} from "@cheatcode/types";
import {
BrowserTakeoverResumeResultSchema,
BrowserTakeoverResumeSchema,
BrowserTakeoverStatusSchema,
type CreateRun,
} from "@cheatcode/types/api";
import { AGENT_FORWARD_ROUTES } from "@cheatcode/types/internal";
import type { Context, Hono } from "hono";
import { z } from "zod";
import type { AgentEnv } from "./agent-env";
Expand All @@ -28,9 +33,10 @@ import {
activeRunForThreadRoute,
agentRunForRunId,
callAgentRun,
enforceRunEntitlementPolicy,
fetchAgentRun,
loadRunEntitlementPolicy,
reconcileAgentRunAdmission,
runEntitlementPolicy,
runForRoute,
sandboxForUser,
startAgentRun,
Expand Down Expand Up @@ -63,11 +69,24 @@ type RejectedRunResult = Exclude<

export function registerAgentRunHttpRoutes(app: Hono<{ Bindings: AgentEnv }>): void {
app.post("/v1/threads/:threadId/runs", createRun);
app.get("/v1/threads/:threadId/runs/stream", streamActiveRun);
app.post("/v1/runs/:runId/cancel", cancelRun);
app.get("/v1/threads/:threadId/browser-takeover", browserTakeoverStatus);
app.post("/v1/threads/:threadId/browser-takeover/start", startBrowserTakeover);
app.post("/v1/threads/:threadId/browser-takeover/resume", resumeBrowserTakeover);
const routes = AGENT_FORWARD_ROUTES.piped;
app.on(routes.streamRun.method, routes.streamRun.path, streamActiveRun);
app.on(routes.cancelRun.method, routes.cancelRun.path, cancelRun);
app.on(
routes.browserTakeoverStatus.method,
routes.browserTakeoverStatus.path,
browserTakeoverStatus,
);
app.on(
routes.browserTakeoverStart.method,
routes.browserTakeoverStart.path,
startBrowserTakeover,
);
app.on(
routes.browserTakeoverResume.method,
routes.browserTakeoverResume.path,
resumeBrowserTakeover,
);
}

async function createRun(c: AgentContext): Promise<Response> {
Expand All @@ -78,61 +97,56 @@ async function createRun(c: AgentContext): Promise<Response> {
await readJsonRequest(c.req.raw, MAX_CREATE_RUN_BODY_BYTES, "Create run request"),
);
const requestIdentity = readRunRequestIdentity(c.req.raw.headers);
const personalization = await loadRequestPersonalization(c.env, parsedUserId, threadId, body);
const policy = await runEntitlementPolicy(c.env, userId);
const sandboxName = await userSandboxName(userId);
const sandbox = await sandboxForUser(c.env, userId);
await syncSandboxQuotaPeriod(sandbox, policy.quotaPeriodEnd);
const result = await persistRunRequest(c.env, {
body,
personalization,
requestIdentity,
threadId,
userId: parsedUserId,
});
if (result.type === "created") {
const outcome = await startAgentRun(c.env, {
body,
modelExplicit: result.modelExplicit,
personalization,
run: result.run,
sandboxName,
userId,
});
return resolveRunAdmission(c.env, parsedUserId, result.run, outcome);
}
if (result.type === "active-run-exists" || result.type === "idempotent-replay") {
const outcome = await reconcileAgentRunAdmission(c.env, userId, result.run.runId);
return resolveRunAdmission(c.env, parsedUserId, result.run, outcome);
}
throw rejectedRunError(result);
}

async function loadRequestPersonalization(
env: AgentEnv,
userId: UserId,
threadId: string,
body: CreateRun,
): Promise<RunPersonalization> {
const { db, close } = createDb(env.HYPERDRIVE, {
audience: "app_agent",
signingSecret: env.DATABASE_CONTEXT_SIGNING_SECRET_AGENT,
});
try {
return await withUserContext(db, userId, async (tx) => {
const thread = await getThread(tx, { threadId: ThreadId(threadId), userId });
return withUserDb(c.env, parsedUserId, async ({ transaction }) => {
const prepared = await transaction(async (tx) => {
const thread = await loadAgentRunThreadContext(tx, {
threadId: ThreadId(threadId),
userId: parsedUserId,
});
if (!thread) {
throw new APIError(404, "not_found_thread", "Thread not found", { retriable: false });
}
return loadRunPersonalization(tx, userId, body.model);
return {
entitlement: await loadRunEntitlementPolicy(tx, parsedUserId),
personalization: await loadRunPersonalization(tx, parsedUserId, body.model),
thread,
};
});
} finally {
await close();
}
const policy = await enforceRunEntitlementPolicy(c.env, userId, prepared.entitlement);
const sandboxName = await userSandboxName(userId);
const sandbox = await sandboxForUser(c.env, userId);
await syncSandboxQuotaPeriod(sandbox, policy.quotaPeriodEnd);
const result = await transaction((tx) =>
persistRunRequest(tx, prepared.thread, {
body,
personalization: prepared.personalization,
requestIdentity,
threadId,
userId: parsedUserId,
}),
);
if (result.type === "created") {
const outcome = await startAgentRun(c.env, {
body,
modelExplicit: result.modelExplicit,
personalization: prepared.personalization,
run: result.run,
sandboxName,
userId,
});
return resolveRunAdmission(parsedUserId, result.run, outcome, transaction);
}
if (result.type === "active-run-exists" || result.type === "idempotent-replay") {
const outcome = await reconcileAgentRunAdmission(c.env, userId, result.run.runId);
return resolveRunAdmission(parsedUserId, result.run, outcome, transaction);
}
throw rejectedRunError(result);
});
}

async function persistRunRequest(
env: AgentEnv,
tx: Database,
thread: AgentRunThreadContext,
input: {
body: CreateRun;
personalization: RunPersonalization;
Expand All @@ -141,34 +155,28 @@ async function persistRunRequest(
userId: UserId;
},
): Promise<CreateRunResult> {
const { db, close } = createDb(env.HYPERDRIVE, {
audience: "app_agent",
signingSecret: env.DATABASE_CONTEXT_SIGNING_SECRET_AGENT,
});
try {
return await withUserContext(db, input.userId, async (tx) => {
const created = await createAgentRunForThread(tx, {
idempotencyKeyHash: input.requestIdentity.keyHash,
personalization: input.personalization,
requestBodyHash: input.requestIdentity.bodyHash,
threadId: ThreadId(input.threadId),
userId: input.userId,
...(input.body.model === undefined ? {} : { modelId: input.body.model }),
});
if (created.type === "created") {
await createThreadMessage(tx, {
agentRunId: created.run.runId,
parts: persistedUserMessageParts(input.body),
role: "user",
threadId: created.run.threadId,
userId: input.userId,
});
}
return created;
const created = await createAgentRunForThread(
tx,
{
idempotencyKeyHash: input.requestIdentity.keyHash,
personalization: input.personalization,
requestBodyHash: input.requestIdentity.bodyHash,
threadId: ThreadId(input.threadId),
userId: input.userId,
...(input.body.model === undefined ? {} : { modelId: input.body.model }),
},
thread,
);
if (created.type === "created") {
await createThreadMessage(tx, {
agentRunId: created.run.runId,
parts: persistedUserMessageParts(input.body),
role: "user",
threadId: created.run.threadId,
userId: input.userId,
});
} finally {
await close();
}
return created;
}

function persistedUserMessageParts(body: CreateRun): UIMessagePart[] {
Expand Down Expand Up @@ -209,40 +217,30 @@ function rejectedRunError(result: RejectedRunResult): APIError {
}

async function resolveRunAdmission(
env: AgentEnv,
userId: UserId,
run: AgentRunHandle,
outcome: AgentRunAdmissionOutcome,
transaction: UserDatabaseSession["transaction"],
): Promise<Response> {
if (outcome.type === "confirmed") {
return withRunLocation(outcome.response, run.runId);
}
if (outcome.type === "ambiguous") {
throw runAdmissionAmbiguousError(run.runId);
}
const reconciliation = await reconcileAbsentRunRow(env, userId, run.runId);
const reconciliation = await reconcileAbsentRunRow(transaction, userId, run.runId);
if (reconciliation === "not-found") {
throw runAdmissionAmbiguousError(run.runId);
}
throw runAdmissionAbsentError(run.runId);
}

async function reconcileAbsentRunRow(
env: AgentEnv,
transaction: UserDatabaseSession["transaction"],
userId: UserId,
runId: AgentRunId,
): Promise<"failed" | "not-found" | "terminal"> {
const { db, close } = createDb(env.HYPERDRIVE, {
audience: "app_agent",
signingSecret: env.DATABASE_CONTEXT_SIGNING_SECRET_AGENT,
});
try {
return await withUserContext(db, userId, (tx) =>
reconcileAbsentAgentRunStart(tx, { runId, userId }),
);
} finally {
await close();
}
return transaction((tx) => reconcileAbsentAgentRunStart(tx, { runId, userId }));
}

function runAdmissionAbsentError(runId: AgentRunId): APIError {
Expand Down
Loading