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.
395 lines
18 KiB
HTML
395 lines
18 KiB
HTML
<!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>
|