# Suna / Kortix source analysis: an AI command center that runs a company as a Git repository

> **Subject**: [kortix-ai/suna](https://github.com/kortix-ai/suna) (product name **Kortix**; the repo name is a historical leftover still called Suna)
> **Based on commit**: `5862e95671cfd198c6a94ceb9c1fefe579f00a8b` (full-checkout deepened edition; `git rev-parse HEAD`)
> **Version**: root `VERSION` is `0.10.14`; the README badge still says `0.9.98` (`README.md:12`) — **treat the `VERSION` file as canonical**
> **Analysis date**: 2026-07-24
> **Document version**: **full-checkout deepened edition** (relative to the old sparse edition, this substantially thickens `apps/web`, the full change-request path, snapshot/providers, and `packages/{manifest-schema,starter,registry,executor-sdk}`; every claim can be traced to `file:line`)
> **Audience**: beginners with a little programming. Each chapter tries to follow “metaphor → real source → critical-path explanation → design trade-offs.”
> **Note**: this lecture uses the same **12-chapter** frame as the sibling analyses of Claude Code / opencode / hermes-agent / Raven / CodeWhale / nanobot / goose / OpenManus, so you can compare them sideways. All `file:line` citations are relative to `参考项目/suna/`. This workspace is a **full tree** (including `apps/web`, `desktop-electron`, `mobile`, and every `packages/*`). Features that cannot be found, or that we are not sure about, are explicitly marked **not found** — never invented.
> **Product axis**: Kortix aims at a “company operating system / general-task Agent platform” (research, deliverables, automation, multi-channel kickoff). It is **not** a Claude Code–style “write code in this repo first” single-machine coding agent. It treats **OpenCode** as the brain inside the sandbox, and itself owns isolation, permissions, connectors, billing, and change requests.

---

## Chapter 1 · Project overview: what it is, who builds it

### One-sentence version

**Kortix (GitHub repo `kortix-ai/suna`) is an AI Command Center that takes “the company” literally and makes it a Git repository**: configuration, agents, skills, memory, and sandbox specs all live in the repo; each session boots an isolated sandbox that runs OpenCode; only changes that have passed a **change request** review can merge back to `main`. The license is **Elastic License 2.0** (`LICENSE:1`), not MIT — it explicitly restricts offering this software as a hosted service to third parties (`LICENSE:18-20`).

The README title *is* the product name (`README.md` opens with `# Kortix`); site metadata pins the positioning as “open-source AI command center for your company” (`apps/web/src/lib/site-metadata.ts:16`). The MANIFESTO’s first sentence is even blunter (`MANIFESTO.md:3`):

> A company is going to be a git repository.

### Understand the positioning with a metaphor

If Claude Code is a “fine-dining sushi bar” (the menu is built around writing code), and OpenManus is a “street-stall research travel agency” (single-machine ReAct + a browser), then Kortix is more like a **gated tech park plus a self-serve workstation system**:

- The **blueprint of the company building** is a Git repo (`kortix.yaml` + `.kortix/opencode/`);
- **Each time you start work**, you get an independent workstation (a cloud sandbox) with the toolchain and OpenCode already installed;
- **If you want to keep the work**, you can only open a CR (change request) for human review — you cannot graffiti the shared lobby wall;
- **The front desk** can be Web / CLI / Desktop / Mobile / Slack / Teams / Email — every utterance still enters the same session lifecycle.

The MANIFESTO states the other half of the bet in the open (`MANIFESTO.md:7-9`): the AGI era needs a “WordPress / command center” — context, agents, skills, triggers, connectors, and memory in one place, not forty tools taped together.

### Who builds it / what it is called

| Item | Evidence |
|---|---|
| Org / repo | `kortix-ai/suna` (README / repo URL) |
| Product name | **Kortix** (README title, CLI `kortix`, package names `@kortix/*`) |
| Historical name Suna | repo name, `suna-migration/`, some comments still keep it |
| License | Elastic 2.0 (`LICENSE:1-20`) |
| Version | `VERSION` → `0.10.14`; README badge still `0.9.98` (`README.md:12`) |
| Language | main repo TypeScript; runtime Bun (API / daemon); pnpm workspace (`package.json:60`; `pnpm-workspace.yaml:1-3`) |
| Web package name | `Kortix-Computer-Frontend`, Next `15.5.21` (`apps/web/package.json:2,159`) |
| Desktop | Electron shell package `@kortix/desktop-electron`, wrapping Web (`apps/desktop-electron/package.json:2-7`) |
| Mobile | Expo app `kortix` (`apps/mobile/package.json:2-4`) |

### Stack at a glance

| Layer | Tech | Location / evidence |
|---|---|---|
| Frontend host | Next.js 15 App Router (Turbopack in development) | `apps/web/package.json:6,159`; `AGENTS.md:179` |
| Client SDK | `@kortix/sdk` (SSoT; hosts must not talk to the API/OpenCode directly) | `AGENTS.md:91-137`; `packages/sdk/src/index.ts:1-39` |
| API | Bun + Hono OpenAPI, default `:8008/v1` | `AGENTS.md:180`; `apps/api/src/index.ts` |
| Data | Supabase + Drizzle (`@kortix/db`) | `AGENTS.md:181`; `packages/db/` |
| Manifest contract | `@kortix/manifest-schema` (`kortix.yaml` validation SSoT) | `packages/manifest-schema/package.json:2-4` |
| Scaffolding | `@kortix/starter` (init / create-repo templates) | `packages/starter/package.json:2-4` |
| Marketplace engine | `@kortix/registry` (shadcn-compatible registry) | `packages/registry/package.json:2-4` |
| Connector client | `@kortix/executor-sdk` (server-side proxy send) | `packages/executor-sdk/package.json:2-5` |
| Sandbox providers | Daytona / Platinum / E2B / local-docker | `provider-parity.test.ts:11-34` |
| Brain inside the sandbox | OpenCode (`opencode serve`) supervised by `kortix-agent` | `apps/sandbox/README.md:15-19`; `kortix-sandbox-agent-server/README.md:6-12` |
| Model gateway | `apps/llm-gateway` + API-side routing | `apps/api/src/llm-gateway/`; `index.ts:740-741` |
| Connector execution | `/v1/executor`, credentials stay on the server | `apps/api/src/executor/execute.ts:1-5` |

```mermaid
flowchart LR
    subgraph Hosts["Host surface"]
      WEB["apps/web Next.js"]
      DESK["desktop-electron"]
      MOB["mobile Expo"]
      CLI["kortix CLI"]
      CH["Slack / Teams / Email / …"]
    end
    SDK["@kortix/sdk"]
    API["apps/api :8008"]
    DB[(Supabase / @kortix/db)]
    SBX["Cloud sandbox<br/>session_id == sandbox_id"]
    DAEMON["kortix-agent"]
    OC["opencode serve"]
    CR["change_requests"]

    WEB --> SDK
    DESK --> WEB
    MOB --> SDK
    CLI --> API
    CH --> API
    SDK --> API
    API --> DB
    API -->|"/v1/p/:id/8000"| SBX
    SBX --> DAEMON --> OC
    OC -->|"commit + kortix cr"| CR
    CR --> API
```

### Axis differences versus other projects in this series (plant the target first)

- **vs Claude Code / opencode on a single machine**: Kortix **embeds OpenCode inside a multi-tenant sandbox OS**, and itself owns identity, billing, connectors, and CRs.
- **vs OpenManus**: OpenManus is an in-process ReAct inheritance chain; Kortix’s “think one step, do one step” lives in the **OpenCode binary**. This repo writes the platform spine. See the short note at the end of this chapter and Chapter 12.
- **vs nanobot / Raven**: nanobot is a personal IM butler + a small core; Kortix is an enterprise command center + Elastic license + sandbox isolation.

### Contrast with OpenManus (short note)

Same axis (general tasks / research / deliverables), different bets: OpenManus bets on “a ReAct inheritance chain you can understand in three hours”; Kortix bets on “the company repo + one session, one sandbox + change request.” On license, OpenManus is MIT; Kortix is Elastic License 2.0. Details are in the sibling lecture *OpenManus-vs-Suna side-by-side comparison*.

---

## Chapter 2 · Panorama architecture: a layered map

### Metaphor: six floors of the tech park

1. **Gate and reception (Hosts + SDK)**: Web/CLI/IM/Mobile enter the park only through `@kortix/sdk` or the official CLI (`AGENTS.md:110-118`).
2. **Dispatch center (`apps/api`)**: session lifecycle, project Git, billing, IAM, channel webhooks, sandbox proxy.
3. **Tool broker (executor + llm-gateway)**: third-party APIs and model traffic are sent from the server; the sandbox holds only a scoped token.
4. **Workstation (sandbox + kortix-agent)**: clone the repo, switch to the session branch, bring up OpenCode, expose 8000 / health checks / static preview.
5. **Archives (git + change_requests + DB mirror)**: truth lives in the sandbox and git; the DB is for list/sync acceleration (`MANIFESTO.md:50`).
6. **Contract layer (`packages`)**: manifest-schema / starter / registry / sdk / db / executor-sdk — so CLI, API, and Web share one set of shapes.

### Directory map (full checkout)

| Path | Responsibility |
|---|---|
| `apps/web/` | Next.js Command Center UI; thin consumer of SDK |
| `apps/api/` | multi-tenant control plane; `src/index.ts` mounts almost every `/v1/*` |
| `apps/cli/` | `kortix init/ship/sessions/cr/…` |
| `apps/sandbox/` | sandbox Docker reference image and entrypoint |
| `apps/kortix-sandbox-agent-server/` | in-sandbox daemon source (compiled into the image) |
| `apps/llm-gateway/` | gateway service implementation (API also has routing hooks) |
| `apps/desktop-electron/` | Electron shell, loads the Web URL |
| `apps/mobile/` | Expo mobile client |
| `packages/sdk/` | the host’s only data layer |
| `packages/db/` | Drizzle schema (including `change_requests`, `session_sandboxes`) |
| `packages/shared/` | cross-package constants/utils |
| `packages/manifest-schema/` | `kortix.yaml` spec and validator |
| `packages/starter/` | project templates (init / create-repo) |
| `packages/registry/` | Marketplace discovery/build primitives |
| `packages/executor-sdk/` | connector-call client |
| `packages/agent-tunnel/` | device tunnel |
| `packages/api-contract/` | Zod wire contracts |
| `packages/llm-catalog/` | model catalog |

### What the Web-side “Command Center” actually looks like

Product copy repeatedly calls it a command center (`apps/web/src/lib/site-metadata.ts:16`; marketing hero `apps/web/src/features/marketing/hero.tsx:46`). The real in-app operating surface is the project workspace under the App Router:

| Route | File | Role |
|---|---|---|
| `/projects/[id]` | `apps/web/src/app/(app)/projects/[id]/page.tsx` | project home: `ProjectHome` composer opens a new session |
| `/projects/[id]/sessions/[sessionId]` | `…/sessions/[sessionId]/page.tsx` | **the session page that mounts `useSession`** |
| `/projects/[id]/customize/…` | customize pages | Agents / Secrets / Sandbox / Review, etc. |
| `/review` | `apps/web/src/app/(app)/review/page.tsx` | Review Center **prototype** (mock, no API) |
| In-project Review | `features/workspace/…/review-view.tsx` | **real data** wired through `ReviewCenterConnected` |

`features/` is cut by domain: `session/` (chat and tool rendering), `workspace/` (sidebar/home), `review-center/`, `project-files/`, `marketplace/`, `providers/` (Auth / LLM provider UI).

### API route spine (one table for the control plane)

`apps/api/src/index.ts` hangs subsystems off a unified Hono app (excerpt, `index.ts:693-851`):

- `/v1/accounts`, `/v1/auth`, `/scim/v2` — identity and enterprise directory
- `/v1/router`, `/v1/generation`, `/v1/usage` — model routing and usage
- `/v1/billing` — Stripe and related billing
- `/v1/platform` — sandbox platform capabilities
- `/v1/projects` — Git-backed projects and sessions (including change-requests)
- `/v1/marketplace` — Agent/skill marketplace
- `/v1/git` — git-proxy (conditionally mounted)
- `/v1/executor` — connector execution
- `/v1/webhooks/*` — Slack/Teams/Telegram/Email/Meet/sandbox lifecycle
- `/v1/tunnel` — device tunnel
- `/v1/p` — **sandbox preview/proxy** (the external entry for OpenCode SSE/HTTP)

The process-level crash guard is deliberately “log only, do not take down the whole machine” (`index.ts:98-119`) — a typical trade-off for a long-lived multi-tenant service.

### Two “main loops” you must separate first (the key)

| Loop | Lives where | Does what |
|---|---|---|
| **Platform session loop** | `apps/api/.../session-lifecycle/` | create → provision sandbox → start/ready → continue/deliver prompt → stop/restart; queues, idempotency, backpressure |
| **Model–tool loop** | OpenCode (binary inside the sandbox) | classic Agent: read context → call tools → observe → think again; this repo **does not rewrite** that chain |

The daemon itself states the boundary (`kortix-sandbox-agent-server/README.md:6-24`): it only supervises `opencode serve`, reverse-proxies HTTP/SSE, health-checks, and static preview; triggers/channels/connectors/secrets are **not** the daemon’s job.

### The SDK is the host’s only data layer (architectural iron law)

`AGENTS.md:91-137` writes this in stone:

- Logic goes into the SDK; hosts must not raw-`fetch` or use `@opencode-ai/sdk`;
- One host, one client: `createKortix` / `configureKortix`;
- **One hook for an entire session**: `useSession(projectId, sessionId)`;
- `apps/web`’s `stores/server-store`, `hooks/opencode/use-*` are **shims** (e.g. `apps/web/src/stores/server-store.ts:2`: `export * from '@kortix/sdk/server-store'`; `hooks/opencode/use-session-sync.ts:2`: `export * from '@kortix/sdk/react'`).

Web’s only wiring port: `apps/web/src/lib/kortix-config.ts:3-30` uses `configureKortix({ backendUrl, getToken, … })` to inject the Supabase JWT and toast/notification sinks.

### Four often-ignored packages (summary)

**`@kortix/manifest-schema`** (`packages/manifest-schema/src/index.ts:1-15`): the canonical schema for `kortix.yaml` plus a pure-function validator. Consumers: CLI `ship` pre-check, backend CR-merge gate, `kortix validate`. No I/O, no DB.

**`@kortix/starter`** (`packages/starter/src/index.ts:1-48`): folder templates + `getStarterFiles()`; the default user template is `general-knowledge-worker`; internally it also keeps `minimal` for clone/seed. API create-repo and `kortix init` share it.

**`@kortix/registry`** (`packages/registry/src/index.ts:1-12`): shadcn-compatible registry discovery/build; **install has already been changed to agent import** (read the source, merge files, land a CR) — there is no longer a deterministic install engine.

**`@kortix/executor-sdk`** (`packages/executor-sdk/src/index.ts:41-50`): a typed client that carries a token; can go through `/executor/projects/:projectId/...`, so “CLI on a laptop” and “session token inside the sandbox” call the same shape, while secrets stay on the server.

### Design trade-offs (architecture layer)

- **Thick platform, thin host**: logic goes into SDK/API; Web is a thin consumer (`AGENTS.md:91-133`). The cost is a huge monorepo and a long path for newcomers.
- **Truth is in the sandbox + git; the DB is a mirror**: lists are fast, but debugging requires looking at three places at once (`MANIFESTO.md:50`).
- **Elastic license**: you can self-host, but you cannot easily turn it into “someone else’s competing SaaS” — the commercial moat is written into the license.

> 🧠 **One sentence**: thick platform, thin host — all the logic is pressed into the SDK and API; Web is only a consumer; truth lives in the sandbox and git; the database is just an acceleration mirror.

---

## Chapter 3 · Startup flow: from command to UI

### Metaphor: get a park pass → be assigned a workstation → power on → open the door for guests

### 3.1 Two “from zero to chat” paths: local / cloud

**Scaffolding (build the company repo on a laptop)**

`kortix init` creates a project that already has an OpenCode runtime and `kortix.yaml` (`apps/cli/src/commands/init.ts`; templates come from `@kortix/starter`, `packages/starter/src/index.ts:1-14`). You can symlink `.kortix/opencode` into Claude/Codex/Cursor native directories so the same agents/skills are shared by multiple coding agents.

**Ship**

`kortix ship`: validate the manifest → commit → fill secrets → push → connect connectors (`apps/cli/src/commands/ship.ts` help text; manifest validation goes through `@kortix/manifest-schema`, `packages/manifest-schema/src/index.ts:5-11`). The product promise is “the same repo, laptop and cloud behave the same” (`MANIFESTO.md:38`).

**Local full-stack development**

`pnpm dev` brings up Web(:3000) + API(:8008) + local Supabase + a cloudflared tunnel (`AGENTS.md:178-193`; root `package.json:5`). Secrets enter the repo as dotenvx ciphertext (`AGENTS.md:195-202`).

### 3.2 How a Session is “born” on the control plane

The core state machine lives in `apps/api/src/projects/session-lifecycle/engine.ts`:

1. **`createSession`** (`engine.ts:44`)
   - Can queue for backpressure via `queuePolicy` (`always` / `on_backpressure` / `never`).
   - Supports idempotency keys; on conflict, checks connector bindings / secrets allowlist; a cross-tenant key collision is rejected outright.
   - Actually building the session goes through `executeCreateSession` → `createProjectSession` / sandbox provision.

2. **Sandbox provision** (`apps/api/src/platform/services/session-sandbox.ts:1-12`)
   - Inserts a row into `kortix.session_sandboxes`; **the primary key is the UUID the caller supplied, and it equals the project session id**.
   - Fire-and-forget: return `provisioning` first; provider `create()` runs in the background.
   - When minting the executor token, a comment writes it in stone: `session_id == sandbox_id`, so the LLM gateway can attribute usage to that session (`session-sandbox.ts:147-149`).
   - Route-layer invariant: `session_id == sandbox_id == git branch name` (`apps/api/src/projects/routes/r7.ts:348`).

3. **`startSession`** (`engine.ts:206`)
   - Calls `openSession`; optional `waitMs` is a server-side long-poll until `ready` or the deadline (so the client does not spin empty).

4. **Host side (Web is checked out)**
   - SDK: `configureKortix` / `createKortix` → `useSession` (`packages/sdk/src/react/use-session.ts:3-19`).
   - Web session page: `ProjectSessionPage` mounts `useSession(projectId, sessionId, { enabled, replayStartStash:false, chatEngine:false })` once (`apps/web/src/app/(app)/projects/[id]/sessions/[sessionId]/page.tsx:46-100`).

### 3.3 Snapshot builder: where the workstation image comes from (deepened)

`apps/api/src/snapshots/builder.ts:1-14` positions the builder as a “thin orchestrator”:

1. `(project, slug)` → `ResolvedTemplate`;
2. Compute a content-addressed snapshot name;
3. **Ask the provider**: if the image is already active, use it; otherwise inline-build.

The key philosophy (same-file comment): **the boot path never trusts a DB row to decide “does the image exist” — it asks the provider every time**; DB `project_snapshot_builds` is only cache + audit + UI “Fix with agent” (`builder.ts:9-13`).

Trigger-source enum (`builder.ts:56-62`): `session-start` / `project-create` / `cr-merge` / `manual` / `background` / `startup` — so **a CR merge also drives a rebuild**; company-config changes show up in the next sandbox.

Cross-replica dedup: `waitForProviderBuild` polls to settlement when the provider already has a same-name build; on timeout it stays `building` and fails closed, so you do not get two same-name builds (`builder.ts:67-72`).

The provider list is constrained by a single source of truth, `KNOWN_PROVIDERS`; the parity test requires `daytona/platinum/e2b/local-docker` to match the template provider set (`apps/api/src/platform/providers/provider-parity.test.ts:11-34`). `local-docker` can be admitted without a cloud-vendor API key (same file `:70-74`).

The sandbox image itself (`apps/sandbox/README.md:15-28`) only bakes git, opencode, and `kortix-agent`; triggers/channels/connectors/secrets **do not enter the image** — they rely on create-time env / HTTP.

### 3.4 In-sandbox boot order (daemon)

`kortix-sandbox-agent-server` README boot flow (`README.md:6-24` + Boot flow section):

1. The supervisor process brings up `opencode serve`; Hono reverse-proxies to `KORTIX_SERVICE_PORT` (default 8000);
2. `/kortix/health`, `/kortix/refresh`;
3. Static preview port `KORTIX_STATIC_PORT` (default 3211) — `apps/web` composes preview URLs with `/proxy/3211/*` and the `p3211-<sandboxId>` subdomain (daemon README:14-18);
4. Materialize the repo **first**, then resolve OpenCode config (source comments emphasize the order).

### 3.5 Web: opening the door from project home to session page

1. `/projects/[id]` renders `ProjectHome` (welcome wall + `ComposerChatInput`) (`project-home.tsx:46-58`; `page.tsx:6-9`).
2. User sends → `writeStartStash` stuffs the first sentence into the SDK start-stash (`page.tsx:17`; `use-session.ts` header comment) → `useNewProjectSession` creates the session and navigates.
3. Arrive at `/sessions/[sessionId]`: `useSession` owns POST `/start`, sandbox switch, SSE, ready-seed, and the canonical OpenCode id (`page.tsx:46-59,87-100`).
4. Fresh sessions use `InstantSessionShell` with a cross-fade; resume uses `SessionStartingLoader` (`page.tsx:362-410`).
5. `ActiveSessionChat` then mounts `SessionLayout` + `SessionChat` (`page.tsx:649-659`); chat-message truth comes from `useSessionSync` (shim → SDK) (`session-chat.tsx:3482-3491`).

### Design trade-offs

- **Async provision + long-poll start**: first packet can take minutes; queues and `waitMs` absorb it.
- **Snapshot treats the provider as truth**: avoids DB drift; the cost is one extra remote query on every boot.
- **The page splits `useSession` lifecycle from the `SessionChat` message engine**: `chatEngine:false` avoids double-mounting `useSessionSync` (`page.tsx:91-95`).

---

## Chapter 4 · Input capture and fan-out: the journey of one sentence

### Metaphor: many storefronts take orders; the kitchen only recognizes one kind of “work ticket”

### 4.1 Entry matrix

| Entry | How it enters the park | Critical path |
|---|---|---|
| Web | `ProjectHome` / `SessionChat` → SDK | `project-home.tsx`; `session-chat.tsx`; `use-session.ts` |
| Desktop | Electron loads the Web URL | `apps/desktop-electron/package.json:12-14` |
| Mobile | Expo + SDK | `apps/mobile/package.json` |
| CLI | `kortix sessions` / `sessions-chat` | `apps/cli/src/commands/sessions*.ts` |
| Slack | webhook → binding → lifecycle | `index.ts:788-792`; `channels/slack/` |
| Teams / Telegram / Email / Meet | each has its own webhook | `index.ts:790-796` |
| Triggers | `projectWebhooksApp` + scheduler | `index.ts:775` |

Channel messages eventually have to become: **a session on some project + a piece of text (or a first-time create+prompt)**.

### 4.2 Web composer path (deepened)

Project-home `onSend`: billing gate → `writeStartStash(sessionId, { prompt })` → create session → `router` into the session page. The session-page comment is explicit: `replayStartStash:false`, because Web has its own pending-prompt handoff (`page.tsx:91-92`); `ActiveSessionChat` `migrateStash(routeId → chatSessionId)` after the canonical OpenCode id is resolved (`page.tsx:521-537`).

`InstantSessionShell` also reads/writes start-stash (`instant-session-shell.tsx` imports `readStartStash, writeStartStash`), which keeps the “sandbox is still waking, user can already type” instant feel.

### 4.3 Continue-chat delivery: `continueSession`

When the session already exists, a channel or scheduler calls `continueSession` (`engine.ts:252`):

1. Read `projectSessions`; abandon immediately if `failed`; if `metadata.deletedAt` exists, treat it as deleted (even if user status is `stopped`) — so a Slack reply cannot “resurrect” a deleted session.
2. `stopped/completed` flip back to `running`.
3. Poll `openSession` until `ready`, total budget `READY_DEADLINE_MS = 300_000` (5 minutes) (`engine.ts:39,320`).
4. `deliverWithRetry`: keep delivering inside a 45s window, healing “just-woke sandbox 404/5xx” (`deliver.ts:12-13,28`).

What **actually stuffs the utterance into the brain** is `postPrompt` (`engine.ts:668-685`):

```text
forwardToSandbox(externalId, port=8000, …)
  POST /session/{opencodeSessionId}/prompt_async?directory=/workspace
  body: { parts: [{ type: 'text', text }] }
```

The control plane **does not parse** tool_calls; it only asynchronously dumps user text into OpenCode.

### 4.4 SDK / cache-key fan-out

The `useSession` comment calls itself “the one hook a host needs to open a session and stream chat” (`use-session.ts:3-19`). The cache key deliberately includes `activeServerKey()` (the current sandbox id), because **each project session is an independent sandbox** (`session_id == sandbox_id`); otherwise switching sessions would mix data (`packages/sdk/src/react/use-opencode-sessions/shared.ts:17-30`).

### 4.5 Slack thread stickiness (short)

`bindChatThread` writes `(platform, workspaceId, threadId) → sessionId` into `chat_threads` — create and webhook continue-chat share the same registry, otherwise follow-ups cannot find a home.

### Design trade-offs

- **Delivery and ack are decoupled**: Slack acks the webhook first, then delivers in the background — you must have heal/retry (`deliver.ts:7-11`).
- **Delete is a soft-delete semantic**: it relies on `metadata.deletedAt`; you cannot look only at the status enum.
- **Web instant shell vs SDK default replay**: the host can turn off `replayStartStash` and use its own UX to hand off the first sentence.

---

## Chapter 5 · Context assembly: packing for the model

### Metaphor: employee handbook (repo) + this work ticket (session) + badge permissions (grant)

Kortix’s context is **not** a giant `messages[]` array assembled in Python the way nanobot does it; it is:

1. **Static intelligence in the repo**: agents / skills / opencode.jsonc
2. **Governance layer compiled on the server**: connectors / secrets allowlist / enable flags
3. **Env and `OPENCODE_CONFIG_CONTENT` injected into the sandbox**
4. **The session transcript OpenCode itself maintains** (inside the sandbox)
5. **Manifest contract**: `@kortix/manifest-schema` guarantees `kortix.yaml` has a legal shape

### 5.1 One concern, one place: behavior vs governance

The long comment in `compile-agent-config.ts` writes the architectural turn clearly:

- **Behavior** (mode/model/temperature/steps/permission/prompt…) lives in `.kortix/opencode/agents/<name>.md` — as-is, that file is already a legal OpenCode agent file.
- **Governance** (connectors/secrets/skills/kortix_cli/workspace/enabled) lives in the `agents:` map of `kortix.yaml`.
- The compiler is a pure function: copy the md parse result into the OpenCode `agent` map; `enabled: false` forces `disable`; skills fold into `permission.skill`.

### 5.2 How manifest validation hard-stops bad config

The header comment of `@kortix/manifest-schema` lists three gates (`packages/manifest-schema/src/index.ts:5-11`):

1. `kortix ship` pre-check (a bad manifest does not get pushed);
2. Backend CR-merge gate (raw git / web edits that bypass the CLI are still blocked);
3. `kortix validate` explicit report.

Errors are structured (path + severity + message + optional row/column); callers can color diagnostics. This contrasts with OpenManus’s “bad config only explodes at runtime” — **Kortix lifts config correctness into a product latch**.

### 5.3 How the daemon stuffs the “packed result” into OpenCode

`buildOpencodeConfigContent` merges several forces (Executor MCP, Kortix LLM gateway provider, Slack permission overlays, `KORTIX_COMPILED_AGENT_CONFIG`). If all are empty, it uses the in-repo OpenCode config entirely. The daemon **only layers; it does not invent behavior itself**.

### 5.4 Runtime env dictionary (birth certificate)

`buildSessionRuntimeEnv` writes the contract keys between the control plane and the daemon (see the old deepened appendix; now woven in):

| Env | Meaning |
|---|---|
| `KORTIX_REPO_URL` / `KORTIX_BASE_REF` | which company to clone, which baseline to grow from |
| `KORTIX_BRANCH_NAME` | **equals `sessionId`** — session branch name and session identity are one |
| `KORTIX_PROJECT_ID` / `KORTIX_SESSION_ID` | coordinates when calling back the API |
| `KORTIX_SERVICE_PORT=8000` | daemon’s external port, also the convention for `/v1/p/:id/8000` |
| `KORTIX_COMPILED_AGENT_CONFIG` | v2 compiled-product JSON string |
| `KORTIX_INITIAL_PROMPT` | optional first sentence |

If you understand this table, you understand where “context assembly” lands on the platform side: it is not assembling `messages[]`; it is **writing env + writing a git branch + writing compiled config**.

### Design trade-offs

- **“OpenCode-native files are the behavior source”** lowers dual-config drift; the cost is that the control plane must learn to read md in git.
- **Governance is not delegated into md**: so an Agent cannot rewrite its own permission boundary and skip YAML — it only takes effect after a CR merges.
- **manifest-schema reused in three places**: one schema serves CLI/API/future tools, avoiding “everyone writes their own YAML dialect.”

> 🧠 **One sentence**: Kortix’s “context assembly” is not assembling a message array in memory; it is writing a git branch + writing environment variables + writing compiled config — truth lands in files, not in a process.

---

## Chapter 6 · The agent main loop: the project’s heart

### Cold water first: this repo has no `while True: think(); act()`

OpenManus’s heart is `ToolCallAgent.think/act`; nanobot’s heart is `AgentRunner`.
**Kortix’s “model heart” is inside the OpenCode process**; this repo’s heart is **Session Lifecycle + Daemon Supervisor + Proxy + (on the host) `useSession`**. Putting that in the title is to stop readers from spinning in `apps/api` looking for ReAct.

### 6.0 First look at a real task: how one Slack message becomes a CR waiting for review

The abstract “three loops” sound winding. First watch a concrete task land. A colleague @’s the bot in Slack: **“Make the landing-page title a bit more forceful.”** Follow that sentence and you will see which baton each loop takes:

| Stage | Who is doing it | What they did | Source anchor |
|---|---|---|---|
| ① Take the order | **Platform loop** | Slack webhook comes in; find (or create) this conversation by `(platform, workspace, thread) → session` | `index.ts:788-792`; `bindChatThread` |
| ② Wake the workstation | **Platform loop** | `continueSession`: if the session was hibernating, wake the sandbox; wait up to 5 minutes until “ready” | `engine.ts:252`; `READY_DEADLINE_MS` |
| ③ Deliver the utterance | **Platform loop** | `deliverWithRetry` keeps delivering inside a 45-second window, healing “the just-woke sandbox has not recovered yet” | `deliver.ts:28`; `postPrompt` (`engine.ts:668-685`) |
| ④ Actually think | **OpenCode loop** | OpenCode in the sandbox reads the repo, thinks one step, edits the landing-page file, thinks again — this is where ReAct happens | inside the OpenCode process (not implemented in this repo) |
| ⑤ Keep a record | **OpenCode loop** | After editing, it does not go straight to trunk; it `git commit` + `kortix cr open` to open a change request | `cr.ts:38-40` |
| ⑥ Report back | **Host loop** | If you have Web open, `useSession` paints every step onto the screen in real time via SSE | `use-session.ts:3-19` |
| ⑦ Govern | **Platform loop** | The CR appears in Review Center; only after a human clicks merge does the company actually change; merge also triggers a sandbox-image rebuild | `r9.ts:19-21`; builder source `cr-merge` |

Once you understand this table, you have Kortix’s biggest difference from OpenManus: **the endpoint of a task is not “the model finished speaking”; it is “produce a CR a human can review.”** The platform loop is responsible for reliably delivering the utterance and waking the workstation; real thinking and doing happen in OpenCode; and every change must pass the change-request human gate before it lands in “the company,” this Git repository.

```mermaid
flowchart LR
    SLACK["💬 Slack: tweak the landing-page title"] --> PLAT
    subgraph PLAT["① Platform loop (this repo)"]
        FIND["Find the session"] --> WAKE["Wake the sandbox"] --> SEND["Reliably deliver the utterance"]
    end
    SEND --> OC
    subgraph OC["② OpenCode loop (inside the sandbox)"]
        THINK["Read repo → think → edit files → think again"] --> COMMIT["git commit + kortix cr open"]
    end
    COMMIT --> CR["📋 change request (open)"]
    CR --> REVIEW["🧑‍⚖️ Human reviews in Review Center"]
    REVIEW -->|"merge"| MAIN["Company trunk changed<br/>+ trigger image rebuild"]
    OC -.->|"SSE live report"| HOST["🖥️ Host loop useSession"]
    style PLAT fill:#eaf1ff
    style OC fill:#e7f8f1
    style CR fill:#fff3df
    style MAIN fill:#fff3df
```

> 🧠 **One sentence**: the platform loop is responsible for “reliably delivering the utterance and waking the workstation”; the OpenCode loop is responsible for “think and do”; the host loop is responsible for “live display” — three batons in a relay; the finish line is a CR waiting for human review, not a sentence that says “I’m done.”

### 6.1 Separate the two loops (this chapter’s core)

```mermaid
flowchart TB
    subgraph PLATFORM["Platform loop (implemented in this repo)"]
      C["createSession"] --> P["provisionSessionSandbox"]
      P --> S["startSession / openSession"]
      S --> D["continueSession / deliverWithRetry"]
      D --> PP["postPrompt → prompt_async"]
      S --> STOP["stop / restart / queue drain"]
    end
    subgraph OPENCODE["OpenCode loop (sandbox binary)"]
      T["Read transcript + tools"] --> M["Call LLM (via gateway)"]
      M --> TOOL["Local tools / Executor / CLI"]
      TOOL --> T
    end
    subgraph HOST["Host loop (SDK + Web)"]
      US["useSession: /start + SSE + sync"] --> UI["SessionChat renders turns"]
    end
    PP --> OPENCODE
    US --> S
    OPENCODE -->|SSE via /v1/p| US
```

| Question | Platform loop answers | OpenCode loop answers |
|---|---|---|
| Is this workstation ready? | `stage==='ready'` (server-truth) | — |
| Did this sentence get in? | `deliverWithRetry` / `postPrompt` | receives `prompt_async` |
| Which tool next? | **not responsible** | model + tool schema |
| Where does the message list come from? | DB mirror can sync | in-sandbox transcript is truth |
| How does the host subscribe? | — | SSE; `useSession` / `useSessionSync` |

### 6.2 Platform heart: the session-lifecycle state machine

```mermaid
stateDiagram-v2
    [*] --> Creating: createSession
    Creating --> Queued: backpressure
    Queued --> Creating: drain queue
    Creating --> Provisioning: insert session_sandboxes
    Provisioning --> Ready: openSession stage=ready
    Provisioning --> Failed: provider/boot error
    Ready --> Running: prompt_async / user chat
    Running --> Stopped: stop/hibernate
    Stopped --> Running: continueSession wake
    Running --> Failed: terminal error
    Ready --> Deleted: deleteSession metadata.deletedAt
```

Key code anchors:

- Create and idempotency: `engine.ts:44`
- Start and long-poll: `engine.ts:206`
- Continue-chat and 5-minute ready wait: `engine.ts:252,39,320`
- Delivery heal: `deliver.ts:28`
- `postPrompt`: `engine.ts:668-685`

`openSession` (`projects/routes/shared.ts`) is responsible for resolving DB row + provider state into `stage`, and can resume when stopped / provision when there is no box — what continue waits for is not just “read status”; it may be **waking a hibernated disk**.

### 6.3 Sandbox heart: supervise OpenCode, do not replace it

Daemon duty list (README:6-12):

1. `spawn` / crash-restart / signal-drain `opencode serve`
2. Reverse-proxy its HTTP + SSE to port 8000
3. `/kortix/health`, `/kortix/refresh`
4. Static preview port 3211

After ready, it lowers liveness frequency (so idle sandboxes do not eat a full CPU) — typical platform-taste optimization.

### 6.4 Host heart: the `useSession` mount contract on Web

SDK header comment (`use-session.ts:3-19`):

> Everything sandbox-shaped is internal… The host imports `createKortix` + `useSession` and NOTHING else runtime-related.

The Web session page executes this faithfully (`page.tsx:46-100`):

- Mount `useSession(projectId, sessionId)` **once**;
- Billing gate blocks accounts with no plan, so you do not poll forever a sandbox that will never provision;
- `chatEngine:false`: the page only eats boot/lifecycle; `SessionChat` itself mounts `useSessionSync`;
- Mid-session reconnects use `useSandboxConnection`; initial ready trusts server `stage` (`page.tsx:55-58,419-424`).

This is why Chapters 4/6 talk about both the “platform loop” and the “host loop”: without `useSession`, Web would still hand-roll a 7-step mount — the page-header comment says they already deleted that set (`page.tsx:49-51`).

### 6.5 End-to-end timing of one user turn

```mermaid
sequenceDiagram
    participant U as User/Web/Slack/CLI
    participant SDK as @kortix/sdk
    participant API as apps/api
    participant P as /v1/p proxy
    participant D as kortix-agent
    participant O as opencode
    participant X as /v1/executor
    participant G as LLM gateway

    U->>SDK: useSession / send
    SDK->>API: /start + stream
    API->>API: continueSession / startSession
    API->>P: forwardToSandbox prompt_async
    P->>D: POST /session/:id/prompt_async
    D->>O: reverse proxy
    O->>G: chat.completions
    O->>X: connector call (scoped token)
    X-->>O: tool result
    O-->>D: SSE events
    D-->>P: SSE
    P-->>SDK: stream
    SDK-->>U: SessionChat turns
    Note over O: afterwards the agent can git commit + kortix cr open
```

### 6.6 Faults and self-heal

- **Delivery layer**: 45s `deliverWithRetry` (`deliver.ts`).
- **Web layer**: a hibernated but resumable sandbox will automatically invalidate `/start` at most 3 times (`page.tsx:106-139`).
- **Daemon layer**: on recoverable errors it can auto-resume a turn (source `turn-auto-resume.ts`; sub-agent failure is left to the parent model).
- **Control plane**: dead-letter marks the session failed.

### 6.7 Explicit boundaries related to the “main loop”

| Question | Answer |
|---|---|
| Where are tool_calls parsed? | Inside OpenCode (no equivalent implementation found in this repo) |
| Where is messages compaction? | Platform-level Curator **not found**; OpenCode may have a compress tool (SDK tool label) |
| Max steps? | Agent-md frontmatter `steps` etc. can be compiled into config; execution is still interpreted by OpenCode |
| Does Web implement ReAct? | **No**; it only renders OpenCode events and turns |

### Design trade-offs

- **Reuse OpenCode = stand on a giant’s shoulders**: you inherit the tool ecosystem immediately; the cost is debugging across three processes: API + daemon + OpenCode.
- **The platform loop is extremely heavy**: idempotency, backpressure, cross-tenant isolation, delete guards — the SaaS multi-tenant tax OpenManus’s single-machine script does not have to pay.
- **The host loop collapses into one hook**: when you swap in mobile/whitelabel you reuse the same ready semantics.

> 🔍 **Source pointers (go check the source)**: `session-lifecycle/engine.ts:44` (createSession)｜`:206` (startSession long-poll)｜`:252` (continueSession)｜`:668-685` (postPrompt → prompt_async)｜`deliver.ts:28` (delivery heal)｜`kortix-sandbox-agent-server/README.md:6-24` (daemon supervises OpenCode)｜`packages/sdk/.../use-session.ts:3-19` (host’s only hook)

### Contrast with OpenManus (short note)

OpenManus: `BaseAgent.run` → `think/act` in the same process. Kortix: the platform only guarantees “workstation ready + the utterance got in”; thinking and tools are in another process. When choosing, do not pair `engine.ts` against `ToolCallAgent.py`.

---

## Chapter 7 · Tool system and permissions: the model’s “hands” and “tight hoop”

### 7.1 Three pairs of hands

1. **OpenCode local tools**: files, shell, its plugins — they run inside the sandbox OS, naturally isolated by session branch. Web uses `features/session/tool/tools/*` to **render** these tool results (e.g. `bash-tool.tsx`, `read-tool.tsx`, `task-tool.tsx`); it does not execute them in the browser.
2. **Kortix Executor (connectors)**: OpenAPI/HTTP/MCP etc., **sent from the API process**; credentials are assembled on the server (`execute.ts:1-5`). The sandbox only sees a scoped token. Clients can use `@kortix/executor-sdk` (`packages/executor-sdk/src/index.ts:41-50`).
3. **In-sandbox `kortix` CLI**: `cr` / `secrets` / `sessions`… callbacks with `KORTIX_CLI_TOKEN` (`cr.ts:38-40`).

### 7.2 Executor: secrets never cross the border

This is the most important move in Kortix’s entire security model; pin it with one diagram first: **the Agent in the sandbox never gets real credentials.** When it wants to call a third party (send Slack, write Notion), it can only hand the request to the server-side Executor; the real API key is attached only on **this side of the API process**; from start to finish the sandbox holds only a restricted temporary token.

```mermaid
flowchart LR
    subgraph SBX["Sandbox (session workstation) · scoped token only"]
        AGENT["OpenCode Agent<br/>“help me call connector X”"]
    end
    subgraph API["apps/api process (trusted) · keeps real credentials"]
        POLICY{"policy engine<br/>is this action allowed?"}
        SECRET["Take out resolved secret<br/>attach auth headers"]
        FETCH["fetch third party"]
    end
    THIRD["🌐 Third-party API"]
    AGENT -->|"binding + args<br/>no real key"| POLICY
    POLICY -->|"always_run"| SECRET
    POLICY -->|"require_approval"| WAIT["🧑 Wait for a human to nod in Review Center"]
    POLICY -->|"block"| DENY["Deny"]
    WAIT -->|"approved"| SECRET
    SECRET --> FETCH --> THIRD
    THIRD -->|"result"| AGENT
    style SBX fill:#fdeded
    style API fill:#eaf1ff
```

`execute.ts` opens with the contract: given binding + resolved secret + args, **here** attach auth headers and `fetch`; sandbox never sees credentials (`execute.ts:1-5`). API mount: `/v1/executor` (`index.ts:772`).

The policy engine (`executor/policy.ts`) evaluates `always_run | require_approval | block` by glob; it can link with Review Center executor-approval rows (see Chapter 10). Its default gear is clever — under `default_mode = risk`, **reads go through directly; writes/dangerous ops need a human nod** (`policy.ts:126-129,161-176`), much safer than “allow everything,” and not so annoying that every read needs confirmation.

> 🧠 **One sentence**: the Agent only hands over the request, never touches the key; real credentials are locked in the API process. Dangerous actions default to needing a human nod — that is “secrets never cross the sandbox boundary.”

### 7.3 Agent permissions and Grant

- The `permission` tree in Markdown frontmatter is issued with OpenCode behavior config.
- Account-level **agent grant**: `resolveAgentGrant` when `mintExecutorToken` (near `session-sandbox.ts:147-154`); fail-safe on failure.
- IAM: `session-gate.ts` is **human login-session** policy — same name, different object from “project session sandbox.”

### 7.4 Sandbox and network boundary

- Providers: `daytona` / `platinum` / `e2b` / `local-docker` (`provider-parity.test.ts:11-34`).
- Preview proxy: `/v1/p/:sandboxId/:port/*` must authenticate + rate-limit.
- Snapshot upload URLs have SSRF-protection tests — platform security awareness is stronger than OpenManus’s default-open browser tools.

### 7.5 Approval and the human latch

- Review Center: native review items + adapted CRs + executor approvals (`review-center-connected.tsx:3-14`).
- Change request: only merging into main changes company truth (full path in Chapter 10).
- Web sessions also have permission / question self-heal (SDK `usePermissionSelfHeal` / `useQuestionSelfHeal`, `session-chat.tsx:151-154`).

### Design trade-offs

- **Connectors execute centrally**: friendly to security and audit; the cost is one extra hop.
- **UI only renders tools**: Web’s tool components are a presentation layer, so execution logic does not leak into the browser.
- **Dual-token model** (user JWT vs sandbox CLI/executor token): higher mental load, but it separates “human” from “workstation robot.”

---

## Chapter 8 · Context compaction and memory

### Honest inventory

| Capability | Status in this repo |
|---|---|
| Conversation transcript storage | sandbox OpenCode + DB sync mirror (`MANIFESTO.md:50`) |
| Company long-term memory | **files into the repo** (`MANIFESTO.md:67`: “Memory — files for now…”) |
| LLM auto-compaction / Dream | Dream / company-brain compound implementation **not found** (vision is in the MANIFESTO) |
| OpenCode `compress` tool | SDK tool registry has a display label; **execution is inside OpenCode** |
| Web tool rendering | `dcp-compress-tool.tsx` / `dcp-distill-tool.tsx` etc. are UI presentation |
| prompt cache | platform-level unified implementation **not found** |
| Context-window field | model-catalog metadata includes context window — it is not a compressor |

### How memory becomes a “compound”

MANIFESTO strategy: skills and memory live in the company repo as Markdown/files, evolving with CRs (`MANIFESTO.md:67`). This is different from Raven/EverOS vector memory — **Kortix first bets on git-diffable text memory**.

The SDK provides `formatTranscript` to export a session as Markdown (`packages/sdk/src/index.ts` transcript export), convenient for humans to read and archive, but it is not an automatic compressor.

### Design trade-offs

- **Memory = versioned files**: reviewable, rollbackable, greppable; long sessions still depend on OpenCode’s own context management.
- For nanobot users who “want a long-resident private-chat butler”: Kortix leans toward “project/company working memory,” not an IM personal diary.

---

## Chapter 9 · Sub-agents and multi-agent: the cloning trick

### 9.1 Multiple Agents in the same project (role clones)

- Multiple `.md` agents + `agents:` governance items in `kortix.yaml`.
- `mode: primary | subagent | all` appears in compiled config — **semantics are interpreted by OpenCode**.
- Web customize → Agents view can edit (`features/workspace/customize/sections/view/agents-view.tsx`).
- SDK `useVisibleAgents` lets the session side choose visible Agents.

### 9.2 Massive parallelism of the same config (workstation clones)

This is Kortix’s key difference from OpenManus PlanningFlow: **parallelism comes from isolated sandboxes, not from scheduling multiple roles in one process**.
`MANIFESTO.md:48`: fifty sessions do not pollute each other; conflicts are handed to git merge.
Each session is independent `session_id == sandbox_id` (SDK `shared.ts:17`; `r7.ts:348`).

### 9.3 Marketplace and inheritance

- `apps/api/src/marketplace/`: catalog-style distribution.
- `@kortix/registry`: discovery/build; install goes through **agent import + CR** (`packages/registry/src/index.ts:8-11`).
- CLI `kortix agents` / `marketplace`.

### 9.4 Where sub-agent failure belongs

Daemon auto-resume comments lean: Task/subagent failure should be handled by the **parent model**. Web has renderers such as `task-tool.tsx`, `agent-spawn-tool.tsx`, proving nesting happens at the OpenCode tool layer.

### Design trade-offs

- **Parallel = many sandboxes**: high bill and cold-start cost; the correctness model is simple (git).
- **OpenManus-style PlanningFlow**: isomorphic implementation **not found** in this repo; long-task planning depends more on Agent prompt / skills / human CR cadence.

### Contrast with OpenManus (short note)

OpenManus uses `PlanningFlow` to dispatch `[AGENT]` step by step in one process; Kortix trades isolation and auditability with “many sandboxes + many agent md + CR.”

---

## Chapter 10 · Ecosystem: commands, extensions, and the full Change Request path

### 10.1 CLI surface (the company remote)

Visible under `apps/cli/src/commands/`: `init`, `ship`, `sessions`, `sessions-chat`, `cr`, `agents`, `skills`, `connectors`, `secrets`, `triggers`, `marketplace`, `channels`, `executor`, `sandbox(es)`, `login/logout`, `self-host`, `doctor`… — covering the closed loop from scaffolding to approval.

### 10.2 Change Request full path (this chapter’s hard goods)

#### Data model

`packages/db/src/schema/kortix.ts:2719-2755`:

- Status enum: `open | merged | closed` (`:2726-2730`)
- Key columns: `number`, `base_ref`/`head_ref`, `head_commit_sha`, `origin_session_id`, `merge_commit_sha`…
- Comment: a CR is **metadata**; the real fetch/diff/merge goes through `apps/api/src/projects/git.ts`; the backend can be GitHub/GitLab/pure git (`:2720-2724`).

Serialization and “request changes” notes land in `metadata.requested_changes` (`apps/api/src/projects/change-requests.ts:1-14,72-95`).

#### API routes

| Method | Path | File |
|---|---|---|
| GET | `/:projectId/change-requests` | `routes/r8.ts:225-227` |
| POST | `/:projectId/change-requests` | `r8.ts:272-274` |
| GET/PATCH | `/:projectId/change-requests/:crId` | `r8.ts:547-586` |
| POST | `…/request-changes` | `r8.ts:641-643` |
| GET | `…/diff`, `…/merge-preview` | `r8.ts:731-796` |
| POST | `…/merge` | `r9.ts:19-21` (and the tail of r8) |
| POST | `…/close`, `…/reopen` | `r9.ts:180-229` |

After a successful merge it can trigger connector reconcile and a snapshot rebuild sourced as `cr-merge` (builder trigger-source enum; `r9.ts` merge handling).

#### CLI

`kortix cr` (`apps/cli/src/commands/cr.ts:13-40`): `ls/show/diff/open/merge/close/reopen`. Inside the sandbox it automatically reads `KORTIX_CLI_TOKEN` + `KORTIX_PROJECT_ID` (`:38-40`) — the Agent does not need to log in.

#### Web UI

1. **Project-files side**: `change-requests-panel.tsx`, `change-request-detail-dialog.tsx`, `open-change-request-dialog.tsx`; hooks `features/project-files/hooks/use-change-requests.ts`.
2. **Sidebar entry**: `project-change-requests-nav.tsx`.
3. **Review Center (real data)**: `ReviewCenterConnected` (`review-center-connected.tsx:3-14,88-148`)
   - CR approve → `useMergeChangeRequest`
   - reject → `useCloseChangeRequest`
   - request changes → persist notes + **deliver back to the originating agent** (backend can wake the sandbox)
   - `cr:` adapted ids do not go through the generic `/act` (it would 409); they must go through the source flow (`:107-109`)
4. **`/review` route**: mock prototype only (`review/page.tsx:8-18`); **do not** treat it as the production API. The production entry is the Review view in project customize (`review-view.tsx:4-32`).
5. **SDK**: `packages/sdk/src/core/rest/projects-client/change-requests.ts`; `packages/sdk/src/react/use-change-requests.ts`.

#### Agent-side docs/skills

The starter template includes change-requests reference docs (`packages/starter/templates/base/.kortix/opencode/skills/kortix-system/references/kortix/change-requests.md`); Web docs: `apps/web/content/docs/work/change-requests.mdx`.

#### Lifecycle in one sentence

```text
commit inside the sandbox → kortix cr open
  → DB change_requests(status=open)
  → Web Review / CLI show/diff
  → merge → git merge + status=merged + (optional) snapshot rebuild
  → or close / request-changes → deliver again for the agent to revise
```

### 10.3 Connectors and MCP / registry / starter

- Pipedream and other catalogs live in `apps/api/src/executor/`.
- `@kortix/executor-sdk` gives external TS code isomorphic calls.
- `@kortix/registry` + API marketplace: discovery and agent import.
- `@kortix/starter`: unified scaffolding, so CLI/API do not each write a template set.

### 10.4 Channel ecosystem

Slack/Teams/Telegram/Email/Meet webhooks enter the API (`index.ts:788-796`). Slack sessions rewrite OpenCode permission (to avoid getting stuck on `question`).

### 10.5 How to extend (for second-party developers)

1. Add an agent `.md` / skill in the repo, open a CR.
2. Hang connector / trigger / sandbox specs on `kortix.yaml` (pass manifest-schema first).
3. Go through marketplace agent import.
4. Build your own connector and expose it via the executor protocol.
5. Deep change: touch `@kortix/sdk` (TDD and export contract, `AGENTS.md:99-108`) or the daemon.

### 10.6 Migration and historical baggage

`projects/suna-migration/`: migrate from the old Suna/AgentPress world to OpenCode DB mapping — proof the product went through one “runtime heart transplant.”

> 🧠 **One sentence**: Change Request is Kortix’s product primitive, not an accessory. Whatever the Agent changed does not count until a CR is merged into trunk by a human — that is the landing form of “AI does the labor, humans govern.”

---

## Chapter 11 · Feature set: auth, models, and “thinking”

### 11.1 Auth

- Humans: Supabase JWT; Web `AuthProvider` (`apps/web/src/features/providers/auth-provider.tsx:29+`) + `configureKortix({ getToken })` (`kortix-config.ts:30-32`).
- Programs: API key / PAT / account token.
- Sandbox: `KORTIX_TOKEN` / CLI token / executor token, layered.
- Preview: `POST /v1/p/auth` plants a cookie.
- Enterprise: SCIM (`/scim/v2`), SSO-related oauth routes.
- Session gate: lifetime / idle / revoked (IAM `session-gate`) — **human sessions**.

### 11.2 Model routing and gateway

- API `mountLlmGateway` (`index.ts:740-741`) + `/v1/router`.
- Project-level routing policy; Web customize has a Gateway view (`gateway-routing.tsx` etc.) + `useGatewayRoutingPolicy`.
- Daemon: when a gateway exists, OpenCode goes through a synthetic `kortix` provider.
- Billing: `billing/` + compute metering; the Web session page has `can_run` / upgrade-dialog gates (`page.tsx:66-85`).

### 11.3 “Thinking” / Reasoning

- Gateway wire can carry `reasoning_effort`; project policy can clamp it.
- Web has `reasoning-effort-selector.tsx` (wired to `useGatewayRoutingPolicy`).
- When sharing a session externally, the API can omit reasoning and mark `reasoning_omitted`.
- **Not found**: a first-class cross-model thinking-blocks peel-off service; rendering is mainly in the SessionChat turn UI.

### 11.4 Other features, quick lookup

| Feature | Status | Evidence |
|---|---|---|
| Multi-channel | yes | `index.ts` webhooks |
| Change Request | yes | DB + r8/r9 + CLI + Web Review |
| Review Center | yes (real data is in-project; `/review` is mock) | `review-center-connected.tsx`; `review/page.tsx` |
| Preview/static site | yes | daemon 3211; `/v1/p` |
| Tunnel | yes | `/v1/tunnel` |
| Self-host | has an entry | CLI `self-host`; `self-host/` |
| Desktop / Mobile | yes | `apps/desktop-electron`; `apps/mobile` |
| Web Command Center | **checked out** | `apps/web` full paths as above |

---

## Chapter 12 · Summary: design philosophy and trade-offs

```mermaid
flowchart TB
    GOAL["Goal: a self-hostable company-grade AI Command Center<br/>company = Git repo"]
    GOAL --> P1["Pillar 1 session = isolated sandbox<br/>session_id == sandbox_id == branch<br/>→ Chapters 3/6"]
    GOAL --> P2["Pillar 2 outsource the brain to OpenCode<br/>platform only does lifecycle/proxy/config<br/>host only mounts useSession<br/>→ Chapters 5/6"]
    GOAL --> P3["Pillar 3 change must go through a CR<br/>API+CLI+Review Center<br/>→ Chapters 7/10"]
    P1 & P2 & P3 --> BASE["Foundation: SDK SSoT + manifest-schema + Executor keeps secrets<br/>+ LLM Gateway + multi-provider snapshot<br/>→ Chapters 2/3/7/11"]
```

### Five distinctive ideas

**① The company is literally a repository.** agents/skills/memory/sandbox specs all go into the repo; greppable, diffable (`MANIFESTO.md:27-36`).

**② `session_id == sandbox_id == git branch`.** Route comment (`r7.ts:348`); executor-token minting comment (`session-sandbox.ts:147-149`); SDK cache-key comment (`shared.ts:17`).

**③ Platform loop ≠ model loop ≠ host loop.** The API intensively farms create/start/continue/deliver; ReAct is left to OpenCode; Web uses `useSession` to eat server-truth ready (Chapter 6).

**④ Secrets never cross the sandbox boundary.** Executor attaches credentials on the server; `executor-sdk` likewise.

**⑤ Change Request is a product primitive.** Schema (`kortix.ts:2719+`) + API r8/r9 + CLI `cr` + Review Center adapter — “AI labor + human governance.”

### Main costs

- **Complexity tax**: monorepo + multi-provider + queues + idempotency + SDK contract; the learning curve is far above OpenManus.
- **Cold-start tax**: snapshot/sandbox at minute scale; compensated with warm, long-poll, deliver heal, Web instant shell.
- **License tax**: Elastic 2.0 restricts hosting a competing product (`LICENSE:18-20`).
- **The brain is invisible**: deep Agent behavior requires reading OpenCode upstream; this-repo analysis cannot replace that.

### Sideways comparison at a glance (for the series docs)

| Dimension | Suna / Kortix |
|---|---|
| Team / license | kortix-ai; **Elastic License 2.0** |
| Stack | TS/Bun monorepo + Hono + Supabase + Next + OpenCode-in-sandbox |
| Architecture | control-plane session-lifecycle + sandbox daemon + OpenCode; SDK SSoT |
| Models | LLM Gateway routing; OpenCode `kortix/` provider |
| Tool scale | OpenCode tools + server-side Executor/MCP + built-in CLI |
| Extension | yaml + md agents/skills + registry/marketplace + connectors |
| Main loop | platform: `session-lifecycle/engine.ts`; model: OpenCode; host: `useSession` |
| Permissions | IAM + agent grant + executor keeps secrets + CR governance |
| Compaction / memory | repo-file memory; platform-level LLM compaction **not seen** |
| Frontend | **full checkout**: Command Center + Session + Review |

**One-sentence close**: Claude Code answers “how to write the code well in this repo”; OpenManus answers “how to use a readable ReAct chain for research deliverables”; nanobot answers “how to let a personal Agent live long-term in a chat app”; and **Kortix/Suna answers — how to turn a company into a cloneable Git repository, so that hundreds or thousands of isolated-sandbox OpenCode laborers, under change-request discipline, improve the company itself day and night**.

### Suggested reading order (full checkout)

1. `MANIFESTO.md` → worldview
2. `AGENTS.md` SDK iron laws + `apps/web/src/lib/kortix-config.ts`
3. `packages/sdk/src/react/use-session.ts` + Web `sessions/[sessionId]/page.tsx`
4. `session-lifecycle/engine.ts` create/start/continue/postPrompt
5. `snapshots/builder.ts` + `session-sandbox.ts` header comments
6. `kortix-sandbox-agent-server/README.md`
7. `change-requests.ts` + `routes/r8.ts`/`r9.ts` + `review-center-connected.tsx`
8. `packages/manifest-schema` / `starter` / `registry` / `executor-sdk` each `package.json` + `src/index.ts`

### Naming mines

| Name you see | What it actually means |
|---|---|
| Suna | historical product/repo name; migration code in `suna-migration/` |
| Kortix | current product name, CLI, npm scope `@kortix/*` |
| session (IAM) | human login session |
| session (project) | Agent workstation; often the same id as sandbox |
| sandbox_id | on most paths **== project session id** |
| opencode_session_id | OpenCode internal conversation id, **not equal to** project session id |
| `/review` vs Review Center | the former is mock; the latter is in-project Connected real data |
| VERSION `0.10.14` vs badge `0.9.98` | treat the `VERSION` file as canonical |

---

## Deepen-weave A: Web Command Center UX close read (reinforces Chapters 2/3/4)

### A.1 Routes and shell

Under the App Router, the `(app)` group is the main logged-in shell. Project page `/projects/[id]` does not chat directly; it is `ProjectHome`: welcome area + composer + shortcut entries (Agents / Secrets / Sandbox / Triggers / Marketplace icon buttons, see the second half of `project-home.tsx`). On send, the parent `ProjectIndexPage` (`page.tsx:24+`) is responsible for:

1. `useProjectCanRun` / `useAccountState` billing gate;
2. `writeStartStash` writes the first sentence;
3. `useNewProjectSession` creates the session and navigates to `/sessions/[sessionId]`.

The session-page comments cut responsibility cleanly (`sessions/[sessionId]/page.tsx:46-59`): **the SDK owns runtime lifecycle**; the page keeps the billing gate, instant-shell/loader cross-fade, fresh-session hand-off, restart/error cards.

### A.2 `useSession` parameter semantics (the host must understand)

```ts
useSession(projectId, sessionId, {
  enabled: !!user && !billingGatePending && !noPlan,
  replayStartStash: false,
  chatEngine: false,
})
```

Evidence: `page.tsx:96-100`.

| Parameter | Meaning |
|---|---|
| `enabled` | do not fire `/start` when there is no user / billing pending / no plan, to avoid spinning empty |
| `replayStartStash` | Web turns off SDK default replay; uses `migrateStash` + InstantShell instead |
| `chatEngine` | when false, do not mount `useSessionSync` at the page layer; hand it to `SessionChat` |

Inside the SDK it still: long-polls `/start`, writes readiness into the connection store, opens SSE, resolves the canonical OpenCode id (`use-session.ts:5-16`). Fresh-session 404 has a grace retry: `FRESH_START_404_RETRIES = 12` × 800ms (`use-session.ts:66-70,79-88`).

### A.3 Shim inventory (proof of thin consumer)

| Web path | Actual export |
|---|---|
| `apps/web/src/stores/server-store.ts:2` | `@kortix/sdk/server-store` |
| `apps/web/src/hooks/opencode/use-session-sync.ts:2` | `@kortix/sdk/react` |
| `apps/web/src/hooks/opencode/use-canonical-opencode-session.ts:2` | `@kortix/sdk/react` |
| `apps/web/src/hooks/opencode/use-opencode-events.ts` | SDK react re-export |
| `apps/web/src/lib/api-client.ts:1` | `@kortix/sdk/api-client` |
| `apps/web/src/lib/opencode-sdk.ts:1` | `@kortix/sdk/opencode-client` |

`AGENTS.md:128-133` is explicit: on conflict **keep the shim**, port new logic into the SDK.

### A.4 Session feature directory map (`features/session/`)

After full checkout this directory is huge (200+ files scale). Responsibilities layered:

- **Shell**: `session-layout.tsx`, `instant-session-shell.tsx`, `session-starting-loader.tsx`, `sandbox-loading-boundary.tsx`
- **Chat core**: `session-chat.tsx` (thousands of lines: turns, optimistic send, permission/question)
- **Composer**: `composer-chat-input.tsx`, `session-chat-input`
- **Tool rendering**: `tool/tools/register.ts` registers bash/read/write/grep/task/connector/executor/dcp-compress…
- **Resume semantics**: `session-resume.ts`, `session-terminal-state.ts`

Tool renderers **only draw UI**; they do not execute tools — execution is in sandbox OpenCode (Chapter 7).

### A.5 Do not mix the two Review Center entries

| Entry | Nature | Evidence |
|---|---|---|
| `/review` | clickable **mock prototype**, no API/auth | `review/page.tsx:8-18` |
| Project Customize → Review | **Connected** real data | `review-view.tsx:4-32`; `review-center-connected.tsx` |

`ReviewCenterConnected` also:

- Pulls `useReviewItems`;
- Maps API → inbox view model (`mapApiReviewItem`);
- Executor approvals go through `resolveApproval` (same client as in-session approval prompts);
- CRs go through merge/close/request-changes (`:88-148`);
- Read-only when `canAct=false` (`:46-49`).

Sidebar `project-change-requests-nav.tsx` and the session list read `useReviewCenterEnabled` / `useReviewSessionSummary`, exposing pending-review counts on the nav.

---

## Deepen-weave B: Snapshot / Provider / Boot close read (reinforces Chapters 3/6)

### B.1 Provision and builder call chain

`session-sandbox.ts:1-12`: after inserting `session_sandboxes` (primary key = caller UUID = session id), fire-and-forget. Background then goes `ensureSandboxImage` (imported from `builder.ts`, `session-sandbox.ts:39-44`) then `getProvider().create()`.

Builder `SnapshotBuildSource` (`builder.ts:56-62`) shows images are not only built on “first session”:

- `project-create`: pre-warm when creating the project;
- `cr-merge`: rebuild after manifest/Dockerfile change;
- `session-start`: fill in when the image is missing;
- `manual` / `background` / `startup`: ops and pre-warm paths.

### B.2 Provider single source of truth

`provider-parity.test.ts:29-31`: `config.KNOWN_PROVIDERS` must equal the `SANDBOX_TEMPLATE_PROVIDERS` set. The four listed: `daytona`, `platinum`, `e2b`, `local-docker` (`:11,34`). Local development can use `local-docker` and does not require a cloud API key (`:70-74`).

Selection/balancing: `session-sandbox.ts` imports `selectProvider`, `readActiveRouting`, `providerFallbackSetting` — supports routing switch and fallback, not a hard-coded vendor.

### B.3 Boot-by-template-id

`sessionBootByTemplateIdEnabled` (`session-sandbox.ts:161-169`) defaults ON: boot by the already-activated pinned template id; only fall back to name-boot on GC’d-pin 404. You can emergency-off with env `KORTIX_SESSION_BOOT_BY_TEMPLATE_ID=0` — a production-rollout escape hatch.

### B.4 Sandbox image boundary (emphasized again)

`apps/sandbox/README.md:15-28`: the image only has what a running session needs; triggers/channels/connectors/secrets are **not in the image**. This matches the MANIFESTO “inject secrets, connectors go through a proxy” (`MANIFESTO.md:62-63`), and also explains why changing the daemon requires rebuilding the snapshot (the feedback loop is on the slow side).

### B.5 `openSession` wakes hibernation

`openSession` is not just a DB read: when stopped + has `externalId` it may `resumeStoppedSandbox` (keep disk/workspace); when there is no usable box it can provision on the open path. So `continueSession`’s 5-minute wait window (`READY_DEADLINE_MS`, `engine.ts:39`) covers real-world “resume or create” time, not mere spinning.

---

## Deepen-weave C: Change Request front/back correspondence table (reinforces Chapter 10)

### C.1 State machine

```mermaid
stateDiagram-v2
    [*] --> open: POST change-requests / kortix cr open
    open --> merged: POST .../merge
    open --> closed: POST .../close
    closed --> open: POST .../reopen
    open --> open: POST .../request-changes\n(metadata + optional deliver to agent)
```

Enum definition: `kortix.ts:2726-2730`. After merge, write `merged_at` / `merged_by` / `merge_commit_sha` (table fields, `kortix.ts:2757+`).

### C.2 “Request changes” is not an empty comment

`recordRequestedChange` (`change-requests.ts:85-95`) appends the human note into `metadata.requested_changes`. API `…/request-changes` (`r8.ts:641-643`) after persist may try to send the prompt back to the originating session (delivery failure only warns; the note is not dropped — see the `request-changes prompt not delivered` log path in r8).

Web Connected layer: if there is no note, toast asks you to write an explanation first (`review-center-connected.tsx:128-134`); on success, distinguish “already sent to the agent” vs “saved only” by `res.delivering` (`:139-144`).

### C.3 CLI ↔ API mapping

| CLI (`cr.ts`) | Roughly the API |
|---|---|
| `cr ls` | GET `.../change-requests` |
| `cr show` | GET `.../change-requests/:id` |
| `cr diff` | GET `.../diff` |
| `cr open` | POST `.../change-requests` |
| `cr merge` | POST `.../merge` |
| `cr close` / `reopen` | POST `.../close` / `.../reopen` |

Login-free inside the sandbox: env vars `KORTIX_CLI_TOKEN` + `KORTIX_PROJECT_ID` (`cr.ts:38-40`). Note `KORTIX_TOKEN` is the sandbox service key, **not** the CLI token (comment in the same place).

### C.4 Product difference from GitHub PRs

The CR layer is not bound to a single hoster (`change-requests.ts:4-8`; `kortix.ts:2720-2724`). Permissions, notifications, Review Center can live on Kortix IAM. The cost is that “click Merge in the GitHub UI” will not automatically sync Kortix CR status — unless you go through the Kortix merge API (or a later integration; this analysis does not invent bidirectional sync that was **not found**).

### C.5 E2E / documentation anchors

- API script: `apps/api/scripts/e2e-change-requests.sh`
- CLI script: `apps/cli/scripts/e2e-cr.sh`
- Flow tests: `tests/src/flows/change-requests.flow.ts`
- User docs: `apps/web/content/docs/work/change-requests.mdx`
- Design-doc citation: `docs/REVIEW_CENTER_DESIGN.md` (Connected header comment, `review-center-connected.tsx:14`)

---

## Deepen-weave D: four packages expanded again (reinforces Chapters 2/5/10)

### D.1 `@kortix/manifest-schema`

- Package description: shared by CLI ship pre-check + backend CR-merge gate + validate (`package.json:4`; `src/index.ts:5-11`).
- Pure function: `(rawToml: string | object) → ManifestValidationResult` (`index.ts:14-15`).
- Constants surface is wide: channel platforms, connector auth/policy, sandbox CPU/memory/disk bounds, trigger types, grantable CLI actions (`index.ts:22-36` imported from `./constants`).
- v2 agent validation is split into `index.v2.ts` (`index.ts:42-48`).
- `connector-headers` is an independent module: forbid certain header names, limit length/count — API parser and executor **share the same rules** (`index.ts:64-66`).

### D.2 `@kortix/starter`

- `getStarterFiles()` walks the directory + `{{var}}` substitution, returns `{ path, content }[]` (`src/index.ts:4-9`).
- User default template: `general-knowledge-worker`; `minimal` is internal clone seed only (`index.ts:36-48`).
- When compiled into the CLI binary, `embedded.generated.json` is the fallback (`index.ts:20-27`) — editing templates in the source tree hot-updates; compiled binaries do not lose templates.

### D.3 `@kortix/registry`

- Provides `buildRegistry` / `loadItem` / `loadRegistry` / validate (`src/index.ts:1-12` export list).
- **Deliberately has no** deterministic install engine: marketplace install is changed to the agent reading the source, merging files, and opening a CR (`index.ts:8-11`). This is consistent with “the company is git + CR discipline” — install itself is also a reviewable change.

### D.4 `@kortix/executor-sdk`

- Elastic-2.0 publishable package (`package.json:5-6`).
- `ExecutorClientOptions`: when `projectId` is present, go through the project-explicit route; user token and sandbox session token are isomorphic (`src/index.ts:41-50`).
- Results can carry `pending_approval` + `execution_id` (`index.ts:28-38`) — the same business object as Review Center executor rows and in-session approval UI.

---

## Deepen-weave E: platform delivery vs host send (reinforces Chapters 4/6)

### E.1 Control-plane delivery (channels / automation)

```text
continueSession
  → wait ready (≤300s)
  → deliverWithRetry (≤45s, interval 1.5s)
  → postPrompt → POST .../prompt_async
```

`deliver.ts:2-11` explains the motive: the old path, on first 404/5xx, told the Slack user “say it again” and dropped the message; now, given the webhook is already acked, it keeps healing.

### E.2 Host send (inside a Web session)

`SessionChat` uses SDK primitives such as `sendAndRecover` / `beginOptimisticSend` / `classifySendError` (`session-chat.tsx:142-154`); message truth comes from `useSessionSync(sessionId)` (`:3482-3491`). Billing 402 becomes a `KortixSendError` of kind `billing` (`use-session.ts:96-107`); the page can pop upgrade.

Both eventually land on OpenCode’s prompt/stream, but:

- The channel path emphasizes **server-side heal**;
- The Web path emphasizes **optimistic UI + SSE sync**.

### E.3 Proxy chain (SSE return path)

1. `forwardToSandbox` / host via `/v1/p/<external>/8000/...`
2. sandbox-proxy auth
3. daemon reverse-proxies `opencode serve`
4. SDK `openEventStream` (framework-agnostic, reusable in a worker) manages timeout, heartbeat, reconnect backoff

AGENTS.md writes that the proxy URL looks like `http://localhost:8008/v1/p/<external_id>/8000/...`, SSE at `…/event` (`AGENTS.md:184-186`).

---

## Deepen-weave F: main-loop comparison with OpenManus / nanobot (reinforces Chapters 6/12)

| Step | OpenManus | nanobot | Kortix/Suna |
|---|---|---|---|
| Receive message | stdin | Channel → MessageBus | Webhook/CLI/SDK/`useSession` → session-lifecycle |
| Assemble context | Memory list hard-truncated | system+memory+history | repo md + env + OpenCode transcript + manifest |
| Call model | `LLM.ask_tool` | AgentRunner + in-house provider | OpenCode → LLM Gateway |
| Call tools | in-process ToolCollection | in-process tools/MCP | sandbox local tools + server-side Executor |
| End | `terminate` tool | model stops or policy | user stop / sandbox reclaim; persistence via CR |
| Permissions | almost open | tool-level + UNTRUSTED | IAM + grant + keep secrets + CR + Review |
| Multi-tenant | none | one person, many channels | account/project/session/sandbox four layers |
| Frontend | almost none | WebUI SPA | Next Command Center + Desktop/Mobile |

Paste this table next to Chapter 6 and you will stop asking “why is there no ReAct in the API.”

---

## Deepen-weave G: beginner debugging checklist (practical)

When you feel “the Agent is not responding,” debug by layer:

1. **API health**: `curl localhost:8008/v1/health` (`AGENTS.md:193`)
2. **Session row**: is `project_sessions.status` provisioning / running / failed?
3. **Sandbox row**: is `session_sandboxes.status` active? is there an `external_id`? (primary key should equal session id)
4. **Proxy**: does `/v1/p/<external>/8000/kortix/health` show daemon=ok and opencode=ok?
5. **OpenCode id**: is `opencode_session_id` empty? On Web look at `useSession(...).opencodeSessionId`
6. **Delivery**: do API logs show `prompt_async non-ok` / deliver deadline (`engine.ts:687-695`; `deliver.ts`)
7. **Web billing gate**: is `noPlan` blocking `enabled`? (`page.tsx:83-97`)
8. **Permissions**: is the connector `block`ed by policy? Does Review Center have a pending approval?
9. **CR**: the Agent said it fixed it but main did not change — did it only commit without `kortix cr open` / without merge?
10. **Manifest**: did `kortix ship` / validate get blocked by `@kortix/manifest-schema`?

---

## Deepen-weave H: inventory still explicitly **not found** after full checkout

Mark the boundary honestly, so OpenCode upstream capabilities are not counted into this repo:

| Item | Status |
|---|---|
| In-repo ReAct `think/act` loop | **not found** (in OpenCode) |
| Dream / automatic company-brain compaction service | **not found** (MANIFESTO vision) |
| Platform-level unified prompt-cache service | **not found** |
| `/review` production API | **none** (mock only) |
| Registry deterministic install engine | **removed** (changed to agent import) |
| OpenCode upstream auto-compact implementation details | not in this repo; no assertion |

---


---

## Writing notes

- **Analysis baseline (full-checkout deepened edition)**: commit `5862e95671cfd198c6a94ceb9c1fefe579f00a8b`; version treats `VERSION`=`0.10.14` as canonical (README badge `0.9.98` inconsistency already marked).
- **Verification method**: critical paths were opened in source and checked; `apps/web`, all `packages/*`, desktop/mobile **are included**. Full OpenCode upstream source is still not in this repo; related conclusions are marked **not found** or “execution is inside OpenCode.”
- **Relative to the old sparse edition**: delete every “`apps/web` not checked out” phrasing; weave command center / `useSession` mount, CR front/back full path, snapshot builder / providers, and the four new packages into the main chapters, rather than stacking them in an appendix.
- **Product contrast**: comparisons with OpenManus / nanobot / Claude Code are based on public positioning and architectural differences; they do not assert closed-source implementation details.

*End of lecture.*
