Architecture
A Turborepo + pnpm monorepo with one rule above all others: the chain is truth, the cache is speed. A user turn never blocks on 0G.
#The monorepo
Six workspaces, each with a single responsibility. The packages carry the logic; the apps wire it to a network and a screen.
packages/
shared/ the contract — Zod schemas are the single source of truth
agent/ the pure, model-agnostic counsel loop — no SDK weight
memory/ consolidation, encrypt-to-self, canonical bytes, reconcile
zerog/ thin typed wrappers over 0G Compute + Storage + Chain
apps/
api/ Node + Express; static-class services, Zod, Drizzle hot cache
web/ Next.js App Router; the crimson proof feed
contracts/ Foundry — AgentRegistry (Phase 3)#The contract package
packages/shared is the spine every lane builds against. Zod schemas are the single source of truth for every shape that crosses a boundary — the HTTP body, the memory object, the 0G seam — and the TypeScript types are derived from those schemas, so a type can never drift from its validator.
// Types are DERIVED from the Zod schemas — never hand-written.
export type StrategyMemory = z.infer<typeof strategyMemorySchema>;
export type Recommendation = z.infer<typeof recommendationSchema>;
export type InferenceReceipt = z.infer<typeof inferenceReceiptSchema>;
export type MemoryReceipt = z.infer<typeof memoryReceiptSchema>;
export type AgentTurn = z.infer<typeof agentTurnSchema>;It also defines the seam the rest of the system depends on — the ComputeClient and StorageClient interfaces. The agent and API build against these shapes; packages/zerog implements them for real, and tests fake them. The browser never sees a real implementation, only the shape.
#Chain is truth, cache is speed
A freshly uploaded 0G Storage object is not reliably downloadable for roughly 3–5 minutes. That single fact dictates the whole architecture: a request must never wait on it. So memory reads come from a Postgres hot cache, writes go to Postgres now and to 0G asynchronously, and a background job reconciles the two.
On the request path: read the cache, never the network. On commit: write the cache, compute the real root hash, return a pending receipt immediately, and fire the upload in the background. Off the request path: a reconcile job confirms each commitment landed and flips it to verified. The user always has a real receipt; the network catches up on its own schedule.
#The composition root
Everything is wired once at startup from validated environment. Secrets live only here. Phase 3 on-chain commits are a single conditional branch on one env var.
export function buildContainer(env: Env): Container {
const now = () => new Date().toISOString();
const { compute, storage } = createZeroG({ /* key, rpc, indexer, provider */ });
const db = createDb(env.DATABASE_URL);
const store = new DrizzleMemoryStore(db);
const registry = env.AGENT_REGISTRY_ADDRESS // Phase 3 only when set
? new AgentRegistryClient({ /* ... */ })
: undefined;
// encrypt-to-self: the agent wallet key IS the memory encryption secret
const memory = MemoryService.init({ store, storage, registry, secret: env.PRIVATE_KEY, now });
const agent = AgentService.init({ compute, memory, store, now });
return { db, memory, agent };
}