Who Gets Hurt If It Dies — a Week of Reworking Structure

Splitting the trading bot into trader/desk processes, pre-commit document hygiene for a zero-lines-of-code rewrite project, and the full teardown of a 49-day-old launchd resident setup

The trading bot had grown to 49 scheduler jobs, and a one-line screen fix restarted the trading daemon that touches the account along with it. On top of that came a pre-commit inspection of the zero-lines-of-code rewrite project that turns this experience into open source, and the teardown of a launchd resident setup that had been running on the Mac mini for 49 days. A week spent on structure rather than code.

What I set out to do

This week I worked on structure more than code. Three events overlapped.

First, the trading bot had grown. 49 scheduler jobs and 37 tables, but only 6 jobs touch the account. To turn on the goal of “20 trading days without a deploy,” I first had to split a structure where every one-line screen fix restarted the trading daemon too (66 times in August alone).

Second, the rewrite project that turns the trading bot’s experience into open source. Zero lines of code — everything under src is .gitkeep, and all there is are 12 design documents and 19 decision-log entries. It was renamed, its history thrown away, and git init redone, so zero commits too. The job was to inspect the documents that would become the first commit.

The third wasn’t planned. On Saturday afternoon I decided to tear down the launchd resident setup that had been running on the Mac mini for 49 days.

How I asked

The split started with an ADR. While revising the existing “single daemon” ADR, I had the measured coupling put into a table — a screen commit restarts the trading daemon, and if one observability-only migration fails, the trading daemon doesn’t come up. The criterion was one sentence: who gets hurt if it dies. If it touches the account, orders, or loss limits, it’s trader; otherwise desk. One repo, one image, only the role differs via environment variable. trader is a deploy-freeze target with no migrations and no HTTP port; desk can deploy daily without ever closing the trading door.

The rewrite project got a pre-commit audit. I had it check that day whether the new name was free on PyPI and npm and record that as decision rationale, and had an agent audit run through the lens of “things that must not remain in a public repo.”

The teardown instruction was one line.

Delete everything that auto-launches, and unload and delete the launchctl entries too

Where it broke

A ghost job surfaced during the split. The early-morning restart job hadn’t run once in 35 days. sys.exit(0) was being called inside a worker thread, and the job runner was swallowing the SystemExit. There had been no problems the whole time, and in an environment where every deploy brings up a fresh container, the state reset itself was meaningless. Instead of fixing it, I removed it.

On the rewrite side, the problem was one line in the decision log. When I flagged that it shouldn’t go into a commit, Claude broke down what that one line would reveal in public history — that the author has an employment relationship, and even that they maintain a private policy document separating personal projects from the company. I had it neutrally rewritten. “boss” 27 times became “the author,” local paths moved to git-untracked files, zero traces of the old name. Running the audit once more turned up 50 more instances of the worldview from the days when a paid web service was the premise. Once the documents were clean, I asked.

Could you build a finance-specialized AI agent from these documents as they stand?

I gave three agents only these documents and had them simulate implementing the first milestone. The verdict was “can’t be built” — the “what and why” was top-tier, but the “how” was empty, so zero of the five stages could be completed.

In the teardown, Claude couldn’t pull the trigger. launchctl was blocked by the permission classifier, so it could only touch the plist files. Instead it came back with the scope split into a table first — only the 14 it had created itself (12 repo residents + monitor + log rotate), excluding other people’s plists and the Telegram bot and report server. It came with a warning too: uncommitted work in attached remote sessions would be left as orphaned worktrees.

Result

The split finished on Sunday. 23 trader jobs, 27 desk jobs — the original 49 plus 1 duplicated health check. I split the watch paths too and confirmed an observability commit doesn’t restart trader. Now the deploy-freeze count can begin.

The rewrite project continued on Saturday night with a deep dive filling the holes the simulation had pointed out. The first commit will still be documents, not code.

For the teardown, I ran the script Claude wrote myself in the evening. The order was the point — back up the 14 plists, unload first (otherwise KeepAlive keeps reviving them), and even that sequentially at 1-second intervals, then verify nothing remains after deletion. Logs and wrapper scripts were left as room to revive. When standing it up, a human pressed login; when taking it down, a human pressed launchctl. A setup built in 40 minutes came down after 49 days, and 14 plists remained in the backup folder. The youngest of them was the rewrite project’s, added three days earlier.

Lessons