Tasuku
Operations

Architecture

Tasuku's Worker, Durable Objects, Workflows, agent runtime container, and publication boundaries.

Tasuku runs as one Cloudflare Worker (tasuku): a Hono API plus the static dashboard, backed by D1, R2, KV, Queues, Durable Objects, and Workflows. Agent code never runs inside that Worker — it runs in a separate agent runtime container, reachable only through a narrow HTTP protocol.

Components

ComponentResponsibility
Worker (Hono API + dashboard)Serves /api/v1 and the static dashboard bundle, verifies inbound GitHub/Slack/Linear webhooks, handles session auth and OAuth, and exposes configuration and query endpoints.
D1Source of truth: configuration, sealed credentials, work items, workflow runs/attempts/events, reviews, schedules, and audit evidence.
R2Blob storage: run artifacts, workspace-changes diffs, output files, and sealed run requests (runs/{run}/{attempt}/request.enc).
KV (CACHE)Short-lived cache, primarily sealed GitHub installation tokens.
Queuestasuku-inbox, tasuku-effects, tasuku-review-batches, each with a dead-letter queue — wake-up delivery for outbox rows that live in D1.
WorkflowsRunAttemptWorkflow (one instance per attempt) and SecretRotationWorkflow; both are durable, resumable Cloudflare Workflows.
Durable ObjectsScheduler, RunEvents, CodexDeviceAuth, Mutex, UsageCache, Migrator, AgentContainerStandard, AgentContainerLarge — see below.
Agent runtime containerRuns Codex, Claude, or OpenCode against a bounded workspace behind an HTTP protocol; the same image runs on Cloudflare Containers, Daytona, or a self-hosted external host.
GitHub AppWebhook identity, OAuth, installation access, repository tokens, and trusted publication identity.
Slack AppMentioned requests, thread context, plan actions, result delivery, and optional operational alerts.
Linear AppTicket events and comments; GitHub repositories remain the code targets.

Durable Objects

Durable ObjectInstancingPurpose
Schedulersingleton (idFromName("global"))Owns every job-status transition: lane accounting (agent work vs. reviews), session FIFO, the review-batch gate, claim, lease recovery, and an alarm-driven tick(). Replaces the Postgres advisory-lock/SKIP LOCKED claim loop.
RunEventsone per runTails D1 workflow_events and fans them out to the dashboard over SSE; keeps a 500-event ring and replays older events from D1 on reconnect.
CodexDeviceAuthone per ${scope}:${org}:${repo}Drives the Codex ChatGPT device-auth flow and token refresh through alarms, without a control-plane subprocess; status mirrored to D1 device_authorizations.
Mutexone per lock key (e.g. linear-token:{org})Generic exclusive lock for the handful of sites that needed pg_advisory_xact_lock under Postgres.
UsageCacheone per organization60 s TTL, in-flight singleflight cache for provider usage lookups; also serializes Codex token refresh so concurrent isolates cannot race it.
Migratorsingleton (idFromName("global"))Applies pending server/drizzle/*.sql files as one D1Database#batch() per file. Called from /api/* and /readyz middleware (both share the same migrations-gate), and from the first step of every Workflow — not from scheduled(), which reaches it indirectly through that same /api/*//readyz gate whenever it makes a request.
AgentContainerStandard / AgentContainerLargeone per attempt (${runId}:${attempt})@cloudflare/containers Container subclasses backing the standard-2 (default) and standard-4 instance classes. No pooling — every attempt gets a fresh container and workspace.

Agent runtime container and protocol

The agent runtime (tasuku-runtime) is a Bun + Hono HTTP server, PID 1 inside the container, running as uid 1000. It is baked into docker.io/amalshaji/tasuku-agent-runtime, and the same image runs unmodified on Cloudflare Containers, Daytona, or a user-hosted external runtime — the Worker talks to all three through one RuntimeClient speaking the same protocol.

EndpointPurpose
GET /healthzProtocol version, agent-toolchain profile, and active/max run counts.
POST /v1/runsIdempotent start keyed by (run_id, attempt_id).
GET /v1/runs/:id/events?after=NNDJSON event tail with disk replay and a heartbeat frame every 5 s.
GET /v1/runs/:id/resultFinal state, exit code, usage, and blob metadata once the run ends.
GET /v1/runs/:id/files/...Workspace-changes diff, artifact HTML, and output files, streamed by the Worker into R2.
DELETE /v1/runs/:idCancel (SIGTERM, then SIGKILL after a grace period).
POST /v1/tools/codex-auth/...Fallback Codex OAuth executed from container egress when Worker egress to chatgpt.com is blocked.

Every request carries a bearer token scoped to the instance (Cloudflare, Daytona) or the organization (external) and an X-Tasuku-Protocol version header; a major-version mismatch fails with 426 rather than running against an incompatible runtime. See Sandbox providers for how each provider is configured.

Run lifecycle

Webhook / API / Cron
  → D1 (durable run + job row)
  → Scheduler DO: kick() → claim → RUN_ATTEMPT_WORKFLOW.create()
  → RunAttemptWorkflow: prepare → acquire-sandbox → execute → publish → finish-attempt → release-sandbox → notify-scheduler
                                        │ NDJSON event tail
                                        ▼
                     AgentContainer DO / Daytona / external host — tasuku-runtime (HTTP :8787)
                                        │
  D1 workflow_events ◀── flush (1 s / 100 events) ──▶ RunEvents DO ── SSE ──▶ dashboard
                                        └──▶ provider_effects (D1) → tasuku-effects queue → GitHub / Slack / Linear

RunAttemptWorkflow runs one instance per attempt (id runId:attempt); a long execution is split into chunks so no single Workflow step needs a 30-minute streaming connection. Inbound webhook and API handlers only write to D1 and call the Scheduler — they never wait on an agent.

Trust boundaries

  • Secrets are sealed in D1 with TASUKU_MASTER_KEY (AES-256-GCM); the sealed run request is written to R2, and only its keys/ids travel through Workflow step results.
  • Cloudflare provider — credential injection. The sealed RunRequest carries placeholders (tasuku-inject-{attempt}-{repositoryId} for GitHub tokens, tasuku-inject-{attempt}-claude for CLAUDE_CODE_OAUTH_TOKEN, and per-header MCP placeholders). The container class's Outbound Worker rewrites the real header/value only on the way out; the agent process inside the container never holds a GitHub or Claude credential. This is credential_mode: INJECTED.
  • Daytona and external-runtime providers — inline credentials. Neither provider can attach to Cloudflare's Outbound Worker hooks, so those two run credential_mode: INLINE: the sealed credential is placed directly in the sandbox's auth.json/environment for the run, the same trust model the Go worker used for every provider.
  • Codex is unsupported on the Cloudflare provider. chatgpt.com (Codex's inference endpoint) rejects Cloudflare's Cf-Worker header (confirmed in experiments/cf-spikes), and @cloudflare/containers has no per-host bypass for its outbound-Worker interception once engaged — so Codex cannot run on CLOUDFLARE_CONTAINER at all. Configuring that combination fails fast: a non-retryable prepare error and a BLOCKING sandbox readiness finding, both with the same message. Codex runs only on DAYTONA/EXTERNAL_RUNTIME, where chatgpt.com is reached with ordinary direct egress (no Worker interception to begin with).
  • Deny-by-default egress tiers (STRICT / BUILD / DEEP_SCAN) are enforced per provider: Outbound Worker allowedHosts (Cloudflare), network_allow_list (Daytona), a documented firewall (external). See Security for the tier host lists and failure modes.

Data boundary

Tasuku remains a single-instance, self-hosted control plane — now backed by D1 and R2 instead of Postgres and S3. Durable operations depend on D1/R2 availability and backup; see Backup and recovery. The dashboard and API are different views of the same organization-scoped state.

On this page