A steward that runs on a schedule instead of on command, so the things I forget to ask about still get looked at.

Solo build2026Live, running unattended

01The problem

I run marketing for three companies as an associate director and operate three businesses of my own, plus a household and a brokerage account. The work was never the bottleneck; the coordination overhead was. Mail that needed a reply sat unanswered, commitments made in a meeting were gone within a day, receivables aged quietly, and money moved through accounts I had no consolidated view of.

The approach I had was a reactive assistant, which means I had to remember to ask — so the things I forgot stayed forgotten. Every tool I owned covered exactly one slice: an invoicing app, a calendar, a broker app, a transcription tool. None of them talked to each other and none of them volunteered anything. The reframe that started this project was simple to state and awkward to build: move from an assistant that acts on command to a steward that runs the picture proactively.

02What I built

A personal steward that runs unattended on a Mac and reports into a private cockpit. Scheduled jobs wake a headless model agent on a fixed calendar, each one carrying a written brain that tells it what to gather, how to judge severity, and what it is forbidden to do. Every judgement is written to a shared database as a durable, deduplicated row rather than a notification that scrolls away, so the cockpit is a ledger of what the steward noticed. Anything that would act outwardly in my name is written as a pending draft first.

  • Four autonomous watcher domains — mail and calendar, receivables, meeting transcripts, portfolio — each on its own schedule with its own safety rules.
  • An observation and action database with a one-to-five severity scale, source-keyed deduplication, and a run log that records every cycle including its failures.
  • A one-page cockpit with no sidebar: portfolio state, a savings countdown, pending approvals, the steward timeline, watcher health and a project board.
  • A money-flow view built from parsed bank and card statements, because Hong Kong banks expose no open API and the PDF is the only interface there is.
  • A human-in-the-loop gate: drafts are created pending, and only rows explicitly marked approved are ever executed.
  • A module registry that makes the cockpit a hub — a new domain is one registry entry plus one route group.
Schematic of the cockpit pageA drawn, abstracted representation of the interface: a single scrolling page with no sidebar, showing a portfolio trend line at the top, two pending-approval cards below it, a three-row steward timeline with a status mark per row, and a watcher health strip at the foot. All content is placeholder.FAT SAI PORTFOLIO PLACEHOLDER VALUES PENDING TIMELINE WATCHERS

Drawn, not captured. One page, no sidebar: a portfolio band, then approvals waiting on a human, then the steward timeline with a mark per row, then watcher health. Every value, bar and mark shown is illustrative placeholder content.

03As a product

Who buys it
Operators who run several businesses at once and are the single point of coordination — founders, agency principals, family-office and portfolio managers. The same architecture fits internal chief-of-staff tooling inside a professional services firm.
Value
Most assistants wait to be asked, which means they only help with what you already remembered. This is a scheduled, opinionated watcher layer: it decides what deserves attention, keeps a permanent record of that judgement, and hands over a drafted action rather than a notification. The unit of output is a reviewed decision, not a chat reply.
Positioning
Deterministic automation tools fire on triggers but cannot judge whether an email actually needs a reply. Chat assistants can judge, but only when prompted, and they forget. Vertical software covers one domain each and never cross-references. This sits in the gap: model judgement on a cron, a database as memory, and a hard approval boundary on anything outward.
Status
Internal tool, built for one operator, not commercialised and never priced. The watcher-plus-observation-database pattern is the part that would license; the domain integrations are personal.

04How it works

Stack
Next.js and TypeScript on Vercel behind a cookie session gate, with Supabase Postgres as shared memory. Watchers are launchd calendar jobs invoking a headless model agent, with Python and Bash grabbers for broker, statement and ledger data. Document generation runs a dual-mode headless browser; historical files sit in object storage behind presigned URLs.
Shape
A loop, not a pipeline. Every cycle opens and closes a run row, so silence is detectable rather than invisible, and every outward action stops at a pending row until a human flips it.
The watcher loop, five levels, one approval gate A vertical flow in five levels. A calendar job wakes inside a watchdog window, the busiest domain every thirty minutes, and gathers from four domains with a source each, at most twenty-five messages a cycle. It judges every item on a one-to-five severity scale, pushing an alert at four and above, then records deduplicated rows in two branches: a pending branch that carries an unsent outward action to a human approval level, and a logged branch that ends there and feeds nothing. Every band is hollow and drawn at a uniform width, with no level measured, because nothing in this loop has ever been counted per branch, so no band claims a quantity. SCHEDULE Calendar job, watchdog set BUSIEST DOMAIN, EVERY 30 MINUTES GATHER Four domains, a source each AT MOST 25 MESSAGES A CYCLE JUDGE Severity one to five, each AT FOUR AND ABOVE, A PUSH ALERT RECORD Deduped on a source key PENDING LOGGED APPROVE Human flips it, then send

Width uniform throughout — nothing in this loop has ever been counted per branch, so every band is drawn at equal width and no quantity is claimed.

Nothing goes out in my name unapproved. An outward action is written as a pending row and the executor only ever picks up rows a human has already flipped; everything else is logged and stops there, and a source that is offline produces no observations at all rather than a warning.

The decision I spent longest on

Putting a language model in the judgement seat of an unattended cron job. Rules cannot tell a newsletter from a client who is actually waiting on you, but a model in a loop has no natural bound — it can hang, hit a usage cap, or spend an hour on one message. The mitigation had to be architectural rather than a better prompt: a run row that opens and closes, a wall-clock watchdog that kills an overrun so the next slot starts clean, and turn limits that cap the agent independently of time. The second call was making outward actions structurally impossible instead of discouraged. The portfolio watcher never touches the broker’s trade-unlock path and never holds that password, so it cannot place an order even if instructed to.

05Retrieval architecture

A steward that runs on a clock has a different retrieval problem from an assistant that answers questions. Nobody types anything, so there is no query to interpret, and nothing accumulates into a body of knowledge to search: each cycle goes back to the source and reads its domain again from scratch.

What is left is one lookup, repeated on every item, and it turns out to be the only one the system needs. The database sits downstream of the judgement instead of upstream of it — which is why the layer that would normally be the centre of a retrieval architecture is the one that is missing.

ModelAt query time only. The model runs inside every scheduled cycle and nowhere else; nothing was ever embedded, summarised or precomputed ahead of it.

Corpus
Live sources, re-read in full each cycle: a fixed mail query and the day’s calendar window, the receivables ledger, transcript files that appeared on disk since the last run, and a read-only snapshot of the brokerage book. Nothing is copied into a store, so nothing can go stale between runs; the price is that every cycle pays for its own gathering.
Ingestion
Calendar entries under launchd, the busiest domain firing every thirty minutes across the working day. Each entrypoint applies its own guard window before it does anything, working hours and skip Sunday, and wraps the run in a wall-clock watchdog, because the failure this system actually had was not a bad answer but a process that hung and looked healthy for about forty-four hours. The mail sweep is capped at twenty-five messages a cycle, and a source that is offline yields zero observations rather than a warning.
Index
None. There is no corpus to index. A watcher never searches a body of knowledge; it re-reads its own domain, so the only lookup a cycle performs is whether an observation already exists for this item’s source key: a mail identifier, an invoice number, a dated snapshot prefix. That single equality check carries the entire deduplication burden, and it is what stops the steward reporting the same email four times a day. An early migration did create an embeddings table with a cosine similarity index, on the assumption that recalling past work would need semantic search. Nothing ever wrote to it and nothing has ever read it, which is a fair measure of how much of a retrieval problem this turned out not to be.
Query
Structured filters, never language. The cockpit reads by domain, status, date and severity, and the portfolio view takes the last sixty snapshots, so a request is a set of column predicates before it reaches the database. Rows marked private are excluded by row-level security and then stripped a second time on the way out, because one gate that can be misconfigured is not a gate.
Selection
The mechanism is an ordinary predicate: the filters above, ordered by recency and severity and capped at what a page can show. The shaping happens before selection rather than after it. Severity is assigned once, at judgement time, on a closed one-to-five scale, so by the time anything is read back the ranking already exists as a stored column. Nothing is scored at read time and nothing is reordered by a second pass, which means every line the cockpit shows can be traced to the cycle that wrote it.
Grounding
The model is allowed to judge and forbidden to act. Anything that would go out in my name is written as a pending row, and the executor step only ever picks up rows a human has already flipped to approved. Where the stake is higher the restriction is structural rather than procedural: the portfolio watcher never calls the broker’s trade-unlock path and does not hold that password, so it cannot place an order even if it were instructed to. Errors are recorded in the run row and never as an alert, because the previous generation of this system was retired for pushing false alarms to a phone.

Present — this layer exists and runs.Absent by decision — the layer is not there, and the sentence beside it is the reason. Every one of the six is answered on every system in this chapter, so the rows can be read across pages.

06Numbers

MeasureFigureBasis
Commits, first to latest122Verified
Cockpit built and shipped, single day32 commitsVerified
Autonomous watcher domains live4Verified
Bank and card statement formats parsed7Verified
Transactions categorised without a human, 13 months99.1%Verified
Longest undetected outage, before the watchdogabout 44 hrsVerified
Typical cycle, against a 900-second kill timeoutabout 50 sVerified
Manual triage displaced, per day30-45 minProjected
Marginal running cost, per monthTens of USDProjected

Verified — counted from the repository, the run log, and in-code constants written at the time of each incident. Projected — no time-tracking study was run, so the triage figure is an informed estimate from the schedule and scope of the four domains. Model usage is covered by an existing subscription rather than metered per run, so the cost is a band inferred from the stack’s tiers, not an invoice.

07Timeline

  1. 2026-04Repository begins. The earliest work is an unrelated app skeleton that was later shelved.
  2. 2026-05The first-generation always-on system is deliberately retired — gateway, hosted dashboard, message processor and its tables all removed after it kept pushing false alerts to a phone.
  3. 2026-07The steward reframe is written down, then shipped: the observation database, the approval interface and the first watcher, with the remaining three domains following within days.
  4. 2026-07The whole thing moves from a local machine to a cloud cockpit in one day, thirty-two commits.
  5. 2026-08A security audit closes two live data-exposure holes and adds an automated guard; the silent-outage class of bug is fixed with a watchdog and a missed-slot detector.

08Looking back

What broke

A hung agent looks exactly like a healthy one. When all four watchers hit a usage cap at the same time, each process blocked waiting for the cap to reset instead of exiting, and because the scheduler will not start a second instance of a job that is still running, the entire steward went dark for roughly forty-four hours with silent logs. Turn limits did not help: they bound turns, not wall-clock time. Before that, the scheduler itself lied — interval-based jobs simply never fired on that machine while calendar-based ones on the same box fired reliably, and two watchers recorded zero runs for two days with no error anywhere. The audit found a third: a database view running under default ownership bypassed row-level security on its base table entirely, and a default-privileges rule was quietly granting broad access on every new object.

What it changed

The pattern is that absence of an error is not evidence of health. The outage detector now counts missed scheduled slots rather than measuring time since the last success, which is the only version of the check that can notice nothing happening. It also settled two product rules that are not preferences: the system never places a trade or moves money, and it never chases a client for payment. Both are relationship or risk decisions, and both are enforced by the agent simply not having the capability, not by an instruction telling it to behave.

Where it stands

Live and running unattended. Four watcher domains on schedule, the cockpit deployed and auth-gated, one user, no revenue. The most recent work is on financial reconciliation accuracy rather than new domains, and the honest next step is a second account holder — which is where the assumptions get tested instead of restated.

Back to Top