Files
growqr-backend/docs/architecture.html
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

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 Claude 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>