# AgentOS blueprint — reconstructed from Danny Postma's Agent SDK talk so an agent can build the system

> Source: <https://gist.github.com/iannuttall/8152098b5ce8e6c1a7499ee561ed93f4>
> Published: 2026-08-14 12:10:25+00:00

Reconstructed from Danny Postma's talk *How I Built My Own AgentOS on Claude's Agent SDK (So You Can Too)* (2026). This document is both a product spec for a human and an implementation prompt for an AI coding agent. Build exactly this system. Do not invent features that are not specified here.

**Role contracts and prompts in this file are reconstructed from the talk, not his verbatim files.** Mark every reconstructed prompt in code comments and docs as such.

Build a personal AgentOS: a control plane + UI on top of cloud-managed agents (Anthropic Claude Agent SDK / Claude managed agents) that lets a human set a goal or task, assign a scoped agent, and walk away. Agents run in ephemeral containers, do the work (plan, implement, review, commit), and only message the human when they are stuck or need a decision.

The system should automate the majority of coding and operational work: cron, webhooks, Kanban tasks, and open-ended goal loops. After each session the container is destroyed. The next session reinitializes cleanly.

- Multi-agent control plane with per-agent prompts, skills, MCPs, repos, env, and collaboration lists
- Least-privilege isolation: per-agent container, per-agent MCP/repo/network access, per-agent filesystem folder ACLs
- Ephemeral session lifecycle: pull repo → inject secrets → do work → commit → destroy container
- Persistent filesystem via Cloudflare R2 + an MCP that enforces read/write/delete server-side
- Task Kanban (
`todo`

/`doing`

/`review`

/`done`

) with assign-agent, run-now, schedule, recurring, attachments, templates, approval gates, and follow-up chains - A default "compound engineer" / feature-build template (9 steps, including human approval gates)
- Goals ("gauntlet loop"): definition of done, orchestrator that spawns the next specialist, progress log, spend/time/stuck caps
- Inbox MCP: agents message the human; human replies resume the session; multiple-choice questions; PWA + push
- Activity feed + live session viewer (watch tool calls in real time)
- Triggers: inbound webhooks spawn a scoped job
- Automations: cron jobs that spawn tasks/agents
- Runner routing: Claude managed-agents cloud vs cheap local VM (Claude
`--dangerously-skip-permissions`

and Grok in yolo mode); per-goal and per-agent routing - YAML-as-code per project + CLI (
`help`

, push/pull sync, create project/goal/task, adjust agents, create skills) - Encrypted secret storage (he used Google's token encryption system; name forgotten — treat as Secret Manager / Cloud KMS)
- UI to inspect/edit/download/preview files on the R2 filesystem

- Deep-dive videos / publishing exact code of his production system
- Open-sourcing his exact agents/skills/prompts (he offered; we do not have them)
- His own future custom runner replacing Claude managed agents
- Game-project specifics (
`fight-for`

repo is only an example of a mounted repo) - Exact spend numbers as product requirements (
`$500/day`

cloud,`$1000`

uncapped goal,`$10`

Hetzner VM are anecdotes, not SLOs)

- A multi-tenant SaaS for other companies. This is a single-operator AgentOS.
- Giving every agent full filesystem, Gmail, GitHub, and production credentials "for convenience."
- Persistent long-lived agent containers. Sessions are throwaway.
- Inventing extra agent roles, extra template steps, or extra UI beyond what he described.
- Copying his verbatim system prompts (unknown). Reconstruct role contracts only.

AgentOS is a **control plane** you own, sitting on top of **Claude managed agents / Agent SDK**.

```
 Human
   │  writes spec / creates task / creates goal / replies in inbox
   ▼
┌──────────────────────────────────────────────────────────┐
│  AgentOS control plane (your app)                        │
│  UI · API · CLI · YAML sync · webhooks · cron            │
│  Kanban tasks · Goals/orchestrator · Inbox · Sessions    │
└───────────────┬──────────────────────────┬───────────────┘
                │ spawn session            │ persist
                ▼                          ▼
     Claude Agent SDK /              R2 filesystem MCP
     local runner (Hetzner)          Secrets store
                │
                ▼
     Ephemeral container
       - clone allowed repos
       - inject allowed env
       - attach allowed MCPs
       - apply network allowlist
       - run agent with skills
       - commit if allowed
       - DESTROY
```

Mental model:

- A
**Project** is the unit of YAML-as-code. It declares agents, skills, templates, MCP connections, repos. - An
**Agent** is a named role with a foundational prompt + role prompt, a model, skills, MCPs, repos, a collaboration list, an environment, and a runner preference. - A
**Task** is a Kanban card. One agent works it. It can be one-shot, scheduled, recurring, gated, or part of a follow-up chain. - A
**Goal** is an open-ended loop. An orchestrator keeps spawning specialists until a human-approved Definition of Done is fully checked, or a safety rail trips. - A
**Session** is one containerized agent run. It is born empty, initialized, executed, committed, and thrown away. - The
**Inbox** is the only human interrupt channel. Agents do not wait on the human unless they are stuck or need a decision. **Least privilege is first-class.** Access is granted per agent, enforced at MCP, network, filesystem, and repo layers. Prompt leaks must not be able to reach anything the agent was not given.

| Layer | Responsibility |
|---|---|
| Control plane + UI | Projects, agents, tasks, goals, inbox, triggers, automations, session records, YAML sync, ACL policy. Your code. |
| Runtime | Claude Agent SDK / Claude managed agents (cloud) or a cheap local runner (Hetzner VM running Claude Code with `--dangerously-skip-permissions` , and Grok in yolo mode). |
| Persistence | Cloudflare R2 (files) via a custom MCP; encrypted secrets (Google Secret Manager / Cloud KMS — he forgot the product name); app DB for domain objects. |

He started in the Claude Code terminal and realized he wanted to overload work, leave, and come back hours later. Cron and triggers require unattended runs. Each session therefore:

- Starts a unique container.
- Pulls the allowed repo(s).
- Gets a scoped view of the filesystem (R2 MCP), allowed MCPs, allowed env, allowed network hosts.
- Does the task.
- Commits (if the agent/repo policy allows).
- Cleans up. The container is destroyed.
- The next session reinitializes the project cleanly. No leftover state except what was committed to git or written to R2 through the MCP.

The SDK already gives: spin up sessions, MCP connections, files API. AgentOS is **your UI and policy on top**: agent catalog, least-privilege walls, Kanban, goals/orchestrator, inbox, triggers, cron, YAML, runner routing, live session viewer.

He said he is starting to work out his own runtime pieces. Do not build a from-scratch agent runtime in MVP. Use the SDK. Local runners are an alternative execution backend with the same control-plane contract.

These are opinionated defaults so an agent can start. They are not his exact stack.

| Concern | Assumption |
|---|---|
| Language | TypeScript (Node 20+) |
| API | Hono or Fastify on a single service |
| DB | Postgres + Prisma (or Drizzle) |
| Queue / cron | A durable job runner (e.g. pg-boss or Inngest). Cron automations and scheduled tasks enqueue jobs. |
| Agent runtime (cloud) | Anthropic Claude Agent SDK / Claude managed agents |
| Agent runtime (local) | A worker on a cheap VM that can run Claude Code (`--dangerously-skip-permissions` ) and Grok in yolo mode |
| Files | Cloudflare R2 + a custom MCP server that enforces ACLs |
| Secrets | Google Secret Manager (he said "Google's token encryption system" and forgot the name; Cloud KMS is the alternative). Never store raw tokens in the app DB. |
| Auth | Single-operator. One human user. Session cookie or personal access for CLI. |
| UI | React + Vite. Sidebar app. Mobile-responsive PWA for inbox + push. |
| Webhooks | Public HTTPS receiver with per-trigger secrets. |
| Push | Web Push (VAPID) for the PWA. |

Do not pretend this is his production stack. It is a buildable default that matches the capabilities he described.

Implement these entities. Names can vary; the fields and relationships cannot be dropped.

A workspace that maps to a YAML AgentOS file.

`id`

,`name`

,`slug`

`yamlDocument`

(canonical on disk / in git; DB is a projection)- has many: Agents, Skills, TaskTemplates, MCPConnections, Repos, Triggers, Automations, Tasks, Goals

`id`

,`projectId`

`name`

(e.g.`default`

,`senior-dev`

,`plan`

,`spec`

,`review-coordinator`

,`feasibility`

,`scope-guardian`

,`coherence`

,`implementation-plan-executioner`

,`librarian`

,`customer-support`

,`diagnostic`

,`linkedin-content`

)`title`

`model`

(e.g. a Claude model for planners; Grok 4.6 for workers — routing is also a runner concern)`foundationalPrompt`

— shared AgentOS prompt: which files/systems exist, which MCPs exist, how inbox works, how to finish a task, least-privilege rules`rolePrompt`

— the one-job contract (see §8)`skillIds[]`

`mcpConnectionIds[]`

`repoAccess[]`

—`{ repoId, mountPath, permissions: git-read | git-write }`

`filesystemGrants[]`

—`{ folderPath, canRead, canWrite, canDelete }`

`collaborationList[]`

— agent ids this agent may spawn as subtasks`environmentId`

`runnerPreference`

—`cloud`

|`local`

|`inherit`

`inboxAccess`

— boolean (some tasks need it; spec step does)**Least privilege default: deny.** An agent gets nothing that is not listed.

Concrete agents he named (implement these as defaults; more can be added via YAML/UI/CLI):

| Agent | One job |
|---|---|
| default | General workhorse |
| senior-dev | Implement / apply review fixes |
| plan | Turn an approved spec into a concrete implementation plan; write it to the task; finish |
| spec | Produce a detailed feature spec (used in the template; approval-gated) |
| review-coordinator | Spawn specialized reviewers; consolidate must-fix / should-fix |
| feasibility | Review a plan for feasibility |
| scope-guardian | Review a plan for scope creep |
| coherence | Review a plan for coherence |
| implementation-plan-executioner | Implement the code from the plan |
| librarian | Update the internal wiki from how the codebase actually works |
| customer-support | Handle support chats via Front MCP only |
| diagnostic | Given a bug + support chat + repo, produce a cause report |
| linkedin-content | Recurring content automation |

You may add agents through YAML. Do not hard-require every named agent for MVP, but ship `default`

, `plan`

, `senior-dev`

, `spec`

, `review-coordinator`

, `feasibility`

, `scope-guardian`

, `coherence`

, `implementation-plan-executioner`

, `librarian`

so the feature template runs.

`id`

,`name`

`networking`

—`open`

|`limited`

`allowedHosts[]`

— e.g.`api.front.com`

. If`limited`

, the container/proxy**blocks everything else at the base level**, including GitHub, even if a leaked prompt asks.- This is independent of MCP grants. Network deny is a second wall.

`id`

,`name`

,`slug`

(e.g.`plan-mode`

invoked as`/plan`

)`kind`

—`prompt`

|`file`

(e.g. a Python script the agent can run)`body`

or`filePath`

on the R2 filesystem- Skills are attached per agent. Plan mode is a skill.

`id`

,`name`

(e.g.`github`

,`front`

,`agentos`

,`r2-fs`

,`inbox`

)`transport`

/ connection config`credentialSecretId`

— pointer into the secret store, not a raw token`allowedOperations`

if the MCP supports scoping- Agents reference connections by id. Example: customer-support gets Front, never Gmail, never GitHub. Plan agent gets plan-mode + AgentOS MCP, not Ahrefs, not GitHub.

Built-in MCPs you must implement:

**AgentOS MCP**— read/write the current task, mark status (except when an approval gate forbids the agent from marking`done`

), spawn a collaborator subtask, read project metadata the agent is allowed to see**Inbox MCP**— send a message to the human; send a multiple-choice question; read replies** R2 filesystem MCP**— list/read/write/delete under granted folders only** GitHub MCP**— only if the agent is granted that connection + repo access

External MCPs he mentioned as examples (configure, do not hardcode product logic): Front, Ahrefs, Gmail (explicitly **not** given to support), MongoDB read-only via env.

`id`

,`name`

(example:`fight-for`

for a game project — example only)`remoteUrl`

`mountPath`

inside the container`credentialSecretId`

(PAT or deploy key)`defaultBranch`

`id`

,`name`

,`providerRef`

(Google Secret Manager / KMS resource name)`purpose`

—`mcp`

|`repo`

|`env`

|`webhook`

- Injected into a session only if the agent/environment lists them.
- Stored encrypted at rest in Google's system. App DB holds only the reference.

`id`

,`projectId`

`name`

,`description`

`status`

—`todo`

|`doing`

|`review`

|`done`

`assigneeType`

—`agent`

|`human`

`assigneeAgentId`

nullable`attachments[]`

— FileObject ids (spec files, etc.)`approvalGate`

— if true,**the assigned agent can never mark this task**. Only the human can. The next follow-up must not start until status is`done`

`done`

.`followUpTaskId`

/`chainId`

+`chainIndex`

— template-generated chain`schedule`

—`once-now`

|`{ runAt }`

|`{ cron, timezone }`

(e.g. every Monday of the month, summarize inbox)`templateId`

nullable`activity[]`

— messages the agent writes into the task/inbox`sessionIds[]`

`id`

,`projectId`

,`name`

(e.g.`compound-engineer-workflow`

)`description`

— "~3-hour fully managed feature build" (his words; actual runs were ~5–6 hours)`variables[]`

— e.g.`branchName`

`steps[]`

— each step: name, assignee agent, prompt, approvalGate, attachmentsFromPrevious, spawnPolicy- Instantiating a template creates a chain of Tasks. Step N+1 is blocked until step N is
`done`

.

`id`

,`projectId`

`title`

,`spec`

(or a generated-from-spec-sheet DoD)`definitionOfDone[]`

— checkboxes, written by the human or generated from a spec sheet, then human-approved`status`

—`active`

|`paused`

|`completed`

|`stopped-spend`

|`stopped-time`

|`stopped-stuck`

`spendCapUsd`

nullable —**required in product UX**; he ran one overnight without a cap and hit $1000`maxDuration`

nullable`stuckThreshold`

— default 19 identical iterations, then orchestrator stops`runnerPreference`

—`cloud`

|`local`

|`auto`

("when busy use cloud; otherwise local"; per-goal override: "this one should only run on local runners")`progressLog`

— append-only, shared across sessions`sharedInbox`

+ shared filesystem folder`sessionIds[]`

`id`

,`projectId`

`name`

(e.g.`customer-support-inbound`

,`bug-report`

)`webhookSecretId`

`agentId`

— the scoped agent that runs`jobPrompt`

/ mapping from payload → task description- Example: support message in → webhook → job. Agent has Front (or equivalent) only, analyzes chat, assigns a support rep or account executive. One trigger fired 600 times.
- Example: support submits a bug → diagnostic agent with
**repo + support chat**, writes a report. If the human approves, start the implement → plan → plan review → fix → E2E test chain. Human only reviews and merges.

`id`

,`projectId`

`name`

(e.g. weekly LinkedIn content, first-of-month LinkedIn content bot)`cron`

,`timezone`

`taskTemplateId`

or inline task spec +`agentId`

- Distinct from Task.recurring: Automations are named cron entries in the sidebar. Recurring tasks are a schedule field on a Task. Implement both; they can share the same scheduler.

`id`

`from`

—`agent`

|`human`

`agentId`

,`sessionId`

,`taskId`

,`goalId`

nullable`kind`

—`text`

|`multiple-choice`

(radio buttons, like Claude's ask-user-question)`body`

,`choices[]`

,`selectedChoice`

`status`

—`open`

|`answered`

|`closed`

- Answering an open message
**resumes the waiting session**(sends the reply back into the agent).

`id`

,`agentId`

,`taskId`

/`goalId`

`runner`

—`cloud`

|`local`

`status`

—`starting`

|`running`

|`waiting-inbox`

|`committing`

|`destroyed`

|`failed`

`containerId`

/ SDK session id`toolCallLog[]`

— for the live viewer`startedAt`

,`endedAt`

,`costUsd`

if available`commitShas[]`

`id`

,`bucketKey`

(R2)`path`

,`projectId`

`mime`

,`size`

- Previewable in UI. Agents never touch R2 except through the filesystem MCP.

These are product requirements, not suggestions.

**Default deny.** An agent has no MCPs, no repos, no env, no filesystem write, no network, no collaboration spawn, unless listed on the agent.**One container per session.** No shared writable container between agents.**Customer-support bot** may have Front MCP. It must never have Gmail. It must never have GitHub / repo access. He was explicit: do not leak codebase info while doing support.**Plan agent** has plan-mode skill + AgentOS MCP. It does**not** get Ahrefs or GitHub MCP.**Network allowlist is a second wall.** If the environment is`limited`

to`api.front.com`

, the process cannot reach GitHub even if a prompt leak or a mis-attached MCP tries.**Filesystem is not a mounted superuser disk.** It is an MCP. Server-side checks enforce folder ACL and verb (read / write / delete separately). An agent that "can write" still cannot delete unless`canDelete`

is true. Unlimited filesystem access will wipe the disk — that is why this exists.**Folder grants are per agent.** An agent may be allowed to read another agent's folder without write.**Secrets are injected only for listed env/MCP/repo credentials.** Encrypted at rest. Even if the app DB is stolen, tokens stay in Google's secret system.**Approval gates are not honor-system.** The API refuses`PATCH status=done`

from an agent session token when`approvalGate=true`

.**Collaboration list is the only spawn path.** Plan agent may spawn listed helpers as subtasks. It cannot spawn an agent that is not on its list.**Prompt injection / leak assumption.** Design as if the model will try to use every tool it has. If it should not be able to do a thing, do not attach the tool, and block the network path.

Implement this state machine exactly.

```
requested
  → provision container (cloud SDK session or local runner slot)
  → inject env from Secret store (only listed keys)
  → attach allowed MCP connections
  → apply environment network policy
  → clone each granted repo to mountPath using repo credential
  → mount R2 filesystem MCP with that agent's folder ACLs
  → inject foundational prompt + role prompt + skills
  → status=running
  → agent works (tool calls streamed to live viewer + activity feed)
  → if inbox question: status=waiting-inbox; pause; on reply, resume with answer
  → if task complete and not approval-gated: AgentOS MCP marks task done / review
  → if git-write granted and work produced: commit, record sha
  → cleanup
  → destroy container
  → status=destroyed
```

Rules:

- After destroy, nothing from the container remains except git commits and R2 writes that went through the MCP.
- The next session must clone/pull again. No "warm" dirty workspace.
- Failures still destroy the container. Persist logs and tool-call history on the Session row first.
- For goals: after destroy, the orchestrator runs (see §10) and may enqueue the next specialist session.

Because sessions are ephemeral, there is no durable container disk.

- Store blobs in Cloudflare R2.
- Expose them only through an
**R2 filesystem MCP**. - UI can list, open, edit, download, preview files (high-level file browser).
- MCP tools (minimum):
`fs.list`

,`fs.read`

,`fs.write`

,`fs.delete`

,`fs.mkdir`

. - Every call is authorized server-side:

```
if !grant.canRead && op in (list, read) → deny
if !grant.canWrite && op in (write, mkdir) → deny
if !grant.canDelete && op == delete → deny
if path is outside granted folder prefix → deny
```

- Do not give the agent a raw S3/R2 SDK or a FUSE mount that bypasses the MCP.
- Per-agent home folder convention:
`/agents/{agentSlug}/`

plus any extra grants. - Shared goal folder:
`/goals/{goalId}/`

granted read/write to agents on that goal (delete still explicit).

Every agent session is prompted as:

**Foundational AgentOS prompt**(shared)** Role prompt**(per agent)** Runtime inputs**— current task/goal, attachments, allowed MCP list, allowed folder list, collaboration list

```
You are running inside AgentOS.

You have only the tools, MCPs, repos, environment variables, and filesystem
folders listed in your session manifest. If a tool is not listed, you cannot
use it and you must not try to. Do not ask for more access. Do not attempt
to reach hosts outside your network policy.

The container you are in will be destroyed at the end of this session.
Persist work by (a) committing to a granted repo if you have git-write, or
(b) writing files through the filesystem MCP. Do not assume a local disk
survives.

When you need a human decision or you are stuck, use the Inbox MCP.
Do not message the human for routine progress. They are not watching.
Write notable progress to the task activity log.

Your job is the role prompt below. Do that job, then finish. Use the
AgentOS MCP to update the task. If this task has an approval gate, you
must NOT mark it done — leave it in review and inbox the human.

You may spawn a collaborator only if they appear on your collaboration list.
Spawn them as a subtask with a tight brief.

Least privilege is a safety rule, not a suggestion.
```

**plan**

```
You are a plan agent. You have one job: turn an approved specification
into a concrete, ordered implementation plan. Write the plan onto the
task (and as a file attachment). Then finish the task. You do not
implement. You do not open unrelated tools.
```

**spec**

```
You are a spec agent. Produce a detailed specification for the requested
feature. Attach the spec file. Refine it if the human replies. You cannot
mark this task done — it is approval-gated. Inbox the human when the spec
is ready for review.
```

**senior-dev**

```
You are a senior developer. Implement the assigned work, or apply review
fixes, in the granted repo. Follow the plan if one is attached. Commit
when done. Run available tests. Inbox the human only if you are blocked.
```

**implementation-plan-executioner**

```
You implement the code according to the attached implementation plan.
Do not re-litigate the plan. Commit. Leave notes in activity.
```

**review-coordinator**

```
You are a review coordinator. Spawn the listed review specialists
(feasibility, scope-guardian, coherence for plans; the code-review
specialists for implementation). Each writes a report. You consolidate
into must-fix and should-fix. Attach the consolidated report. Do not
implement fixes yourself.
```

**feasibility** / **scope-guardian** / **coherence**

```
You review the attached plan only through your lens
(feasibility / scope / coherence). Write a report. Finish.
```

**librarian**

```
You update the internal wiki (filesystem folder you are granted) to
reflect how the codebase actually works after this change. Do not
change product code.
```

**customer-support**

```
You handle inbound customer support. You have the support MCP (e.g. Front)
only. Analyze the conversation. Assign the correct human rep or account
executive. You do not have Gmail. You do not have GitHub. You must not
exfiltrate or request codebase information.
```

**diagnostic**

```
You diagnose a bug. You have the repo and the customer-support chat.
Produce a cause report. Do not implement until a human approves and a
follow-up implementation chain is started.
```

**linkedin-content**

```
You produce the scheduled LinkedIn content. Use only the MCPs and folders
you were granted. Inbox if you need a human approval before posting, if
posting is even in your tool list.
```

**default**

```
You are the default AgentOS agent. Do the assigned task with the tools
you have. Finish or inbox if stuck.
```

Orchestrator is **not** a user-facing chat agent. It is control-plane code that runs after each goal session (see §10). It may call a model to choose the next specialist; that call still uses a tight reconstructed prompt:

```
You are the AgentOS goal orchestrator. Read the progress log, the
definition of done, and the last session summary. Choose the next
specialist agent from the allowed list (or declare the goal complete
if every DoD checkbox is satisfied, or stop if stuck/spend/time rails
trip). Output a structured decision. Do not do the specialist's work.
```

Columns: **todo → doing → review → done**.

A task is "to do, doing, review, done." Per subject of the task, an agent starts working on it.

- name
- description
- attachments
- assign an agent (e.g. senior-dev)
- run: immediately | schedule a datetime | recurring (cron; example: every Monday of the month, summarize inbox)
- optional: start from a template

A step can be marked `approvalGate: true`

. Then:

- Agent work can move the card to
`review`

. - AgentOS MCP / API reject any agent attempt to set
`done`

. - Human must manually put it on
`done`

. - Follow-up tasks stay blocked until that happens.

Used for: spec approval, and the final human PR/deploy review.

A template is a chain of follow-up tasks. Each step names an agent and a prompt. Completing step N (status `done`

) enqueues step N+1 (`todo`

→ runner picks it up).

Agents write details into the task activity / inbox. The UI shows this next to the card.

Ship this as the built-in template `compound-engineer-workflow`

. He described it as a ~3-hour fully managed feature build; a concrete run was spawned 15:00, done 21:00 (~5–6 hours), PR ready the next day. 99% of the time it works because **E2E testing is implemented inside the workflow**. Include an E2E step in implementation and in the post-bugfix chain.

Template variables: at least `branchName`

(and whatever the feature title/spec needs).

| # | Step | Assignee | Approval gate | Notes |
|---|---|---|---|---|
| 1 | Write a spec | `spec` |
yes |
Agent produces a detailed spec, attaches the spec file, may refine via inbox. Human reads, approves, marks `done` . Next step cannot start before that. |
| 2 | Plan | `plan` |
no (but tells human to review via inbox/activity) | One job: spec → concrete ordered plan. Writes details in activity/inbox. |
| 3 | Plan review | `review-coordinator` |
no | Coordinator spawns four review agents: he named feasibility, scope-guardian, coherence — implement those three plus a fourth plan-review specialist (`plan-risk` or a second coherence/feasibility pass; he said "four different review agents" and named three). Each writes a report. Coordinator consolidates must-fix / should-fix. |
| 4 | Revise plan | `plan` |
no | Gets the plan from step 2 and the review from step 3; adjusts. |
| 5 | Implementation | `implementation-plan-executioner` |
no | Implements the code. E2E tests run as part of this work. |
| 6 | Code review | `review-coordinator` |
no | Reviews the code; how it should be fixed. Consolidated must-fix / should-fix. |
| 7 | Apply review fixes | `senior-dev` |
no | Applies the review fixes. |
| 8 | Librarian | `librarian` |
no | Updates the internal wiki based on how the codebase works. |
| 9 | Human review of deployment / PR | `human` |
yes |
Human checks out the PR, reviews, merges. |

After a **bug-report trigger** that the human approved, the chain is: implement → plan → plan review → fix → E2E test, then human reviews and merges. Reuse the same agents; do not invent a second product.

For unstructured, open-ended work (he also called this the gauntlet loop).

Daily workflow he described: write a spec in the morning, throw it into the goal system, it writes a Definition of Done, he approves the DoD, it runs 5–6 hours, end of day he gets a PR, reviews, merges.

- Human creates a Goal with a spec (or a task-like brief).
- System (or a planning call) drafts
`definitionOfDone[]`

checkboxes from the spec sheet if the human did not write them. - Human approves the DoD. Do not start the loop without that approval.
- Orchestrator picks the first specialist (senior-dev, plan, etc.) and spawns a session.
- Session runs the lifecycle in §6. Shared inbox + shared filesystem + append-only progress log.
**At the end of every session**, the orchestrator:- reads progress logs
- reads definitions of done
- reads what was implemented
- marks DoD checkboxes that are satisfied
- if all checkboxes satisfied → Goal
`completed`

- else if safety rail trips → stop
- else spawn the next specialist

- Repeat until done or stopped.

| Rail | Behavior |
|---|---|
| Spend cap | Stop spawning when estimated/actual spend ≥ cap. UX should make a cap hard to forget. |
| Maximum time | Stop when wall-clock since start ≥ maxDuration. |
| Stuck detection | If the same iteration happens 19 times (same specialist + same unresolved DoD + no meaningful progress-log delta), orchestrator stops. |

A goal without a spend cap is allowed only if the human explicitly confirms. Default to requiring a cap.

- Progress log (append-only, visible in UI)
- Shared inbox thread for the goal
- Shared R2 folder

Inbox is an MCP agents call. It is also a UI surface.

Capabilities:

- Send a text message to the human
- Send a multiple-choice question with radio buttons (same idea as Claude's ask-user-question)
- Human replies in the UI; the reply is sent back into the waiting session and the session resumes
- Chat / communicate across multiple turns
- Agents message
**only when stuck or they need a decision**— not for chatter

PWA:

- Mobile-responsive
- Installable
- Web push when something is done or needs help

Do not build a second messaging product. Inbox is the interrupt channel for tasks, goals, and triggers.

- Global activity feed of agent actions / inbox / task transitions.
- Live session viewer: watch tools being called in real time while a session is
`running`

. - Persist tool-call logs on the Session so a finished run can be replayed.

- Each Trigger has a public URL + webhook secret.
- On valid POST: create a Task (or Goal job) assigned to the trigger's scoped agent, enqueue a session immediately.
- Agent receives a sanitized payload (do not dump raw headers/secrets into the prompt).

Examples to ship as seed config (not hardcoded business logic):

**Support inbound**— payload is a conversation. Agent:`customer-support`

. Tools: Front (or configured support MCP) only. Output: assignment to a support rep or AE. This pattern fired 600 times for him.**Bug report**— support submits a bug in the backend. Agent:`diagnostic`

with repo**and** support chat. Output: cause report. Human approval then starts the fix chain with E2E.

- Named automations with cron + timezone + agent + task body or template.
- Examples: weekly LinkedIn content; first-of-the-month LinkedIn content bot.
- Scheduler creates a Task and a Session on fire.
- Recurring Tasks (§9.2) use the same scheduler.

Originally everything ran on Claude managed agents API — expensive (anecdote: ~$500/day). He added a $10 Hetzner VM running Claude with `--dangerously-skip-permissions`

and Grok in yolo mode.

Routing rules to implement:

| Signal | Where it runs |
|---|---|
Control plane `busy` (cloud queue saturated / cost policy) |
prefer `local` if a local runner is healthy |
| Otherwise | `cloud` is fine |
Goal.runnerPreference = `local` |
only local runners |
Agent.runnerPreference = `cloud` (planners) |
Claude cloud |
Agent.runnerPreference = `local` (workers) |
Grok 4.6 / local Claude, fast |
| Per-goal override | wins over default |

Implement a `Runner`

interface:

```
provision(session) → handle
streamToolCalls(handle) → events
injectReply(handle, inboxAnswer)
destroy(handle)
```

Two backends: `CloudClaudeRunner`

, `LocalVmRunner`

. Local VM is a worker process that pulls jobs; it is not the control plane.

Do not build "his own future runner" beyond this interface. He said he is starting to work out his own stuff — that is an unknown, not a spec.

Every project has an AgentOS YAML file that mimics the online UI: agents, skills, templates, MCP connections, repo access, prompts.

Example shape (illustrative, reconstructed):

```
# agentos.yml
project: acme
agents:
  spec:
    title: Spec agent
    model: claude-opus-4
    skills: [inbox]
    mcp: [agentos, inbox, r2-fs]
    repos: []
    environment: limited-none
    runner: cloud
    prompt: |   # reconstructed — not his verbatim file
      You are a spec agent. ...
  plan:
    title: Plan agent
    model: claude-opus-4
    skills: [plan-mode]
    mcp: [agentos, inbox, r2-fs]
    repos: []
    collaboration: []
    environment: limited-none
    runner: cloud
    prompt: |
      You are a plan agent. ...
  senior-dev:
    title: Senior dev
    model: grok-4.6
    skills: []
    mcp: [agentos, inbox, r2-fs, github]
    repos:
      - id: app
        mount: /workspace/app
        permissions: git-write
    environment: open
    runner: local
    prompt: |
      You are a senior developer. ...
skills:
  plan-mode:
    kind: prompt
    body: |
      /plan — enter plan mode and produce an ordered implementation plan.
templates:
  - id: compound-engineer-workflow
    variables: [branchName]
    steps:
      - { name: Write a spec, agent: spec, approvalGate: true }
      - { name: Plan, agent: plan }
      - { name: Plan review, agent: review-coordinator }
      - { name: Revise plan, agent: plan }
      - { name: Implementation, agent: implementation-plan-executioner }
      - { name: Code review, agent: review-coordinator }
      - { name: Apply review fixes, agent: senior-dev }
      - { name: Librarian, agent: librarian }
      - { name: Human PR review, agent: human, approvalGate: true }
```

CLI (he named these):

| Command | Behavior |
|---|---|
`agentos help` |
usage |
`agentos push` |
sync local YAML → control plane |
`agentos pull` |
sync control plane → local YAML |
`agentos project create` |
create a project |
`agentos goal create` |
create a goal from details (used after local Claude brainstorming) |
`agentos task create` |
create a task; CLI knows which agents to apply from YAML/template |
`agentos agent update` / create |
adjust agents |
`agentos skill create` |
create a new skill |

Most of the time he chats with Claude locally while brainstorming. When done, he tells the CLI to create a goal or session on AgentOS with the details. The CLI creates the task/goal and applies the right agents. Templates have variables (branch name, etc.); Claude Code implements the template steps locally as a convenience, but AgentOS is the system of record once created.

Auth: CLI uses a personal token against the control-plane API.

Single app, sidebar sections — "all the different aspects":

**Agents**— list (default, senior-dev, plan, …). Detail: name, title, model, foundational + role prompt, skills, MCPs, repos, filesystem grants, collaboration list, environment, runner preference.**Skills**— list/create; prompt or file (e.g. Python script).** Files**— R2 browser: open, edit, download, preview.** MCPs**— connections, credential refs, which agents use them.** Repos**— name, mount path, credential ref, how it connects to the GitHub MCP.** Environment variables**— inject into sessions; values come from the secret store.** Tasks**— Kanban; create form; template picker; activity on the card.** Goals**— DoD checkboxes, progress log, spend/time/stuck, runner preference.** Inbox**— thread, radio-button questions, reply. This view is also the PWA home.** Triggers**— webhook URL, secret rotation, target agent, recent fires.** Automations**— cron list.** Sessions**— live viewer + history of tool calls.** Activity**— global feed.

Mobile: inbox + push is the required responsive surface. The rest can be desktop-first.

Postgres-oriented. Adjust names, not relationships.

```
// types — implement as Prisma/Drizzle models

type Project = {
  id: string
  name: string
  slug: string
  yaml: string
  createdAt: Date
}

type Agent = {
  id: string
  projectId: string
  name: string
  title: string
  model: string
  foundationalPrompt: string
  rolePrompt: string
  skillIds: string[]
  mcpConnectionIds: string[]
  repoAccess: { repoId: string; mountPath: string; permissions: "git-read" | "git-write" }[]
  filesystemGrants: { folderPath: string; canRead: boolean; canWrite: boolean; canDelete: boolean }[]
  collaborationList: string[]
  environmentId: string
  runnerPreference: "cloud" | "local" | "inherit"
  inboxAccess: boolean
}

type Environment = {
  id: string
  projectId: string
  name: string
  networking: "open" | "limited"
  allowedHosts: string[]
}

type Skill = {
  id: string
  projectId: string
  name: string
  slug: string
  kind: "prompt" | "file"
  body?: string
  filePath?: string
}

type McpConnection = {
  id: string
  projectId: string
  name: string
  config: unknown
  credentialSecretId?: string
}

type Repo = {
  id: string
  projectId: string
  name: string
  remoteUrl: string
  mountPath: string
  credentialSecretId: string
  defaultBranch: string
}

type SecretRef = {
  id: string
  projectId: string
  name: string
  providerRef: string
  purpose: "mcp" | "repo" | "env" | "webhook"
}

type Task = {
  id: string
  projectId: string
  name: string
  description: string
  status: "todo" | "doing" | "review" | "done"
  assigneeType: "agent" | "human"
  assigneeAgentId?: string
  attachmentIds: string[]
  approvalGate: boolean
  chainId?: string
  chainIndex?: number
  scheduleKind: "now" | "at" | "cron"
  runAt?: Date
  cron?: string
  timezone?: string
  templateId?: string
}

type TaskTemplate = {
  id: string
  projectId: string
  name: string
  description: string
  variables: string[]
  steps: TemplateStep[]
}

type TemplateStep = {
  name: string
  agentName: string // "human" for the final PR step
  prompt: string
  approvalGate: boolean
}

type Goal = {
  id: string
  projectId: string
  title: string
  spec: string
  definitionOfDone: { id: string; text: string; done: boolean }[]
  dodApproved: boolean
  status: "active" | "paused" | "completed" | "stopped-spend" | "stopped-time" | "stopped-stuck"
  spendCapUsd?: number
  spendUsd: number
  maxDurationMinutes?: number
  stuckThreshold: number // default 19
  runnerPreference: "cloud" | "local" | "auto"
  progressLog: string
}

type Trigger = {
  id: string
  projectId: string
  name: string
  webhookSecretId: string
  agentId: string
  jobPrompt: string
}

type Automation = {
  id: string
  projectId: string
  name: string
  cron: string
  timezone: string
  agentId: string
  taskTemplateId?: string
  taskBody?: string
}

type InboxMessage = {
  id: string
  from: "agent" | "human"
  agentId?: string
  sessionId?: string
  taskId?: string
  goalId?: string
  kind: "text" | "multiple-choice"
  body: string
  choices?: { id: string; label: string }[]
  selectedChoiceId?: string
  status: "open" | "answered" | "closed"
}

type Session = {
  id: string
  agentId: string
  taskId?: string
  goalId?: string
  runner: "cloud" | "local"
  status: "starting" | "running" | "waiting-inbox" | "committing" | "destroyed" | "failed"
  runtimeHandle?: string
  toolCallLog: unknown[]
  startedAt: Date
  endedAt?: Date
  costUsd?: number
  commitShas: string[]
}

type FileObject = {
  id: string
  projectId: string
  path: string
  bucketKey: string
  mime: string
  size: number
}
```

Single-operator API. Session-cookie for the UI; bearer token for the CLI and runners.

```
POST   /projects
GET    /projects/:id
PUT    /projects/:id/yaml          # push
GET    /projects/:id/yaml          # pull

GET    /projects/:id/agents
PUT    /projects/:id/agents/:name
GET    /projects/:id/skills
POST   /projects/:id/skills

GET    /projects/:id/files?path=
GET    /projects/:id/files/content?path=
PUT    /projects/:id/files/content
DELETE /projects/:id/files/content  # UI only if operator; agents use MCP

POST   /projects/:id/tasks
PATCH  /projects/:id/tasks/:id      # human may set done on gated tasks
POST   /projects/:id/tasks/:id/run
POST   /projects/:id/templates/:id/instantiate

POST   /projects/:id/goals
POST   /projects/:id/goals/:id/approve-dod
POST   /projects/:id/goals/:id/pause

GET    /inbox
POST   /inbox/:id/reply             # resumes session

GET    /sessions
GET    /sessions/:id                # includes toolCallLog; SSE at /sessions/:id/live

POST   /hooks/:triggerId            # public, HMAC/secret
GET    /projects/:id/triggers
GET    /projects/:id/automations

# Runner / MCP internal (not public)
POST   /internal/sessions/:id/tool-events
POST   /internal/sessions/:id/cost
```

AgentOS MCP maps to the same task/goal/spawn endpoints but is authorized with a **session-scoped token** that is limited to that agent’s ACL. Approval-gate `done`

is rejected for that token.

Inbox MCP: `inbox.send`

, `inbox.ask`

(choices required), `inbox.read`

.

Filesystem MCP: see §7.

Do not start phase N+1 until phase N acceptance tests pass.

- TS service, Postgres, single-user auth, empty React shell with the sidebar routes (can 404).
`agentos help`

stub CLI.

- Project + Agent + Task CRUD.
- Cloud runner via Claude Agent SDK: create session, attach a hardcoded AgentOS MCP + Inbox MCP, run, destroy.
- Kanban UI: create task, assign default agent, run now, watch status go todo → doing → done.
- Session record with basic tool-call log.
- Foundational + default role prompt (reconstructed).
**Done when:** you can create a task, an agent session starts, the agent updates the task through the MCP, the container/session is destroyed, the card is`done`

.

- Environment network policy (
`open`

|`limited`

+ allowlist) enforced at the runner proxy. - Per-agent MCP / repo / env grants.
- R2 + filesystem MCP with server-side read/write/delete ACLs and per-agent folders.
- Secret refs via Google Secret Manager (or Cloud KMS). Inject at session start only.
- UI file browser (open/edit/download/preview).
**Done when:** a support-style agent with only a fake Front MCP cannot call GitHub or read another agent's folder; a plan agent cannot use a GitHub connection that exists on the project but is not granted to it; delete is denied without`canDelete`

.

- TaskTemplate + instantiate.
- Approval gates enforced in API + MCP.
- Follow-up chain scheduler.
- Seed
`compound-engineer-workflow`

with all 9 steps and the four plan-review specialists. - Schedule-at and recurring cron on tasks.
**Done when:** instantiating the feature template creates 9 cards; step 2 does not start until a human marks step 1`done`

; an agent token cannot mark step 1`done`

.

- Goal + DoD generate/approve + progress log.
- Orchestrator after every session.
- Spend cap, max time, stuck-at-19.
- Per-goal runner preference.
**Done when:** a small goal with a 2-item DoD completes by spawning at least two specialist sessions; a goal with stuckThreshold=2 stops after two no-progress iterations; a spend cap of $0.00 refuses to spawn.

- Webhook receiver + secret.
- Seed support-inbound and bug-report trigger shapes.
- Bug-report approval → fix chain (implement → plan → plan review → fix → E2E).
- Named cron automations (LinkedIn examples as fixtures).
**Done when:** a signed webhook creates a task and session for the scoped agent; a cron entry fires once in a test clock.

`agentos.yml`

schema matching UI.`push`

/`pull`

/`project create`

/`goal create`

/`task create`

/ agent update /`skill create`

.**Done when:** a YAML file pushed from CLI produces the same agents+template you can see in the UI, and`pull`

is a no-op after`push`

.

- Inbox as installable PWA, web push on "needs help" and "done".
- Multiple-choice radio questions.
- Live session viewer (SSE/websocket of tool calls).
- Activity feed.
- Local runner worker + routing (planners cloud, workers local/Grok).
**Done when:** an agent`inbox.ask`

shows radio buttons on a phone-sized viewport; answering resumes the session; a planner session is routed cloud and a worker session can be forced local.

Automated tests an implementing agent must add:

**Session destroy:** after a successful run, no runner handle remains; a second task reclones the repo rather than reusing a dirty workspace.**ACL filesystem:** write without`canWrite`

fails; delete without`canDelete`

fails; path escape (`../`

) fails.**ACL MCP:** agent without GitHub connection cannot invoke GitHub tools even if the project has one.**Network wall:** limited environment to`api.front.com`

cannot open`github.com`

.**Approval gate:** agent session token`PATCH done`

→ 403; human token → 200; follow-up still`todo`

until then.**Template chain:** 9 tasks, order respected, variables (branchName) interpolated into prompts.**Inbox resume:**`waiting-inbox`

+ reply → session continues with the answer in context.**Multiple-choice:** message with`choices`

renders and stores`selectedChoiceId`

.**Goal rails:** spend / time / 19-iteration stuck all set`stopped-*`

and do not spawn.**DoD approval:** goal will not spawn before`dodApproved`

.**Webhook auth:** bad secret → 401; good secret → task+session.**YAML round-trip:** push then pull is identity (modulo whitespace).**Least-privilege support agent:** fixture agent with Front only; test asserts GitHub, Gmail, and repo clone are absent from the session manifest.**Orchestrator spawn list:** cannot spawn an agent not on the collaboration / project allow list.

Manual / demo script (human):

- Morning: write a spec, create a goal, approve DoD, leave.
- Evening: PR exists or inbox asked a real question.
- Trigger: POST a fake support payload, see an assignment note.
- Phone: receive a push, answer a radio question.

| Item | Status |
|---|---|
| His exact foundational and role prompt files | Unknown. Use reconstructed contracts in §8 and label them. |
| Exact Google secret product name | He forgot. Use Secret Manager; Cloud KMS is acceptable if you need envelope encryption. |
| His own future custom runner | Mentioned as in progress. Not specified. Use SDK + local VM interface only. |
| Exact fourth plan-review agent name | He said four reviewers and named feasibility, scope-guardian, coherence. Add a fourth `plan-risk` specialist with a reconstructed "risk / missing-test" lens, and label it reconstructed. |
| Exact E2E harness | "E2E testing is implemented inside the workflow" — require the implementation step (and bugfix chain) to run the repo's existing E2E if present; do not invent a product-wide test framework. |
| Front / Ahrefs / Gmail / MongoDB | Examples of MCP/env grants. Integrate as configurable connections, not hardcoded vendors in the core. |
`fight-for` game repo |
Example mount only. |
| Cost figures ($500/day, $1000 night, $10 VM) | Anecdotes that motivate runner routing and spend caps, not SLOs. |
| Open-sourced agents/skills/prompts | Offered, not provided. Do not block on them. |
| Deep-dive / code-walk videos | Future content, not a spec. |
| Multi-user / teams / billing | Not described. Single operator. |
Exact CLI binary name beyond `agentos` |
Use `agentos` . |
| Exact YAML filename | He said "an AgentOS file." Use `agentos.yml` at project root. |

- Implement in the phase order in §21.
- Least privilege is not a phase-2 nice-to-have you skip; Phase 1 may hardcode grants, Phase 2 must enforce them.
- Do not add Slack, email-the-user, or extra chat products. Inbox is the human channel.
- Do not persist a container to "save time."
- Do not give agents raw cloud credentials.
- Do not copy this document's transcript-of-origin story into the product UI.
- Every reconstructed prompt file must start with a comment:
`Reconstructed from Danny Postma's AgentOS talk — not his verbatim prompt.`

- If a choice is not specified here, pick the simplest thing that preserves isolation and the session lifecycle.

You define agents (plan, senior-dev, …) with only the tools they need. You file a task or a goal. AgentOS starts a throwaway container, clones the allowed repo, injects allowed secrets, and lets that agent work. When it needs you, you get an inbox push. When it is done, the container is gone and a commit or a PR is left behind. Recurring jobs and webhooks use the same path. A feature template runs spec (you approve) → plan → multi-agent plan review → revise → implement with E2E → code review → fixes → wiki → you merge. A goal loop keeps dispatching specialists until the definition of done is checked, or spend/time/stuck rails stop it.
