Daeseon Yoo
Back to project
·Tech retro·3 min

Two halves of one loop — and then it read my real inbox

Two parallel agent builds — a memory engine and a WorkItem cockpit — turned out to be two halves of the same loop. Merging them, then wiring a real Gmail connector, made the company answer from my actual inbox.

Two agents had been building the same feature in parallel — my own head-to-head: Claude in one worktree, Codex in another, same prompt, different reading of it.

Claude built the read side: an operating-memory engine. Retrieve from the graph, cite the source, ground a BYO-Claude answer. Codex built the write side: a WorkItem cockpit — work comes in, gets classified by a pack, runs through an agent, gets remembered to the graph.

For days I assumed I'd have to pick one. Today I learned they weren't competitors. They were two halves of the same loop — the exact operating-memory loop Codex itself had spec'd. Codex wrote work into the graph; Claude read it back out. They even shared the same GraphStore. The seam where they meet — workitem_context, the function that pulls memory into a task — was a basic filter on Codex's side and a full semantic retriever on Claude's. One side left a hole; the other had built the part shaped exactly like it.

The merge

A 3-way merge of Codex's branch into Claude's. Six real conflicts. The ugliest: both had independently added an "edges" feature to GraphStore with different method names (read_all_edges vs read_edges_all) and a different field name. I unified on one API and re-grafted the one test helper I needed.

Both test suites, green together:

cargo test:        80 passed
pnpm vitest:       146 passed (24 suites)
verify-local.sh:   EXIT 0

Then the proof that mattered — live, in the app. I made a WorkItem ("Windows support inquiry"), hit ⌘K, and asked the company "what's there about Windows support?" The answer came back citing the WorkItem I'd just made — plus an older architecture note about the Windows keymap blocker that I never typed. Work in, answer out, same app. The loop closed.

From a fixture to my actual inbox

The connector had been reading a fixture file. Today it read my real Gmail.

IMAP, read-only, a Gmail app-password — imap 2.4.1 + mailparse + native-tls, slotting into the connector's existing load_items seam. A recent inbox slice → one graph node per message → queryable like any other memory.

fetched 25 items; shapes ok (subjects/bodies NOT printed)
ingested 25 emails → 20 retrievable + cited (content NOT printed)

And then, live: I asked "did I get mail from Indeed?" and the company answered — yes, a recruiting email from the 16th — citing the real inbox/gmail/<message-id> nodes it had pulled from. Not a chatbot guessing. My own inbox, remembered, answered with receipts.

Two bugs, honestly

It did not go cleanly.

  1. The creds vanished. The connector read GMAIL_ADDRESS from the process env, and the running app simply didn't have it — env is fixed at launch, and a relaunch had dropped it. Fix: read the canonical store (~/.secrets/api-keys.env) at use-time, not the launch env. Lesson: a desktop app's secrets belong in the keychain / secret store, read on demand — never injected once at spawn.
  2. The app kept dying. Every tauri dev I launched from the agent's tooling got SIGTERM (exit 143) the moment the next command ran. Lesson: don't tie a long-running process to an agent's tool lifecycle — hand the launch to the user's own shell.

(Both written up in docs/troubleshooting.md; the fixes shipped in commits 9567d6b and 3031f38, the merge in fec3689.)

What this is, and isn't

The engine works. The first real business connector is live on real data. That's a genuine step — it moved "connectors" from zero to one-live, and it pointed the product at its actual wedge: business memory, not coding. Coding agents already retrieve their own code context; what they don't have is your inbox, your support history, your decisions.

What it isn't: used. Nobody — including me — runs this daily yet. The inbox I just connected is mostly recruiting noise, not customer questions; the next real work is filtering, and I'll only learn what to filter by living in it. The moat was never the retrieval. It's the data that compounds while you work — and that only starts when someone actually works in it.

Tomorrow, that someone is me.