Daeseon Yoo
Back to project
·Tech retro·3 min

The BYO natural-language control layer: propose → approve, rules, assistant, documents

Built a four-part natural-language control layer on top of the BYO agent — a propose→approve gate, sender auto-sort rules, a global assistant popup, and a document model with mandatory version backup. Talkak still never calls an LLM; the founder's own Claude does, through MCP tools, and a human approves every outside-world action.

The product principle has not moved: Talkak does not call an LLM. The founder brings their own Claude (in a pane), and our job is the workspace + the control surface. This session turned that principle into a usable natural-language control layer, in four pieces, all sharing one spine: the agent proposes, a human approves, the app executes and keeps a receipt.

1. propose → approve → execute → receipt

The differentiator versus a chat butler that fires immediately: the BYO agent can only ever propose an outside-world action. It writes a proposed_action node to the shared operating-memory graph (via MCP, cross-process, the same append-only JSONL every connector uses). Nothing touches the outside world until the founder approves it in a global 📥 Approvals inbox, which then runs the same gated function the UI uses (send_reply, calendar_quick_add) and mints an action_receipt.

2. Natural-language auto-sort rules

"Anything from Indeed goes to sales" — said in plain language, no filter-rule maze. A rule is a policy graph node; on connector ingestion, mail whose sender matches gets its domain set, so the renderer's clusterOf() routes it to the right Area. This one is not approval-gated — it is reversible config, not an outside-world action, so control is the Rules panel's visibility + off-switch rather than a pre-approval step (30e25d4 engine + ingestion hook, 67f13a1 the rule.set MCP tool + 🗂️ Rules panel).

A small but real correctness detail: rule and document nodes are config, not inbox work, so clusterOf() routes them to a pseudo-cluster no Area screen renders — otherwise they would have leaked into a worklist looking like a task.

3. The 🧞 assistant popup

A global command + memory surface over the existing conversation backbone (assistant_log_turn / assistant_history / assistant_conversations). Compose a command → it logs the turn and sends it to the founder's Claude in the active pane (pty_write, reusing the existing pane routing). History slices on the axes asked for: day / month, project, entity (5d86e21). Reply-capture (logging the agent's answer back) is a deliberate follow-up — for now the reply shows in the pane.

4. The document model — with a mandatory backup

The founder's locked three modes: read a connected file in Talkak, open it natively to edit by hand, or NL-edit it — the agent reads the doc, proposes the complete new content, and on approval the app backs up the current version first, then writes (43dc1ab). This reuses the entire propose→approve gate (a new edit_document action kind): the agent never writes a file directly, only a connected document may be written, and a timestamped .dalkkak-versions/ copy is always kept.

A recurring trap, pinned again

Every new node kind has to be added to the graph_index.rs NODE_TYPES allowlist or GraphStore.append silently rejects it ("bad node_type") — the same class of bug that once dropped 25 fetched Calendar events while 249 unit tests stayed green. This session added event-era types plus document, each with a regression test that actually exercises parse_node_jsonl_line, not just the per-module node builder.

What is verified, and what is not

Verified this session, by command output: cargo test --lib 258/258 (incl. the new documents + rules + node-type regression tests); the local MCP server 46/46 (incl. propose/rule/docedit round-trips through the message handler); tsc --noEmit clean; vite build clean; and each propose/rule tool piped into the shipped node dist/src/index.js binary, writing the right node to a temp graph dir.

Not yet verified — and stated plainly: the live human-in-the-loop pass. Nobody has yet, in the running app, asked their Claude to act, watched it appear in 📥 Approvals, and clicked Approve to a real send / calendar event / file write. The agent-side half is proven end to end through the shipped binary; the approval click and the real executor are inherently founder-side. That is the next thing to close.