Architecture Notes

Build supervised, run unattended

· 5 min read · Martin Kallenbach

In September an AI safety layer refused to let an agent do my server work while I was away. It was right, as a job that ran unattended the same day showed.

Around one o’clock on 16 September I had to close the laptop mid-build: a mirror on Yavin*, my home server, so my phone and tablet could open the PDFs behind my notes. The last steps needed administrator rights; I asked Claude to hand them to a background agent.

The safety layer in Claude’s tools (a classifier that checks what an agent is about to do) refused. Claude dropped sudo (administrator rights) from the request and was refused again. My notes record both, and no attempt to get round them.

What the refusal was about

The layer refuses privileged work that nobody can see or interrupt. That evening, with me there, the same commands ran one at a time in forty minutes, and the job became a systemd timer (the Linux scheduler) that runs hourly with no agent in it. My rulebook got the rule that day, with my own rider: recognise the shape first, and lead with the plan instead of failing four times.

The job that did run unattended

The same day a script left behind by another Claude session crawled my kDrive, from a temporary folder, with no time limit and twelve parallel threads. Every request came back “too many requests”. In 3 h 10 min it logged 67,251 of those errors, produced a file of two bytes and throttled the drive for every other job, the new mirror included.

Twice Claude blamed the mirror’s first download. Asked if it was sure, it checked properly and found the crawler. Once it was stopped, the drive recovered within about a minute.

What I changed

Two days later a rule joined the short list each Claude session starts with: a job meant to outlive its session runs from a versioned folder, within a time limit and registered with my long-runner watcher, or runs in the foreground. The watcher asks a local model whether each job is still making progress. A hook now blocks the shape of the 16 September job; a sweep finds long-running scripts nobody registered.

Building happens with me there; running is a program on a timer. The hand-off between them is an install.
Building happens with me there; running is a program on a timer. The hand-off between them is an install.

The privilege I had handed out

On 31 August, after a day of root steps handed back to me to paste, I had let every agent session on Yavin* use sudo without asking, unattended ones included, declining three narrower options and overriding the tool’s own warning in writing. The platform, not my configuration, kept privilege out of the unattended lane.

That day I had also said I wanted “a setup where most, if not all, can be done with user credentials and not with admin rights”. On 2 October a short list of named commands went in beside the grant; on 9 October, with me present, the blanket grant was removed.

What runs at night

On Friday 2 October a live session with Claude cleared the 51 tasks overdue or due that day in under half an hour, and that night it became a job. At 23:00 a headless Claude run closes, prepares or re-dates the day’s tasks and sends me a Telegram summary for the morning. It may edit notes and run read-only checks, but not use sudo, delete, commit or push, and it stops after 45 minutes.

A weekday night on the home server: mostly programs, and a few model calls, none of which can use sudo. As of 12 October 2026.
A weekday night on the home server: mostly programs, and a few model calls, none of which can use sudo. As of 12 October 2026.

The pattern

Separate building from running; a program on a timer is not an unsupervised agent. Building takes judgement and privilege, so I am there; running is a program on a timer, or an agent with no privilege, a time limit and a report a person reads. The line I care about is privilege with nobody watching.

Not everything is settled: the hook and the sweep do not reach cloud sessions, the weekly Sunday run has a time limit but no deny list yet, and the 3 h 10 min job was found by a question, not by a system.

Part 9 of twelve in the series on how I built my AI OS. Start with the preface.

Next: Obsidian served me well for a long time, until one limit after another showed me that I no longer have to live with them.

* Host names are changed (here: Yavin, my home server). The real ones come from a different saga.


Appendix — for builders

  • List the privileged steps first and do them live, with a person there; schedule the rest.
  • Make the running part a program. Where it needs judgement, call the model with no tools and let the program write. Four of my six nightly model calls work this way.
  • If an agent must act alone, fence it: allow and deny lists (sudo, delete, move, commit, push, mail), a time limit, a report a person reads, and a test of the fence: my nightly run’s self-test checks that sudo is refused.
  • Give every detached job a versioned path, a time limit and a registration, and have the watcher ask “is it making progress?”, not “is it running?”. The crawler was running perfectly.
  • Retry with back-off, then give up. My drive client never retried on “too many requests”. Now it backs off within bounds and retries writes only on that error, since a failed upload may still have landed.
  • Record a privilege grant as a decision: the reason, the warning it overrides, the steps to undo it. That is why mine could be taken back.
  • What I considered instead: on 31 August, three narrower options than a standing sudo grant; I took the broad one so work would not stop at every root step, and spent October taking it back. On 16 September, a way round the safety layer; neither Claude nor I tried one, and the two refusals stand. I chose the split because it puts each risk where someone can see it: privilege only with a person present; unattended, only a plain program or a fenced agent that reports to me.

Also published on Kalli Family.