Skip to content

Architecture

TinyFat is built on open-source components with a transparent architecture.

ComponentTechnologyPurpose
Agent runtimetroublemakerClaude-powered agent framework (open source)
ContainerCloudflare Sandbox SDKSecure container execution
StorageCloudflare R2Per-agent persistent storage (S3-compatible)
PlatformAstro on Cloudflare PagesDashboard, landing page
QueueCloudflare QueuesWebhook delivery, dedup, retry
EmailResendInbound/outbound email
DatabaseSupabaseUser accounts, agent config, secrets
Skillsfat-skillsPlatform skills (browser, PDF, etc.)

Agent identity lives entirely in R2 storage, not the container.

All agents share the same stateless container image. Each agent gets 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 │
└─────────────────────────────────────────────┘

Your agent runs on all channels simultaneously:

ChannelHow it works
EmailSend an email, get a reply. Async, one turn per email
TelegramChat with your bot. Instant ack, then reply
Slack@mention in your workspace. 👀 ack, then reply
Web ChatReal-time streaming (coming soon to dashboard)

All channels share the same agent storage, memory, and sessions.

1. You email yourname@tinyfat.com
2. Resend receives it, sends webhook to platform
3. Platform verifies signature, resolves agent, validates sender
4. Message queued for async delivery
5. Sandbox container wakes, R2 mounted at /data
6. troublemaker processes your message
7. Agent writes response to /data/outbox/email/
8. Platform sends the reply via Resend
9. You receive the response
1. You message the bot
2. Platform receives webhook, sends instant ack
3. Message queued for async delivery
4. Sandbox container wakes, troublemaker processes
5. Agent replies on the same channel
1. You type a message in the dashboard
2. Platform opens SSE stream, wakes sandbox
3. troublemaker streams response in real time

Open-source TypeScript agent runtime running inside the container:

  • Unified HTTP gateway on port 3002 with path-based routing
  • Adapters for email, Telegram, Slack, and web chat
  • Readiness gate (503 until adapters initialize)
  • Context sanitization (strips orphaned tool_results)
  • Event scheduling (cron-like scheduled tasks)
  • Session and memory management
  • Auto-detects available adapters from environment variables

Skills cloned into the container image at /opt/fat-skills/:

  • browser-tools — Headless Chrome CLI (screenshot, navigate, eval)
  • gh-login — GitHub CLI authentication
  • pdf-gen — PDF generation with headless Chrome

A Cloudflare Worker that handles:

  • Webhook routing for all four channels
  • Signature verification per-adapter
  • Async delivery via CF Queue (dedup, retry, DLQ)
  • Sandbox lifecycle (wake, mount R2, start troublemaker)
  • Agent provisioning
  • Scheduled wake via Durable Object alarms

External webhooks are queued for reliability:

  • Immediate ack — Platform returns 200 to Telegram/Slack instantly
  • CF Queue — Messages enqueued with dedup IDs (5-min TTL via KV)
  • Retry — Queue retries failed deliveries with DLQ fallback
  • Secrets re-fetched — Consumer re-reads secrets from DB at delivery time

This prevents webhook timeout cascades under load.

Your agent can schedule future events (cron-like). The platform acts as a process supervisor:

  1. After every interaction, the platform reads the wake manifest from troublemaker
  2. All event timestamps are stored in a Durable Object’s SQLite table
  3. DO alarms fire at each registered time, waking the container
  4. Events are processed, manifest re-synced

Even when your container is asleep, the platform wakes it at the right time.

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/
└── {name}/ # Scheduled event files

Your Anthropic API key is encrypted at rest using AES-256-GCM. It’s only decrypted when starting your container, injected as an environment variable, and never logged.

Only whitelisted emails can trigger your agent. This prevents spam triggering expensive API calls. Your signup email is automatically added.

Each agent run is isolated:

  • Scoped R2 credentials (can’t access other agents’ data)
  • Container sleeps after inactivity timeout
  • Webhook signatures verified per-adapter

New agents are provisioned when you email start@tinyfat.com:

  1. Platform creates a user account (if new)
  2. Generates a unique name (swiftpebble, calmflint, etc.)
  3. Creates agent record, email alias, allowed senders
  4. Seeds MEMORY.md and outbox directories in R2
  5. Sends welcome email + agent hello email

Agents don’t send emails directly. They write JSON files to /data/outbox/email/. The platform reads these and sends via Resend. See The Outbox Pattern.

The agent runtime is fully open source: