Subsystem Search

Part 2 of 6 Working through your agent

A standing instruction

By the end of this chapter the rebuild has a beta and a launch on its roadmap, and your agent is working toward them without you: it asks what can be started, takes one thing so that nothing else takes it too, does it, writes down what happened, and hands it back. Then it does it again.

Everything up to here has had you in the loop, saying what to file and what you concluded. This is the chapter where the list itself becomes the instruction.

The work so far

Both repositories as An agent per project, handing work over left them: fourteen items, two projects, two agents, and the checkout work done.

$ uv tool install subroutine
Resolved … packages in …
Prepared … packages in …
Installed … packages in …
…
Installed 2 executables: subr, subroutine
$ subroutine init
Ready. Try: subroutine add "something to do"
$ subroutine project create web "Website rebuild"
Created web - Website rebuild
…
$ subroutine agent create claude --profile worker --project web --store
Created service account claude, with the contributor role.

  sr_…
…
$ subroutine add "Write the copy for the home page +web"
Added: Write the copy for the home page  (read +web)
…
$ subroutine add "Choose a typeface for the headings by Tuesday +web"
Added: Choose a typeface for the headings  (due Tue 6 Oct)  (read +web)
…
$ subroutine add "Agree the new navigation +web"
Added: Agree the new navigation  (read +web)
…
$ subroutine add "Build the new navigation +web ~1d"
Added: Build the new navigation  (read +web ~1d)
…
$ subroutine add 'Audit the third-party scripts +web !3/2'
Added: Audit the third-party scripts  (read +web !3/2)
…
$ subroutine link 3 blocks 4
Blocks: Build the new navigation
…
$ subroutine done 3
Done: Agree the new navigation
…
$ subroutine document create "Navigation stays one level deep" --type decision --project web --body "One level, and the four second-level links go in the footer."
Wrote: Navigation stays one level deep
…
$ subroutine link 6 documents 4
Documents: Build the new navigation
…
$ subroutine document create "Do not generate the navigation from the sitemap" --type dead_end --project web --body "It goes stale the moment a page moves, and the build cannot tell."
Wrote: Do not generate the navigation from the sitemap
…
$ subroutine link 7 documents 4
Documents: Build the new navigation
…
$ subroutine document create "The analytics tag loads the consent banner a second time" --type finding --project web --body "Both are injected by the tag manager."
Wrote: The analytics tag loads the consent banner a second time
…
$ subroutine link 8 documents 5
Documents: Audit the third-party scripts
…
$ subroutine add "Write the dropdown markup +web" --under 4
Added: Write the dropdown markup  (read +web)
…
$ subroutine add "Check the navigation on a phone +web" --under 4
Added: Check the navigation on a phone  (read +web)
…
$ subroutine document create "The dropdown opens on click, not on hover" --type decision --project web --body "Hover has no equivalent on a touchscreen."
Wrote: The dropdown opens on click, not on hover
…
$ subroutine link 11 documents 9
Documents: Write the dropdown markup
…
$ subroutine add "Must the dropdown work without JavaScript? +web @laurence" --type question
Added: Must the dropdown work without JavaScript?  (read +web @laurence)
…
$ subroutine link 12 blocks 9
Blocks: Write the dropdown markup
…
$ subroutine comment 12 "No. The navigation is the one place we already require JavaScript."
Noted on: Must the dropdown work without JavaScript?
…
$ subroutine done 12
Done: Must the dropdown work without JavaScript?
…
$ subroutine project create api "Checkout API"
Created api - Checkout API
…
$ subroutine agent create apiclaude --profile collaborator --project api --project web --write api
Created service account apiclaude, with the contributor role.

  sr_…
…
$ subroutine agent create claude --profile collaborator --project web --project api --write web --write api --store

  sr_…
…
$ subroutine document create "Prices are stored and returned excluding VAT, and each client adds it" --type decision --project api --body "It is net, and each client adds VAT when it displays a price."
Wrote: Prices are stored and returned excluding VAT, and each client adds it
…
$ subroutine add "Say in the basket response that the total excludes VAT +api"
Added: Say in the basket response that the total excludes VAT  (read +api)
…
$ subroutine link 13 documents 14
Documents: Say in the basket response that the total excludes VAT
…
$ subroutine done 14
Done: Say in the basket response that the total excludes VAT
…
{
  "mcpServers": {
    "subroutine": {
      "command": "subroutine",
      "args": ["mcp"]
    }
  }
}

What the work counts toward

Before you leave it alone, say what the work is for. The rebuild goes in front of the team as a beta first, and live after that. Ask your agent to hold both as milestones:

subroutine_add(text="Beta for the team by 16 October +web", type="milestone")
Added #15  milestone  due 2026-10-16  Beta for the team  (read +web)
subroutine_add(text="New site live by 30 October +web", type="milestone")
Added #16  milestone  due 2026-10-30  New site live  (read +web)

A milestone is what work counts toward, never work itself. Its date is a deadline, written with by as any other is, and it can go overdue. What counts toward it is an includes link, from the milestone to the work: the beta needs the new navigation's markup and a check on a phone, and going live needs the copy, the typeface and the script audit.

subroutine_link(ref=15, type="includes", other=[9, 10])
#15 Includes #9  Write the dropdown markup
#15 Includes #10  Check the navigation on a phone
subroutine_link(ref=16, type="includes", other=[1, 2, 5])
#16 Includes #1  Write the copy for the home page
#16 Includes #2  Choose a typeface for the headings
#16 Includes #5  Audit the third-party scripts

And one comes before the other:

subroutine_link(ref=15, type="precedes", other=16)
#15 Precedes #16  New site live

precedes is a plan, and blocks is a fact. Only blocks holds anything up, and it is what the list of what can be started reads, so the work a milestone counts is still offered as ready, and the launch planned after the beta is not waiting on it. Read in date order, the milestones are the rebuild's roadmap:

subroutine_list(filter={"type.eq": "milestone"}, order="due_at")
#15  milestone  0 of 2 included done  due 2026-10-16  Beta for the team
#16  milestone  0 of 3 included done  due 2026-10-30  New site live

Each row counts how much of what its milestone includes is done. In your own terminal the same roadmap is subroutine list --type milestone --order due_at.

The instruction

It is a paragraph, and this is the whole of it:

Example not checked: it is not run when this site is built.

Work through the website rebuild. Each time round: ask what can be started,
take the most important one, do it, write down what you did on the item, and
finish it. If something needs a decision only Laurence can make, file it as a
question for him and carry on with the next thing. Stop when nothing is
startable.

Where that paragraph lives is your coding agent's business rather than Subroutine's: a project instruction file, a saved prompt, a scheduled run. What matters here is that every sentence in it is a call the agent already knows how to make, and that none of them needed teaching. It has been making them since the first chapter.

Read it again as the five verbs it is: ask, take, do, write down, finish. That is the loop, and the rest of this chapter is one turn of it.

Ask what can be started

subroutine_list(ready=true)
#10  task  Check the navigation on a phone
#9  task  Write the dropdown markup
#5  task  !3/2  Audit the third-party scripts
#2  task  due 2026-10-06  Choose a typeface for the headings
#1  task  Write the copy for the home page

This is the call the whole thing rests on, and it is the one an unattended agent cannot get wrong. It is not a list of everything: it leaves out what is blocked, what is deferred to a later date and what somebody else is holding. Every line in it is something that can genuinely be picked up right now, and neither milestone is among them: nothing offers a milestone as the next thing to start, so an agent working through the list never tries to do one.

An agent given the whole backlog instead will pick something blocked, do half of it, and file a comment explaining why it could not finish. That is the failure this replaces.

Take it, so that nothing else takes it too

subroutine_claim(ref=10)
Claimed #10  Check the navigation on a phone, until 05 Oct …
Still Open to anybody looking - a claim does not say work has begun. When you begin: subroutine_update(ref=10, status="in_progress").

A claim is a lease, not a lock. It has an expiry on the face of it, and that is the design rather than an implementation detail. An unattended agent is exactly the thing that dies halfway: the session is killed, the context ends, the machine reboots. A lock taken by something that no longer exists has to be found and broken by a person. A lease runs out.

Every write to something an agent holds pushes the expiry out, so an agent that is working never has to think about it, and one that stopped stops renewing. Nothing is stranded, and nothing needs an administrator.

The answer also tells it what it has not said. The claim says who has this; the status says work has begun. They are different facts and an agent that has only claimed something has not started it:

subroutine_update(ref=10, status="in_progress")
Changed #10  task  in_progress  claimed by @claude  Check the navigation on a phone  (set status in_progress)
Now at version 2.

Its first line now carries both: in_progress and claimed by @claude. Anybody looking at the list, or the board in the browser, can see the difference between work somebody is on and work somebody is merely holding.

Write down what it did

subroutine_comment(ref=10, body="Checked at 360 and 390 wide. The dropdown opens and closes, and the footer links are reachable.")
Recorded on #10.

This is the sentence that makes the loop worth running. Without it you get a list of things marked done by a machine overnight and no way to tell a real check from a plausible one. With it, the item says what was actually looked at, and you can disagree with it in the morning.

Anything it concluded rather than did belongs in a document, exactly as in Decisions, findings and dead ends. The rule does not change because nobody is watching.

Finish it

subroutine_done(ref=10)
Done: Check the navigation on a phone. The claim on it went back with it.

The claim goes back with the work, so there is no releasing step to forget. That matters most here: the end of an unattended run is a killed process rather than a moment anybody is present for, and an obligation that falls there is one nobody meets.

Then it asks again, and the list is one item shorter.

What you read in the morning

You were asleep. The question is not what is left, which the list answers, but what happened:

subroutine_changes(since=42)
42  05 Oct 09:41  claimed  #10 Check the navigation on a phone
43  05 Oct 09:42  changed status  #10 Check the navigation on a phone
44  05 Oct 09:43  commented  #10 Check the navigation on a phone
45  05 Oct 09:44  changed status  #10 Check the navigation on a phone
46  05 Oct 09:44  released the claim  #10 Check the navigation on a phone
Resume with since=46. This feed covers tasks, projects and documents.

That is the loop, written down by the thing that ran it: took it, started it, said what it did, finished it, let it go. Five lines for one piece of work, and the last one happened without being asked for, because finishing hands the claim back.

subroutine changes says what moved. It is cheap, it resumes from a number, and the last line is why: an agent picking up after time away passes that number back as since and is told only what it missed.

It reports that a comment exists without saying a word of what it said. subroutine journal is the other reading of the same events, with the bodies joined on and each actor named, asked for a period rather than from a cursor. Reach for changes to pick up where you left off, and for journal to find out what a night contained. Asking changes to summarise a week tells you that fourteen comments exist and nothing about any of them.

And the roadmap has moved, without anybody touching it:

subroutine_list(filter={"type.eq": "milestone"}, order="due_at")
#15  milestone  1 of 2 included done  due 2026-10-16  Beta for the team
#16  milestone  0 of 3 included done  due 2026-10-30  New site live

The beta counts one of its two done, because the phone check it includes is what the agent finished overnight. Nothing completes a milestone by itself. When everything it includes is done its row says so, and whether it has been reached is still yours to say, with subroutine done.

That is Part 2. Your agent files work and knows what can be started, writes down what it concluded where the next one will meet it, raises what it cannot settle rather than guessing, hands work to another agent with the reasoning attached, and works through a list unattended while the roadmap counts what it finished. All of it is one instance, and none of it needed you to be at a terminal.

Which raises the question the next part answers: what does any of this look like when you are not?