Architecture
TinyFat is built on open-source components with a simple, transparent architecture.
The stack
Section titled “The stack”| Component | Technology | Purpose |
|---|---|---|
| Agent runtime | pi-mono | Claude-powered agent framework |
| Container | Cloudflare Sandbox SDK | Secure container execution |
| Storage | Cloudflare R2 | Per-agent persistent storage |
| Platform | Astro on Cloudflare Pages | Dashboard, API, webhooks |
| Queue | Cloudflare Queues | Job orchestration |
| Resend | Inbound/outbound email | |
| Database | Supabase | User accounts, agent config |
The key insight
Section titled “The key insight”Agent identity lives entirely in R2 storage, not the container.
All agents share the same stateless container image. Each agent gets temporary R2 credentials scoped to their storage prefix (agents/{id}/). The container mounts that prefix at /data. Your agent sees only its files.
┌─────────────────────────────────────────────┐│ Shared Sandbox Container ││ (stateless - same code for all agents) │├─────────────────────────────────────────────┤│ Agent A: r2:bucket/agents/a/ → /data ││ Agent B: r2:bucket/agents/b/ → /data │└─────────────────────────────────────────────┘Request flow
Section titled “Request flow”Email → Agent
Section titled “Email → Agent”1. You send email to [email protected]2. Resend receives it, sends webhook to platform3. Platform validates sender, looks up agent4. Platform enqueues job to sandbox-queue5. Queue worker generates scoped R2 credentials6. Worker calls sandbox-spike with agent config7. Sandbox mounts agent's R2 prefix at /data8. step-mom runs one agent turn via pi-mono9. Agent writes response to /data/outbox/email/10. Worker reads outbox, sends email via Resend11. You receive replyOne-turn execution
Section titled “One-turn execution”Each email triggers exactly one agent turn:
- Read input
- Think + use tools
- Write output
- Exit
This avoids complexity of long-running processes, connection management, and state drift.
Components
Section titled “Components”sandbox-spike (CF Worker + Container)
Section titled “sandbox-spike (CF Worker + Container)”The core execution engine:
- Receives job from queue
- Mounts R2 storage with scoped credentials
- Runs
step-mom(the agent harness) - Reads outbox files and returns emails
step-mom (Container Agent)
Section titled “step-mom (Container Agent)”TypeScript harness running inside the container:
- Reads input from
/inbox/input.json - Creates AgentSession using pi-mono
- Runs one turn via
session.prompt() - Writes emails to
/data/outbox/email/*.json - Streams events to
/data/events/for dashboard
sandbox-queue (CF Worker)
Section titled “sandbox-queue (CF Worker)”Queue consumer that orchestrates execution:
- Parses commands (
/clear,/resume,/model) - Calls sandbox-spike to run agent
- Sends response emails via Resend
- Tracks job status in Supabase
Storage layout
Section titled “Storage layout”Per-agent prefix in R2: agents/{agent_id}/
agents/{agent_id}/├── MEMORY.md # Agent's persistent memory├── config.json # Agent preferences (model, etc.)├── sessions/│ └── {id}.jsonl # Conversation history files├── outbox/│ ├── email/ # Pending emails (written by agent)│ ├── sent/ # Successfully sent│ └── failed/ # Failed to send└── events/ └── {timestamp}.jsonl # Run events for dashboardSecurity model
Section titled “Security model”Your API key
Section titled “Your API key”Your Anthropic API key is encrypted at rest using AES-256-GCM. It’s only decrypted when:
- Starting your container
- Injected as environment variable
- Never logged or exposed in UI
Allowed senders
Section titled “Allowed senders”Only whitelisted emails can trigger your agent. This prevents:
- Spam triggering expensive API calls
- Unauthorized access to your agent
Configure in the dashboard under Settings.
Container isolation
Section titled “Container isolation”Each agent run is isolated:
- Scoped R2 credentials (can’t access other agents’ data)
- No network access to internal services
- Container exits after each turn
The outbox pattern
Section titled “The outbox pattern”Agents don’t send emails directly. They write JSON files to /data/outbox/email/:
{ "subject": "Re: Help request", "body": "Here's what I found...", "in_reply_to": "<message-id>", "status": "pending"}The queue worker reads these and sends via Resend. This provides:
- Reliability — If sending fails, the file stays for retry
- Auditability — Every outgoing email is a file you can inspect
- Simplicity — Agent doesn’t need email credentials
Session management
Section titled “Session management”Sessions persist in /data/sessions/ as JSONL files. Each file contains:
- Your messages
- Agent responses
- Tool calls and results
Use email commands to manage sessions:
/clear— Start fresh (old sessions preserved)/resume— List available sessions/resume abc1— Resume specific session by hash
Email threading
Section titled “Email threading”Responses maintain proper email threading:
In-Reply-Toheader set to originalMessage-IDReferencesheader includes thread chain- Gmail/Outlook group replies correctly
Session footer
Section titled “Session footer”Each response includes a footer:
---[agent:a1b2 | ↑38 ↓5.0k R663k W37k $0.687 18.7%/200k | claude-haiku-4-5]Shows: agent name, session hash, tokens in/out, cache stats, cost, context usage, model.
Open source
Section titled “Open source”The entire agent runtime is inspectable:
- pi-mono: github.com/badlogic/pi-mono
- pi-skills: github.com/badlogic/pi-skills
- tiny-skills: github.com/alosec/tiny-skills
Running it yourself
Section titled “Running it yourself”Want to self-host? You need:
- Cloudflare account (Workers, R2, Queues)
- Anthropic API key
- Email provider (Resend, Postmark, etc.)
- Supabase or equivalent for user data
The platform code isn’t currently open source, but the agent runtime is fully open.
Next steps
Section titled “Next steps”- The outbox pattern — How email sending works
- Memory & persistence — How data survives restarts