Files
growqr-backend/docs/architecture.html
NinjasPyajamas 2d471c61b4 feat: introduce workflow job management and agent orchestration
- Added workflow job actor to manage job application workflows.
- Implemented agent catalog for various workflow agents.
- Created service agents for interview, roleplay, and Q-Score functionalities.
- Enhanced user authentication to automatically create users if they do not exist.
- Updated configuration to support new LLM provider and API keys.
- Introduced new routes for agent and workflow management.
- Refactored Docker management to improve Gitea admin user creation and token generation.
- Removed deprecated Anthropics SDK integration.
2026-05-21 23:17:26 +05:30

395 lines
18 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>GrowQR — Architecture</title>
<style>
:root {
--bg: #f7f8fa;
--ink: #0d1117;
--muted: #5b636e;
--line: #1f2328;
--mono: ui-monospace, "JetBrains Mono", "Fira Code", "SF Mono", Menlo, Consolas, monospace;
--sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Inter, system-ui, sans-serif;
/* Per-service palette */
--c-ui: #2563eb; --c-ui-bg: #eff6ff;
--c-auth: #7c3aed; --c-auth-bg: #f5f3ff;
--c-pay: #db2777; --c-pay-bg: #fdf2f8;
--c-threads: #0d9488; --c-threads-bg: #f0fdfa;
--c-actor: #d97706; --c-actor-bg: #fffbeb;
--c-runtime: #059669; --c-runtime-bg: #ecfdf5;
--c-memory: #4f46e5; --c-memory-bg: #eef2ff;
--c-git: #e11d48; --c-git-bg: #fff1f2;
--c-db: #475569; --c-db-bg: #f1f5f9;
}
* { box-sizing: border-box; }
html, body {
margin: 0;
background: var(--bg);
color: var(--ink);
font-family: var(--sans);
-webkit-font-smoothing: antialiased;
}
.wrap {
max-width: 1280px;
margin: 32px auto 64px;
padding: 0 24px;
}
header {
display: flex;
align-items: baseline;
justify-content: space-between;
gap: 16px;
border-bottom: 1px solid #d6dbe1;
padding-bottom: 16px;
margin-bottom: 24px;
}
h1 {
margin: 0;
font-family: var(--mono);
font-size: 22px;
letter-spacing: -0.01em;
}
header .meta {
color: var(--muted);
font-family: var(--mono);
font-size: 12px;
}
.lede {
color: var(--muted);
font-size: 14px;
line-height: 1.55;
max-width: 920px;
margin: 0 0 28px;
}
.card {
background: #ffffff;
border: 1px solid #d6dbe1;
border-radius: 12px;
padding: 28px;
box-shadow: 0 1px 0 rgba(13,17,23,0.02), 0 6px 22px rgba(13,17,23,0.05);
}
svg.diagram {
width: 100%;
height: auto;
display: block;
font-family: var(--mono);
}
.legend {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
gap: 18px 28px;
margin-top: 28px;
font-size: 13.5px;
line-height: 1.5;
}
.legend .item { display: flex; gap: 10px; align-items: flex-start; }
.legend .swatch {
width: 14px; height: 14px; border-radius: 3px;
margin-top: 4px; flex: 0 0 14px;
border: 1.5px solid currentColor;
}
.legend h3 {
font-family: var(--mono);
font-size: 13px;
margin: 0 0 4px;
letter-spacing: 0.02em;
}
.legend p { margin: 0; color: var(--muted); }
footer {
margin-top: 28px;
color: var(--muted);
font-size: 12px;
font-family: var(--mono);
text-align: right;
}
@media print {
body { background: white; }
.card { box-shadow: none; border-color: #000; }
header { border-color: #000; }
}
</style>
</head>
<body>
<div class="wrap">
<header>
<h1>GrowQR — Architectural Diagram</h1>
<div class="meta">v1.0</div>
</header>
<p class="lede">
Every user gets their own private <strong>Grow Agent</strong> (a Rivet Kit actor) that orchestrates sub-agents
and owns a dedicated sandboxed runtime — an OpenCode container for tool execution and a Gitea container for
long-term memory. The frontend talks to the actor backend over a persistent connection; agents stream events,
commit memory to the user's private git, and read/write structured state in the shared database.
</p>
<div class="card">
<svg class="diagram" viewBox="0 0 1240 800" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="GrowQR architecture diagram">
<defs>
<marker id="arrow" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse">
<path d="M0,0 L10,5 L0,10 z" fill="#1f2328"/>
</marker>
<style>
.lbl { font-family: ui-monospace, "JetBrains Mono", Menlo, monospace; font-size: 12.5px; fill: #0d1117; }
.lbl-sm { font-family: ui-monospace, "JetBrains Mono", Menlo, monospace; font-size: 11px; fill: #0d1117; }
.lbl-tiny { font-family: ui-monospace, "JetBrains Mono", Menlo, monospace; font-size: 10.5px; fill: #5b636e; }
.lbl-title { font-family: ui-monospace, "JetBrains Mono", Menlo, monospace; font-size: 13.5px; fill: #0d1117; font-weight: 700; }
.edge { fill: none; stroke: #1f2328; stroke-width: 1.5; stroke-linecap: round; stroke-linejoin: round; }
.pill { font-family: ui-monospace, "JetBrains Mono", Menlo, monospace; font-size: 10px; fill: #5b636e; }
.pill-bg { fill: #ffffff; }
</style>
</defs>
<!-- ============================================================ -->
<!-- UI (frontend cluster) -->
<!-- ============================================================ -->
<g>
<rect x="60" y="160" width="230" height="260" rx="8"
fill="#eff6ff" stroke="#2563eb" stroke-width="1.6"/>
<text class="lbl-tiny" x="175" y="410" text-anchor="middle" fill="#2563eb">Vercel / OpenNext</text>
<rect x="80" y="180" width="190" height="60" rx="5"
fill="#ffffff" stroke="#2563eb" stroke-width="1.2"/>
<text class="lbl-title" x="175" y="208" text-anchor="middle">UI</text>
<text class="lbl-tiny" x="175" y="226" text-anchor="middle">Next.js 16 · React 19</text>
<rect x="80" y="255" width="190" height="135" rx="5"
fill="#ffffff" stroke="#2563eb" stroke-width="1.2"/>
<text class="lbl" x="92" y="276">frontend JS</text>
<text class="lbl-sm" x="92" y="298"> auth</text>
<text class="lbl-sm" x="92" y="316"> actors mgmt</text>
<text class="lbl-sm" x="92" y="334"> chat / event stream</text>
<text class="lbl-sm" x="92" y="352"> payments</text>
</g>
<!-- auth -->
<g>
<rect x="60" y="450" width="110" height="58" rx="6"
fill="#f5f3ff" stroke="#7c3aed" stroke-width="1.6"/>
<text class="lbl" x="115" y="476" text-anchor="middle">auth</text>
<text class="lbl-tiny" x="115" y="494" text-anchor="middle" fill="#7c3aed">Clerk v6</text>
</g>
<!-- payments -->
<g>
<rect x="180" y="450" width="110" height="58" rx="6"
fill="#fdf2f8" stroke="#db2777" stroke-width="1.6"/>
<text class="lbl" x="235" y="476" text-anchor="middle">payments</text>
<text class="lbl-tiny" x="235" y="494" text-anchor="middle" fill="#db2777">Stripe</text>
</g>
<!-- ============================================================ -->
<!-- Threads API -->
<!-- ============================================================ -->
<g>
<rect x="470" y="30" width="260" height="100" rx="8"
fill="#f0fdfa" stroke="#0d9488" stroke-width="1.6"/>
<text class="lbl-title" x="490" y="58">Threads API</text>
<text class="lbl-sm" x="490" y="82"> session tracking</text>
<text class="lbl-sm" x="490" y="100"> message logs</text>
<text class="lbl-tiny" x="490" y="120" fill="#0d9488">Hono · /api/rivet/*</text>
</g>
<!-- ============================================================ -->
<!-- Actor Backend -->
<!-- ============================================================ -->
<g>
<rect x="470" y="200" width="260" height="200" rx="8"
fill="#fffbeb" stroke="#d97706" stroke-width="1.6"/>
<text class="lbl-tiny" x="600" y="392" text-anchor="middle" fill="#d97706">Actor Backend · Hono + Rivet Kit</text>
<rect x="490" y="220" width="220" height="135" rx="5"
fill="#ffffff" stroke="#d97706" stroke-width="1.2"/>
<text class="lbl-tiny" x="600" y="348" text-anchor="middle">Actor manager</text>
<text class="lbl" x="510" y="248">Actor Runner</text>
<text class="lbl" x="510" y="272">Actor Engine</text>
<text class="lbl" x="510" y="296">Actor Storage</text>
<text class="lbl-tiny" x="510" y="320" fill="#d97706">growAgent · subAgent</text>
</g>
<!-- ============================================================ -->
<!-- Agent Runtime -->
<!-- ============================================================ -->
<g>
<rect x="810" y="200" width="260" height="220" rx="8"
fill="#ecfdf5" stroke="#059669" stroke-width="1.6"/>
<text class="lbl-tiny" x="940" y="395" text-anchor="middle" fill="#059669">SandBoxed Runtime · per-user Docker</text>
<text class="lbl-tiny" x="940" y="412" text-anchor="middle" fill="#059669">service scale</text>
<rect x="830" y="220" width="220" height="145" rx="5"
fill="#ffffff" stroke="#059669" stroke-width="1.2"/>
<text class="lbl-tiny" x="940" y="358" text-anchor="middle">Agent runtime · OpenCode</text>
<text class="lbl-title" x="850" y="248">Agent Runtime</text>
<text class="lbl-sm" x="850" y="274"> sub-agents</text>
<text class="lbl-sm" x="850" y="294"> skills loading</text>
<text class="lbl-sm" x="850" y="314"> tool execution</text>
<text class="lbl-sm" x="850" y="334"> dockerized</text>
</g>
<!-- ============================================================ -->
<!-- Memory API -->
<!-- ============================================================ -->
<g>
<rect x="390" y="470" width="260" height="120" rx="8"
fill="#eef2ff" stroke="#4f46e5" stroke-width="1.6"/>
<text class="lbl-title" x="410" y="498">Memory API</text>
<text class="lbl-sm" x="410" y="524"> tracking memory</text>
<text class="lbl-sm" x="410" y="544"> 3 layers of memory</text>
<text class="lbl-tiny" x="410" y="572" fill="#4f46e5">commit_memory · read_memory · list_memory</text>
</g>
<!-- ============================================================ -->
<!-- Git manager -->
<!-- ============================================================ -->
<g>
<rect x="810" y="470" width="260" height="170" rx="8"
fill="#fff1f2" stroke="#e11d48" stroke-width="1.6"/>
<text class="lbl-tiny" x="940" y="630" text-anchor="middle" fill="#e11d48">service Scale</text>
<rect x="830" y="490" width="220" height="60" rx="5"
fill="#ffffff" stroke="#e11d48" stroke-width="1.2"/>
<text class="lbl-title" x="940" y="524" text-anchor="middle">Users-repos</text>
<text class="lbl-tiny" x="940" y="568" text-anchor="middle">Git manager · per-user Gitea</text>
<text class="lbl-tiny" x="940" y="586" text-anchor="middle">growqr-memory.git</text>
</g>
<!-- ============================================================ -->
<!-- Database -->
<!-- ============================================================ -->
<g>
<rect x="320" y="680" width="700" height="80" rx="10"
fill="#f1f5f9" stroke="#475569" stroke-width="1.8"/>
<text class="lbl-title" x="670" y="720" text-anchor="middle" font-size="17">DB / PG / AWS RDS</text>
<text class="lbl-tiny" x="670" y="742" text-anchor="middle">users · user_stacks · actors · repos · opencode_sessions · events</text>
</g>
<!-- ============================================================ -->
<!-- Orthogonal edges -->
<!-- ============================================================ -->
<!-- UI → Threads API (up then right) -->
<path class="edge" d="M 175,160 L 175,80 L 470,80" marker-end="url(#arrow)"/>
<rect class="pill-bg" x="220" y="68" width="78" height="14" rx="2"/>
<text class="pill" x="259" y="78" text-anchor="middle">fetch · JWT</text>
<!-- UI → Actor Backend (straight horizontal) -->
<path class="edge" d="M 290,290 L 470,290" marker-end="url(#arrow)"/>
<rect class="pill-bg" x="320" y="278" width="80" height="14" rx="2"/>
<text class="pill" x="360" y="288" text-anchor="middle">rivet-client</text>
<!-- UI → auth (down) -->
<path class="edge" d="M 115,420 L 115,450" marker-end="url(#arrow)"/>
<!-- UI → payments (down) -->
<path class="edge" d="M 235,420 L 235,450" marker-end="url(#arrow)"/>
<!-- Actor Backend → Threads API (straight up) -->
<path class="edge" d="M 600,200 L 600,130" marker-end="url(#arrow)"/>
<!-- Actor Backend → Agent Runtime (right) -->
<path class="edge" d="M 730,280 L 810,280" marker-end="url(#arrow)"/>
<rect class="pill-bg" x="732" y="266" width="80" height="14" rx="2"/>
<text class="pill" x="772" y="276" text-anchor="middle">spawn_sub_agent</text>
<!-- Agent Runtime → Actor Backend (left, SSE back) -->
<path class="edge" d="M 810,340 L 730,340" marker-end="url(#arrow)"/>
<rect class="pill-bg" x="732" y="346" width="90" height="14" rx="2"/>
<text class="pill" x="777" y="356" text-anchor="middle">SSE events</text>
<!-- Actor Backend → Memory API (down) -->
<path class="edge" d="M 550,400 L 550,470" marker-end="url(#arrow)"/>
<!-- Memory API → Git manager (right) -->
<path class="edge" d="M 650,520 L 810,520" marker-end="url(#arrow)"/>
<rect class="pill-bg" x="690" y="508" width="74" height="14" rx="2"/>
<text class="pill" x="727" y="518" text-anchor="middle">Gitea REST</text>
<!-- Actor Backend → DB (down, x=660) -->
<path class="edge" d="M 660,400 L 660,680" marker-end="url(#arrow)"/>
<rect class="pill-bg" x="668" y="528" width="56" height="14" rx="2"/>
<text class="pill" x="696" y="538" text-anchor="middle">drizzle</text>
<!-- Git manager → DB (down, x=940) -->
<path class="edge" d="M 940,640 L 940,680" marker-end="url(#arrow)"/>
</svg>
</div>
<div class="legend">
<div class="item" style="color: var(--c-ui)">
<span class="swatch" style="background: var(--c-ui-bg)"></span>
<div>
<h3>UI</h3>
<p style="color: var(--muted)">Next.js 16 + React 19 on Vercel / OpenNext. Auth flows, chat composer, event console, and actor management. Talks to the actor backend over the Rivet Kit client and REST endpoints with a Clerk JWT.</p>
</div>
</div>
<div class="item" style="color: var(--c-auth)">
<span class="swatch" style="background: var(--c-auth-bg)"></span>
<div>
<h3>auth</h3>
<p style="color: var(--muted)">Clerk on browser and server. JWT is verified on every request; users are mirrored into Postgres on first sight.</p>
</div>
</div>
<div class="item" style="color: var(--c-pay)">
<span class="swatch" style="background: var(--c-pay-bg)"></span>
<div>
<h3>payments</h3>
<p style="color: var(--muted)">Stripe billing — plans, metering, webhooks, and customer portal. Flows through the same JWT identity as the rest of the app.</p>
</div>
</div>
<div class="item" style="color: var(--c-threads)">
<span class="swatch" style="background: var(--c-threads-bg)"></span>
<div>
<h3>Threads API</h3>
<p style="color: var(--muted)">Hono routes for session listing and message logs. All persistent state flows through Postgres; the Rivet handler is mounted at <code>/api/rivet/*</code>.</p>
</div>
</div>
<div class="item" style="color: var(--c-actor)">
<span class="swatch" style="background: var(--c-actor-bg)"></span>
<div>
<h3>Actor Backend</h3>
<p style="color: var(--muted)">Rivet Kit actors orchestrated by Hono. Two actor types — <em>growAgent</em> (one master per user) and <em>subAgent</em> (workers). Runner, Engine, and Storage are provided by Rivet; durable state mirrors into Postgres.</p>
</div>
</div>
<div class="item" style="color: var(--c-runtime)">
<span class="swatch" style="background: var(--c-runtime-bg)"></span>
<div>
<h3>Agent Runtime</h3>
<p style="color: var(--muted)">Per-user OpenCode container spawned via <code>dockerode</code> on first sign-in. Hosts sub-agent sessions, skill loading, and sandboxed tool execution. Streams events back over SSE which the Grow Agent re-broadcasts to the UI.</p>
</div>
</div>
<div class="item" style="color: var(--c-memory)">
<span class="swatch" style="background: var(--c-memory-bg)"></span>
<div>
<h3>Memory API</h3>
<p style="color: var(--muted)">Three-layer memory surface exposed to the configured OpenCode Zen model as tools (<code>commit_memory</code>, <code>read_memory</code>, <code>list_memory</code>). L1 in-actor state, L2 session in Postgres, L3 long-term in the user's Gitea repo.</p>
</div>
</div>
<div class="item" style="color: var(--c-git)">
<span class="swatch" style="background: var(--c-git-bg)"></span>
<div>
<h3>Git manager · Users-repos</h3>
<p style="color: var(--muted)">Per-user Gitea container. Backend creates an admin user, mints an access token, and bootstraps a private <code>growqr-memory</code> repo. Long-term memory commits land here as plain markdown.</p>
</div>
</div>
<div class="item" style="color: var(--c-db)">
<span class="swatch" style="background: var(--c-db-bg)"></span>
<div>
<h3>DB / PG / AWS RDS</h3>
<p style="color: var(--muted)">Postgres + Drizzle ORM. Tables: <code>users</code>, <code>user_stacks</code>, <code>actors</code>, <code>repos</code>, <code>opencode_sessions</code>, <code>events</code>. In production this is AWS RDS; in development it's the Postgres service in docker-compose.</p>
</div>
</div>
</div>
<footer>
GrowQR · architecture overview
</footer>
</div>
</body>
</html>