Get started

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.

workspace layoutbash
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.

packages/shared/src/types.tsts
// 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.

Design note
The rule, made concrete

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.

apps/api/src/container.tsts
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 };
}