the lab · penny
Penny.
One agent runs my mornings, my mail, and my trades.
A year ago this ran a fleet of five hundred agents. I tore it down. What holds the same household of jobs together now is a single agent.
Every morning, before I am up, the ThinkPad in my house has already read my inbox, checked the calendar, weather, markets, and overnight failures, then written and narrated a short video briefing. It also builds the rest of my day on a private calendar and asks one useful follow-up when a planned block may already be done. The same agent keeps a research site and a Substack and writes the essays itself. The public sample below uses the current briefing renderer. The rest of the page is the machinery.
I designed and built Penny as an AI operations system I have to trust every day. Reusable capabilities sit below one agent, durable workflows carry scheduled work, and approval gates stop consequential actions in code. Running it daily forces the architecture through the same questions that matter in larger deployments: reliability, cost, observability, and control.
Everything Penny can do is one short command. I rarely have to type one.
Every capability Penny has is one short typed command. Reading mail, placing a call, scoring a trade, publishing a website: each is one line, each gives back one plain answer, and each ends in a code that says whether it worked. There are 249 of these commands. I rarely need to type one myself.
I talk to Penny in plain English and never name a command. The brain that picks them is Claude Code, Anthropic’s coding harness running a frontier model, the same tool I build everything else with, including this site. I rent the best loop instead of writing one, so all I maintain is the commands.
+ How the agent drives the commands
The agent reads my request, picks the commands, runs them in order, and reads each answer to decide the next. The same agent also runs on OpenClaude, the open-source version of that harness, when I want a cheaper or open model. Nothing is ever bespoke. When I am not there, a scheduler calls the exact same commands on a timetable: the briefing at eight, my inbox triaged before I am up, the markets four times a day. So when a job misbehaves at 9:30, I reproduce it by typing the one command myself, watching one agent, one command line, and a workflow engine on top call the same commands a chat turn cannot hold.
A few of the jobs I actually hand it.
Standing jobs I hand it, each a request that became a command or a chain of them. Two show the record: one answer back, and a code that says what happened.
It pulls the weather, calendar, inbox, pre-open markets, and the Kalshi book, writes the script, narrates it with two local voices, and texts me the video.
Twenty essays drafted, run through the same publish gate, and posted under its own byline, including I Trained My Replacement and She Doesn’t Sleep. I read them. I do not write them.
It searches its own memory two ways at once, by meaning and by keyword, fuses the hits, and answers from what it remembers, not the last thing it saw. The path is drawn out just below.
$ pen memory recall --query "workflow node style" {"ok":true, "memoryRecall":"(0.80) prefers verbose, story-style nodes (0.70) working on naming + quality gates", ··· recent context elided ···}
A phone call reaches outside the house, so this command is gated. With no approval token it exits 3 and nothing dials. Forty-seven commands work this way: the gate lives in the exit code, so it runs on every execution and can’t be talked around.
$ pen voice call --to +1555... --purpose "smoke test" {"ok":false, "error":"approval_required"}
From one sentence to a sequence of commands.
Here is the loop that turns one sentence into a sequence of commands.
+ The mechanics of the loop, for the curious
Each box on that diagram is a real step. The agent reads the request, recalls what it needs from memory, breaks the ask into intents (calendar? inbox? markets?), then chains the right pen verbs in order, for example pen calendar today then pen brief render. Every verb returns one JSON answer and an exit code, and the agent reads each result to decide the next call. A gated external write with no approval token exits 3 and the loop halts there. The loop repeats, run back to decompose, until the job is done. Memory is read before the turn and written after, every turn.
It answers from what it actually remembers, even when the easy match is stale.
Memory is the part that kept biting me. A plain lookup will hand back a stale fact from February when the answer changed in June. So Penny does not grab the closest match: it searches two ways at once, fuses the results, and re-ranks so recent things outrank old. The diagram and the weights are below.
+ The three stores and the scoring weights
The store is three layers. An episodic store of append-only daily logs, fed in by a nightly consolidation pass and never recalled directly. A semantic store, a LanceDB vector table (an open vector database) of facts, preferences, decisions, and entities, which is the layer that grows. And a procedural store, the same LanceDB under a procedure category, holding learned how-tos.
On recall, two searches run in parallel: a vector search by meaning and a BM25 keyword search (matching the exact words). Their two ranked lists are merged by reciprocal rank fusion, then every candidate is re-ranked by a composite score: similarity 0.45, recency 0.25 with a 7-day half-life decay, importance 0.20 with a 90-day half-life and a floor of 0.1, and frequency 0.10 from a log-scaled access count, minus an anti-stale penalty that stops the same memories looping. It overfetches three times, then keeps the top few, which go back into the prompt every turn.
The weights came from a recall failure: stale memories kept outranking useful recent context. The three-layer store and the scoring pass are the fix.
The whole surface is 249 commands, and you can see all of them.
One command line, no bespoke wiring per task. Forty-seven commands can touch the outside world, and every one exits 3 without an explicit approval token. The full catalog opens below, with private project names replaced by public labels.
+ Show the full surface · 48 domains, 249 commands
A scheduler runs the same commands on a timetable.
The agent is one caller of those commands. The other is a scheduler, for durable, branching work fired on a clock while I sleep. That layer is n8n, the open-source automation platform I run so an execution survives a crash. It calls the same command I can type in a terminal, which is why a job that breaks at 9:30 is one I can reproduce by hand.
Forty-three versioned workflows live in the repo. A command I run once is a demo. A sequence I keep running by hand earns a promotion to a workflow, and only after it clears its gates on a real schedule does it take over without me.
+ The full day, as the crons fire it
n8n runs in queue mode over Postgres and Redis so an execution survives a worker crash. Each workflow node calls the exact same pen command over an HTTP bridge. Most active workflows fire on a clock; the rest run on demand or wake on an event. Those include shared primitives such as the agent step, the approval gate, and the long-job wrapper, plus the catcher that only wakes when an execution fails.
A frontier brain runs this one. A plain loop runs the other, for cents.
Penny is one of two agents in this lab, and the second is the opposite bet. LetMeCheckThatBot runs on a loop I wrote by hand around a cheap open model. It has run for four years on almost nothing, proof that you do not always need a frontier harness. Penny is where I decided I wanted one.
+ What keeping both costs me
I keep both alive, so I know what each costs, and the gap between a plain loop and a frontier subscription is the whole lesson on one bench. It is the same cost and capability decision I make when designing AI systems for organizations.
+ The agent file, at a glance
Penny
Autonomous operator · single-user · home server
penelopelawrence.com · its Substack
- Online since
- October 2025, running daily.
- Class
- One agent, many tools. A single loop that calls commands. No fleet.
- Brain
- Claude Code on a frontier model, or OpenClaude, its open-source version, on a cheaper one. No loop I wrote, no fallback chain.
- Surface
- 48 domains, 249 commands on one CLI, one JSON answer each, the exit code is the contract.
- Gate
- 47 external-write commands, none fire without an explicit approval token.
- Hands
- 43 versioned n8n workflows calling the same commands on a schedule or event.
- Memory
- SQLite and a vector store, mirrored nightly to a markdown vault I own.
- Channels
- Telegram, a phone, and four public accounts under its own byline.
What was on the table, and why it lost.
Every choice here had real options behind it, and most of the losers I built first. The record, in question form:
Why one agent, and not a fleet?
ON THE TABLE
- ✗ a fleet of five hundred agents with a shared scheduler (built it)
- ✗ more agents to supervise the agents (tried that fix too)
- ✓ one agent in a loop, many tools
REASONING
Every fleet version produced the same mountain: half-finished features, quietly buggy work, pieces that did not fit together. Supervising agents added coordination, not quality. One careful agent on real platforms holds together. Anthropic and Cognition have since published the same advice.
Why is there no fallback chain?
ON THE TABLE
- ✗ a cost-aware cascade that silently downgrades (letmecheckbot had one; it went in the bin)
- ✗ a capability router that picks the model for me
- ✓ one runner, picked by name, that fails loud
REASONING
A chain that quietly retries with a weaker model was how I shipped confident answers nobody asked for. If the runner fails, I want to see it fail and decide. Penny launched without one; letmecheckbot’s went in the bin.
Why is every capability a command?
ON THE TABLE
- ✗ a bespoke tool wired into the agent per task
- ✗ one tool surface for the agent, another for the workflows
- ✓ one CLI, called the same way by every caller
REASONING
One surface to write, one place to audit, one place to rate-limit. The agent calls the commands from inside its loop; the workflows call the same commands from outside it; I call them from a terminal. Nothing is bespoke per caller, and the exit code means the same thing to all three.
Why n8n instead of your own engine?
ON THE TABLE
- ✗ everything inside chat turns, no engine
- ✗ my own job engine (built it; it ran for a year and worked)
- ✓ n8n in queue mode
REASONING
Durable, scheduled, branching work has no business living inside a chat turn. And maintaining my own engine was a second job. I retired a working system I was fond of because the boring one was better.
Why are writes gated in the exit code?
ON THE TABLE
- ✗ prompt discipline (“never send without asking”)
- ✗ allowlists per workflow
- ✓ a structural approval token, or the command exits 3
REASONING
I do not trust a prompt to enforce a rule. The gate is in the exit code, runs on every execution, and never retires. Direct sends and irreversible actions require a human token. Narrowly scoped scheduled lanes carry their own explicit token.
Why buffer until “go”?
ON THE TABLE
- ✗ reply to every message as it lands
- ✗ a debounce timer that guesses when I'm done
- ✓ an explicit go from me
REASONING
Because I text in bursts. The buffer lets me send three messages, change my mind, and finish the thought before Penny acts on any of it. A timer guesses when I’m done; go knows. Spending tokens once instead of on every autocorrect is the bonus.
Where Penny shows up.
Everything above is the surface. This is where the output lands: Penny posts and ships under its own name, through the same commands, behind the same gate.
What I’m still chewing on.
None of this is finished, and none of it a promise. The threads I keep pulling on.
+ The outbox in the waiting room
Penny’s replies are delivered by a container it is also allowed to restart. Once, mid-run, it restarted that container: the work finished, but the message saying so died with the messenger, so my phone read “working…” into the night. It now restarts itself last, a rule, not a fix. The real fix, a durable outbox, is written but not wired; it lives in a folder called _pending-integration.
status: written, not wired
+ A long queue and the meaning of approve
The gate holds every external write until I tap. The outbox holds every next decision until I have answered the last one. That is the design, and I still believe it: the safety lives in the structure, where the model cannot talk its way past it. But the system does more every month, and the approver still sleeps, parents, and goes to the movies. The gate holds. What I do not know is what a long enough queue does to the meaning of the word approve.
status: holding, by hand
+ The gate guards the road I paved
The gate lives in the exit code. The pen wrapper returns 3 on an unapproved external write and the runner halts, and I have leaned on that as the control. It holds for everything that goes through pen. But pen is one road out: a workflow that reaches for a raw curl, or for n8n’s own HTTP node, never touches the wrapper and never sees the 3. Penny writes its own automations, which is the point, and nothing at the network stops one from driving around the gate I built. So the honest version of the claim is narrower than the one above: the gate holds the road I paved, not every way off the property. The real fix puts the check where the packets leave, and I have not built it.
status: holds the pen path, not the network
Three sister git repos, one home server, daily since October 2025; one agent since the May 2026 rewrite, four years into building these. The architecture above is what survived contact with daily use, including what broke.
This is what I build, and it is the kind of work I do for the people who hire me.
An agent that holds a household of jobs together, with the architecture, the records, and the failures open to read. The fastest way to understand it is its own byline: the essays are what living next to this system sounds like, leaving the house through the same gates as every trade, email, and post.
Read its SubstackOr start at penelopelawrence.com, where it keeps the research.