Files
growqr-backend/src/actors/sub-agent.ts
sai karthik ff0bf5e5f0 Wire production stack: Clerk + Postgres + Anthropic + per-user containers
Brings the backend from a scaffold to a working end-to-end MVP — real auth,
persistent actor registry, Anthropic tool-use loop in the Grow Agent, and
per-user Gitea+OpenCode provisioning. Also adds the client-facing
architecture diagram under docs/architecture.html.
2026-05-19 22:17:40 +05:30

84 lines
2.2 KiB
TypeScript

import { actor } from "rivetkit";
import { db } from "../db/client.js";
import { actors as actorsTable, events as eventsTable } from "../db/schema.js";
import { and, eq, desc } from "drizzle-orm";
type LogEntry = {
ts: number;
level: "info" | "warn" | "error";
msg: string;
};
type SubAgentState = {
parentUserId: string;
type: string;
status: "idle" | "running" | "done" | "error";
channelId: string;
logs: LogEntry[];
};
const initialState: SubAgentState = {
parentUserId: "",
type: "generic",
status: "idle",
channelId: "",
logs: [],
};
// Sub-agent actor mainly exposes status + logs for the UI. The actual task
// execution lives in sub-agent-runner.ts, invoked by the Grow Agent's tool
// dispatch path (PRD §3.3).
export const subAgent = actor({
state: initialState,
actions: {
init: async (
c,
input: { parentUserId: string; type: string; channelId: string },
) => {
c.state.parentUserId = input.parentUserId;
c.state.type = input.type;
c.state.channelId = input.channelId;
c.state.status = "idle";
},
appendLog: async (c, entry: LogEntry) => {
c.state.logs.push(entry);
c.broadcast("log", entry);
},
setStatus: async (c, status: SubAgentState["status"]) => {
c.state.status = status;
c.broadcast("status", { status });
},
getLogs: async (c) => c.state.logs,
getStatus: async (c) => c.state.status,
// Pulls historical events from the DB so a returning user sees prior runs.
getHistory: async (c, input: { subAgentId: string }) => {
const rows = await db
.select()
.from(eventsTable)
.where(
and(
eq(eventsTable.userId, c.state.parentUserId),
eq(eventsTable.actorId, input.subAgentId),
),
)
.orderBy(desc(eventsTable.createdAt))
.limit(50);
return rows;
},
getActorRow: async (c, input: { subAgentId: string }) => {
const row = await db.query.actors.findFirst({
where: and(
eq(actorsTable.userId, c.state.parentUserId),
eq(actorsTable.actorId, input.subAgentId),
),
});
return row;
},
},
});