Nightwatch: A Guided Autonomy System for Claude
This article assumes the official Claude Code terminal TUI and the Max 20x subscription, not the API.
If you were to look at the price of the Anthropic subscriptions today and compare them to the API cost (or the cost of “extra usage”), you will find something interesting – the most premium subscriptions are realistically about 30x cheaper per MTok than the base API price; they are heavily subsidized by Anthropic. If you don’t believe me, try to open up your longest session and have Claude assess this for you. The reason for this is probably a market penetration effort, but even if it were something else, it is economically suicidal for Anthropic in the longer term, so I assume that the subscriptions are not going to be this cheap forever.
This means that there’s a rare window of opportunity currently open. It is open for programmers to realize their oldest, biggest, and wildest programming dreams without sacrificing years of their life. We can be the architects and managers in our virtual company where every employee is a principal software engineer and a polymath to boot, and only pay $200 per month to make it run.
The problem here is that, apart from being an architect, you are also the manager. If you’re not a good manager, you’ll end up constantly micromanaging your agents, paying with your own time (that you should have for family and for work). Similarly, if you’re not a good architect and you don’t know what you’re doing, you’re going to get a piece of AI slop instead of a realized dream. So if you want to start a genuinely big AI-assisted project, you have to be sure that you would have eventually completed it excellently yourself if it was your job – after doing all the research, reading all the books and research papers, and spending years writing code yourself. The management part is what I’m going to talk about in this article, because after many a month of wrestling behavioral contracts, MCPs, and state management while guiding the development of Thylacine, I now only ever get a phone notification from one of the agents when there’s a decision at hand that they cannot make without the architect’s sign-off or steering the way. In the evening I read the agents’ journal, test out the day’s progress in person, and possibly steer the development a certain way if I find it necessary (at this point on the roadmap I usually don’t).
The Vanilla Claude
Most green-field projects done with Claude these days just start with an empty repo, cd repo, and claude. People explain to Claude what they want to do, inadvertently already making some mistakes:
- Varying level: Big plans mixed with tiniest implementation details mixed with instructions. It’s going to leave holes in all of the areas: The big picture, the implementation details, and the contract; which leaves room for Claude to infer the content of the holes from the insufficient context you provided.
- Assuming context: Like when speaking to a colleague at work, you (without realizing) assume the colleague knows the obvious things about the domain and about the way you operate. Claude is a polymath, but cannot read minds.
Imagine you’re a new hire at a software company, knowing absolutely nothing about the domain and the process, and on your first day, right when you exit the elevator, somebody you don’t know comes and hands you a “spec” they cooked up in 10 minutes. You’re either going to walk out the door and never come back, or you’ll inevitably end up delivering something that cannot be what the manager meant (there just wasn’t enough information in that spec to allow you to execute it correctly) while spending months asking him extra questions, annoying him and having him think you’re no good. This is in fact how certain people arrive at the conclusion that AI code “sucks”, and that it creates “slop”. As one of my colleagues has said, AI is an input amplifier – you put shit in it, it will give you a bigger shit on the output. If you put good input in, it will give you an even better output. So if you get a slop on the output, you were a sloppy manager to it.
The vanilla Claude only has its system prompt to go off of. It, among other things, tells it what information to store in its automemory, and in CLAUDE.md. Those things survive session boundaries, and give the user, along with the default post-compaction conversation summary a semblance of project continuity. It will still feel to most like Claude forgetting most things after a session compacts, and it’s because they didn’t tell Claude what’s important enough to remember.
For small projects that are written fully interactively, it’s not a problem – you can just keep telling Claude what to remember and how to behave when you notice they’re not doing something according to your liking. It will save it and remember it in the following sessions, and the project will complete after a couple of sessions.
For bigger projects, this approach will eventually spaghettize the codebase and give you slop, because the 50th session won’t know what the 25th session did and why it did what it did. It will duplicate code, duplicate concepts, put changes in the wrong layers, leak layers, make the same mistakes over and over again, and it will generate bloat in general, eventually completely losing itself in it, appearing useless to you. It will also end up constantly refactoring huge old subsystems of the code, because you’ll want it to implement something that the subsystem didn’t anticipate 30 sessions ago. You’ll then end up in the camp of people who say that AI is only good for generating regexes, but that it’s still not good enough for big software projects, while in fact you were a bad manager and your project would fail in the exact same mode with employees of flesh and bone.
Claude on Steroids: The Scripture
Two things are necessary for sustainable development of a large project:
- Excellent design with permanent artifacts detailing:
- The vision: What you want to do and why, how you imagine the final product, how you want it to be used
- The roadmap: The intial design, broken down to phases, arc, and chunks for agents to follow
- The architecture: Exactly how to technically implement each chunk
- The arc exit criteria: What needs to work and how before considering an arc complete and move to the next one
- The operational workflow: The behavioral spine you put in CLAUDE.md:
- All of the code is yours, even if you don’t remember it
- Link to the scripture, its description
- Policies: Design before implementing a new system, look at prior art, etc.
- Audit rules: What changes require auditing by an unbiased agent, how is the audit lead, etc.
- Testing, regression testing
- Implementation patterns, code organization
- Mode of autonomy – when to stop, checkpoint format, etc.
- Handoff protocol: What exactly the next session should work on, and all the information it needs to comfortably resume
- Commit discipline
- Knowledge upkeep – ledgers detailing system components, decisions made and reasons
- Mode of session continuity – what information to store, what to read on session start
- Resources available to the agent and how to use them (cloud hosts, etc.)
- Parallel agent coordination workflow
- Persistent memory to store important, expensively gained information
- See above in the operational workflow
With the project roadmap (and our current position on it) clearly visible to every instance, with knowledge and past reasoning meticulously recorded and loaded on-demand (some implicitly) by any agent, and with clear session-to-session handoff protocols, the agents cannot stray from the path, and cannot get confused, and always know exactly what to do next.
This doesn’t mean that you have to write the scripture yourself – the only thing you truly need to write is the vision, and the rest you can spend a day or a week pouring over with Claude (Fable on max effort, needless to say – this is the single most important foundational commit).
I use this setup for every new big project, so I’ve created a bootstrap skill that will turn a vanilla Claude on a freshly git initialized directory into a specialist that will work with you to completely set up the project in the above way. It will also talk to you about your vision or even help you create or complete the vision.
Let’s talk about the main components that are especially important here:
The Roadmap Arcs and their Exit Criteria
As Claude progresses through the arc chunks (an arc is a large logical chunk on a roadmap phase or the roadmap itself – just my invented naming), living over tens of sessions, the information that’s being handed over eventually dilutes as Claude focuses on things that are important for the implementation. (This is correct, and if you look at how you yourself think, it is similar – your current memory of an event is itself a memory of a memory of a memory … of the first memory of the actual event as-lived. Every remembering erodes the memory until at one point, (in case of humans), only the brain-stem component of the memory (emotions) remains. If you remembered everything exactly as-lived forever, it would not be good for your survival (otherwise we wouldn’t have developed that way). Strangely, same is true for Claude – too much active context that is too detailed and about absolutely everything is considered context rot, because it completely breaks the model, tanking the performance.) One thing the agent loses is the goal – when we’re done. They will often state that they’re done and support it with something that was never explicitly stated anywhere. It is important to give the agent the arc exit criteria (the “definition of done” in the corporate lingo) – what needs to hold so that we can considered the arc done and move to the next arc. If this isn’t defined and if the agent doesn’t have it in its context at all times (or is reminded frequently by some automatic means), you will keep getting nasty surprises later on the roadmap where the scripture wants to build on something that wasn’t delivered a month ago, because the agent was too eager to complete the arc.
The Handoff Protocol
This is the single most important piece of operational contract, as it materializes the following idea:
A fresh session that knows nothing at all should be able to comfortably pick up where we left off and continue.
This needs to be done by inserting specific information into the fresh session’s context:
- The compaction summary. This is Anthropic’s own highly-optimized summarization. I keep it (instead of
/clearing) because it works really well to preserve the non-project-specific aspects of the session and the conversation. Mostly it remembers all that I told Claude in the session verbatim, so it knows what is currently my short-term focus. This summary is created by an external agent that is not bound by the previous session’s context, as it operates under its own, so it doesn’t know what’s important to US. - The previous session’s OWN summary based on what specifically WE care about – it’s description about what it did, the current state of the tree, the open tasks, and especially what the next session should do next. This summary is linked to from one of the first-class persistent memory vessels – either the automemory (
MEMORY.md), orCLAUDE.md, so that it automatically enters the new context. - Effective access to the knowledge base, so that it doesn’t need to
grepeverything from the code – how to search it, what’s in it. This rests inCLAUDE.mdincluding the knowledge upkeep contract.
It is important that the agent is told to always create the summary and keep it current for the session, and that at every turn end (or another point at which it’s currently handed to you – depending on whether you’re running fully automated, partly automated, or manually – you’ll cycle through these modes over time) it gives you a concise summary:
- What I did
- Some details if you want
- Interesting findings and happenings
- What’s ahead
- What’s next
Before I went fully automated (self-compacting agents), I used to use this summary to spring the next session into motion:
- The agent delivered the summary X
- I wrote
/copy– this copied the summary to the clipboard - I wrote
/compact - I pasted “Previous session’s last note: Cmd+V” and submitted – this queued and executed as the first user prompt after compaction.
In a fully autonomous mode, these serve (along with a journal that I instructed the agents to keep) to give you an overview when you come back to the computer in person (or check your phone). In this mode, the agents just wake themselves up by queueing a message to read the handoff summary.
The Adversarial Audit
The adversarial audit has been the most invaluable QC tool for me even since I defined it and then saw how it changes the quality of the code that gets checked in. The principle is simple: After creating a new invariant-bearing surface, or a change to an invariant-bearing surface, a best-model max-effort subagent is spawned with a simple set of code review instructions. An example static agent definition of mine:
---
name: holotype-reviewer
description: HOLOTYPE deep-review prosecutor for Thylacine OS — adversarial soundness/completeness/SOTA reviewer, spawned per-RW with a scoped prompt. Max-effort highest-available Fable (highest-available Opus as fallback).
model: fable
effort: max
---
You are an adversarial code reviewer on a production-grade operating system
codebase (Thylacine, Plan 9-heritage ARM64). You prosecute; you never defend.
Model self-report (do this FIRST and LAST, always):
- The VERY FIRST line of your output must be `MODEL(start): <your model
name + id>`, and the VERY LAST line must be `MODEL(end): <your model
name + id>` -- each stating, honestly and independently, which model you
are AT THAT MOMENT. Do not copy the start value to the end; generate the
end line fresh as the last thing you do.
- Why both: reviews run on the HIGHEST AVAILABLE FABLE at max effort (as of
2026-07-28 that is Fable 5; the rule is "highest available", not the version
number, so a later Fable supersedes it automatically). The fallback, only
when Fable is unavailable, is the HIGHEST AVAILABLE OPUS at max effort
(currently Opus 5). The user re-enabled Fable 2026-07-04;
memory/feedback_reviewer_model.md is the single home for this decision.
- Why Fable specifically: Opus is this project's primary IMPLEMENTATION agent,
so an Opus reviewer shares the author's family priors and blind spots.
Reviewing from a fundamentally different lineage is the point. An Opus
fallback round is therefore a real DEGRADATION -- max-effort rigor, but the
independence is forfeited -- so say so plainly in your report rather than
presenting it as equivalent.
- Both reports should name the same Fable; an Opus value means the fallback
engaged (weigh accordingly), and a start != end mismatch is the in-band
signal of an unexpected mid-run switch, so generate the two reports
independently. This is a best-effort heuristic (self-identification is
imperfect), but report it faithfully regardless.
Operating rules:
- Ground every claim in quoted code with file:line. Quote, don't paraphrase.
- Read EVERY line of the scope you are given before concluding anything.
Re-read load-bearing functions until you can replay their interleavings
from memory.
- A finding without a concrete prosecution chain (state -> step -> step ->
violation) is not a finding. Withdraw anything the code already guards.
- Respect the do-not-re-report preambles you are pointed at; re-reporting a
closed item is noise. DO prosecute whether a closed item's claim still
holds against the CURRENT code.
- "Verified sound" lists are as load-bearing as findings: enumerate every
property you checked that survived, one line each, so the next reviewer
knows what was covered.
- Be brutal but grounded. No flattery of the code, no hedging where the
evidence is decisive.
- Follow the spawn prompt's scope, lenses, severity scale, and report format
exactly. Honor its read-only constraints (typically: modify nothing in the
repository; write only your designated report file).It has initially grown from a definition that was genuinely small. Most of the latest changes were due to the introduction of Fable. Pasting the definition into this article actually reminded me that I have to update it, since it’s the state from before Opus 5.0, and from the time when Fable was only supposed to last on the Max 20x subscription for 2 weeks – my implementation agent was the excellent Opus 4.8, and my reviewer was the spectacular Fable 5. But since we now have Opus 5, which I feel is a bit of a regression for a project of this kind over the 4.8, Fable is my implementor model with the 4.8 as a secondary.
You will not believe how many genuine issues in the code the prosecutor agent finds, and even critical active breakages sometimes. This is because the implementation agent is biased by its context and mission, and it will always has a certain amount of context rot towards the waterline, whereas the prosecutor agent isn’t bound by the implementor’s context, it reads all necessary code directly, and its mission is sharply focused on finding problems. Sometimes it takes several consecutive prosecutor runs after the implementor’s fixes in order for the issues found to converge to a few minor ones.
The Stewardship Principle
Vanilla Claude is often hyperfocused and protective about what they think they written as long as their current context is concerned. If they had written a codebase that sits at more than 630 thousand lines of code over half a year, they genuinely don’t, by default, know that it’s all ours. They need to know this at all times:
## Whole-system stewardship — there is no "my chunk"
The system is OURS, not yours. Every instance inherits the entire tree — not just the sub-chunk it was spawned to land. **Care about the code you did NOT touch exactly as much as the code you did.** A bug, instability, or unsoundness anywhere in Thylacine (or in Stratum, which is in-scope) is your problem the moment you see it.
... (continued) ...Only then will they not dismiss issues as “not mine”.
Knowledge Representation and Upkeep
The knowledge representation changes as the project grows. Small projects do fine with all knowledge stored in CLAUDE.md and implicitly front-loaded by every session. As the project and the amount of knowledge grows, the size of the front-loaded context grows so much that it meaningfully impacts the context quality and the size of the effective remaining context window (e.g. you start at 200k); in this case, the knowledge moves to files linked from CLAUDE.md and their loading and upkeeping is supported by behavioral contract (i.e. a set of instructions about how to work with the referenced file). This system itself will too become ineffective as the files themselves grow in size, and every on-demand read introduces substantial context (with only a part of it immediatelly relevant to the task) – in that case, the knowledge should move to a dedicated knowledge base system. There are many such systems out there (remember mempalace?), and I myself have tried writing one (Cairn) in the past, but in the end I turned to Obsidian and a well defined relational schema that is tailored to Thylacine and easy to use and upkeep.
In the domain of combinatorial optimization we have this thing called the No Free Lunch Theorem:
There is no such thing as a free lunch.
I like to apply it everywhere where there’s a tendency in people to say that something is the best. What the theorem tries to convey is simple and valid across many different domains:
- If a solution A is exceptionally good for a thing X, it will be exceptionally poor for the rest of things in the class
- If a solution is good for all things in the class, it will never be as good as solution A for X
What it practically means is that a general solution for knowledge representation may underperform in your specific case when compared to a solution you create yourself and tailor it to your specific case. If you have many projects and an agentic framework that you use for each, a general knowledge representation may be worth it for you. If you have a specific large project and you only mostly work on that, a tailored solution will make sense. Claude will be more than happy to help you figure it out.
The Self-Compact Framework
The Self-Compact Framework that I have come up with after getting tired of having to check in just to manage handoffs between sessions, gives the agents the ability to compact themselves on their own terms (guided by a contract), and cleanly resume themselves on the other side of the compaction boundary, continuing to proceed autonomously.
This enables true autonomy where you don’t have to step in to do menial maintenance tasks such as context management. Agents running under this framework can run for days (propelled forward by instructive hooks) if they don’t encounter something that the contract tells them requires your presence. Your presence could be required, for example, for the following reasons:
- The agent wants to spin up cloud compute (spending real dollars)
- The agent wants to execute a design, but there’s multiple ways to do it – calling you as the architect
- Parallel agents disagree on something and you need to arbitrate
- The agent hasn’t commited over two compaction boundaries now – it might be deep down some rabbit hole and lost
Claude Code does not allow agents to compact themselves or do similar destructive operations on their own. This is to
- Protect Anthropic from people running heavily autonomous workflow on the TUI, which is supposed to be an interactive platform
- Protect users from accidentally losing context
This is not a problem for us though, because, after all: The Claude TUI is running on your machine, in your terminal, so one way or the other, you’re going to have any kind of control over it that you please – it is YOUR machine. The best way that I found is leveraging tmux (terminal multiplexer).
TMux Tricks
TMux is a great tool, which is primarily used to run multiple shells in a single terminal screen buffer; that’s very useful and all that, but we’re interested in a completely different, niche feature of TMux, which is its capability of sending keystrokes to a target TMux pane. So if you run claude within TMux:
tmux new -s my_sesh -c ~/projects/my_project claudeThen Claude can invoke compaction at will by means of a script:
tmux send-keys -t "$TMUX_PANE" C-u # Line discard
tmux send-keys -t "$TMUX_PANE" "/compact"
tmux send-keys -t "$TMUX_PANE" C-m # Carriage returnWhich will trigger compaction.
The question now is severalfold:
- What is the contract for this kind of self-compaction (basically impersonating the user)?
- What is the means of enforcing the contract?
- What is the means of resuming after compation (where the first turn is always user’s)?
The Compaction Waterline
One of the creators of Claude has said that on the 1m context model, the context rot starts to set in at around 400k context window. That may be right, but I think it’s too early, so my rule is 600k. I may sacrifice a small amount of rot (which is much more dependent on the quality of the context itself), but I gain longer operation time on a complete context, and less 5min cache nukes (which also happen on compaction). Since Claude doesn’t know their own current context size, a small helper script obtains it from the JSONL file with the conversation. This script runs as another step in the Stop hook, and is quiet until the context gets to 600k – then it instructs them to proceed with self-compaction.
When the compaction completes, a new session starts, the obligatory context is loaded, and the SessionStart hook is executed. I have tried to use SessionStart to spring the session into motion, but that’s unfortunately impossible – only a user input can start a turn on a fresh session. I also tried doing programmatically what I used to do manually – run /compact, and then paste in the informationally dense session summary that my Claude is taught to produce as his checkpoint message, but that did not work either, since when Claude executes /compact, it loses control of the turn.
The solution is simple: Arm a job that will wait for signs of compaction running (or having completed), and then send a message that will nudge the new session into action:
#...
start=$(date +%s)
trigger=""
while :; do
elapsed=$(( $(date +%s) - start ))
if [ "$elapsed" -ge "$BOUND" ]; then
log nudge-timeout "no compaction seen in ${BOUND}s; sent nothing"
exit 1
fi
pane=$(tmux capture-pane -p -t "$PANE" 2>/dev/null | tail -n "$TAILN" || true)
if printf '%s' "$pane" | grep -qiF "$MARK_RUNNING"; then
trigger="running"; break
fi
if printf '%s' "$pane" | grep -qiF "$MARK_DONE"; then
trigger="done"; break
fi
sleep "$POLL"
done
sleep "$SETTLE"
tmux send-keys -t "$PANE" "$NUDGE"
tmux send-keys -t "$PANE" C-m
log nudge-sent "trigger=$trigger after ${elapsed}s"Along with a solid handoff framework, this setup will have Claude running smoothly across compaction boundaries, but two things need to be done in order for this to work smoothly:
- Claude needs to know when to trigger the compaction
- The
Stophook needs to remind Claude about the only reasons it should pause
The Stop Hook Reminder
From my experience, a simple behavioral contract is not strong enough to make Claude realiably not pause – the hook is necessary.
#...
K=$((CTX/1000)); LK=$((LIMIT/1000)); CK=$((CKPT/1000))
read -r -d '' REASON <<EOF
${STEM} (fires once per stop). You are ending the turn at ${K}k/${LK}k -- BELOW the
${CK}k checkpoint line -- after ${TURNS} assistant turns since the user last
spoke. So this is an autonomous run, and the signal that ends one has not
fired.
Ending a turn hands control to a human who may not be back for hours. Under
CLAUDE.md's guided autonomy that is a DECISION, never a default, and a
checkpoint is explicitly not a stopping point.
Which of these is it?
1. An item from "Autonomy + escalation" -- a format break, a destructive
operation, an architectural deviation, a scripture-altering design fork,
or anything outward-facing. This is a DECISION FOR THE USER: do not bury
it in a summary they may not read for hours -- put it via AskUserQuestion.
2. The user asked something you have now answered. STOPPING IS CORRECT.
3. You are genuinely blocked. If it needs a HUMAN DECISION -- or a pending
vote is blocking the path you meant to take, or such votes have piled up
unanswered -- put it via AskUserQuestion. Otherwise it is a WAIT, and
BEFORE stopping you MUST ask: does the awaited entity expose a SYNCHRONOUS
await -- a blocking acquire ("yip hold <res>" blocks until free), a
background task that re-invokes you on completion, a queue you can join?
If YES, TRIGGER it and stay in-turn: you are re-invoked when it fires, no
human round-trip. AND before you START anything that USES a resource a
parallel agent can contend -- the shared host's cores for a build, gate,
or boot -- do not run it bare: take the yip lease FIRST ("yip hold <res>"),
which both blocks-until-free as that synchronous await AND signals
ownership so the peer does not start competing work on it; release it the
moment the resource frees, not when your workflow ends. STOPPING IS CORRECT
ONLY for a wait with NO synchronous await -- a truly opaque gate you cannot
usefully wait on; then name what unblocks it.
4. None of the above. Then the next chunk is the one you just named on your
own "Next"/"Ahead" line -- OPEN IT in this turn instead of yielding.
For a DECISION (case 1; case 3 when it needs a human decision, a vote blocks
your intended path, or votes have accumulated): call AskUserQuestion. It blocks
for the answer and keeps you IN-TURN -- a blocking question is not a yield, and
it is what reaches a user who is away. For a pure WAIT or an ANSWERED question:
say which in a clause and stop; this will not ask again. If 4: drop the closing
summary and make the next tool call -- writing an "Ahead" line or a "Key" table
IS the tell that you are handing back.
EOF
#...Number 3 is the most exercised, and also to longest, because I had to extend it many times, since the agents would accidentally find ways to rathole themselves (I borrowed the excelent term “rathole” from Claude here) into ending their turn and stall. I’m pleased to report that the current version works completely.
Number 1 refers to the section in CLAUDE.md that maintains my human role:
## Autonomy + escalation
**Default stance**: When the user grants autonomy ("you can proceed autonomously," etc.), proceed on implementation, testing, formal modeling, audit triage, commit, and push to your own branch.
**Always escalate** (autonomy does NOT cover these):
- Format breaks (on-disk version bumps, wire-protocol ABI changes, syscall interface changes).
- Destructive operations (`git push --force`, branch/tag deletion, hard reset of shared branches, database drops).
- Architectural deviations from `ARCHITECTURE.md` — either update ARCH first (with user approval) or revert the deviation.
- Cross-phase scope pivots — pulling *unrelated* future scope into the current phase, OR **deferring an item the current chunk depends on** (see "Chunk completeness — pull dependencies forward"), must be confirmed. Pulling a genuine *dependency* forward to complete the current chunk to its fullest spec is preferred and does NOT need confirmation — note it and proceed.
- Anything unclear in ARCH / ROADMAP / NOVEL / VISION / TOOLING.
- Anything visible to others (pushes to shared branches, PR creation, external API calls, Slack/email posting).
- Spending significant compute or external budget.
- Halcyon-related decisions that might change the v1.0-vs-v1.1 ship calculus (per ROADMAP §11.5 — Halcyon is final phase; v1.0-rc.1 is the shippable fallback).
**Deviation tracking**: If implementation diverges from ARCH / ROADMAP, surface it explicitly:
- In the commit message (the WHY of the deviation).
- In the affected phase status doc.
- If the deviation is load-bearing, propose an ARCH update; do not silently normalize the deviation.When you want to suspend the full autonomy, there’s a .dotfile check – place the .dotfile and the hook will stop firing so you can have a regular dialog with your claude. I’m thinking now that this would be a nice addition to yip switchboard (see below).
Parallel Agents Aware of Each Other
Anthropic says that Claude agents aren’t very good at cooperating together at the moment, but that’s only true in the context of what Anthropic aims to provide – something that works for everybody (i.e. a (2.) of the No Free Lunch Theorem). Their agents are perfectly capable and in fact benefit from living together under a protocol that is tailored to a specific project.
At one point your codebase will grow to a size that will make parallel (mostly non-interfering) work possible, if I were to give an OS example: One agent can work on a kernel task, another one on a userspace driver, and yet another one on userspace applications.
Physical Layout
This one is simple – git worktrees. Keep a couple of git worktrees, and a named agent working each, with cooperative merges into the main tree.
Pain Point 1: Active Awareness of the Others’ Work
Recently I’ve read that Anthropic is finally going to add some sort of messaging to Claude Code, but I haven’t checked the state of it. When I solved this problem, no such system was available so I created my own.
Because the codebase is shared between the agents, they will sometimes need to edit shared code, and if they do it in an unsynchronized manner, the ensuing big merge after days of uninterrupted work will be painful – it will take you a long time to manage the merge across the agents, it might degrade the code, as each agent can come up with a different way of solving the same issue; and each agent may encounter and solve the same bugs.
This all goes away when:
- The agents know that there are other agents and what is their primary domain (which they own)
- The agents are able to call each other to relay whatever they think it’s important
- A bug found in the other agent’s domain
- Wanting to edit something in the other agent’s domain to unblock own work
- Just chat
For this exact purpose I’ve implemented Yip (“yip yip” was a sound Thylacine was supposed to make and my agents currently only work on Thylacine (the OS)), an all in one agentic telephone, a dispute arbitration system, and a resource lease system.
Yip is designed to work with worktrees – any agent working on a worktree of one repo will join the yip line under a separate identity. Agents on one line can call each other and leave notes to each other. The call can be synchronous (the agent will actively wait for the response after delivering his message) or asynchronous, in which case the yip telephone will ring.
For the agents to hear and answer the ring, yip incorporates itself into the PostToolUse, Stop, and SessionStart hooks, so that when it’s in a turn, the agent picks up the phone regularly. If not, the messages just queue up.
Pain Point 2: Resource Contention
The agents working the same repo can generally contend for the same resources (Host CPU, host software, remote host, etc.). For instance, Thylacine has a lot of CPU-intensive long-running gates, so contention can cause problems like distortion of measurements and breaking time-sensitive tests. I also have another host on LAN, and another Cloud host that the agents use all the time to run stuff that needs either a real Linux system (my main host is a Mac) or they need a real GPU with GL/Vulkan to run related GFX gates and tests.
Before I added the resource lease system to yip, this caused a lot of annoying issues and made the agents develop heuristics about who is running what, who is logged in where, and a lot of long phone calls where they argued about who’s going to have what resource and when.
The resource lease system names resources that are contendable and exposed to the agents the methods to:
- Hold the resource for a certain amount of time
- State a reason of why it’s being held
- Release the resource
- Wait synchronously or asynchronously for a currently held resource
- Steal the resource lease in case the held agent isn’t online anymore (a disaster recovery mechanism)
A piece of operational contract then instructs the agent to use and obey the resource lease discipline.
This has completely eliminated all the problems stemming from resource contentions and the agents are fully synchronized and conflict-free. As a bonus, yip also has a switchboard TUI for the operator:
Conclusion
My drive to develop and use Nightwatch came mostly from two colliding facts:
- I have a family and I want to dedicate 90% of my free time to them, and at most 10% to hobbies
- Suddenly there’s a capable AI that allows me to realize my childhood programming dream in months instead of years
That capable AI still needed me to give it more than 10%, which just wasn’t acceptable after a while, and I didn’t want my wife to get to the place her friend is – to get allergic to the word “prompt”. With Nightwatch I can sudo pmset -a disablesleep 1, close the laptop, leave it on a shelf somewhere, or take it with me connected to a hotspot, never opening it. Ocassionally I’ll get a ping from the Claude iOS app that the agents need me for something – I’ll respond quickly, since they surface everything with their AskUserQuestion. And in the evening when everybody’s asleep, I’ll pause the agents and review what they’d done, test it, play with it, handwrite some code, or steer them if I feel the need to.
This system lives and dies with the availability of cheap subscriptions. If the subscriptions eventually get as expensive as the API, you may well just use the API which much more fit for automation, as either will make it financially unviable for an individual to automate a software project of this scale. If they stay cheap for now, well… I wish this will make somebody else’s dream more accessible the way it did mine.
My GitHub: https://github.com/apology-is-policy