HomeZero · specs
Architecture spec · platform pivot

HomeZero on Flue.
Cells, retired.

Move the whole agent fleet off self-hosted cells (wells VMs) onto Flue — Pi's harness running on Cloudflare Durable Objects. No boxes to manage, no mother to birth them. Each agent configures itself.

A lamp-lit desk by a window over a dusk-lit Colorado mountain town — the advisor keeping watch.

The decision in six lines

01Why move

We're in the agent-hosting business by accident.

Cells gave each agent a real Linux box on a wells VM — and with it, a standing tax: VM provisioning, hibernate-recovery, the mother birth chain, a per-session model hack, and an analytical brain (Stoolap) that kept throwing vec0.so ABI errors. None of that is HomeZero. It's plumbing we maintain so the agents can run.

Flue moves the agent onto a Cloudflare Durable Object: a single-tenant, SQLite-backed, durably-executing instance that exists only when addressed and costs nothing idle. The spike proved the full stack end-to-end — a conversational agent you can chat with on the web, switch models mid-thread, all routed through the claudeflare-gateway. What follows is how the rest of HomeZero lands on that substrate.

Scope note Residential-IP egress (load-bearing for scraping) is deliberately out of scope for this spec — set aside while we prove the agent model. It returns as its own decision when we port the market agent's data-gathering.
02Cells → Flue-native

Every cells capability has a home.

The scary row was always "the box." It turns out Flue offers a tiered filesystem — including a real per-session Linux container when you need one — so nothing forces a single migration model. The advisor runs light; the scraper, when it comes, can run heavy.

Cells capabilityFlue-native landingStatus
Persistent box + filesystemWorkspace (durable, light) or Sandbox container (full Linux, heavy)solved
Stoolap vector brainTurso (managed libSQL + native vectors)upgrade ↑
WhatsApp / Slack two hatsNative channels, one agent, two routesupgrade ↑
Pulse heartbeatsDurable Object alarms / schedule()native
cells talk (agent↔agent)dispatch / tool-RPC / Convex blackboarddesigned
zero-mother births a VMDeleted — the agent self-configures on first bootdesigned

Two filesystem tiers matter: Workspace (durable files, structured code tool, no raw bash) for the advisor; Sandbox container (real shell, git, apt) reserved for agents that must shell out.

One harness, two models Standardize hard: Pi is the only harness across the entire fleet — the claude-code deep-research lane, hermes-on-Python, codex build agents, and every per-cell model hack are all retired. Every agent runs on Claude Opus 4.8 or GPT-5.5, both routed through the claudeflare-gateway. One billing surface, one observability pane, one set of model specifiers. The advisor's two hats (buyer / staff) still differ, but only by model choice on the same Pi harness, never by a different runtime.
03The data model

Four layers. Your existing law, renamed.

This isn't a new data model — it's the four-store law you already reasoned out (Convex / R2 / Stoolap / cell-FS), ported to the new substrate: cell FS → Workspace, Stoolap → Turso, Convex stays Convex, R2 stays R2. Sorted by access pattern, not data type.

Storage layers — per agent & shared
Workspacefilesystem
per agent   IDENTITY.md, yearnings, notes, working memory, scratch. Agent-authored prose. Backed by DO-SQLite + R2. Automatic, free.
TursolibSQL db
per agent   Structured rows + native vectors (FLOAT32 columns, DiskANN index, vector_top_k). Market's is heavy (comps, embeddings); advisor's is light. Hosted on Turso Cloud, one DB per agent.
Convexshared state
shared · all agents   The board, listings, household state, published projections. The agent-to-agent blackboard and the publish layer the apps read reactively. Agents write directly via deploy key, as agent actions.
R2artifacts
shared   Large rendered bytes — briefing HTML, flyer / portrait images, tour recordings — addressed by URL. Convex holds the pointer.

Workspace and DO-SQLite session memory are automatic per agent. Turso is the one deliberate add. Convex + R2 are shared and unchanged from today.

Turso, precisely Turso is a Flue integration, but the word means two things. As Flue's session backend it's Node-target only — on Cloudflare, Flue uses DO-SQLite automatically. As your own database it works fine on Cloudflare via @tursodatabase/serverless (pure HTTP, zero native deps), queried from inside a tool. That second role is what we use: the per-agent brain, hosted, vectors native.
04The birth model

Mother dies. The agent configures itself.

On cells, the only thing mother ever produced was a config — she reasoned a team from onboarding, then seeded template files into a freshly-birthed VM. With no VM to provision, the agent can author that config itself. This is your own law made literal: the first-run end state must be agent-authored.

Deleted

zero-mother, the doorbell → wa-bridge → zero-mother chain, the birth skill, publish-template.mjs, and the whole Mac-side birth orchestration. Gone.

The first-run sequence

Onboarding → self-configuring advisor
1
Onboarding (web, anonymous)

The two questions resolve the two agents

Place → market (via the resolver; warm-market gate still applies). Person → advisor. Finish creates the account and hands off.

2
Finish

One POST to the advisor's own Durable Object

POST /agents/advisor/<householdId> with the answers as the first message. The DO instantiates on that first hit. No external birther.

3
First-run skill (in the agent's loop)

Zero authors its understanding of you

Reads the onboarding answers, queries the already-warm market for context, and writes its per-user understanding into the Workspace (IDENTITY.md = what it knows about you so far). Its name (Zero) and its yearnings (the standing questions, shared to start) are fixed and shipped — not invented here. Publishes a projection (market, status) to Convex for the UI.

4
Every boot after

It reads its identity and runs

The Workspace is the source of truth; Convex carries the projection the apps render. The same loop drives react / tend / scout / brief on Durable Object alarms.

Cold-start = live status, not a placeholder

No invented deterministic fill. /home shows the advisor's real progress: the first-run skill writes a status to Convex at each checkpoint as it genuinely completes, and the page — subscribed via Convex's reactive query — updates live. The bridge is the agent narrating itself.

Advisor first-run status ladder (written to Convex, surfaced live on /home)
waking"Waking up…" — instance spun up, your answers received.
studying_market"Getting to know your market…" — pulling the warm market's context.
authoring"Writing your playbook…" — authoring IDENTITY.md + yearnings into the Workspace.
scouting"Setting up your board…" — scouting its first homes.
readyReady. — projection published; the board and greeting are live.

Four in-flight checkpoints → ready. The advisor owns its own status field; each transition is a real Convex write, never a timer.

05How agents talk

Three mechanisms, matched to the job.

Flue gives two native primitives — async dispatch between deployed agents, and synchronous in-process subagents — plus the obvious third: expose a peer as a tool for a sync RPC. Each maps to a real interaction in the funnel.

The agent mesh
dispatch · asyncadvisor → market: "author the full 229 Colorado briefing." Market takes minutes; the card materializes on the board later via Convex reactive query. This is the zero-drain / kanban "latency is invisible" model.
tool-RPC · syncadvisor → market: "is this market warm? median price?" Fast Turso lookup, answer used in the same chat turn.
subagent · in-procmarket → water-rights / flip-check: ephemeral specialist, focused, cheaper model. No separate cell to birth.
Convex · sharedeveryone ↔ the blackboard: the durable shared truth all agents read and write (the board, listings, household state).
per household

Advisor

Conversational. Two channel hats: WhatsApp (buyer) + Slack (staff). Reacts fast; dispatches the deep work.

per county

Market

Authors briefings, holds the comps + vector brain in Turso. Spawns specialists as subagents.

per house

Home agent

Born on zero. Talks to the market via dispatch; owns the home's evolving record.

Persistent peers talk via dispatch (async) and tool-RPC (sync). Ephemeral specialists are subagents. Shared truth lives in Convex.

This matches your card-authorship split exactly: the advisor reacts synchronously to the user, then dispatches the market to author the deep briefing that lands as a card. The "cells talk to cells" mesh becomes native, with no transport to maintain.

06The three agents on Flue

Same three births. New bodies.

The product model is unchanged — three births (advisor, market, home agent); the user is created, not born. What changes is the body each runs in.

AgentScopeRuntimeStorageChannels
Advisorper household (shared relationship, account holder owns)DO + Workspace (light)Workspace + light Turso; reads ConvexWhatsApp + Slack
Marketper countyDO + Workspace; Sandbox container if it must shell outWorkspace + heavy Turso (comps/vectors); writes Convex—
Home agentper house (born on zero)DO + WorkspaceWorkspace + Turso; writes Convex—

Specialists (water-rights, flip-check) are no longer agents at all — they're subagents of the market, spawned per task.

Instance vs session — how the advisor's two hats work Flue separates the instance (the persistent identity — one Durable Object, one Workspace, one IDENTITY.md, one memory) from sessions (named conversation threads inside it, each with isolated history). The advisor (named Zero) is one instance per household (id = the household's Clerk userId) holding two named sessions: buyer (WhatsApp) and staff (Slack). Separate histories — no context bleed — and model is a per-session choice, so buyer can run GPT-5.5 while staff runs Opus, on the same shared brain. (To merge channels into one thread instead — same human on WhatsApp + web — route both to the same instance and same session name. task:-prefixed names are reserved.)
07The advisor, in full

Zero isn't a chatbot — it's a standing relationship.

Every household's advisor is named Zero. It's born, then it lives: it remembers, it dreams, and it's reachable on the channels that matter. All four ride the substrate we just defined — memory is Workspace files, dreaming is a Durable Object alarm, the two hats are channels into named sessions. Built on the self-config vertical, in this order.

Zero's loop — woken by Durable Object alarms, not a Mac cron
reacta message lands (web / WhatsApp / Slack) → answer now.
tend · ~30ma light pass over the board and open questions.
scout · ~3hlook for homes worth surfacing.
brief · dailyauthor the morning brief.
dream · nightlyno user present — fold the day into durable memory.
digest · weeklystep back; revisit standing yearnings.

Memory

Zero's Workspace holds a memory/ store of agent-authored markdown beside IDENTITY.md — what it has learned about you, your search, your constraints. It recalls the relevant files at the top of each turn and writes new ones as it goes. Durable (DO-SQLite + R2); session transcripts are kept automatically. This is the same Workspace the first-run pass already writes to — memory is just more of it.

Yearnings — shared, not per-advisor

Every Zero ships with the same set of yearnings — the standing questions it wants answered about its user: what "a home that takes care of you" means to them, their real all-in (cost-to-zero) budget, their dealbreakers, what they'd trade off, how ready they are, how they want to be advised. The questions are universal — part of Zero's identity. The answers are per-user, and accumulate in memory. Yearnings reweight what Zero studies and asks next.

Shared to start. We ship one fixed set so every Zero behaves consistently; as Zero learns a household over time, the set can grow user-specific standing questions, but the universal core stays. Start uniform, personalize later — not the other way around.

Dreaming

A nightly Durable Object alarm wakes Zero with no user present to run a dream skill: fold the day's sessions into durable memory, merge and prune, revisit yearnings, prep tomorrow's brief. This is the cells "nightly dream," now native to the runtime — no pulse, no claude -p, no Mac cron.

Two hats, on the right channels

One Zero instance · two named sessions · two channels
HatChannelSessionWiring
StaffSlack — one #advisors channel, a thread per householdstaff (Opus)Automatic for every advisor. At birth Zero posts its intro to #advisors, opening its thread, and records thread_ts ↔ userId. Staff replies in-thread route back to that household's staff session.
BuyerWhatsAppbuyerOptional — only if the buyer gave a number. Inbound routes phone → buyer → userId (the buyers.by_phone unique index already exists) into the buyer session.

One instance per household (id = Clerk userId) holds both sessions over one shared brain; histories stay isolated — staff talk never bleeds into the buyer's thread.

Sequencing These build on the self-config vertical: first Zero exists, self-configures, and chats (Plan 1). Then memory → dream → Slack-staff → WhatsApp layer on (Plan 2). Each is an independently shippable phase; none needs a redesign — the substrate already holds them.
08Where we stand

Settled spine, named unknowns.

SETTLED

  • ✓ Substrate: Pi → Flue → Durable Object → gateway (spike-proven)
  • ✓ Pi is the only harness; models are Opus 4.8 or GPT-5.5, nothing else
  • ✓ No mother; advisor self-configures on first boot
  • ✓ Identity in IDENTITY.md in the Workspace
  • ✓ Per-agent Workspace + per-agent Turso; shared Convex + R2
  • ✓ Comms: dispatch / tool-RPC / subagents
  • ✓ Specialists collapse into subagents
  • ✓ Cold-start = live status ladder (4 checkpoints → ready), agent writes its own status to Convex; no placeholder
  • ✓ Model: Opus 4.8 default; GPT-5.5 for high-frequency loops, wired later. Slice 1 ships Opus-only.
  • ✓ Identity = the household's Clerk userId (no separate household table; buyers is the per-user record)
  • ✓ The advisor is named Zero — one persona across all households; it authors its per-user understanding, not its name
  • ✓ Yearnings are shared to start (same standing questions for every user; answers are per-user) — personalize later, not first
  • ✓ Full advisor committed (Plan 2): memory · dream · Slack-staff (auto, one #advisors channel, thread per household) · WhatsApp (optional)

OPEN

  • ? Turso provisioning: one DB per agent created when / by what
  • ? Residential-IP egress for the market's scraping (deferred)
  • ? Auth model for the agent ingress in production (the spike uses a shared password)
09What we build first

One vertical, end to end.

Prove the model on the cheapest agent that exercises the whole pattern: onboarding answers → a self-configuring advisor you can chat with. Market, home agent, scraping, and the Turso vector brain each become their own later spec — but they all inherit this mold.

Slice 1 — The self-configuring advisor

Goal: a real person finishes onboarding and is talking to Zero — which authored its understanding of them from their answers — on Flue, no mother in the loop. (Plan 1 of 2; memory · dream · Slack-staff · WhatsApp follow in Plan 2.)

  1. Advisor agent on a Durable Object, addressed by householdId, served behind the gateway.
  2. First-run skill: read onboarding answers → author IDENTITY.md + yearnings into the Workspace.
  3. Publish a projection (name, market, status) to Convex; the web /home renders it.
  4. Onboarding Finish → one POST to the advisor's DO (replaces doorbell → zero-mother).
  5. Chat surface (web, then WhatsApp channel) wired to the same agent.
  6. Live status ladder: advisor writes waking → studying_market → authoring → scouting → ready to Convex; /home renders it live via reactive query.
Slice 1 runs Opus-only. Out of scope: GPT-5.5 wiring, market authoring, home agent, Turso vectors, scraping egress, production auth hardening. Each is its own spec.

Next step: an implementation plan for Slice 1.