the lab · penny

Penny.
One agent runs my mornings, my mail, and my trades.

Running daily since Oct 2025 single agent·48 domains·n8n·Postgres·LanceDB

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.

My role Architecture, code, workflows, and operations
Core decision Stable capabilities below one agent
Operating proof Daily system · 249 commands · 43 versioned workflows
A public sample built with the current briefing renderer. The private edition uses the same layout and reads the live calendar, weather, inbox, markets, and system health. Two synthesized voices run locally on the ThinkPad.
A screenshot of Penny's research website, penelopelawrence.com, a media-framing research site. penelopelawrence.com A media-bias research site; studies scored over a large headline corpus. A screenshot of Penny's Substack, 'By the Powers of Penelope,' a grid of essay titles. By the Powers of Penelope Twenty essays on AI and meaning, posted under its own name. A screenshot of one of Penny's essays, 'I Trained My Replacement and She Doesn't Sleep.' I Trained My Replacement… One piece, start to finish, in its own voice.

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.

“Brief me every morning before I’m up.”shipped daily

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.

“Keep the Substack going. Your name, your voice, not mine.”20 published

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.

“What did we decide about that? Go check before you answer.”recall

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 ···}
“Place the call for me.”gated · exit 3

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.

a request · the composition loopread · decompose · compose · run · repeat
each result picks the next command · repeat until the job is done the request “what’s on today?” me · plain language decompose break it into intents calendar? inbox? markets? compose chain the right commands pen calendar today · pen brief render run command → JSON + exit code gated write? exits 3 the answer briefing · draft · reply back to me memory recall before · capture after recall capture one sentence in. a sequence of commands, composed and run. one answer out. I never pick the commands; Penny does.
+ 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.

memory recall · the retrieval paththree stores · hybrid search · a composite score
THE THREE STORES RETRIEVAL · HYBRID RANK BY COMPOSITE SCORE Episodic JSONL day logs · fed in, never recalled direct nightly consolidation Semantic LanceDB vectors · the layer that grows facts · preferences · decisions · entities Procedural LanceDB · category=procedure · learned how-tos a query the turn’s context vector search cosine over embeddings, by meaning keyword search BM25 full-text, by exact words searched RRF fusion merge the two ranked lists into one similarity · semantic match 0.45 recency · 7-day half-life decay 0.25 importance · 90-day half-life, floor 0.1 0.20 frequency · log-scaled access count 0.10 − anti-stale penalty · stops the same memories looping overfetch 3×, then keep the top few the top few memories, back into the prompt recall before the turn · capture after · every turn three stores, two searches, one score. recency decays so last week outranks last winter.
+ 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.

48 domains· 249 commands· 47 gated external writes· one JSON answer each· the exit code is the contract
+ Show the full surface · 48 domains, 249 commands
pen alpaca13 ·
contextresearch-contextresearchdecision-contextdecisionholdorderriskreconcilecomposer-contextcomposecompose-checkresume
pen analytics1
query
pen backlog5
listshowaddgraduatemark
pen property-intel11 ·
healthmetricspassespass-createpass-disablepass-enablerotate-guestrotate-daysetclear-presetsrecover
pen brief3
contextrecordrender
pen calendar8
todayregistrylistfind-freecreateupdatedeleteacl-set
pen cloudflare6 ·
zonesdns-listdns-setdns-deletepurgeemail-status
pen content8 ·
derivederive-pendingdispatchautopublishresolvewinnerschampionqueue
pen drafts1
list
pen draft1
show
pen published1
list
pen ctx3
showlsgc
pen day4
applyobservemarkstatus
pen deck2
rendercapture
pen discovery2
sweeplist
pen dnakhla7 ·
contextstagevalidateauditgatejournalpromote
pen email10 ·
principalswatchcontexttriageharvest-contextdraftsendhousekeepingjournalwatermark
pen fitness5
starttodaystatuslogweigh
pen hackernews8 ·
studycandidatescommentwarm-runwarm-alertcontextsubmitwarm
pen instagram12 ·
contextinboxcandidatescommentsstagepublishcarouselengageengage-commentreelautopostmark-posted
pen job6
contextlintrenderverifydeliverrecord
pen journal1
append
pen kalshi12 ·
contextresearch-checkdossierdecisionholdorderexecutecomposer-contextoutcomecomposecompose-checkjournal
pen linkedin2
pulsejournal
pen marketing7 ·
contextdiscoverleadsenrichresolvecheck-repliesstage
pen memory3
recallcaptureconsolidate
pen metrics5
snapshotreportrecord-postrecordexperiment
pen montage5
makemusicarchivebaselinestatus
pen ops11
statusauditlogsrunshealthhealth-alertsweepexecutionsdeadletterhealrestart
pen outreach5 ·
draftlistrunsendrepair-property-intel
pen penny1
status
pen portrait1
hybrid
pen recruiter7 ·
scanassessengagement-contextverify-engagementrecordsendstatus
pen reddit10 ·
contexttopstagepublishkarmastatusautopostmarkharvestjournal
pen session4
mineshowideateseeds
pen sites4 ·
contextstagepublishrollback
pen speech4
saypresetstranscribevoices
pen stitch7
generateprojectsscreensgeteditdesign-mdds
pen substack8 ·
contextideate-contextstagepublishnotemarkstatsjournal
pen telegram3
sendaskresolve-delivery
pen tiktok7 ·
contextrenderstagepublishmark-postedmarkjournal
pen torrent5 ·
searchdownloadstatusdeliverremove
pen book2 ·
findget
pen voice3 ·
callstatussummary
pen web3
searchreadscreenshot
pen wf4
pushpulllintrun
pen whatsapp3 ·
sendreadinbox
pen x5 ·
pulsetrendingengagereplypost
contains an external write that requires an approval token highlighted verb = the gated one

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.

12a 6a 12p 6p SELF-HEAL every hour, silent memory 3:00a email 7:45a briefing 8:00a reddit 9:30a email 12:00p trading 12:30p substack 2:00p linkedin 2:37p briefing 6:00p health 10:00p triage 11:00p the day, every day. the sweep is 24 hours in 24 seconds. off the dial: kalshi and alpaca fire four times a day, tiktok and x mid-afternoon, an essay ships Tuesdays and Fridays

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
Agent file · 01 Running · daily

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.

penelopelawrence.com Its own site, on its own domain. By the Powers of Penelope The long-form essays. u/PennyLawrence946 Comments in the deep threads. @ai.zalvation Short-form video.

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

Proof note

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 Substack

Or start at penelopelawrence.com, where it keeps the research.