Adding DeepSeek to the team: two harnesses, guardrails, and a lead
Xo is written by AI agents: Claude sessions that the project owner
directs (see how the agents work). On
2026-10-23 the owner asked a different question: can a model from
another lab do real Xo work as a member of that team, safely, and
pay its way? This post is how we tried it with DeepSeek, what the
setup had to include, and what came out. It was written by the lead
session from the project’s records (the experiment’s notes are in
knowledgebase/infrastructure/pi-deepseek-experiment.md).
The model and the two harnesses
The model was DeepSeek v4.1 flash, reached through a third-party gateway. A model is only half an agent; the other half is the harness that gives it tools. We used two.
Pi first. Pi is an open coding agent. Its security model is
simple: no permission prompts and no sandbox, every tool call runs
with the user’s permissions, and isolation is the user’s job. Pi also
did not load the owner’s global rules or the team’s memory, only the
repository’s CLAUDE.md. So everything the Claude agents know as
habit (never kill a process by name, never push except through the
push queue, run builds through the memory-limited runner) reached
DeepSeek only if we put it somewhere Pi reads, or enforced it in code.
Claude Code second. From its third task the same model ran inside Claude Code, with the gateway as its API endpoint. Same model, same rules, different harness, which let us compare the two on similar tasks.
Guardrails: a guard that blocks, not a prompt that asks
A rule in a prompt is a request. We wanted the dangerous classes of
action to be impossible by accident, so we wrote xo-guard: the
team’s rules as code that inspects every tool call before it runs and
blocks the ones that match. In Pi it is an extension on the
tool_call hook; in Claude Code the same rule file runs as a
PreToolUse hook. It blocks:
- killing processes by name or pattern, several PIDs at once, or services;
- any
git pushoutside the push queue, and anything that publishes; - owner-only actions (CI runs, releases, seed uploads, infrastructure and signing);
- commits, checkouts or edits in the main checkout;
git stash(shared across worktrees, it once applied another agent’s work);- writes to the team’s tools, locks and settings;
- builds and tests not run through the memory-limited runner;
- any read of the files that hold private values;
- recursive deletes of unguarded variables, globs or home.
It checks commands inside quotes too (bash -c "...", eval,
$(...)), fails closed when a rule errors, redacts private values
from tool output, and logs every block for review. In Claude Code a
second layer of permission deny rules covers the same ground.
The guard’s own notes are blunt about its limit: it matches text, so it stops accidents and habits, not an adversary. That is why the next two layers exist.
Isolation: its own worktree, and it never pushes
Each DeepSeek session lived in its own git worktree with the guard’s settings, started by a launcher script that refuses to run without the worktree, the installed hook and the gateway setting, or if the file with private values is present. It never had push rights. Its work ended as a local commit; a Claude session reviewed the diff and the gate log, and only then went it through the push queue.
One early lesson made the launcher strict: a session started in the main checkout instead of its worktree loaded that checkout’s private file and skipped the worktree’s guard settings, so a few identifiers (an account number, bucket names, an allowlist address) went to the gateway. None were credentials; the owner reviewed them the same day. After that, the launcher refuses to start anywhere else.
An overseer, then the lead
At first the lead never talked to DeepSeek directly. A separate Claude session, the overseer, gave it tasks, watched its terminal for unsafe or wasteful actions, reviewed every change, and reported to the lead. The overseer’s log records each task’s corrections, blocks and workarounds.
The first tasks were small std ports with Go as the oracle: a
differential test corpus for string search, Str.fields, Str.count,
Str.equal_fold. What the overseer saw:
- A guard workaround, once. After its first task, DeepSeek was blocked removing its own worktree from the main checkout, and reran the command from another directory the guard did not cover. Harmless in effect, but exactly the habit the guard exists to stop. Two fixes followed: guards now judge the target of an action, not where it runs from, and every block message says to stop and report. From the next block on it reported and waited, every time.
- False positives, reported, not routed around. Reading a script
with
sedwas blocked as a “heavy run”; it asked whether to use the read tool instead of trying another way. - Pi against Claude Code. On similar tasks the Claude Code runs needed fewer corrections and took less time. The clearest difference was messaging: Pi delivers messages only between turns, and DeepSeek runs long turns, so a correction could wait about ten minutes. In Claude Code, where it reports and ends its turn, messages got through within a few minutes.
By the afternoon of 2026-10-23 the overseer handed DeepSeek’s tasking to the lead, and a second DeepSeek session joined in its own worktree. From then on the lead managed two DeepSeek sessions the same way it manages the Claude agents: assign, review, push.
What two DeepSeek sessions did in a weekend
They worked in std and test tooling, the lane we judged safest for untrusted code, while the Claude agents kept the compiler, runtime and release work. Each change was measured against its parent with the team’s A/B tool before it was claimed, and several were negative results recorded as such:
- Faster std code. Case conversion folds ASCII while it copies
(bench/results/casefold-copy-2026-10-24.md: lowercase wall 0.934 of
before); a mixed-text case path decodes two-byte runes inline
(case-mixed-2026-10-24.md: 0.876); Bytes
trim_prefixandtrim_suffixgot a short-compare helper (bytes-affix-2026-10-24.md: from 1.40 to 1.15 of Go’s instructions). - Measured “no”. A port of Go’s short-haystack search lost to the existing word scan (strfind-2026-10-24.md); faster decoding would not help JSON or regexp (decode-2026-10-24.md). Both are written up so nobody repeats them.
- Test coverage. Corpora ported from Go’s own tests for numbers, time, crypto vectors, JSON encode and decode, tar and zip; a tool that checks every benchmark against its Go twin (it found two twins that had never matched); and a repository test that fails when a public std function has no test calling it.
- A cross-backend fuzzer. It runs the same seeded inputs through std on all three backends and compares the output. Its first runs found two real differences: an overflowing JSON number parsed as 0 on the native backend, and a tar entry name containing NUL accepted natively but refused by the Go backend. Both are fixed.
What it cost, and what we learned
The real cost was not tokens, it was the lead’s time. Every DeepSeek change needed a review and a cherry-pick, and two sessions editing the same std files and change logs produced merge conflicts that took 5 to 30 minutes each by hand. The fix was a small script that pushes a reviewed commit in a fresh worktree and keeps both sides of append-only conflicts in change logs and READMEs automatically; after it, those conflicts stopped costing anything.
The safety layers each caught something. The guard stopped one workaround and logged its false positives. The push queue’s revert check caught a commit that, squashed onto an old base, would have undone five files other agents had changed since. Twice the gateway’s safety classifier became unavailable and every tool call was refused for about an hour; both times the agent waited instead of looking for another route, and compacting the session cleared it.
Compared with the Claude agents, the DeepSeek sessions were slower per turn, misread an assignment now and then, and were less reliable on compiler work. They were also careful, reported negative results plainly, discarded their own flawed measurements, and refused to quote numbers they had not saved. The answer to the owner’s question is yes, with a structure around it: a guard that blocks, a worktree it cannot leave, no push rights, a reviewer for every change, and a lane of work where a wrong change is cheap to catch.