SIDE PROJECT · CURRENT

Penny

One operating surface for messages, research, scheduling, markets, publishing, and the services behind them.

channels agent one command per tool approval the world
SCALE

304 commands across 56 domains, exposed through one surface.

IMPACT

It replaced a fleet of five hundred agents with one, and has run every day since October 2025.

BYLINE

It posts under its own byline, with a published body of work.

CONTROL

Operations with real consequences are checked in code and policy.

THE REASONING

Architecture map of Penny: the real components and how they connect
THE ARCHITECTURE MAP
Why one agent, not a fleet

OPTIONS CONSIDERED

  • Rejected
    Fleet of 500 specialized micro-agents: Shared scheduler coordinating narrow single-purpose agents. Built and ran in production, producing fragmented state and compounding coordination overhead.
  • Rejected
    Hierarchical supervisor agent: Meta-agent layer auditing and orchestrating worker agents. Added meta-communication complexity without improving output quality.
  • Chosen
    Single agent with broad tool capability: One careful agent running sequentially in an inspectable loop across stable CLI tools and deterministic exit codes.

DECISION & REASONING

  • A year ago this ran a fleet of five hundred agents. I tore it down.
  • 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 no fallback chain

OPTIONS CONSIDERED

  • Rejected
    Cost-aware fallback cascade: Automatic failover to cheaper or faster models on error. Shipped confident hallucinations and degraded output silently when upstream failed.
  • Rejected
    Dynamic capability router: Heuristic routing layer picking models per prompt. Obscured model behavior and added failure surface before execution began.
  • Chosen
    Named single runner that fails loud: Explicit model selection per task. Fails immediately and visibly on error so humans decide whether to retry, adjust, or stop.

DECISION & 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.
Why n8n, not my own engine

OPTIONS CONSIDERED

  • Rejected
    In-chat turn execution: Handling stateful loops, retries, and timers directly inside agent turns. Brittle across restarts and couples long-running jobs to chat lifecycles.
  • Rejected
    Bespoke custom workflow engine: Homegrown job queue and state machine. Ran reliably for a year but created constant maintenance overhead for plumbing instead of capabilities.
  • Chosen
    n8n in Redis queue mode: Battle-tested orchestration engine running workflows as thin coordinators calling deterministic CLI commands, with visual state and built-in retries.

DECISION & REASONING

  • Durable, scheduled, branching work has no business living inside a chat turn.
  • Maintaining my own engine was a second job.
  • I retired a working system I was fond of because the boring one was better.
Why the gate is an exit code

OPTIONS CONSIDERED

  • Rejected
    Prompt discipline guardrails: Instructing the model in system instructions to ask before acting. Prompts degrade under context pressure, jailbreaks, and tool hallucination.
  • Rejected
    Per-workflow destination allowlists: Configuring authorized actions per workflow trigger. Fragmented security policy across 56 domains with high configuration drift.
  • Chosen
    Structural approval token gating (Exit 3): The CLI wrapper enforces security deterministically. Irreversible actions require an explicit token or the process halts with exit code 3.

DECISION & REASONING

  • I do not trust a prompt to enforce a rule.
  • The gate is in the exit code. It 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 messages buffer until go

OPTIONS CONSIDERED

  • Rejected
    Immediate per-message execution: Invoking the agent on every inbound chat bubble. Triggers race conditions, duplicate runs, and premature actions during burst texting.
  • Rejected
    Debounce timeout heuristic: Waiting 15 to 30 seconds of inactivity before firing. Adds unnecessary latency when ready, interrupts when composing longer multi-message thoughts.
  • Chosen
    Deterministic buffer flushed by explicit ‘go’: Inbound messages accumulate in a Redis list until an explicit command flushes the buffer as a single coherent prompt.

DECISION & 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.
Why every integration is one command

OPTIONS CONSIDERED

  • Rejected
    Bespoke per-service agent tool bindings: Direct TypeScript or Python SDK integrations defined inside the agent loop. Brittle schemas, poor testability outside LLM sessions.
  • Rejected
    Split surfaces for agent and workflows: Separate API clients for n8n workflows and chat agent. Duplicated authorization logic, diverging behaviors, and double the audit burden.
  • Chosen
    Unified CLI surface (pen <domain> <action>): Single binary contract called identically by the agent, n8n workflows, and human operator from terminal. Centralized logging, exit codes, and auth.

DECISION & REASONING

  • 56 domains is a lot of integrations for one person to keep working. Each one gets the same shape or none of them stay working.
  • One surface to write, one place to audit, one place to rate-limit.
  • Three callers: the agent from inside its loop, the workflows from outside it, me from a terminal.
  • Nothing is bespoke per caller, and the exit code means the same thing to all three.
Durable outbox and container restart sequencing · pending integration

INCIDENT & RESOLUTION

  • Penny’s replies are delivered by a container it is also permitted to restart.
  • During one execution, the agent restarted that delivery container mid-run. The workload completed, but the outbound confirmation was dropped when the container halted.
  • The immediate mitigation sequences container restarts last.
  • The permanent resolution implements a durable outbox queue, currently staged in _pending-integration.
Approval boundary audit: pen CLI wrapper vs raw HTTP · open limitation

BOUNDARY AUDIT & LIMITATION

$ pen voice call --to +1555... --purpose "smoke test"
{"ok":false, "error":"approval_required"}
  • The pen wrapper returns exit code 3 on unapproved external writes, halting execution across all commands invoked through pen.
  • However, direct HTTP requests via curl or n8n HTTP Request nodes bypass the wrapper and avoid exit-code evaluation.
  • Because Penny generates automation workflows directly, unvetted workflows could theoretically execute outbound calls without wrapper gating.
  • Comprehensive enforcement requires egress packet inspection at the container network boundary.