Architecture

How the pieces fit together

Six patterns. Each one fixes a specific failure mode. Together they turn a stateless chatbot into something that feels like staff.

The three memory layers

A good agent has the same memory shape a human does. Trying to fit all of it into one prompt is what makes most agents brittle.

Layer 1 — Daily notes

memory/YYYY-MM-DD.md

One file per day. Append-only. The raw timeline — decisions, errors, fixes, conversations worth a line. Local-only.

Layer 2 — Long-term curated

MEMORY.md

The distilled version. Every few days during a heartbeat, the agent pulls lessons forward from daily files. Prunes what's stale. Loaded only in main sessions — never in group chats.

Layer 3 — Per-person

memory/people/<name>.md

Role, comms style, what they own, what to keep private from them. Loaded on demand when that person comes up. The difference between 'stranger talking to a bot' and 'person I know.'

The heartbeat pattern

Don't make your agent purely reactive. A cron pings it every 20–30 minutes. The agent reads HEARTBEAT.md — a small, tiered checklist — and either does something useful or replies HEARTBEAT_OK and goes quiet.

Heavy work goes to sub-agents, never inline. The main session stays responsive so you can interrupt.

The goal: helpful without being annoying. A few useful nudges per day. Not a chatbot pinging you on a timer.

Tier 0
Housekeeping
Daily log, trajectory tips, project map. ~15 sec.
Tier 1
Production health
Health-check curls on the apps you care about. Non-200 = alert immediately. ~60 sec.
Tier 2
Active project monitoring
Quick status checks per project. Never multi-step work inline — delegate.
Tier 3
Reactive monitoring
Errors, bug reports, in-flight tasks, CI. Fire only if state changed.
Tier 4
Daily rituals
Once-per-day work, gated by a state file so it only runs once.
Tier 5
Weekly
Security advisories, project context freshness, dependency updates. Cadence-gated.

Sub-agent by default

The single most important rule. If a task takes more than 2–3 tool calls, spawn a sub-agent. The main thread has to stay responsive — if your agent is 15 minutes into a log investigation, it can't talk to you. That's unacceptable.

Goes to sub-agent
  • • Any coding task (use Claude Code in tmux)
  • • Multi-page research / deep web fetches
  • • Cross-system debugging
  • • Batch operations iterating over N records
  • • Anything with 4+ sequential tool calls
Stays inline
  • • Quick lookups (1–2 tool calls)
  • • Single-file config changes
  • • Status checks, reactions, acknowledgments
  • • Sending a message you already drafted

Project context auto-inject

When your agent is routed to a project — a Telegram thread, a Slack channel, a Discord server — it should know which project this is and load the right context.

Each project has its own CONTEXT.md with business context, stack, key files, gotchas, current focus. Your harness maps inbound metadata to a project and injects the file automatically.

projects/
├── project-a/
│   └── CONTEXT.md   # business + stack + gotchas
├── project-b/
│   └── CONTEXT.md
└── project-c/
    └── CONTEXT.md
text

The system improves itself

Your agent isn't a static prompt. After every non-trivial task — especially failures — it appends a trajectory tip to memory/trajectory-tips.md. Before any debugging session, it greps the file for relevant tips and injects them as context.

### [RECOVERY] Render fromService env vars missing — 2026-04-27
**Task type:** deploy / Render
**What happened:** Deploy failed with "ConnectionNotEstablished".
fromService refs in render.yaml only inject when services are first created
via Blueprint. Pre-existing services get nothing.
**Fix/Tip:** Before debugging Render deploy fails, check service env vars
via GET /v1/services/{id}/env-vars. If DATABASE_URL absent, set it manually.
Never expect fromService to populate retroactively.
markdown

Plus: weekly self-review during a heartbeat, proposed updates for identity files (which require human approval), and skills that compound when you reach for the same workflow twice.