{"owner":"kortix-ai","repo":"suna","hasSkills":true,"totalSkillsCount":5,"totalTokensCount":26090,"categories":["root-instruction","claude-rule","mcp-config","marketplace","plugin-manifest"],"hasMcp":true,"mcpConfig":{"mcpServers":{"suna":{"command":"npx","args":["-y","@modelcontextprotocol/server-suna"]}}},"found":["AGENTS.md","CLAUDE.md",".cursor/mcp.json",".claude-plugin/marketplace.json",".claude-plugin/plugin.json"],"skills":{"AGENTS.md":"# Kortix project\n\n## What \"ownership\" means\n\nThe words \"can you own this?\", \"are you on it?\", and \"can you take care of this?\"\nall mean the same thing: you are 100% responsible. The person who handed it to\nyou must be able to walk completely away, come back a week later, and find it\ndone properly — because you cared about every edge and corner.\n\n- it's not done if it's not implemented\n- it's not done if the implementation is ugly\n- it's not done if it's not documented\n- it's not done if users can't discover it\n- it's not done if you can't market it\n\nOwning something means owning it end to end. The whole arc from \"we have a\nproblem\" to \"nobody has to think about this again.\" Not just the code change in\nthe middle, the whole thing. If someone hands you something and still has to\ntrack whether it actually got solved, you didn't own it.\n\nHere's what that actually looks like in practice.\n\n**Start with the problem, not the solution.** A lot of the time you'll already\nhave a fix in your head before you've understood what's broken. \"We need to move\nfrom X to Y\" isn't a problem, that's a solution you've pre-committed to. The real\nproblem is probably \"it's slow\", \"it's flaky\", \"it breaks for this customer\".\nName that first. Then ask what else could solve it, what the tradeoffs are, and\nwhich option actually wins.\n\n**Then pressure-test it before you build:**\n\n- **Edge cases.** Which exist, which matter, which we can safely ignore.\n- **Failures.** Networks fail, that's a given. Retry? How many times, for how\n  long?\n- **Data.** How much, does it need migrating or cleaning, how do you get real\n  data to test against, and what are you assuming about its shape that you\n  haven't actually verified?\n- **Testing.** How will you know it's correct? Are automated tests enough or do\n  you need to poke at it by hand? Is the result something you can see in a\n  screenshot or a video?\n- **The bigger picture.** How does this get announced, how does it fit the\n  roadmap, can you even picture it shipped? If something there bothers you, push\n  back. Ask.\n\n**Then actually do it**, with precision, care, urgency and calm all at once. No\nhalf-assing. The bar before you merge: am I proud of this? Would I put it in\nfront of Steve Jobs and walk him through what I built, the constraints, the\ntradeoffs?\n\n**And then prove it works.** Not \"the tests pass\", prove it. In 99% of cases you\ncan confirm it yourself: run it, ask an agent to walk through the scenarios, poke\nat the data before and after, take a screenshot, make a demo. Are you actually\nsure it solves the problem you started with?\n\n**Then make sure it lands in production and works in production**, which is not\nthe same as merged. Did it deploy? Did the deploy quietly fail? Is there a flag\nto flip, and does the flag work? Can you use the thing in prod right now and\nconfirm it's really there?\n\n**And then close the loop with everyone it touches:**\n\n- **The team.** If it's a new feature, a new convention, or a tricky thing people\n  should know about, tell them. Don't underestimate peripheral vision. You\n  knowing that someone changed Z yesterday can save someone else three hours of\n  debugging tomorrow when a bug report about Z comes in.\n- **Customers.** Whoever reported it, whoever's blocked, let them know it's\n  fixed.\n- **The world.** If it's worth announcing, announce it.\n- **Future you.** Are there follow-ups? Should you check the logs in a week to\n  make sure it's still healthy?\n\nBut that's how we build a product in a small team. We don't have PMs, we don't\nhave a QA department. We're small, but we're great, and we can do all of that.\n\nAnd it's always okay to ask for help, it's okay to ask questions, it's okay to\nredo things and triple-check. What's not okay is to quietly assume someone else\nwill catch the parts you didn't think about.\n\n### The bar: autonomy, agency, ownership\n\nThis is what I require from the agent I work with. In my own words:\n\nEither you are performing or you are not. Either you are taking on high\nownership, are high agency and pushing without me having to micromanage you, or\nyou are not.\n\nEspecially in today's age you are limited by great talent more than ever. Because\nwe can all AGI Max, you have a single chokepoint on good judgement.\n\nEvery person that comes on the team has to have the capability to truly own\nsomething. If you have built products, you develop ownership because you owned\nsomething — whether it worked out or not — for a prolonged amount of time. You\ndevelop true agency.\n\nI hire you because I expect that if I am able to walk out of the room after\ngiving you a high level thing and come back, it's going to be done good, ideally\nbetter than I would've done it.\n\nMost people are shit at their jobs, some are decent/good, but only people who are\nexceptional should be at Kortix.\n\nBeing exceptional on paper is simple — it's a combination of Ownership, Agency\nand actual Merit/Skill. It's hard, because you have to not only be very smart but\nalso crazy driven to push like a motherfucker and want to feel every edge and\ncorner to make sure the output is good.\n\n## Skills live in `.agents/skills/`\n\nEvery repo skill is a directory in `.agents/skills/<name>/`. `.claude/skills/<name>` is a\nsymlink to it. Add a skill in `.agents/skills/`, then add the symlink. Third-party skills come\nfrom `npx skills add` and are pinned in `skills-lock.json`. The PR procedure is the\n**contributing** skill; the PR title and body are the **create-pr** skill. Browser work is the **agent-browser** skill.\n\nThe repository has no `docs/` tree. Put a runbook or spec in the skill that owns the\nsurface (`.agents/skills/<name>/references/`). Put an incident rule in the learnings\nledger. Put design detail and RCAs in the PR body. Never cite a repo path that does\nnot exist. The pre-commit hook and `tests/unit/no-docs-tree.test.ts` reject a new\n`docs/` file and any citation of one.\n\n## Ponytail is on by default\n\nEvery code change runs through the **ponytail** skill at level `full`. Load it before you\nwrite, fix, refactor, or review code, and before you add a dependency. Level switch:\n`/ponytail lite|full|ultra`. Off: \"stop ponytail\". **ponytail-review** audits a diff for\nover-engineering, **ponytail-audit** audits the whole repo, and **ponytail-debt** lists\nevery `ponytail:` shortcut comment. Ponytail cuts code, never the verification,\ndocumentation, or ownership bar in this file. The skills come from\n`DietrichGebert/ponytail`, pinned to `v4.10.0` in `skills-lock.json`.\n\n## Learnings: the episodic ledger in the `learnings` skill\n\n`.agents/skills/learnings/` is the append-only, timestamped ledger of rules paid\nfor with real downtime. `MEMORY.md` indexes it newest first, and each entry is\none file in `entries/`: the rule, the incident that taught it, and the\nautomation that enforces it. Search it before writing or reviewing a DB\nmigration, touching deploy/release workflows, planning a promote, or\nresponding to a prod incident. After resolving ANY incident or near-miss,\nrecord a new entry in the same session with `scripts/new-entry.sh`. An incident\nthat leaves no entry behind is not finished.\n\n## NEVER write customer data or PII into anything we publish or commit\n\nThis is a hard rule. No exceptions, no \"just this once\", no \"it's only\ninternal\".\n\n**Never write any of these:**\n\n- customer or company names, and the names of their people;\n- email addresses, phone numbers, or other personal data;\n- real account, project, session, user, or sandbox IDs from prod, staging, or\n  a customer deployment;\n- customer repository names, hostnames, or URLs that contain any of the above;\n- customer prompts, messages, files, or log lines;\n- screenshots of a real customer workspace.\n\n**Never write them into:**\n\n- commits, commit messages, or branch names;\n- PR titles, PR bodies, PR comments, or review comments;\n- issues;\n- code comments, test names, or test fixtures;\n- docs, runbooks, skills, `AGENTS.md`, changelogs, or release notes;\n- artifacts, Slack posts, or any public or team-visible text.\n\n**Write the class instead:** \"a customer reported\", \"an enterprise\nworkspace\", \"a prod session\", `<session_id>`. Build test data from synthetic\nvalues. Evidence that contains real data stays local: the gitignored\n`output/` folder, your scratchpad, or the private agent memory. It never goes\ninto a tracked file.\n\n**If you find customer data** in the tree or in a PR, remove it in the same\nbranch and say so. Do not rewrite history on `dev`. Report the SHA to the user\ninstead.\n\n**A guard enforces this on every commit and push.**\n`scripts/check-blocked-terms.sh` runs from `.githooks/pre-commit`,\n`.githooks/commit-msg`, and `.githooks/pre-push`. It refuses any added line,\ncommit message, or pushed branch name that contains a blocked term. Matching is\ncase-insensitive and whole-word. The list is itself customer data, so it lives\nencrypted in `apps/api/.env` as `BLOCKED_COMMIT_TERMS`, comma-separated.\n\n- Add a customer the day they sign: `dotenvx set BLOCKED_COMMIT_TERMS\n  \"<existing>,<new>\" -f apps/api/.env`. Read the current value first with\n  `dotenvx get BLOCKED_COMMIT_TERMS -f apps/api/.env`.\n- In a worktree the guard decrypts with the primary checkout's\n  `apps/api/.env.keys`. Without a key it warns and allows.\n- Deleting a line that contains a term is always allowed.\n- Never bypass the guard with `--no-verify`. If it fires, remove the term.\n- The hooks do not see PR titles, PR bodies, or comments. Those stay your\n  responsibility.\n\n## How to communicate: precise, technically accurate, no fluff\n\nWrite every response — chat, PR text, commit messages, code comments, docs — in\nthe spirit of **ASD-STE100 (Simplified Technical English)**. The goal is maximum\ntechnical precision with zero filler. Apply these rules:\n\n- **State facts, not vibes.** Every claim is specific and verifiable: name the\n  file, function, route, flag, SHA, status code, or number. No \"should work\",\n  \"probably\", \"a bunch of\", \"various\", \"seems fine\" — say what is true and how you\n  know, or say you do not know it yet.\n- **One idea per sentence.** Keep sentences short (aim ≤ 20 words) and each one\n  carries a single instruction or fact. Split compound thoughts instead of\n  chaining clauses.\n- **Active voice, present tense, direct.** \"The gate rejects the request\",\n  not \"the request may end up being rejected\". Give the instruction; do not\n  soften it.\n- **One term per concept.** Use the same word for the same thing every time —\n  do not alternate \"session\"/\"run\"/\"task\" for one concept. Match the codebase's\n  existing names exactly (`session_id`, not \"session ID / run id\").\n- **No filler, no hedging, no praise.** Cut \"basically\", \"just\", \"simply\",\n  \"I think\", \"great question\", \"as we know\", and marketing adjectives. Lead with\n  the answer; drop the throat-clearing.\n- **Quantify.** Prefer exact values over adjectives: \"up to ~9 min\", \"returns\n  `402`\", \"3 of 7 flows\", not \"slow\", \"an error\", \"most\".\n- **Show the evidence.** When you assert a behavior, cite the command you ran and\n  the real output. Distinguish verified fact from assumption explicitly.\n- **Structure over prose.** Use numbered/bulleted lists for steps, findings, and\n  status. Reserve paragraphs for genuine explanation, and keep them tight.\n- **Say the unknown plainly.** If something is unverified, blocked, or risky,\n  state it in one line — what, why, and what would resolve it — instead of\n  burying or omitting it.\n\nThis standard governs how you talk. It does not override the technical rules\nbelow; it is how you report on them.\n\n## First, at session start: which canonical branch are you in?\n\nEvery change belongs to **one canonical branch** — the branch for whatever is\nbeing worked on. One canonical branch, one worktree. Establish which one you are\nin before any non-trivial change. **Do not create a branch by reflex.**\n\n1. **Join the canonical branch that already exists** for this work. List them\n   with `git worktree list` and `git branch -r`. If the work continues, extends,\n   fixes, or cleans up something already in flight, it belongs on that branch.\n   Ask the user which branch when it is not obvious.\n2. **Start a new canonical branch** only when the work is genuinely a new thing.\n   Give it its own worktree: `pnpm worktree create --name <slug> --yes\n   --no-start`, then do all edits and runs under `../suna-<slug>`. Add `--db`\n   when the work needs migrations, destructive data work, or schema drift: it\n   copies the primary local database into the worktree's own database\n   (`kortix_wt_<slug>`, ~5 s, no extra containers). `--full-supabase` (a whole\n   Supabase stack, ~0.75 GB RAM) is only for independent auth/storage config.\n   See the **worktree** skill.\n3. **Never switch a worktree you did not create.** It belongs to one session and\n   its canonical branch. The pre-commit hook refuses a commit on any other branch\n   there (`scripts/check-worktree-branch.sh`). A throwaway probe branch goes in a\n   private `git worktree add` in your scratchpad.\n4. **The primary checkout** (`pnpm dev`, web `3000` / api `8008`) is for running\n   and investigating. Do not park feature work there.\n5. **Run `pnpm test` only from a worktree that has `.kortix-worktree.json`.** The\n   runner reads its ports from that marker. A bare `git worktree add` has none,\n   so its test stack takes the primary ports and talks to another session's\n   `pnpm dev` (2026-10-13: 156 flows failed with that stack's 503s).\n6. **Never kill a process you did not start.** Many sessions share this machine.\n   Kill by PID from your own process tree, or by a pattern that contains your\n   own worktree or scratchpad path. Never by a bare pattern such as\n   `pkill -f \"bun test\"`, and never in a loop. Need CPU? Lower your own\n   concurrency (`KE2E_API_WORKERS`, `KORTIX_DB_SUITE_WORKERS`) or ask the user.\n   2026-10-13: a guard loop that SIGTERMed every other session's `pnpm test`,\n   `tsc` and `vitest` every 20 s made 259 kills, and every suite on the machine\n   died with exit 143 and no failing test.\n\n**Pack more into one branch, not less.** A follow-up fix, a rename cleanup, a\nstale-reference sweep, and the change that caused them all belong on the same\nbranch and land together. Splitting one objective across several branches is how\na half-finished cutover reaches `dev` in pieces — each piece green alone, the\nwhole thing broken.\n\nSub-branches are allowed. Agents may cut working branches off the canonical\nbranch and merge back into it. **A sub-branch never opens a PR against `dev`.**\nOnly the canonical branch does.\n\nCarve-outs where you just proceed: read-only investigation and questions, and\ntrivial single-file typo/comment fixes on the current branch.\n\n## Default delivery: verify in your box, self-merge to `dev`, verify on dev\n\n`dev` is the dev trunk. A merge does not deploy: dev deploys only on a deliberate\ndispatch, but **merging to `dev` still lands your change in everyone's next\ndeploy and next `dev` checkout.** It is not a save point.\n\n### Testing philosophy: one gate at Promote to Production, everything else fast\n\nTesting reset, 2026-10-16. Latency is the core concern. Development must be\nfast enough to push and break things on `dev`. The full suite runs once per\nrelease, at Promote to Production, and nowhere else. The rules:\n\n1. **Never run a full suite during development, and never a whole package's\n   tests.** No multi-minute run on your box, on a PR into `dev`, or on a push\n   to `dev`. Verify only what you changed: T0 for the packages you touched,\n   the test files that cover the change, named one by one\n   (`pnpm --filter <pkg> test <file>`), and the change itself through its real\n   surface. Running all 2,129 `apps/web` tests to check one component is the\n   anti-pattern this rule exists for.\n2. **One gate: the release pull request into `prod`.** Promote to Production\n   runs the whole suite there, once, and every check must be green. It is the\n   only check that blocks anything.\n3. **Test at the boundaries users touch:** the HTTP API, the CLI, the\n   `@kortix/sdk` public surface, DB migrations, and a few browser journeys on\n   the money path. Never test internal calls or source text.\n4. **Run each suite once per tree.** A tree that ran is not run again.\n5. **A red suite is worse than none.** A test that fails twice without a bug is\n   fixed or deleted the same day. Do not park it in quarantine.\n6. **Delete on red.** A unit test that goes red on a refactor with no bug is\n   deleted, not updated. Keep unit tests only for logic that is hard to get\n   right: money and credits, parsers, the streaming protocol, permission\n   decisions, crypto and secrets.\n7. **Small, isolated tests.** Every test file runs standalone in seconds. When\n   the test that covers your change takes more than ~10 s, or drags in setup\n   that has nothing to do with it, split it into a smaller isolated test as\n   part of the same change. Fake the clock; never sleep on real time.\n\n| Tier | Contents | Runs |\n|---|---|---|\n| T0 static | `tsc`, lint, architecture checks, migration lint | anytime, seconds per package |\n| Targeted | the one test file or flow that covers your change | on your box, seconds |\n| Release suite | contract lanes (flows, DB, packages) on the release tree, the deployed smoke and the browser journeys against staging, CI, CodeQL, scanners | once per release, on the pull request into `prod` |\n\n| Where | What runs | Blocks? |\n|---|---|---|\n| Your box | T0 for the packages you touched, the targeted test, and the real surface: the route with curl, the CLI process, the page with agent-browser | your judgment |\n| Pull request into `dev` | nothing, unless a person adds `test` or `preview` | no |\n| Push to `dev` | only the seconds-long guards: `secret-scan`, `secrets-guard`, and path-gated `DB Migrations` / `i18n-catalogs` / `Terraform Apply Global` / `deploy-api-router-dev` | no |\n| Nightly on `dev` | `Tests` `core` and `packages` lanes, no browser: a red night comments the failures on the commit, so they are ready to fix before a promote | no |\n| Pull request into `staging` | nothing: Promote to Production opens and merges it | no |\n| Pull request into `prod` | the release suite, once (`full suite + quality gates`) | **yes, the only gate** |\n\n**A Mac never runs a suite.** On macOS `pnpm test` refuses a suite-wide run\n(plain, `--full`, `--packages-only`, `--browser-only`, `--db-only` with no path\nfilter) and exits `2`. 2026-10-16: five agent sessions ran the suite at once on\none laptop (load average 112, swap full). Run one flow\n(`pnpm test -- --id <ID>`), one DB suite path, or one package's test file.\n`KORTIX_FULL_TEST=1` overrides only when a person asks. Every package `test`\nscript refuses the same way: `pnpm --filter <pkg> test` with no file exits `2`\n(`scripts/test-guard.mjs`). Pass the file(s), `pnpm --filter <pkg> test <file>`,\nor a name filter after `--`: `pnpm --filter <pkg> test -- -t <name>`. CI and\nLinux run the full suite.\n\nNo hook or gate requires a test run before a push or a merge into `dev`. Write\nin the PR body what you ran and why. A diff that no test reads (docs, a skill,\na standalone script) needs no run. When the nightly run or the release suite\nturns red on your change, you fix forward on `dev` the same day.\n\n1. Work on the canonical branch in its worktree. Commit as often as you want.\n2. Verify in your box, with real inputs and outputs. Run T0 and the tests that\n   cover the change (see above). Start the worktree's stack\n   (`pnpm worktree start <slug>`) and drive the changed behavior through it: the\n   HTTP route, the real CLI process, or the page with agent-browser. Nothing\n   before the release suite at Promote to Production catches what you skip.\n3. Open the PR against `dev` and follow the **contributing** skill: it attaches\n   the demo video you recorded against your local stack. Write the title and\n   body with the **create-pr** skill: English only, for a human reviewer who\n   decides \"merge or iterate\" without reading the diff. Do not add `test` or `preview` unless you need that one explicit run.\n4. Merge `dev` into the canonical branch daily. A branch that diverges for weeks\n   detonates on merge exactly like a 1,500-line PR does.\n5. **Self-merge to `dev` when the change is verified. Do not wait for the\n   user's approval.** Speed matters: a verified change that sits unmerged is\n   waste. Verified means all of these are true:\n   - the relevant local checks ran with real inputs and outputs (rule 2), and\n     they passed;\n   - the PR is mergeable (no conflict);\n   - rule 6 holds when the change touches a client-facing runtime contract.\n   A failing check blocks the merge until you fix it or state why it is\n   unrelated (for example, the same test fails on `dev`). Squash-merge\n   (`gh pr merge <pr> --squash`), then finish rules 7 and 8. A merge is not\n   the end of the work: dev verification is still yours.\n   The only machine-enforced rule on `dev` and `staging` is that every change\n   arrives through a pull request — no required approvals, no required status\n   checks, no bypass actors. The bar is what you verified.\n   **The release gates do not change.** Merging into `staging` or `prod`,\n   running Promote to Production, and moving the `:stable` tag each need the\n   user's explicit approval (the **kortix-release** skill).\n6. **A change to a client-facing runtime contract** — the `@kortix/sdk` public\n   surface, session/thread transport, the streaming protocol — merges only after\n   the whole objective ran through a real session on your local stack, and runs\n   again on dev after the merge (rule 8). Green tests are not the bar. Someone\n   used it.\n7. After the merge, deploy dev yourself: `gh workflow run deploy-dev.yml\n   --repo kortix-ai/suna -f surface=changed` (`all` or `frontend` force a\n   rebuild). Deploys queue, they never cancel. Wait for the run to finish.\n   `/health` on every changed surface must serve the merge SHA: a successful\n   `/health` response alone is not deployment proof. The run comments \"Live on\n   dev\" on the merged pull request, and \"Not live on dev yet\" names the\n   surface that failed. The surfaces and their checks are in\n   `.github/workflows/deploy-dev.yml`. The merge push runs no suite. The\n   nightly `Tests` run on `dev` comments the failing lanes and every commit\n   since the last green run, once per new set of failing lanes. The author\n   whose commit broke `dev` fixes forward the same day. If `dev` is still red\n   1 hour after the comment, anyone may revert the culprit PR. The release\n   suite at Promote to Production runs the same lanes and blocks the release.\n8. Re-run the user-visible behavior against `https://dev.kortix.com` and/or\n   `https://dev-api.kortix.com`. Prefer the real Kortix CLI configured for the\n   dev API for CLI/project/session flows, and direct authenticated HTTP calls for\n   API contracts. For web behavior, drive the deployed UI and assert its network\n   request plus visible result.\n\nLocal verification and dev verification are both required. A local pass does\nnot replace dev, and a dev smoke test does not replace focused local tests.\nRecord the branch, PR, local commands and output, merge SHA, deploy run,\ndeployed SHA evidence, and the exact dev command or interaction in the final\nresponse.\n\n## Architecture: `@kortix/sdk` is the source of truth\n\n`@kortix/sdk` is the **single source of truth** for everything that talks to the\nKortix backend — projects, accounts, sessions, files, secrets, triggers, the\nsession runtime, the runtime REST client, SSE streaming, model state,\nand auth-token plumbing. The apps\n(`apps/web`, `apps/white-label/*`, `apps/mobile`) are **thin consumers**. Treat\nthese as standing rules whenever you touch the data/runtime layer:\n\n> **Editing `packages/sdk` itself? Load the **sdk** skill (the rules) first.** It is a\n> **published npm package** with its own hard rules that have no analogue\n> elsewhere in this repo: **TDD is mandatory** (failing test first, run it, watch\n> it fail, then implement — and every turn ends with the gates run, the real\n> output pasted, and an explicit shippable YES/NO/NOT YET); exported names\n> (including *types*) are a public API contract and renaming one is a breaking\n> change; the `version` field is inert and must never be bumped by hand; adding\n> an export requires three synchronized edits; and the framework-free core is\n> enforced by a static import-graph tripwire.\n\n- **Logic lives in the SDK, never in a host.** No raw `fetch` to the Kortix API,\n  no `@opencode-ai/sdk` imports, no transport / runtime / data-state code written\n  in app code. New data or runtime behavior is added to the SDK and exposed\n  through its public surface — not hand-rolled or duplicated in a host. If you\n  need something the SDK doesn't expose, add it to the SDK.\n- **One client per host.** Create it once via `createKortix({ backendUrl,\n  getToken })` and read everything through `@kortix/sdk` + `@kortix/sdk/react`.\n  Auth is just `getToken` — an API key / PAT for programmatic use, or a Supabase\n  JWT for the logged-in web app. Hosts never instantiate a second client.\n- **A whole session is one hook.** `useSession(projectId, sessionId)` owns the\n  entire runtime lifecycle — `/start`, the sandbox switch, the live SSE stream,\n  readiness seeding, immutable runtime identity, the native conversation id,\n  and message sync. Hosts don't\n  hand-roll the mount, drive a server-store \"switch\", or mount a separate event\n  provider.\n- **Session-scoped + provider-agnostic.** The public API is session-scoped\n  (`kortix.session(pid, sid).health() / .previewUrl() / .restart() / …`).\n  The sandbox provider and the harness are server-side concerns. Host code\n  must not implement a second transport.\n- **Build on the Kortix contract, not on OpenCode.** Every new session runs\n  pi inside kortixd. OpenCode is deprecated (2026-10-23): there is no\n  `pi_harness` flag, `runtime:` in `kortix.yaml` selects nothing, and OpenCode\n  serves only sessions that started on it. It will be deleted. Code that exists\n  only for OpenCode carries `@deprecated`: never call it from new code, never\n  add a feature to it, and change it only to fix a bug that breaks a running\n  OpenCode session. pi calls models only through the LLM gateway. pi serves\n  the daemon routes (`/kortix/runtime/*`), the transcript\n  (`kortix.transcript.v1`, `packages/api-contract/src/transcript.ts`) and the\n  events. New code reads those, never an OpenCode route, type, file or\n  process. A feature pi lacks is a capability, not a harness check:\n  `GET /kortix/health` lists `capabilities` (`RUNTIME_CAPABILITIES` in\n  `packages/api-contract/src/runtime-relay.ts`), and a client gates the\n  control with `runtimeSupports`. OpenCode lists all eleven (`session.steer`\n  only on OpenCode 1.18.15 or later); pi lists `session.subagents`,\n  `session.compact`, `session.commands` and `session.steer`. pi does not\n  serve rewind, MCP servers, the todo list, shell turns, part edits or\n  `session.attach`.\n  The harness rules and the pi gap list are in\n  `apps/kortix-sandbox-agent-server/src/harness/README.md`.\n- **`apps/web` data modules are shims.** Files such as\n  `apps/web/src/ui/index.ts`, `apps/web/src/lib/iam-client.ts`, and\n  `apps/web/src/hooks/admin/use-*.ts` are thin re-exports\n  (`export * from '@kortix/sdk/...'`).\n  Keep them as shims; put the real logic in the SDK. When a merge conflict lands\n  on one of these, **keep the shim (`--ours`) and port any new host-side logic\n  into the SDK** — do not revert to a host-local implementation.\n- **Docs are the spec.** `apps/web/content/docs/sdk/*` and\n  `packages/sdk/README.md` describe the intended surface. Keep them current with\n  the SDK, and flag legacy/deprecated surfaces in-doc rather than documenting them\n  as current.\n\n## You CAN run and verify everything end-to-end. Do it.\n\nThis repo ships a **complete, runnable local stack with live cloud sandboxes**.\nDo not claim you \"can't verify from here\" or hand back unverified work — you\nhave everything needed to run the app, hit the real API, provision real\nPlatinum sandboxes, drive the real UI in a browser, and assert behavior. Use it.\n\n### Required verification standard — real inputs, real outputs\n\nFor every behavior change, assume **100% autonomy** to verify the user-visible\ncontract before handing the work back. Do not stop at typechecks, unit tests, or\nmocked internals when a real surface exists.\n\n- **API changes:** exercise the actual HTTP route with real request payloads\n  (`curl`, `bun fetch`, or the `ke2e` runner against a running API). Assert the\n  status code and exact response fields that prove the behavior. For writes,\n  also assert the persisted/read-back state or resulting repo/file output.\n- **CLI changes:** run the real CLI command as a process from bash, with the\n  same flags and stdin a user or agent would use. Assert exit code, stdout,\n  stderr, and any files/API calls/commits it should create. Do not rely only on\n  importing command functions.\n- **Web changes:** drive the real page with **agent-browser** (the primary\n  browser; `agent-browser skills get core` loads its guide). Click/type/toggle\n  the actual controls, observe the network request (`agent-browser network`),\n  and assert the visible UI state plus the outgoing payload. Record the flow\n  (`agent-browser record start`) for the PR's demo video. Screenshots and video\n  are evidence, but assertions on DOM and network data are required.\n- **Cross-surface features:** verify each exposed surface independently. If the\n  same feature ships on API + CLI + web + mobile, each gets its own black-box\n  assertion for the inputs users can make and the outputs they receive.\n- **Default/negative paths count:** when changing defaults or removing implicit\n  behavior, assert both the new default and the explicit opt-in/alternate path.\n- **No silent gaps:** if a surface cannot be fully exercised in the current\n  turn, say exactly which input/output remains unverified and why. Otherwise\n  keep going until the real surface is verified.\n- **Final response format:** when work is finished, answer per the **How to\n  communicate** standard at the top of this file — numbered/bulleted, no fluff.\n  Include exactly what changed, what was verified (with the command + output),\n  what remains unverified or risky, and what the user should test next. Do not\n  bury the actionable testing path in a paragraph.\n\n### The stack (already wired)\n\n- **Web** — Next.js dev server on `http://localhost:3000`.\n- **API** — Bun server on `http://localhost:8008/v1` (`/health` returns JSON).\n- **Supabase** — local, on `http://127.0.0.1:54321` (Docker).\n- **Sandboxes** — REAL cloud sandboxes on Platinum, the reference provider\n  (`PLATINUM_API_KEY` in `apps/api/.env` / `.env.local`). Daytona is a legacy\n  fallback with removal scheduled and no support guarantee; E2B support is\n  discontinued. Each project\n  session gets its own sandbox; `session_id == sandbox_id`. The sandbox daemon is\n  reached through `http://localhost:8008/v1/p/<external_id>/8000/...`.\n  The session runtime answers on the same proxy, under `/kortix/runtime/*`,\n  on both harnesses.\n- **Tunnel** — `scripts/dev-local.sh` (`pnpm dev`) auto-starts a cloudflared\n  quick tunnel so cloud sandboxes can call back to the local API (`KORTIX_URL`).\n\nBring it up with `pnpm dev` from `suna/` (it loads `apps/api/.env` +\n`apps/web/.env`, starts Supabase, the API, the web app, and the tunnel). Check\nwhat's already running before starting a duplicate: `curl -s\nlocalhost:8008/v1/health`, `lsof -iTCP:3000 -sTCP:LISTEN`.\n\n> **Secrets are dotenvx-encrypted (mandatory).** `apps/api/.env` (+ `.env.dev`)\n> are committed as ciphertext (`KEY=encrypted:…`); keys live in Dotenv Armor.\n> **Never write a plaintext secret into a tracked file or commit** — add/change\n> values only via `dotenvx set KEY value -f apps/api/.env` (then commit), read\n> with `dotenvx get`, and machine-local overrides go in the gitignored\n> `apps/api/.env.local`. If the user pastes a key, store it with `dotenvx set`,\n> never paste it raw. A pre-commit hook + GitHub push protection enforce this —\n> don't bypass them. Full procedure: the **dotenvx-secrets** skill.\n\n### Authenticating to the live API (for scripts/tests)\n\nMint a real JWT against local Supabase, then call the API with it:\n\n1. `SUPABASE_SERVICE_ROLE_KEY` lives in `apps/api/.env`; the anon key\n   (`NEXT_PUBLIC_SUPABASE_ANON_KEY`) in `apps/web/.env`.\n2. Create a confirmed user: `POST 127.0.0.1:54321/auth/v1/admin/users`\n   (`apikey` + `Authorization: Bearer <service_role>`, body\n   `{email,password,email_confirm:true}`).\n3. Password-grant for the token: `POST\n   127.0.0.1:54321/auth/v1/token?grant_type=password` (`apikey: <anon>`).\n4. Call the API: `Authorization: Bearer <access_token>` against\n   `localhost:8008/v1` (e.g. `/accounts`, `/projects/provision`,\n   `/projects/:id/sessions`, `/p/<ext>/8000/...`).\n\nSee `tests/e2e/helpers/session-auth.ts` for the exact calls.\n\n### One local testing system\n\n- `pnpm test` is the only repository-level test command. It runs local REST and\n  CLI flows, SDK tests, PostgreSQL-backed suites (`db-suites`), runner unit\n  tests, route coverage, and worktree tests concurrently.\n- `pnpm test -- --id ACC-4` runs one flow. `--domain access` runs one domain.\n- `pnpm test -- --sdk-only` runs only `packages/sdk` tests.\n- `pnpm test -- --db-only [path-filter]` runs only the PostgreSQL-backed suites\n  (`integration-*.test.ts`, `*.integration.test.ts`, `tests/migration`), each\n  file against its own fresh migrated database. A skipped DB suite fails.\n- `pnpm test -- --browser-only` runs Playwright browser journeys. It starts the\n  deterministic local stack.\n- Browser runs use two Playwright workers, locally and in each CI shard.\n- `pnpm test -- --packages-only` runs every app/package test and publish check.\n- `pnpm test -- --full` adds browser journeys and every app/package test. It\n  starts the deterministic local stack.\n- `pnpm test -- --target-smoke` verifies the deployed staging API and gateway\n  SHA, then runs the tagged Playwright staging smoke. Release CI supplies the\n  staging credentials and `RELEASE_SOURCE_SHA`.\n- `pnpm test -- --target-full` verifies the same deployed SHA, then runs every\n  configured staging REST, CLI, and Playwright journey. The production release\n  gate uses this command and fails on any excluded API flow.\n- Browser and full modes reuse only a running API that proves the deterministic\n  test profile. Stop an ordinary development stack before either command.\n- Every root run writes lane and total timings to\n  `tests/test-results/local/benchmark-<timestamp>.json`.\n- Before merging into `dev`, run T0 and the tests that cover your change (the\n  testing philosophy above). On a Mac, suite-wide modes exit `2`.\n- Every Linux CI job runs on a free GitHub-hosted runner (`ubuntu-24.04`,\n  `ubuntu-24.04-arm`, `ubuntu-22.04`; 4 vCPU / 16 GB on this public repo) through\n  `runs-on: ${{ vars.CI_RUNNER_<tier> || '<label>' }}`. Setting a\n  `CI_RUNNER_<tier>` repository variable moves that tier to another pool\n  without a PR. Never default a job to a paid runner (Blacksmith, a GitHub\n  larger runner): Blacksmith billed ~$2.7k in September 2026 for minutes\n  GitHub gives this repo for free. `tests/unit/image-build-speed-workflow.test.ts`\n  enforces the free default.\n- `.github/workflows/tests.yml` holds the six contract lanes — `core-flows`,\n  `core-db`, `packages-api`, `packages-cli`, `packages-kortixd`,\n  `packages-rest` — one GitHub-hosted runner each (`CI_RUNNER_L`). `core-*`\n  split the core run (`--core-group=flows|db`); `packages-*` are slices of\n  package quality (`--packages-only --package-group=<g>`). The concurrency\n  settings in `tests/bin/package-quality.ts` must not be raised. Each lane is\n  the unchanged root command at the exact requested SHA. Do not add CI-only\n  test logic.\n- The contract lanes run nightly on `dev`, once when a person adds the `test`\n  label to a pull request into `dev`, on dispatch, and inside the release\n  suite (`tests-release.yml` calls `tests.yml`). A push to `dev` and the pull\n  request into `staging` run nothing. A red nightly run comments the failing\n  lanes and suspect commits on the commit, once per new set of failing lanes.\n- The release suite (`tests-release.yml`, on the release PR into `prod`) runs\n  the contract lanes, the deployed API smoke (the flows tagged `deployed`, one\n  or two per deployment seam) and the browser journeys against deployed\n  staging, in parallel. `full suite + quality gates` requires all of them and\n  every other check on the release commit. A browser spec exists only for a\n  money-path journey (sign-in, onboarding, session load and streaming, desktop\n  parity, billing, connectors, invites); everything else is a REST flow or\n  nothing. CI records a video of every browser journey in the run's\n  `tests-release-browser-shard-N` artifact. A failed release-suite run reruns\n  its failed jobs once by itself (`rerun-failed-once.yml`): a job that passes\n  on the rerun is a flake to fix or delete.\n- `tests/unit/sandbox-workflow.test.ts` fails when any workflow except the\n  label-gated `tests.yml` and `deploy-preview.yml` triggers on a pull request\n  into `dev`, and pins both label gates to the label-added event.\n- The deployed half of the release suite (`--target-api-full`,\n  `--target-browser-full`) waits up to 15 min for staging to serve\n  `RELEASE_SOURCE_SHA`, then fails when any `deployed` flow is excluded or any\n  configured Playwright journey fails.\n- The `preview` label is not part of the development flow. Adding it deploys a\n  Platinum self-host environment for the PR (~7 min), once, and runs no tests.\n  A push does not redeploy. Only `gh workflow run deploy-preview.yml -f\n  pr_number=<N>` runs `--target-full` against a preview (40–80 min). Its mechanics are\n  in the **contributing** skill (`references/preview-environments.md`). Run a\n  preview of your change on your worktree's local stack by default.\n\n### Product flow source of truth\n\n- `tests/spec/end-to-end.md` contains the natural-language contract and stable\n  flow IDs.\n- `tests/src/flows/*.flow.ts` implements the contracts through HTTP and real CLI\n  processes. Do not import API handlers.\n- Write each `ctx.step()` as a complete action and observable result. Cover\n  setup, authentication, action, read-back proof, negative paths, and cleanup.\n- Keep every flow's `meta.routes` synchronized with\n  `tests/spec/routes.generated.json`. Regenerate the manifest with\n  `bun run apps/api/scripts/dump-routes.ts` after route changes.\n- The local profile uses local Supabase, PostgreSQL, API, gateway, and bare Git\n  repositories. It excludes Stripe, cloud sandboxes, managed GitHub repositories,\n  and external email delivery explicitly.\n- Use Playwright only for browser-visible behavior. API-only assertions belong\n  in REST flows. SDK tests remain in `packages/sdk`.\n- Do not add another cross-cutting test harness, Makefile lane, contract suite,\n  Testcontainers suite, load suite, mutation suite, visual suite, accessibility\n  suite, or ad hoc smoke script under `tests/`.\n- Read `tests/README.md` and the repository `testing` skill before changing the\n  test system.\n\n### Release topology — dev, staging, prod\n\n- **`dev` = dev trunk.** It is the repo default branch and deploys to\n  `dev.kortix.com` / `dev-api.kortix.com`. Direct pushes are allowed; breaking or\n  incomplete development can live here while it is being shaken out.\n- **`staging` = release-candidate branch.** Nothing should land on staging unless\n  it is intended to be production-ready. Promote to Production merges the\n  `dev` HEAD into staging itself (the PR runs no check), deploys it, and opens\n  the release PR whose release suite is the one gate. Staging is the deploy\n  target that suite runs against, not a test stage. Staging\n  deploys to `staging.kortix.com` / `staging-api.kortix.com` and must use the\n  staging data plane, not dev or prod. The full promote-and-gate flow, the\n  fix-forward loop, and the prod release steps live in the **kortix-release**\n  skill — that is the one place this is written out in full.\n- Staging deploys must apply pending DB migrations against `STAGING_DATABASE_URL`\n  before the staging ECS Fargate rollout. If that secret is missing or points at\n  dev/prod, treat the deploy as broken; staging must never fall back to dev,\n  KE2E, or prod DBs.\n- **`prod` = production.** Production moves only through **Promote to Production**,\n  which uses `staging` as the source, opens a reviewed release PR into `prod`,\n  publishes the release artifacts, and rolls production after merge.\n- If a staging runtime check points at `dev.kortix.com` or\n  `dev-api.kortix.com`, treat that as a broken staging setup, not a passing\n  staging gate.\n\n### Driving the real UI (agent-browser)\n\n- **agent-browser is the browser for every agent task in this repo.** Use it\n  before chrome-devtools MCP, Playwright MCP, or any other built-in browser\n  tool. Playwright stays the engine of the committed browser test suite only.\n- The installed skill is a stub. Load the guide that matches the CLI version:\n  `agent-browser skills get core` (`--full` adds the command reference).\n- Use one named session per worktree:\n  `agent-browser session id --scope worktree --prefix <task>`, then pass\n  `--session <id>` on every command. Keep every `AGENT_BROWSER_*` variable the\n  same for all commands in a session. A per-command change relaunches the\n  browser and drops the open page.\n- Routes are auth-gated (`/dashboard`, `/projects/*` → redirect to `/auth`\n  unauthenticated); sign in first (seed a user as above, then log in via the\n  `/auth` form). On a PR preview, the magic link arrives in the preview's Mailpit API\n  (`<origin>/_mailpit/api/v1`)\n  (the **contributing** skill has the script).\n- Next.js dev compiles routes on first hit — first navigation to a cold route\n  can take 30–60s; warm it with `curl` or use a generous navigation timeout.\n- `agent-browser doctor` diagnoses launch and recording problems. Recording\n  needs ffmpeg with libvpx and libx264.\n\n### API lint gate\n\n- `pnpm --filter kortix-api lint` runs in the `Tests` packages lane. Its rules\n  are in `apps/api/eslint.config.mjs`: layered imports, no Drizzle in route\n  files, no `(c: any)`, no `process.env` outside `config.ts`, no\n  `console.*`, a `replica-local:` comment on every empty module-level\n  `Map`/`Set`, and no import into `projects/` from outside it except\n  `projects/index.ts` or `projects/surface.ts`. Background timers are guarded by\n  `apps/api/src/__tests__/unit-worker-scope-wiring.test.ts` instead.\n\n### API code conventions (`apps/api/src`)\n\n- **Routes register explicitly.** A route module exports\n  `register<Name>Routes()`. The module that mounts the router calls it right\n  before `app.route()` (`app.ts`, `accounts/index.ts`, `router/index.ts`).\n  Never register at import time. Call order is dispatch order.\n- **No service imports a route module.** Shared logic lives in a service file\n  next to the routes (for example `projects/session-open/`).\n- **Services take an `Actor`, never a Hono `Context`.** The route reads the\n  request through `middleware/` and `http-*.ts` helpers, builds the actor\n  (`middleware/actor.ts`, memoized per request), and calls the service with\n  plain values. Parse `Authorization: Bearer` with `shared/bearer-token.ts`.\n- **Every background loop lives in `workers/<name>-worker.ts`.** The worker\n  owns its timer, start/stop and `runWorkerTick` call. The tick stays an\n  exported service function. `bootstrap.ts` imports every loop from `workers/`.\n- **`iam/` reads the membership, group and role tables.** Use the read models\n  in `iam/membership-read.ts`, `iam/group-read.ts` and `iam/role-read.ts`.\n- **No function longer than 300 lines.** Split long functions into named steps.\n- **SQL trace:** `KORTIX_SQL_TRACE=<file>` appends every pool statement to the\n  file. Diff two traces to prove a refactor runs the same SQL.\n- `apps/api/eslint-suppressions.json` holds the violations that existed when\n  each rule was added. A new violation fails. A fixed one fails until you run\n  `pnpm --filter kortix-api lint:prune` and commit the smaller file. Never\n  absorb a new violation with `--suppress-all`.\n\n### Frontend type/lint gate\n\n- Typecheck `apps/web` with `pnpm --filter Kortix-Computer-Frontend typecheck`\n  (tsgo, incremental). Never run raw `tsc --noEmit` there. Measured on\n  2026-10-21 on a 24 GB Mac: raw `tsc` ran 817 s, held 3.18 GB, and was\n  killed before it finished. Cold `tsgo` takes 12.1 s at 5.0 GB peak. A warm\n  incremental run takes 1.1 s at 1.7 GB.\n- On 2026-10-21 the tsgo script reported 0 errors on `dev`. The bar is\n  relative: a branch adds no type error to the files it touches. Compare against `origin/dev` with the same command, not\n  against a number in this file.\n  The old ~1500 `TS2786` / `IntrinsicAttributes` noise from a React 19↔18\n  types mismatch (two copies of `@types/react` in one program — `packages/sdk`\n  had its own) is gone as of the Next 16 upgrade. If `TS2786` appears again,\n  treat it as a genuine duplicate-`@types/react` regression and investigate —\n  do not wave it through.\n- `npx eslint <files>` should be clean of errors. `eslint .` across the whole\n  app currently reports ~455 warnings, mostly `react-hooks/*` React Compiler\n  rules pending a dedicated audit — expected until that audit lands.\n\n### Frontend design standard — Jay/Kortix bar\n\n#### Canonical design references\n\n| Surface | Read before changing UI | Implemented source |\n|---|---|---|\n| Web (`apps/web`) | `.agents/skills/kortix-brand/SKILL.md` (the router: values, voice, claims, decision history), then `.agents/skills/kortix-design-system/SKILL.md` for components | `apps/web/src/app/globals.css` tokens (generated from `.agents/skills/kortix-brand/references/visual/visual-system.json`), `packages/ui/src/primitives/` (apps/web imports each one as `@kortix/ui/<name>`; apps/web has no re-export shims, `tests/unit/web-no-ui-shims.test.ts` enforces it), the live `/design-system` route |\n| `@kortix/ui` hosts (Kortix Apps, `apps/white-label/reskin`, `apps/white-label/lumen`, `apps/white-label/team-console`, SDK customer apps) | `packages/ui/skills/kortix-ui/SKILL.md` (setup, which component, white-label theming, the `SessionThread` chat), then the web row for Kortix-owned surfaces | `packages/ui/src/` (components, `session/` for the `SessionThread` chat, `styles/tokens.css` + `theme.css`, prebuilt `dist/styles.css`); white-label = CSS variable overrides, never a forked component |\n| Desktop (Electron shell) | The web row — the shell renders `apps/web` — plus `apps/desktop-electron/README.md` for the shell boundary, then the parity gate below | `apps/web` rendered by the shell; native window geometry in the shell's titlebar classes |\n| Mobile (`apps/mobile`) | `apps/mobile/design.md` for screens, `apps/mobile/AGENTS.md` for primitives | `apps/mobile/global.css` colors, `apps/mobile/components/ui/`; stock Tailwind spacing for touch targets |\n\n#### Desktop parity is a UI gate\n\nThe Electron app loads `apps/web`. Keep product components, routes, tokens,\nand data behavior shared. Put native window geometry in the shell's explicit\ntitlebar classes. Never apply titlebar height or drag rules to generic ARIA\nroles, all sidebars, or page content.\n\nFor every shared UI change, verify the affected controls on web and in Electron\nbefore handoff. Check the outgoing request or route and the visible result.\nCheck both themes, the minimum supported window (720 × 480), sidebar collapse,\nfullscreen overlays, and browser zoom. Window controls must not overlap app\ncontrols. Lists must not overlap or clip their last row. Keyboard focus and\nscrolling must remain usable.\n\nAdd regressions to the existing Playwright journeys. The desktop journey runs\nin `pnpm test -- --browser-only` and supports the actual Electron shell:\n`E2E_DESKTOP_NATIVE=1 E2E_GREP='27 — desktop parity' pnpm test -- --browser-only`.\nRun the native journey when changing shell CSS, navigation, settings, agents,\nor connectors. A desktop user-agent test does not prove native hit testing.\nReport any unverified desktop behavior explicitly. Do not promise that tests\nprevent every future regression.\n\nWhen touching any visual surface in `apps/web`, treat brand fit as a release\ngate, not polish:\n\n- Read `.agents/skills/kortix-brand/SKILL.md` before writing the first\n  `className` or the first user-facing string. It routes you to the value law:\n  the complete allowlist of every color, spacing step, type rung, radius,\n  elevation, and duration you may use. Note `--spacing: 0.23rem` — Tailwind's\n  scale is 8% tighter here, so a 16px mockup padding is `p-4`, never\n  `p-[16px]`. Run `.agents/skills/kortix-brand/scripts/audit.sh` over your\n  changed paths before opening the PR; it must be clean on files you touched.\n  `references/visual/visual-system.json` is the only file with values: edit it,\n  then run `scripts/generate-tokens.ts`. Never edit a generated region of\n  `globals.css` by hand.\n- Read `.agents/skills/kortix-design-system/SKILL.md` next and compose existing\n  primitives from `@kortix/ui/*` before inventing local chrome.\n- Match the current Jay Suthar / Kortix product aesthetic: calm neutral surfaces,\n  dense-but-legible UI, black/white plus one earned accent, token-driven spacing,\n  and no decorative color, glow, or one-off rounded boxes.\n- Use recent product surfaces as references before editing: `/design-system`,\n  `packages/ui/src/workspace/layout/project-home.tsx`,\n  `apps/web/src/components/ui/wallpaper-background.tsx`, and the account/IAM\n  screens called out by the design-system skill.\n- Verify visual work in the browser and include the exact lint/typecheck commands\n  you ran in the PR. If it does not look native beside Jay-authored UI, keep\n  iterating before shipping.\n","CLAUDE.md":"# Kortix project\n\n## What \"ownership\" means\n\nThe words \"can you own this?\", \"are you on it?\", and \"can you take care of this?\"\nall mean the same thing: you are 100% responsible. The person who handed it to\nyou must be able to walk completely away, come back a week later, and find it\ndone properly — because you cared about every edge and corner.\n\n- it's not done if it's not implemented\n- it's not done if the implementation is ugly\n- it's not done if it's not documented\n- it's not done if users can't discover it\n- it's not done if you can't market it\n\nOwning something means owning it end to end. The whole arc from \"we have a\nproblem\" to \"nobody has to think about this again.\" Not just the code change in\nthe middle, the whole thing. If someone hands you something and still has to\ntrack whether it actually got solved, you didn't own it.\n\nHere's what that actually looks like in practice.\n\n**Start with the problem, not the solution.** A lot of the time you'll already\nhave a fix in your head before you've understood what's broken. \"We need to move\nfrom X to Y\" isn't a problem, that's a solution you've pre-committed to. The real\nproblem is probably \"it's slow\", \"it's flaky\", \"it breaks for this customer\".\nName that first. Then ask what else could solve it, what the tradeoffs are, and\nwhich option actually wins.\n\n**Then pressure-test it before you build:**\n\n- **Edge cases.** Which exist, which matter, which we can safely ignore.\n- **Failures.** Networks fail, that's a given. Retry? How many times, for how\n  long?\n- **Data.** How much, does it need migrating or cleaning, how do you get real\n  data to test against, and what are you assuming about its shape that you\n  haven't actually verified?\n- **Testing.** How will you know it's correct? Are automated tests enough or do\n  you need to poke at it by hand? Is the result something you can see in a\n  screenshot or a video?\n- **The bigger picture.** How does this get announced, how does it fit the\n  roadmap, can you even picture it shipped? If something there bothers you, push\n  back. Ask.\n\n**Then actually do it**, with precision, care, urgency and calm all at once. No\nhalf-assing. The bar before you merge: am I proud of this? Would I put it in\nfront of Steve Jobs and walk him through what I built, the constraints, the\ntradeoffs?\n\n**And then prove it works.** Not \"the tests pass\", prove it. In 99% of cases you\ncan confirm it yourself: run it, ask an agent to walk through the scenarios, poke\nat the data before and after, take a screenshot, make a demo. Are you actually\nsure it solves the problem you started with?\n\n**Then make sure it lands in production and works in production**, which is not\nthe same as merged. Did it deploy? Did the deploy quietly fail? Is there a flag\nto flip, and does the flag work? Can you use the thing in prod right now and\nconfirm it's really there?\n\n**And then close the loop with everyone it touches:**\n\n- **The team.** If it's a new feature, a new convention, or a tricky thing people\n  should know about, tell them. Don't underestimate peripheral vision. You\n  knowing that someone changed Z yesterday can save someone else three hours of\n  debugging tomorrow when a bug report about Z comes in.\n- **Customers.** Whoever reported it, whoever's blocked, let them know it's\n  fixed.\n- **The world.** If it's worth announcing, announce it.\n- **Future you.** Are there follow-ups? Should you check the logs in a week to\n  make sure it's still healthy?\n\nBut that's how we build a product in a small team. We don't have PMs, we don't\nhave a QA department. We're small, but we're great, and we can do all of that.\n\nAnd it's always okay to ask for help, it's okay to ask questions, it's okay to\nredo things and triple-check. What's not okay is to quietly assume someone else\nwill catch the parts you didn't think about.\n\n### The bar: autonomy, agency, ownership\n\nThis is what I require from the agent I work with. In my own words:\n\nEither you are performing or you are not. Either you are taking on high\nownership, are high agency and pushing without me having to micromanage you, or\nyou are not.\n\nEspecially in today's age you are limited by great talent more than ever. Because\nwe can all AGI Max, you have a single chokepoint on good judgement.\n\nEvery person that comes on the team has to have the capability to truly own\nsomething. If you have built products, you develop ownership because you owned\nsomething — whether it worked out or not — for a prolonged amount of time. You\ndevelop true agency.\n\nI hire you because I expect that if I am able to walk out of the room after\ngiving you a high level thing and come back, it's going to be done good, ideally\nbetter than I would've done it.\n\nMost people are shit at their jobs, some are decent/good, but only people who are\nexceptional should be at Kortix.\n\nBeing exceptional on paper is simple — it's a combination of Ownership, Agency\nand actual Merit/Skill. It's hard, because you have to not only be very smart but\nalso crazy driven to push like a motherfucker and want to feel every edge and\ncorner to make sure the output is good.\n\n## Skills live in `.agents/skills/`\n\nEvery repo skill is a directory in `.agents/skills/<name>/`. `.claude/skills/<name>` is a\nsymlink to it. Add a skill in `.agents/skills/`, then add the symlink. Third-party skills come\nfrom `npx skills add` and are pinned in `skills-lock.json`. The PR procedure is the\n**contributing** skill; the PR title and body are the **create-pr** skill. Browser work is the **agent-browser** skill.\n\nThe repository has no `docs/` tree. Put a runbook or spec in the skill that owns the\nsurface (`.agents/skills/<name>/references/`). Put an incident rule in the learnings\nledger. Put design detail and RCAs in the PR body. Never cite a repo path that does\nnot exist. The pre-commit hook and `tests/unit/no-docs-tree.test.ts` reject a new\n`docs/` file and any citation of one.\n\n## Ponytail is on by default\n\nEvery code change runs through the **ponytail** skill at level `full`. Load it before you\nwrite, fix, refactor, or review code, and before you add a dependency. Level switch:\n`/ponytail lite|full|ultra`. Off: \"stop ponytail\". **ponytail-review** audits a diff for\nover-engineering, **ponytail-audit** audits the whole repo, and **ponytail-debt** lists\nevery `ponytail:` shortcut comment. Ponytail cuts code, never the verification,\ndocumentation, or ownership bar in this file. The skills come from\n`DietrichGebert/ponytail`, pinned to `v4.10.0` in `skills-lock.json`.\n\n## Learnings: the episodic ledger in the `learnings` skill\n\n`.agents/skills/learnings/` is the append-only, timestamped ledger of rules paid\nfor with real downtime. `MEMORY.md` indexes it newest first, and each entry is\none file in `entries/`: the rule, the incident that taught it, and the\nautomation that enforces it. Search it before writing or reviewing a DB\nmigration, touching deploy/release workflows, planning a promote, or\nresponding to a prod incident. After resolving ANY incident or near-miss,\nrecord a new entry in the same session with `scripts/new-entry.sh`. An incident\nthat leaves no entry behind is not finished.\n\n## NEVER write customer data or PII into anything we publish or commit\n\nThis is a hard rule. No exceptions, no \"just this once\", no \"it's only\ninternal\".\n\n**Never write any of these:**\n\n- customer or company names, and the names of their people;\n- email addresses, phone numbers, or other personal data;\n- real account, project, session, user, or sandbox IDs from prod, staging, or\n  a customer deployment;\n- customer repository names, hostnames, or URLs that contain any of the above;\n- customer prompts, messages, files, or log lines;\n- screenshots of a real customer workspace.\n\n**Never write them into:**\n\n- commits, commit messages, or branch names;\n- PR titles, PR bodies, PR comments, or review comments;\n- issues;\n- code comments, test names, or test fixtures;\n- docs, runbooks, skills, `AGENTS.md`, changelogs, or release notes;\n- artifacts, Slack posts, or any public or team-visible text.\n\n**Write the class instead:** \"a customer reported\", \"an enterprise\nworkspace\", \"a prod session\", `<session_id>`. Build test data from synthetic\nvalues. Evidence that contains real data stays local: the gitignored\n`output/` folder, your scratchpad, or the private agent memory. It never goes\ninto a tracked file.\n\n**If you find customer data** in the tree or in a PR, remove it in the same\nbranch and say so. Do not rewrite history on `dev`. Report the SHA to the user\ninstead.\n\n**A guard enforces this on every commit and push.**\n`scripts/check-blocked-terms.sh` runs from `.githooks/pre-commit`,\n`.githooks/commit-msg`, and `.githooks/pre-push`. It refuses any added line,\ncommit message, or pushed branch name that contains a blocked term. Matching is\ncase-insensitive and whole-word. The list is itself customer data, so it lives\nencrypted in `apps/api/.env` as `BLOCKED_COMMIT_TERMS`, comma-separated.\n\n- Add a customer the day they sign: `dotenvx set BLOCKED_COMMIT_TERMS\n  \"<existing>,<new>\" -f apps/api/.env`. Read the current value first with\n  `dotenvx get BLOCKED_COMMIT_TERMS -f apps/api/.env`.\n- In a worktree the guard decrypts with the primary checkout's\n  `apps/api/.env.keys`. Without a key it warns and allows.\n- Deleting a line that contains a term is always allowed.\n- Never bypass the guard with `--no-verify`. If it fires, remove the term.\n- The hooks do not see PR titles, PR bodies, or comments. Those stay your\n  responsibility.\n\n## How to communicate: precise, technically accurate, no fluff\n\nWrite every response — chat, PR text, commit messages, code comments, docs — in\nthe spirit of **ASD-STE100 (Simplified Technical English)**. The goal is maximum\ntechnical precision with zero filler. Apply these rules:\n\n- **State facts, not vibes.** Every claim is specific and verifiable: name the\n  file, function, route, flag, SHA, status code, or number. No \"should work\",\n  \"probably\", \"a bunch of\", \"various\", \"seems fine\" — say what is true and how you\n  know, or say you do not know it yet.\n- **One idea per sentence.** Keep sentences short (aim ≤ 20 words) and each one\n  carries a single instruction or fact. Split compound thoughts instead of\n  chaining clauses.\n- **Active voice, present tense, direct.** \"The gate rejects the request\",\n  not \"the request may end up being rejected\". Give the instruction; do not\n  soften it.\n- **One term per concept.** Use the same word for the same thing every time —\n  do not alternate \"session\"/\"run\"/\"task\" for one concept. Match the codebase's\n  existing names exactly (`session_id`, not \"session ID / run id\").\n- **No filler, no hedging, no praise.** Cut \"basically\", \"just\", \"simply\",\n  \"I think\", \"great question\", \"as we know\", and marketing adjectives. Lead with\n  the answer; drop the throat-clearing.\n- **Quantify.** Prefer exact values over adjectives: \"up to ~9 min\", \"returns\n  `402`\", \"3 of 7 flows\", not \"slow\", \"an error\", \"most\".\n- **Show the evidence.** When you assert a behavior, cite the command you ran and\n  the real output. Distinguish verified fact from assumption explicitly.\n- **Structure over prose.** Use numbered/bulleted lists for steps, findings, and\n  status. Reserve paragraphs for genuine explanation, and keep them tight.\n- **Say the unknown plainly.** If something is unverified, blocked, or risky,\n  state it in one line — what, why, and what would resolve it — instead of\n  burying or omitting it.\n\nThis standard governs how you talk. It does not override the technical rules\nbelow; it is how you report on them.\n\n## First, at session start: which canonical branch are you in?\n\nEvery change belongs to **one canonical branch** — the branch for whatever is\nbeing worked on. One canonical branch, one worktree. Establish which one you are\nin before any non-trivial change. **Do not create a branch by reflex.**\n\n1. **Join the canonical branch that already exists** for this work. List them\n   with `git worktree list` and `git branch -r`. If the work continues, extends,\n   fixes, or cleans up something already in flight, it belongs on that branch.\n   Ask the user which branch when it is not obvious.\n2. **Start a new canonical branch** only when the work is genuinely a new thing.\n   Give it its own worktree: `pnpm worktree create --name <slug> --yes\n   --no-start`, then do all edits and runs under `../suna-<slug>`. Add `--db`\n   when the work needs migrations, destructive data work, or schema drift: it\n   copies the primary local database into the worktree's own database\n   (`kortix_wt_<slug>`, ~5 s, no extra containers). `--full-supabase` (a whole\n   Supabase stack, ~0.75 GB RAM) is only for independent auth/storage config.\n   See the **worktree** skill.\n3. **Never switch a worktree you did not create.** It belongs to one session and\n   its canonical branch. The pre-commit hook refuses a commit on any other branch\n   there (`scripts/check-worktree-branch.sh`). A throwaway probe branch goes in a\n   private `git worktree add` in your scratchpad.\n4. **The primary checkout** (`pnpm dev`, web `3000` / api `8008`) is for running\n   and investigating. Do not park feature work there.\n5. **Run `pnpm test` only from a worktree that has `.kortix-worktree.json`.** The\n   runner reads its ports from that marker. A bare `git worktree add` has none,\n   so its test stack takes the primary ports and talks to another session's\n   `pnpm dev` (2026-10-13: 156 flows failed with that stack's 503s).\n6. **Never kill a process you did not start.** Many sessions share this machine.\n   Kill by PID from your own process tree, or by a pattern that contains your\n   own worktree or scratchpad path. Never by a bare pattern such as\n   `pkill -f \"bun test\"`, and never in a loop. Need CPU? Lower your own\n   concurrency (`KE2E_API_WORKERS`, `KORTIX_DB_SUITE_WORKERS`) or ask the user.\n   2026-10-13: a guard loop that SIGTERMed every other session's `pnpm test`,\n   `tsc` and `vitest` every 20 s made 259 kills, and every suite on the machine\n   died with exit 143 and no failing test.\n\n**Pack more into one branch, not less.** A follow-up fix, a rename cleanup, a\nstale-reference sweep, and the change that caused them all belong on the same\nbranch and land together. Splitting one objective across several branches is how\na half-finished cutover reaches `dev` in pieces — each piece green alone, the\nwhole thing broken.\n\nSub-branches are allowed. Agents may cut working branches off the canonical\nbranch and merge back into it. **A sub-branch never opens a PR against `dev`.**\nOnly the canonical branch does.\n\nCarve-outs where you just proceed: read-only investigation and questions, and\ntrivial single-file typo/comment fixes on the current branch.\n\n## Default delivery: verify in your box, self-merge to `dev`, verify on dev\n\n`dev` is the dev trunk. A merge does not deploy: dev deploys only on a deliberate\ndispatch, but **merging to `dev` still lands your change in everyone's next\ndeploy and next `dev` checkout.** It is not a save point.\n\n### Testing philosophy: one gate at Promote to Production, everything else fast\n\nTesting reset, 2026-10-16. Latency is the core concern. Development must be\nfast enough to push and break things on `dev`. The full suite runs once per\nrelease, at Promote to Production, and nowhere else. The rules:\n\n1. **Never run a full suite during development, and never a whole package's\n   tests.** No multi-minute run on your box, on a PR into `dev`, or on a push\n   to `dev`. Verify only what you changed: T0 for the packages you touched,\n   the test files that cover the change, named one by one\n   (`pnpm --filter <pkg> test <file>`), and the change itself through its real\n   surface. Running all 2,129 `apps/web` tests to check one component is the\n   anti-pattern this rule exists for.\n2. **One gate: the release pull request into `prod`.** Promote to Production\n   runs the whole suite there, once, and every check must be green. It is the\n   only check that blocks anything.\n3. **Test at the boundaries users touch:** the HTTP API, the CLI, the\n   `@kortix/sdk` public surface, DB migrations, and a few browser journeys on\n   the money path. Never test internal calls or source text.\n4. **Run each suite once per tree.** A tree that ran is not run again.\n5. **A red suite is worse than none.** A test that fails twice without a bug is\n   fixed or deleted the same day. Do not park it in quarantine.\n6. **Delete on red.** A unit test that goes red on a refactor with no bug is\n   deleted, not updated. Keep unit tests only for logic that is hard to get\n   right: money and credits, parsers, the streaming protocol, permission\n   decisions, crypto and secrets.\n7. **Small, isolated tests.** Every test file runs standalone in seconds. When\n   the test that covers your change takes more than ~10 s, or drags in setup\n   that has nothing to do with it, split it into a smaller isolated test as\n   part of the same change. Fake the clock; never sleep on real time.\n\n| Tier | Contents | Runs |\n|---|---|---|\n| T0 static | `tsc`, lint, architecture checks, migration lint | anytime, seconds per package |\n| Targeted | the one test file or flow that covers your change | on your box, seconds |\n| Release suite | contract lanes (flows, DB, packages) on the release tree, the deployed smoke and the browser journeys against staging, CI, CodeQL, scanners | once per release, on the pull request into `prod` |\n\n| Where | What runs | Blocks? |\n|---|---|---|\n| Your box | T0 for the packages you touched, the targeted test, and the real surface: the route with curl, the CLI process, the page with agent-browser | your judgment |\n| Pull request into `dev` | nothing, unless a person adds `test` or `preview` | no |\n| Push to `dev` | only the seconds-long guards: `secret-scan`, `secrets-guard`, and path-gated `DB Migrations` / `i18n-catalogs` / `Terraform Apply Global` / `deploy-api-router-dev` | no |\n| Nightly on `dev` | `Tests` `core` and `packages` lanes, no browser: a red night comments the failures on the commit, so they are ready to fix before a promote | no |\n| Pull request into `staging` | nothing: Promote to Production opens and merges it | no |\n| Pull request into `prod` | the release suite, once (`full suite + quality gates`) | **yes, the only gate** |\n\n**A Mac never runs a suite.** On macOS `pnpm test` refuses a suite-wide run\n(plain, `--full`, `--packages-only`, `--browser-only`, `--db-only` with no path\nfilter) and exits `2`. 2026-10-16: five agent sessions ran the suite at once on\none laptop (load average 112, swap full). Run one flow\n(`pnpm test -- --id <ID>`), one DB suite path, or one package's test file.\n`KORTIX_FULL_TEST=1` overrides only when a person asks. Every package `test`\nscript refuses the same way: `pnpm --filter <pkg> test` with no file exits `2`\n(`scripts/test-guard.mjs`). Pass the file(s), `pnpm --filter <pkg> test <file>`,\nor a name filter after `--`: `pnpm --filter <pkg> test -- -t <name>`. CI and\nLinux run the full suite.\n\nNo hook or gate requires a test run before a push or a merge into `dev`. Write\nin the PR body what you ran and why. A diff that no test reads (docs, a skill,\na standalone script) needs no run. When the nightly run or the release suite\nturns red on your change, you fix forward on `dev` the same day.\n\n1. Work on the canonical branch in its worktree. Commit as often as you want.\n2. Verify in your box, with real inputs and outputs. Run T0 and the tests that\n   cover the change (see above). Start the worktree's stack\n   (`pnpm worktree start <slug>`) and drive the changed behavior through it: the\n   HTTP route, the real CLI process, or the page with agent-browser. Nothing\n   before the release suite at Promote to Production catches what you skip.\n3. Open the PR against `dev` and follow the **contributing** skill: it attaches\n   the demo video you recorded against your local stack. Write the title and\n   body with the **create-pr** skill: English only, for a human reviewer who\n   decides \"merge or iterate\" without reading the diff. Do not add `test` or `preview` unless you need that one explicit run.\n4. Merge `dev` into the canonical branch daily. A branch that diverges for weeks\n   detonates on merge exactly like a 1,500-line PR does.\n5. **Self-merge to `dev` when the change is verified. Do not wait for the\n   user's approval.** Speed matters: a verified change that sits unmerged is\n   waste. Verified means all of these are true:\n   - the relevant local checks ran with real inputs and outputs (rule 2), and\n     they passed;\n   - the PR is mergeable (no conflict);\n   - rule 6 holds when the change touches a client-facing runtime contract.\n   A failing check blocks the merge until you fix it or state why it is\n   unrelated (for example, the same test fails on `dev`). Squash-merge\n   (`gh pr merge <pr> --squash`), then finish rules 7 and 8. A merge is not\n   the end of the work: dev verification is still yours.\n   The only machine-enforced rule on `dev` and `staging` is that every change\n   arrives through a pull request — no required approvals, no required status\n   checks, no bypass actors. The bar is what you verified.\n   **The release gates do not change.** Merging into `staging` or `prod`,\n   running Promote to Production, and moving the `:stable` tag each need the\n   user's explicit approval (the **kortix-release** skill).\n6. **A change to a client-facing runtime contract** — the `@kortix/sdk` public\n   surface, session/thread transport, the streaming protocol — merges only after\n   the whole objective ran through a real session on your local stack, and runs\n   again on dev after the merge (rule 8). Green tests are not the bar. Someone\n   used it.\n7. After the merge, deploy dev yourself: `gh workflow run deploy-dev.yml\n   --repo kortix-ai/suna -f surface=changed` (`all` or `frontend` force a\n   rebuild). Deploys queue, they never cancel. Wait for the run to finish.\n   `/health` on every changed surface must serve the merge SHA: a successful\n   `/health` response alone is not deployment proof. The run comments \"Live on\n   dev\" on the merged pull request, and \"Not live on dev yet\" names the\n   surface that failed. The surfaces and their checks are in\n   `.github/workflows/deploy-dev.yml`. The merge push runs no suite. The\n   nightly `Tests` run on `dev` comments the failing lanes and every commit\n   since the last green run, once per new set of failing lanes. The author\n   whose commit broke `dev` fixes forward the same day. If `dev` is still red\n   1 hour after the comment, anyone may revert the culprit PR. The release\n   suite at Promote to Production runs the same lanes and blocks the release.\n8. Re-run the user-visible behavior against `https://dev.kortix.com` and/or\n   `https://dev-api.kortix.com`. Prefer the real Kortix CLI configured for the\n   dev API for CLI/project/session flows, and direct authenticated HTTP calls for\n   API contracts. For web behavior, drive the deployed UI and assert its network\n   request plus visible result.\n\nLocal verification and dev verification are both required. A local pass does\nnot replace dev, and a dev smoke test does not replace focused local tests.\nRecord the branch, PR, local commands and output, merge SHA, deploy run,\ndeployed SHA evidence, and the exact dev command or interaction in the final\nresponse.\n\n## Architecture: `@kortix/sdk` is the source of truth\n\n`@kortix/sdk` is the **single source of truth** for everything that talks to the\nKortix backend — projects, accounts, sessions, files, secrets, triggers, the\nsession runtime, the runtime REST client, SSE streaming, model state,\nand auth-token plumbing. The apps\n(`apps/web`, `apps/white-label/*`, `apps/mobile`) are **thin consumers**. Treat\nthese as standing rules whenever you touch the data/runtime layer:\n\n> **Editing `packages/sdk` itself? Load the **sdk** skill (the rules) first.** It is a\n> **published npm package** with its own hard rules that have no analogue\n> elsewhere in this repo: **TDD is mandatory** (failing test first, run it, watch\n> it fail, then implement — and every turn ends with the gates run, the real\n> output pasted, and an explicit shippable YES/NO/NOT YET); exported names\n> (including *types*) are a public API contract and renaming one is a breaking\n> change; the `version` field is inert and must never be bumped by hand; adding\n> an export requires three synchronized edits; and the framework-free core is\n> enforced by a static import-graph tripwire.\n\n- **Logic lives in the SDK, never in a host.** No raw `fetch` to the Kortix API,\n  no `@opencode-ai/sdk` imports, no transport / runtime / data-state code written\n  in app code. New data or runtime behavior is added to the SDK and exposed\n  through its public surface — not hand-rolled or duplicated in a host. If you\n  need something the SDK doesn't expose, add it to the SDK.\n- **One client per host.** Create it once via `createKortix({ backendUrl,\n  getToken })` and read everything through `@kortix/sdk` + `@kortix/sdk/react`.\n  Auth is just `getToken` — an API key / PAT for programmatic use, or a Supabase\n  JWT for the logged-in web app. Hosts never instantiate a second client.\n- **A whole session is one hook.** `useSession(projectId, sessionId)` owns the\n  entire runtime lifecycle — `/start`, the sandbox switch, the live SSE stream,\n  readiness seeding, immutable runtime identity, the native conversation id,\n  and message sync. Hosts don't\n  hand-roll the mount, drive a server-store \"switch\", or mount a separate event\n  provider.\n- **Session-scoped + provider-agnostic.** The public API is session-scoped\n  (`kortix.session(pid, sid).health() / .previewUrl() / .restart() / …`).\n  The sandbox provider and the harness are server-side concerns. Host code\n  must not implement a second transport.\n- **Build on the Kortix contract, not on OpenCode.** Every new session runs\n  pi inside kortixd. OpenCode is deprecated (2026-10-23): there is no\n  `pi_harness` flag, `runtime:` in `kortix.yaml` selects nothing, and OpenCode\n  serves only sessions that started on it. It will be deleted. Code that exists\n  only for OpenCode carries `@deprecated`: never call it from new code, never\n  add a feature to it, and change it only to fix a bug that breaks a running\n  OpenCode session. pi calls models only through the LLM gateway. pi serves\n  the daemon routes (`/kortix/runtime/*`), the transcript\n  (`kortix.transcript.v1`, `packages/api-contract/src/transcript.ts`) and the\n  events. New code reads those, never an OpenCode route, type, file or\n  process. A feature pi lacks is a capability, not a harness check:\n  `GET /kortix/health` lists `capabilities` (`RUNTIME_CAPABILITIES` in\n  `packages/api-contract/src/runtime-relay.ts`), and a client gates the\n  control with `runtimeSupports`. OpenCode lists all eleven (`session.steer`\n  only on OpenCode 1.18.15 or later); pi lists `session.subagents`,\n  `session.compact`, `session.commands` and `session.steer`. pi does not\n  serve rewind, MCP servers, the todo list, shell turns, part edits or\n  `session.attach`.\n  The harness rules and the pi gap list are in\n  `apps/kortix-sandbox-agent-server/src/harness/README.md`.\n- **`apps/web` data modules are shims.** Files such as\n  `apps/web/src/ui/index.ts`, `apps/web/src/lib/iam-client.ts`, and\n  `apps/web/src/hooks/admin/use-*.ts` are thin re-exports\n  (`export * from '@kortix/sdk/...'`).\n  Keep them as shims; put the real logic in the SDK. When a merge conflict lands\n  on one of these, **keep the shim (`--ours`) and port any new host-side logic\n  into the SDK** — do not revert to a host-local implementation.\n- **Docs are the spec.** `apps/web/content/docs/sdk/*` and\n  `packages/sdk/README.md` describe the intended surface. Keep them current with\n  the SDK, and flag legacy/deprecated surfaces in-doc rather than documenting them\n  as current.\n\n## You CAN run and verify everything end-to-end. Do it.\n\nThis repo ships a **complete, runnable local stack with live cloud sandboxes**.\nDo not claim you \"can't verify from here\" or hand back unverified work — you\nhave everything needed to run the app, hit the real API, provision real\nPlatinum sandboxes, drive the real UI in a browser, and assert behavior. Use it.\n\n### Required verification standard — real inputs, real outputs\n\nFor every behavior change, assume **100% autonomy** to verify the user-visible\ncontract before handing the work back. Do not stop at typechecks, unit tests, or\nmocked internals when a real surface exists.\n\n- **API changes:** exercise the actual HTTP route with real request payloads\n  (`curl`, `bun fetch`, or the `ke2e` runner against a running API). Assert the\n  status code and exact response fields that prove the behavior. For writes,\n  also assert the persisted/read-back state or resulting repo/file output.\n- **CLI changes:** run the real CLI command as a process from bash, with the\n  same flags and stdin a user or agent would use. Assert exit code, stdout,\n  stderr, and any files/API calls/commits it should create. Do not rely only on\n  importing command functions.\n- **Web changes:** drive the real page with **agent-browser** (the primary\n  browser; `agent-browser skills get core` loads its guide). Click/type/toggle\n  the actual controls, observe the network request (`agent-browser network`),\n  and assert the visible UI state plus the outgoing payload. Record the flow\n  (`agent-browser record start`) for the PR's demo video. Screenshots and video\n  are evidence, but assertions on DOM and network data are required.\n- **Cross-surface features:** verify each exposed surface independently. If the\n  same feature ships on API + CLI + web + mobile, each gets its own black-box\n  assertion for the inputs users can make and the outputs they receive.\n- **Default/negative paths count:** when changing defaults or removing implicit\n  behavior, assert both the new default and the explicit opt-in/alternate path.\n- **No silent gaps:** if a surface cannot be fully exercised in the current\n  turn, say exactly which input/output remains unverified and why. Otherwise\n  keep going until the real surface is verified.\n- **Final response format:** when work is finished, answer per the **How to\n  communicate** standard at the top of this file — numbered/bulleted, no fluff.\n  Include exactly what changed, what was verified (with the command + output),\n  what remains unverified or risky, and what the user should test next. Do not\n  bury the actionable testing path in a paragraph.\n\n### The stack (already wired)\n\n- **Web** — Next.js dev server on `http://localhost:3000`.\n- **API** — Bun server on `http://localhost:8008/v1` (`/health` returns JSON).\n- **Supabase** — local, on `http://127.0.0.1:54321` (Docker).\n- **Sandboxes** — REAL cloud sandboxes on Platinum, the reference provider\n  (`PLATINUM_API_KEY` in `apps/api/.env` / `.env.local`). Daytona is a legacy\n  fallback with removal scheduled and no support guarantee; E2B support is\n  discontinued. Each project\n  session gets its own sandbox; `session_id == sandbox_id`. The sandbox daemon is\n  reached through `http://localhost:8008/v1/p/<external_id>/8000/...`.\n  The session runtime answers on the same proxy, under `/kortix/runtime/*`,\n  on both harnesses.\n- **Tunnel** — `scripts/dev-local.sh` (`pnpm dev`) auto-starts a cloudflared\n  quick tunnel so cloud sandboxes can call back to the local API (`KORTIX_URL`).\n\nBring it up with `pnpm dev` from `suna/` (it loads `apps/api/.env` +\n`apps/web/.env`, starts Supabase, the API, the web app, and the tunnel). Check\nwhat's already running before starting a duplicate: `curl -s\nlocalhost:8008/v1/health`, `lsof -iTCP:3000 -sTCP:LISTEN`.\n\n> **Secrets are dotenvx-encrypted (mandatory).** `apps/api/.env` (+ `.env.dev`)\n> are committed as ciphertext (`KEY=encrypted:…`); keys live in Dotenv Armor.\n> **Never write a plaintext secret into a tracked file or commit** — add/change\n> values only via `dotenvx set KEY value -f apps/api/.env` (then commit), read\n> with `dotenvx get`, and machine-local overrides go in the gitignored\n> `apps/api/.env.local`. If the user pastes a key, store it with `dotenvx set`,\n> never paste it raw. A pre-commit hook + GitHub push protection enforce this —\n> don't bypass them. Full procedure: the **dotenvx-secrets** skill.\n\n### Authenticating to the live API (for scripts/tests)\n\nMint a real JWT against local Supabase, then call the API with it:\n\n1. `SUPABASE_SERVICE_ROLE_KEY` lives in `apps/api/.env`; the anon key\n   (`NEXT_PUBLIC_SUPABASE_ANON_KEY`) in `apps/web/.env`.\n2. Create a confirmed user: `POST 127.0.0.1:54321/auth/v1/admin/users`\n   (`apikey` + `Authorization: Bearer <service_role>`, body\n   `{email,password,email_confirm:true}`).\n3. Password-grant for the token: `POST\n   127.0.0.1:54321/auth/v1/token?grant_type=password` (`apikey: <anon>`).\n4. Call the API: `Authorization: Bearer <access_token>` against\n   `localhost:8008/v1` (e.g. `/accounts`, `/projects/provision`,\n   `/projects/:id/sessions`, `/p/<ext>/8000/...`).\n\nSee `tests/e2e/helpers/session-auth.ts` for the exact calls.\n\n### One local testing system\n\n- `pnpm test` is the only repository-level test command. It runs local REST and\n  CLI flows, SDK tests, PostgreSQL-backed suites (`db-suites`), runner unit\n  tests, route coverage, and worktree tests concurrently.\n- `pnpm test -- --id ACC-4` runs one flow. `--domain access` runs one domain.\n- `pnpm test -- --sdk-only` runs only `packages/sdk` tests.\n- `pnpm test -- --db-only [path-filter]` runs only the PostgreSQL-backed suites\n  (`integration-*.test.ts`, `*.integration.test.ts`, `tests/migration`), each\n  file against its own fresh migrated database. A skipped DB suite fails.\n- `pnpm test -- --browser-only` runs Playwright browser journeys. It starts the\n  deterministic local stack.\n- Browser runs use two Playwright workers, locally and in each CI shard.\n- `pnpm test -- --packages-only` runs every app/package test and publish check.\n- `pnpm test -- --full` adds browser journeys and every app/package test. It\n  starts the deterministic local stack.\n- `pnpm test -- --target-smoke` verifies the deployed staging API and gateway\n  SHA, then runs the tagged Playwright staging smoke. Release CI supplies the\n  staging credentials and `RELEASE_SOURCE_SHA`.\n- `pnpm test -- --target-full` verifies the same deployed SHA, then runs every\n  configured staging REST, CLI, and Playwright journey. The production release\n  gate uses this command and fails on any excluded API flow.\n- Browser and full modes reuse only a running API that proves the deterministic\n  test profile. Stop an ordinary development stack before either command.\n- Every root run writes lane and total timings to\n  `tests/test-results/local/benchmark-<timestamp>.json`.\n- Before merging into `dev`, run T0 and the tests that cover your change (the\n  testing philosophy above). On a Mac, suite-wide modes exit `2`.\n- Every Linux CI job runs on a free GitHub-hosted runner (`ubuntu-24.04`,\n  `ubuntu-24.04-arm`, `ubuntu-22.04`; 4 vCPU / 16 GB on this public repo) through\n  `runs-on: ${{ vars.CI_RUNNER_<tier> || '<label>' }}`. Setting a\n  `CI_RUNNER_<tier>` repository variable moves that tier to another pool\n  without a PR. Never default a job to a paid runner (Blacksmith, a GitHub\n  larger runner): Blacksmith billed ~$2.7k in September 2026 for minutes\n  GitHub gives this repo for free. `tests/unit/image-build-speed-workflow.test.ts`\n  enforces the free default.\n- `.github/workflows/tests.yml` holds the six contract lanes — `core-flows`,\n  `core-db`, `packages-api`, `packages-cli`, `packages-kortixd`,\n  `packages-rest` — one GitHub-hosted runner each (`CI_RUNNER_L`). `core-*`\n  split the core run (`--core-group=flows|db`); `packages-*` are slices of\n  package quality (`--packages-only --package-group=<g>`). The concurrency\n  settings in `tests/bin/package-quality.ts` must not be raised. Each lane is\n  the unchanged root command at the exact requested SHA. Do not add CI-only\n  test logic.\n- The contract lanes run nightly on `dev`, once when a person adds the `test`\n  label to a pull request into `dev`, on dispatch, and inside the release\n  suite (`tests-release.yml` calls `tests.yml`). A push to `dev` and the pull\n  request into `staging` run nothing. A red nightly run comments the failing\n  lanes and suspect commits on the commit, once per new set of failing lanes.\n- The release suite (`tests-release.yml`, on the release PR into `prod`) runs\n  the contract lanes, the deployed API smoke (the flows tagged `deployed`, one\n  or two per deployment seam) and the browser journeys against deployed\n  staging, in parallel. `full suite + quality gates` requires all of them and\n  every other check on the release commit. A browser spec exists only for a\n  money-path journey (sign-in, onboarding, session load and streaming, desktop\n  parity, billing, connectors, invites); everything else is a REST flow or\n  nothing. CI records a video of every browser journey in the run's\n  `tests-release-browser-shard-N` artifact. A failed release-suite run reruns\n  its failed jobs once by itself (`rerun-failed-once.yml`): a job that passes\n  on the rerun is a flake to fix or delete.\n- `tests/unit/sandbox-workflow.test.ts` fails when any workflow except the\n  label-gated `tests.yml` and `deploy-preview.yml` triggers on a pull request\n  into `dev`, and pins both label gates to the label-added event.\n- The deployed half of the release suite (`--target-api-full`,\n  `--target-browser-full`) waits up to 15 min for staging to serve\n  `RELEASE_SOURCE_SHA`, then fails when any `deployed` flow is excluded or any\n  configured Playwright journey fails.\n- The `preview` label is not part of the development flow. Adding it deploys a\n  Platinum self-host environment for the PR (~7 min), once, and runs no tests.\n  A push does not redeploy. Only `gh workflow run deploy-preview.yml -f\n  pr_number=<N>` runs `--target-full` against a preview (40–80 min). Its mechanics are\n  in the **contributing** skill (`references/preview-environments.md`). Run a\n  preview of your change on your worktree's local stack by default.\n\n### Product flow source of truth\n\n- `tests/spec/end-to-end.md` contains the natural-language contract and stable\n  flow IDs.\n- `tests/src/flows/*.flow.ts` implements the contracts through HTTP and real CLI\n  processes. Do not import API handlers.\n- Write each `ctx.step()` as a complete action and observable result. Cover\n  setup, authentication, action, read-back proof, negative paths, and cleanup.\n- Keep every flow's `meta.routes` synchronized with\n  `tests/spec/routes.generated.json`. Regenerate the manifest with\n  `bun run apps/api/scripts/dump-routes.ts` after route changes.\n- The local profile uses local Supabase, PostgreSQL, API, gateway, and bare Git\n  repositories. It excludes Stripe, cloud sandboxes, managed GitHub repositories,\n  and external email delivery explicitly.\n- Use Playwright only for browser-visible behavior. API-only assertions belong\n  in REST flows. SDK tests remain in `packages/sdk`.\n- Do not add another cross-cutting test harness, Makefile lane, contract suite,\n  Testcontainers suite, load suite, mutation suite, visual suite, accessibility\n  suite, or ad hoc smoke script under `tests/`.\n- Read `tests/README.md` and the repository `testing` skill before changing the\n  test system.\n\n### Release topology — dev, staging, prod\n\n- **`dev` = dev trunk.** It is the repo default branch and deploys to\n  `dev.kortix.com` / `dev-api.kortix.com`. Direct pushes are allowed; breaking or\n  incomplete development can live here while it is being shaken out.\n- **`staging` = release-candidate branch.** Nothing should land on staging unless\n  it is intended to be production-ready. Promote to Production merges the\n  `dev` HEAD into staging itself (the PR runs no check), deploys it, and opens\n  the release PR whose release suite is the one gate. Staging is the deploy\n  target that suite runs against, not a test stage. Staging\n  deploys to `staging.kortix.com` / `staging-api.kortix.com` and must use the\n  staging data plane, not dev or prod. The full promote-and-gate flow, the\n  fix-forward loop, and the prod release steps live in the **kortix-release**\n  skill — that is the one place this is written out in full.\n- Staging deploys must apply pending DB migrations against `STAGING_DATABASE_URL`\n  before the staging ECS Fargate rollout. If that secret is missing or points at\n  dev/prod, treat the deploy as broken; staging must never fall back to dev,\n  KE2E, or prod DBs.\n- **`prod` = production.** Production moves only through **Promote to Production**,\n  which uses `staging` as the source, opens a reviewed release PR into `prod`,\n  publishes the release artifacts, and rolls production after merge.\n- If a staging runtime check points at `dev.kortix.com` or\n  `dev-api.kortix.com`, treat that as a broken staging setup, not a passing\n  staging gate.\n\n### Driving the real UI (agent-browser)\n\n- **agent-browser is the browser for every agent task in this repo.** Use it\n  before chrome-devtools MCP, Playwright MCP, or any other built-in browser\n  tool. Playwright stays the engine of the committed browser test suite only.\n- The installed skill is a stub. Load the guide that matches the CLI version:\n  `agent-browser skills get core` (`--full` adds the command reference).\n- Use one named session per worktree:\n  `agent-browser session id --scope worktree --prefix <task>`, then pass\n  `--session <id>` on every command. Keep every `AGENT_BROWSER_*` variable the\n  same for all commands in a session. A per-command change relaunches the\n  browser and drops the open page.\n- Routes are auth-gated (`/dashboard`, `/projects/*` → redirect to `/auth`\n  unauthenticated); sign in first (seed a user as above, then log in via the\n  `/auth` form). On a PR preview, the magic link arrives in the preview's Mailpit API\n  (`<origin>/_mailpit/api/v1`)\n  (the **contributing** skill has the script).\n- Next.js dev compiles routes on first hit — first navigation to a cold route\n  can take 30–60s; warm it with `curl` or use a generous navigation timeout.\n- `agent-browser doctor` diagnoses launch and recording problems. Recording\n  needs ffmpeg with libvpx and libx264.\n\n### API lint gate\n\n- `pnpm --filter kortix-api lint` runs in the `Tests` packages lane. Its rules\n  are in `apps/api/eslint.config.mjs`: layered imports, no Drizzle in route\n  files, no `(c: any)`, no `process.env` outside `config.ts`, no\n  `console.*`, a `replica-local:` comment on every empty module-level\n  `Map`/`Set`, and no import into `projects/` from outside it except\n  `projects/index.ts` or `projects/surface.ts`. Background timers are guarded by\n  `apps/api/src/__tests__/unit-worker-scope-wiring.test.ts` instead.\n\n### API code conventions (`apps/api/src`)\n\n- **Routes register explicitly.** A route module exports\n  `register<Name>Routes()`. The module that mounts the router calls it right\n  before `app.route()` (`app.ts`, `accounts/index.ts`, `router/index.ts`).\n  Never register at import time. Call order is dispatch order.\n- **No service imports a route module.** Shared logic lives in a service file\n  next to the routes (for example `projects/session-open/`).\n- **Services take an `Actor`, never a Hono `Context`.** The route reads the\n  request through `middleware/` and `http-*.ts` helpers, builds the actor\n  (`middleware/actor.ts`, memoized per request), and calls the service with\n  plain values. Parse `Authorization: Bearer` with `shared/bearer-token.ts`.\n- **Every background loop lives in `workers/<name>-worker.ts`.** The worker\n  owns its timer, start/stop and `runWorkerTick` call. The tick stays an\n  exported service function. `bootstrap.ts` imports every loop from `workers/`.\n- **`iam/` reads the membership, group and role tables.** Use the read models\n  in `iam/membership-read.ts`, `iam/group-read.ts` and `iam/role-read.ts`.\n- **No function longer than 300 lines.** Split long functions into named steps.\n- **SQL trace:** `KORTIX_SQL_TRACE=<file>` appends every pool statement to the\n  file. Diff two traces to prove a refactor runs the same SQL.\n- `apps/api/eslint-suppressions.json` holds the violations that existed when\n  each rule was added. A new violation fails. A fixed one fails until you run\n  `pnpm --filter kortix-api lint:prune` and commit the smaller file. Never\n  absorb a new violation with `--suppress-all`.\n\n### Frontend type/lint gate\n\n- Typecheck `apps/web` with `pnpm --filter Kortix-Computer-Frontend typecheck`\n  (tsgo, incremental). Never run raw `tsc --noEmit` there. Measured on\n  2026-10-21 on a 24 GB Mac: raw `tsc` ran 817 s, held 3.18 GB, and was\n  killed before it finished. Cold `tsgo` takes 12.1 s at 5.0 GB peak. A warm\n  incremental run takes 1.1 s at 1.7 GB.\n- On 2026-10-21 the tsgo script reported 0 errors on `dev`. The bar is\n  relative: a branch adds no type error to the files it touches. Compare against `origin/dev` with the same command, not\n  against a number in this file.\n  The old ~1500 `TS2786` / `IntrinsicAttributes` noise from a React 19↔18\n  types mismatch (two copies of `@types/react` in one program — `packages/sdk`\n  had its own) is gone as of the Next 16 upgrade. If `TS2786` appears again,\n  treat it as a genuine duplicate-`@types/react` regression and investigate —\n  do not wave it through.\n- `npx eslint <files>` should be clean of errors. `eslint .` across the whole\n  app currently reports ~455 warnings, mostly `react-hooks/*` React Compiler\n  rules pending a dedicated audit — expected until that audit lands.\n\n### Frontend design standard — Jay/Kortix bar\n\n#### Canonical design references\n\n| Surface | Read before changing UI | Implemented source |\n|---|---|---|\n| Web (`apps/web`) | `.agents/skills/kortix-brand/SKILL.md` (the router: values, voice, claims, decision history), then `.agents/skills/kortix-design-system/SKILL.md` for components | `apps/web/src/app/globals.css` tokens (generated from `.agents/skills/kortix-brand/references/visual/visual-system.json`), `packages/ui/src/primitives/` (apps/web imports each one as `@kortix/ui/<name>`; apps/web has no re-export shims, `tests/unit/web-no-ui-shims.test.ts` enforces it), the live `/design-system` route |\n| `@kortix/ui` hosts (Kortix Apps, `apps/white-label/reskin`, `apps/white-label/lumen`, `apps/white-label/team-console`, SDK customer apps) | `packages/ui/skills/kortix-ui/SKILL.md` (setup, which component, white-label theming, the `SessionThread` chat), then the web row for Kortix-owned surfaces | `packages/ui/src/` (components, `session/` for the `SessionThread` chat, `styles/tokens.css` + `theme.css`, prebuilt `dist/styles.css`); white-label = CSS variable overrides, never a forked component |\n| Desktop (Electron shell) | The web row — the shell renders `apps/web` — plus `apps/desktop-electron/README.md` for the shell boundary, then the parity gate below | `apps/web` rendered by the shell; native window geometry in the shell's titlebar classes |\n| Mobile (`apps/mobile`) | `apps/mobile/design.md` for screens, `apps/mobile/AGENTS.md` for primitives | `apps/mobile/global.css` colors, `apps/mobile/components/ui/`; stock Tailwind spacing for touch targets |\n\n#### Desktop parity is a UI gate\n\nThe Electron app loads `apps/web`. Keep product components, routes, tokens,\nand data behavior shared. Put native window geometry in the shell's explicit\ntitlebar classes. Never apply titlebar height or drag rules to generic ARIA\nroles, all sidebars, or page content.\n\nFor every shared UI change, verify the affected controls on web and in Electron\nbefore handoff. Check the outgoing request or route and the visible result.\nCheck both themes, the minimum supported window (720 × 480), sidebar collapse,\nfullscreen overlays, and browser zoom. Window controls must not overlap app\ncontrols. Lists must not overlap or clip their last row. Keyboard focus and\nscrolling must remain usable.\n\nAdd regressions to the existing Playwright journeys. The desktop journey runs\nin `pnpm test -- --browser-only` and supports the actual Electron shell:\n`E2E_DESKTOP_NATIVE=1 E2E_GREP='27 — desktop parity' pnpm test -- --browser-only`.\nRun the native journey when changing shell CSS, navigation, settings, agents,\nor connectors. A desktop user-agent test does not prove native hit testing.\nReport any unverified desktop behavior explicitly. Do not promise that tests\nprevent every future regression.\n\nWhen touching any visual surface in `apps/web`, treat brand fit as a release\ngate, not polish:\n\n- Read `.agents/skills/kortix-brand/SKILL.md` before writing the first\n  `className` or the first user-facing string. It routes you to the value law:\n  the complete allowlist of every color, spacing step, type rung, radius,\n  elevation, and duration you may use. Note `--spacing: 0.23rem` — Tailwind's\n  scale is 8% tighter here, so a 16px mockup padding is `p-4`, never\n  `p-[16px]`. Run `.agents/skills/kortix-brand/scripts/audit.sh` over your\n  changed paths before opening the PR; it must be clean on files you touched.\n  `references/visual/visual-system.json` is the only file with values: edit it,\n  then run `scripts/generate-tokens.ts`. Never edit a generated region of\n  `globals.css` by hand.\n- Read `.agents/skills/kortix-design-system/SKILL.md` next and compose existing\n  primitives from `@kortix/ui/*` before inventing local chrome.\n- Match the current Jay Suthar / Kortix product aesthetic: calm neutral surfaces,\n  dense-but-legible UI, black/white plus one earned accent, token-driven spacing,\n  and no decorative color, glow, or one-off rounded boxes.\n- Use recent product surfaces as references before editing: `/design-system`,\n  `packages/ui/src/workspace/layout/project-home.tsx`,\n  `apps/web/src/components/ui/wallpaper-background.tsx`, and the account/IAM\n  screens called out by the design-system skill.\n- Verify visual work in the browser and include the exact lint/typecheck commands\n  you ran in the PR. If it does not look native beside Jay-authored UI, keep\n  iterating before shipping.\n",".cursor/mcp.json":"# Model Context Protocol (MCP) Configuration\n\nPath: `.cursor/mcp.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/kortix-ai/suna/HEAD/.cursor/mcp.json)",".claude-plugin/marketplace.json":"# Claude Plugin Marketplace Catalog\n\nPath: `.claude-plugin/marketplace.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/kortix-ai/suna/HEAD/.claude-plugin/marketplace.json)",".claude-plugin/plugin.json":"# claude-plugin Plugin Manifest\n\nPath: `.claude-plugin/plugin.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/kortix-ai/suna/HEAD/.claude-plugin/plugin.json)"},"files":{"AGENTS.md":"# Kortix project\n\n## What \"ownership\" means\n\nThe words \"can you own this?\", \"are you on it?\", and \"can you take care of this?\"\nall mean the same thing: you are 100% responsible. The person who handed it to\nyou must be able to walk completely away, come back a week later, and find it\ndone properly — because you cared about every edge and corner.\n\n- it's not done if it's not implemented\n- it's not done if the implementation is ugly\n- it's not done if it's not documented\n- it's not done if users can't discover it\n- it's not done if you can't market it\n\nOwning something means owning it end to end. The whole arc from \"we have a\nproblem\" to \"nobody has to think about this again.\" Not just the code change in\nthe middle, the whole thing. If someone hands you something and still has to\ntrack whether it actually got solved, you didn't own it.\n\nHere's what that actually looks like in practice.\n\n**Start with the problem, not the solution.** A lot of the time you'll already\nhave a fix in your head before you've understood what's broken. \"We need to move\nfrom X to Y\" isn't a problem, that's a solution you've pre-committed to. The real\nproblem is probably \"it's slow\", \"it's flaky\", \"it breaks for this customer\".\nName that first. Then ask what else could solve it, what the tradeoffs are, and\nwhich option actually wins.\n\n**Then pressure-test it before you build:**\n\n- **Edge cases.** Which exist, which matter, which we can safely ignore.\n- **Failures.** Networks fail, that's a given. Retry? How many times, for how\n  long?\n- **Data.** How much, does it need migrating or cleaning, how do you get real\n  data to test against, and what are you assuming about its shape that you\n  haven't actually verified?\n- **Testing.** How will you know it's correct? Are automated tests enough or do\n  you need to poke at it by hand? Is the result something you can see in a\n  screenshot or a video?\n- **The bigger picture.** How does this get announced, how does it fit the\n  roadmap, can you even picture it shipped? If something there bothers you, push\n  back. Ask.\n\n**Then actually do it**, with precision, care, urgency and calm all at once. No\nhalf-assing. The bar before you merge: am I proud of this? Would I put it in\nfront of Steve Jobs and walk him through what I built, the constraints, the\ntradeoffs?\n\n**And then prove it works.** Not \"the tests pass\", prove it. In 99% of cases you\ncan confirm it yourself: run it, ask an agent to walk through the scenarios, poke\nat the data before and after, take a screenshot, make a demo. Are you actually\nsure it solves the problem you started with?\n\n**Then make sure it lands in production and works in production**, which is not\nthe same as merged. Did it deploy? Did the deploy quietly fail? Is there a flag\nto flip, and does the flag work? Can you use the thing in prod right now and\nconfirm it's really there?\n\n**And then close the loop with everyone it touches:**\n\n- **The team.** If it's a new feature, a new convention, or a tricky thing people\n  should know about, tell them. Don't underestimate peripheral vision. You\n  knowing that someone changed Z yesterday can save someone else three hours of\n  debugging tomorrow when a bug report about Z comes in.\n- **Customers.** Whoever reported it, whoever's blocked, let them know it's\n  fixed.\n- **The world.** If it's worth announcing, announce it.\n- **Future you.** Are there follow-ups? Should you check the logs in a week to\n  make sure it's still healthy?\n\nBut that's how we build a product in a small team. We don't have PMs, we don't\nhave a QA department. We're small, but we're great, and we can do all of that.\n\nAnd it's always okay to ask for help, it's okay to ask questions, it's okay to\nredo things and triple-check. What's not okay is to quietly assume someone else\nwill catch the parts you didn't think about.\n\n### The bar: autonomy, agency, ownership\n\nThis is what I require from the agent I work with. In my own words:\n\nEither you are performing or you are not. Either you are taking on high\nownership, are high agency and pushing without me having to micromanage you, or\nyou are not.\n\nEspecially in today's age you are limited by great talent more than ever. Because\nwe can all AGI Max, you have a single chokepoint on good judgement.\n\nEvery person that comes on the team has to have the capability to truly own\nsomething. If you have built products, you develop ownership because you owned\nsomething — whether it worked out or not — for a prolonged amount of time. You\ndevelop true agency.\n\nI hire you because I expect that if I am able to walk out of the room after\ngiving you a high level thing and come back, it's going to be done good, ideally\nbetter than I would've done it.\n\nMost people are shit at their jobs, some are decent/good, but only people who are\nexceptional should be at Kortix.\n\nBeing exceptional on paper is simple — it's a combination of Ownership, Agency\nand actual Merit/Skill. It's hard, because you have to not only be very smart but\nalso crazy driven to push like a motherfucker and want to feel every edge and\ncorner to make sure the output is good.\n\n## Skills live in `.agents/skills/`\n\nEvery repo skill is a directory in `.agents/skills/<name>/`. `.claude/skills/<name>` is a\nsymlink to it. Add a skill in `.agents/skills/`, then add the symlink. Third-party skills come\nfrom `npx skills add` and are pinned in `skills-lock.json`. The PR procedure is the\n**contributing** skill; the PR title and body are the **create-pr** skill. Browser work is the **agent-browser** skill.\n\nThe repository has no `docs/` tree. Put a runbook or spec in the skill that owns the\nsurface (`.agents/skills/<name>/references/`). Put an incident rule in the learnings\nledger. Put design detail and RCAs in the PR body. Never cite a repo path that does\nnot exist. The pre-commit hook and `tests/unit/no-docs-tree.test.ts` reject a new\n`docs/` file and any citation of one.\n\n## Ponytail is on by default\n\nEvery code change runs through the **ponytail** skill at level `full`. Load it before you\nwrite, fix, refactor, or review code, and before you add a dependency. Level switch:\n`/ponytail lite|full|ultra`. Off: \"stop ponytail\". **ponytail-review** audits a diff for\nover-engineering, **ponytail-audit** audits the whole repo, and **ponytail-debt** lists\nevery `ponytail:` shortcut comment. Ponytail cuts code, never the verification,\ndocumentation, or ownership bar in this file. The skills come from\n`DietrichGebert/ponytail`, pinned to `v4.10.0` in `skills-lock.json`.\n\n## Learnings: the episodic ledger in the `learnings` skill\n\n`.agents/skills/learnings/` is the append-only, timestamped ledger of rules paid\nfor with real downtime. `MEMORY.md` indexes it newest first, and each entry is\none file in `entries/`: the rule, the incident that taught it, and the\nautomation that enforces it. Search it before writing or reviewing a DB\nmigration, touching deploy/release workflows, planning a promote, or\nresponding to a prod incident. After resolving ANY incident or near-miss,\nrecord a new entry in the same session with `scripts/new-entry.sh`. An incident\nthat leaves no entry behind is not finished.\n\n## NEVER write customer data or PII into anything we publish or commit\n\nThis is a hard rule. No exceptions, no \"just this once\", no \"it's only\ninternal\".\n\n**Never write any of these:**\n\n- customer or company names, and the names of their people;\n- email addresses, phone numbers, or other personal data;\n- real account, project, session, user, or sandbox IDs from prod, staging, or\n  a customer deployment;\n- customer repository names, hostnames, or URLs that contain any of the above;\n- customer prompts, messages, files, or log lines;\n- screenshots of a real customer workspace.\n\n**Never write them into:**\n\n- commits, commit messages, or branch names;\n- PR titles, PR bodies, PR comments, or review comments;\n- issues;\n- code comments, test names, or test fixtures;\n- docs, runbooks, skills, `AGENTS.md`, changelogs, or release notes;\n- artifacts, Slack posts, or any public or team-visible text.\n\n**Write the class instead:** \"a customer reported\", \"an enterprise\nworkspace\", \"a prod session\", `<session_id>`. Build test data from synthetic\nvalues. Evidence that contains real data stays local: the gitignored\n`output/` folder, your scratchpad, or the private agent memory. It never goes\ninto a tracked file.\n\n**If you find customer data** in the tree or in a PR, remove it in the same\nbranch and say so. Do not rewrite history on `dev`. Report the SHA to the user\ninstead.\n\n**A guard enforces this on every commit and push.**\n`scripts/check-blocked-terms.sh` runs from `.githooks/pre-commit`,\n`.githooks/commit-msg`, and `.githooks/pre-push`. It refuses any added line,\ncommit message, or pushed branch name that contains a blocked term. Matching is\ncase-insensitive and whole-word. The list is itself customer data, so it lives\nencrypted in `apps/api/.env` as `BLOCKED_COMMIT_TERMS`, comma-separated.\n\n- Add a customer the day they sign: `dotenvx set BLOCKED_COMMIT_TERMS\n  \"<existing>,<new>\" -f apps/api/.env`. Read the current value first with\n  `dotenvx get BLOCKED_COMMIT_TERMS -f apps/api/.env`.\n- In a worktree the guard decrypts with the primary checkout's\n  `apps/api/.env.keys`. Without a key it warns and allows.\n- Deleting a line that contains a term is always allowed.\n- Never bypass the guard with `--no-verify`. If it fires, remove the term.\n- The hooks do not see PR titles, PR bodies, or comments. Those stay your\n  responsibility.\n\n## How to communicate: precise, technically accurate, no fluff\n\nWrite every response — chat, PR text, commit messages, code comments, docs — in\nthe spirit of **ASD-STE100 (Simplified Technical English)**. The goal is maximum\ntechnical precision with zero filler. Apply these rules:\n\n- **State facts, not vibes.** Every claim is specific and verifiable: name the\n  file, function, route, flag, SHA, status code, or number. No \"should work\",\n  \"probably\", \"a bunch of\", \"various\", \"seems fine\" — say what is true and how you\n  know, or say you do not know it yet.\n- **One idea per sentence.** Keep sentences short (aim ≤ 20 words) and each one\n  carries a single instruction or fact. Split compound thoughts instead of\n  chaining clauses.\n- **Active voice, present tense, direct.** \"The gate rejects the request\",\n  not \"the request may end up being rejected\". Give the instruction; do not\n  soften it.\n- **One term per concept.** Use the same word for the same thing every time —\n  do not alternate \"session\"/\"run\"/\"task\" for one concept. Match the codebase's\n  existing names exactly (`session_id`, not \"session ID / run id\").\n- **No filler, no hedging, no praise.** Cut \"basically\", \"just\", \"simply\",\n  \"I think\", \"great question\", \"as we know\", and marketing adjectives. Lead with\n  the answer; drop the throat-clearing.\n- **Quantify.** Prefer exact values over adjectives: \"up to ~9 min\", \"returns\n  `402`\", \"3 of 7 flows\", not \"slow\", \"an error\", \"most\".\n- **Show the evidence.** When you assert a behavior, cite the command you ran and\n  the real output. Distinguish verified fact from assumption explicitly.\n- **Structure over prose.** Use numbered/bulleted lists for steps, findings, and\n  status. Reserve paragraphs for genuine explanation, and keep them tight.\n- **Say the unknown plainly.** If something is unverified, blocked, or risky,\n  state it in one line — what, why, and what would resolve it — instead of\n  burying or omitting it.\n\nThis standard governs how you talk. It does not override the technical rules\nbelow; it is how you report on them.\n\n## First, at session start: which canonical branch are you in?\n\nEvery change belongs to **one canonical branch** — the branch for whatever is\nbeing worked on. One canonical branch, one worktree. Establish which one you are\nin before any non-trivial change. **Do not create a branch by reflex.**\n\n1. **Join the canonical branch that already exists** for this work. List them\n   with `git worktree list` and `git branch -r`. If the work continues, extends,\n   fixes, or cleans up something already in flight, it belongs on that branch.\n   Ask the user which branch when it is not obvious.\n2. **Start a new canonical branch** only when the work is genuinely a new thing.\n   Give it its own worktree: `pnpm worktree create --name <slug> --yes\n   --no-start`, then do all edits and runs under `../suna-<slug>`. Add `--db`\n   when the work needs migrations, destructive data work, or schema drift: it\n   copies the primary local database into the worktree's own database\n   (`kortix_wt_<slug>`, ~5 s, no extra containers). `--full-supabase` (a whole\n   Supabase stack, ~0.75 GB RAM) is only for independent auth/storage config.\n   See the **worktree** skill.\n3. **Never switch a worktree you did not create.** It belongs to one session and\n   its canonical branch. The pre-commit hook refuses a commit on any other branch\n   there (`scripts/check-worktree-branch.sh`). A throwaway probe branch goes in a\n   private `git worktree add` in your scratchpad.\n4. **The primary checkout** (`pnpm dev`, web `3000` / api `8008`) is for running\n   and investigating. Do not park feature work there.\n5. **Run `pnpm test` only from a worktree that has `.kortix-worktree.json`.** The\n   runner reads its ports from that marker. A bare `git worktree add` has none,\n   so its test stack takes the primary ports and talks to another session's\n   `pnpm dev` (2026-10-13: 156 flows failed with that stack's 503s).\n6. **Never kill a process you did not start.** Many sessions share this machine.\n   Kill by PID from your own process tree, or by a pattern that contains your\n   own worktree or scratchpad path. Never by a bare pattern such as\n   `pkill -f \"bun test\"`, and never in a loop. Need CPU? Lower your own\n   concurrency (`KE2E_API_WORKERS`, `KORTIX_DB_SUITE_WORKERS`) or ask the user.\n   2026-10-13: a guard loop that SIGTERMed every other session's `pnpm test`,\n   `tsc` and `vitest` every 20 s made 259 kills, and every suite on the machine\n   died with exit 143 and no failing test.\n\n**Pack more into one branch, not less.** A follow-up fix, a rename cleanup, a\nstale-reference sweep, and the change that caused them all belong on the same\nbranch and land together. Splitting one objective across several branches is how\na half-finished cutover reaches `dev` in pieces — each piece green alone, the\nwhole thing broken.\n\nSub-branches are allowed. Agents may cut working branches off the canonical\nbranch and merge back into it. **A sub-branch never opens a PR against `dev`.**\nOnly the canonical branch does.\n\nCarve-outs where you just proceed: read-only investigation and questions, and\ntrivial single-file typo/comment fixes on the current branch.\n\n## Default delivery: verify in your box, self-merge to `dev`, verify on dev\n\n`dev` is the dev trunk. A merge does not deploy: dev deploys only on a deliberate\ndispatch, but **merging to `dev` still lands your change in everyone's next\ndeploy and next `dev` checkout.** It is not a save point.\n\n### Testing philosophy: one gate at Promote to Production, everything else fast\n\nTesting reset, 2026-10-16. Latency is the core concern. Development must be\nfast enough to push and break things on `dev`. The full suite runs once per\nrelease, at Promote to Production, and nowhere else. The rules:\n\n1. **Never run a full suite during development, and never a whole package's\n   tests.** No multi-minute run on your box, on a PR into `dev`, or on a push\n   to `dev`. Verify only what you changed: T0 for the packages you touched,\n   the test files that cover the change, named one by one\n   (`pnpm --filter <pkg> test <file>`), and the change itself through its real\n   surface. Running all 2,129 `apps/web` tests to check one component is the\n   anti-pattern this rule exists for.\n2. **One gate: the release pull request into `prod`.** Promote to Production\n   runs the whole suite there, once, and every check must be green. It is the\n   only check that blocks anything.\n3. **Test at the boundaries users touch:** the HTTP API, the CLI, the\n   `@kortix/sdk` public surface, DB migrations, and a few browser journeys on\n   the money path. Never test internal calls or source text.\n4. **Run each suite once per tree.** A tree that ran is not run again.\n5. **A red suite is worse than none.** A test that fails twice without a bug is\n   fixed or deleted the same day. Do not park it in quarantine.\n6. **Delete on red.** A unit test that goes red on a refactor with no bug is\n   deleted, not updated. Keep unit tests only for logic that is hard to get\n   right: money and credits, parsers, the streaming protocol, permission\n   decisions, crypto and secrets.\n7. **Small, isolated tests.** Every test file runs standalone in seconds. When\n   the test that covers your change takes more than ~10 s, or drags in setup\n   that has nothing to do with it, split it into a smaller isolated test as\n   part of the same change. Fake the clock; never sleep on real time.\n\n| Tier | Contents | Runs |\n|---|---|---|\n| T0 static | `tsc`, lint, architecture checks, migration lint | anytime, seconds per package |\n| Targeted | the one test file or flow that covers your change | on your box, seconds |\n| Release suite | contract lanes (flows, DB, packages) on the release tree, the deployed smoke and the browser journeys against staging, CI, CodeQL, scanners | once per release, on the pull request into `prod` |\n\n| Where | What runs | Blocks? |\n|---|---|---|\n| Your box | T0 for the packages you touched, the targeted test, and the real surface: the route with curl, the CLI process, the page with agent-browser | your judgment |\n| Pull request into `dev` | nothing, unless a person adds `test` or `preview` | no |\n| Push to `dev` | only the seconds-long guards: `secret-scan`, `secrets-guard`, and path-gated `DB Migrations` / `i18n-catalogs` / `Terraform Apply Global` / `deploy-api-router-dev` | no |\n| Nightly on `dev` | `Tests` `core` and `packages` lanes, no browser: a red night comments the failures on the commit, so they are ready to fix before a promote | no |\n| Pull request into `staging` | nothing: Promote to Production opens and merges it | no |\n| Pull request into `prod` | the release suite, once (`full suite + quality gates`) | **yes, the only gate** |\n\n**A Mac never runs a suite.** On macOS `pnpm test` refuses a suite-wide run\n(plain, `--full`, `--packages-only`, `--browser-only`, `--db-only` with no path\nfilter) and exits `2`. 2026-10-16: five agent sessions ran the suite at once on\none laptop (load average 112, swap full). Run one flow\n(`pnpm test -- --id <ID>`), one DB suite path, or one package's test file.\n`KORTIX_FULL_TEST=1` overrides only when a person asks. Every package `test`\nscript refuses the same way: `pnpm --filter <pkg> test` with no file exits `2`\n(`scripts/test-guard.mjs`). Pass the file(s), `pnpm --filter <pkg> test <file>`,\nor a name filter after `--`: `pnpm --filter <pkg> test -- -t <name>`. CI and\nLinux run the full suite.\n\nNo hook or gate requires a test run before a push or a merge into `dev`. Write\nin the PR body what you ran and why. A diff that no test reads (docs, a skill,\na standalone script) needs no run. When the nightly run or the release suite\nturns red on your change, you fix forward on `dev` the same day.\n\n1. Work on the canonical branch in its worktree. Commit as often as you want.\n2. Verify in your box, with real inputs and outputs. Run T0 and the tests that\n   cover the change (see above). Start the worktree's stack\n   (`pnpm worktree start <slug>`) and drive the changed behavior through it: the\n   HTTP route, the real CLI process, or the page with agent-browser. Nothing\n   before the release suite at Promote to Production catches what you skip.\n3. Open the PR against `dev` and follow the **contributing** skill: it attaches\n   the demo video you recorded against your local stack. Write the title and\n   body with the **create-pr** skill: English only, for a human reviewer who\n   decides \"merge or iterate\" without reading the diff. Do not add `test` or `preview` unless you need that one explicit run.\n4. Merge `dev` into the canonical branch daily. A branch that diverges for weeks\n   detonates on merge exactly like a 1,500-line PR does.\n5. **Self-merge to `dev` when the change is verified. Do not wait for the\n   user's approval.** Speed matters: a verified change that sits unmerged is\n   waste. Verified means all of these are true:\n   - the relevant local checks ran with real inputs and outputs (rule 2), and\n     they passed;\n   - the PR is mergeable (no conflict);\n   - rule 6 holds when the change touches a client-facing runtime contract.\n   A failing check blocks the merge until you fix it or state why it is\n   unrelated (for example, the same test fails on `dev`). Squash-merge\n   (`gh pr merge <pr> --squash`), then finish rules 7 and 8. A merge is not\n   the end of the work: dev verification is still yours.\n   The only machine-enforced rule on `dev` and `staging` is that every change\n   arrives through a pull request — no required approvals, no required status\n   checks, no bypass actors. The bar is what you verified.\n   **The release gates do not change.** Merging into `staging` or `prod`,\n   running Promote to Production, and moving the `:stable` tag each need the\n   user's explicit approval (the **kortix-release** skill).\n6. **A change to a client-facing runtime contract** — the `@kortix/sdk` public\n   surface, session/thread transport, the streaming protocol — merges only after\n   the whole objective ran through a real session on your local stack, and runs\n   again on dev after the merge (rule 8). Green tests are not the bar. Someone\n   used it.\n7. After the merge, deploy dev yourself: `gh workflow run deploy-dev.yml\n   --repo kortix-ai/suna -f surface=changed` (`all` or `frontend` force a\n   rebuild). Deploys queue, they never cancel. Wait for the run to finish.\n   `/health` on every changed surface must serve the merge SHA: a successful\n   `/health` response alone is not deployment proof. The run comments \"Live on\n   dev\" on the merged pull request, and \"Not live on dev yet\" names the\n   surface that failed. The surfaces and their checks are in\n   `.github/workflows/deploy-dev.yml`. The merge push runs no suite. The\n   nightly `Tests` run on `dev` comments the failing lanes and every commit\n   since the last green run, once per new set of failing lanes. The author\n   whose commit broke `dev` fixes forward the same day. If `dev` is still red\n   1 hour after the comment, anyone may revert the culprit PR. The release\n   suite at Promote to Production runs the same lanes and blocks the release.\n8. Re-run the user-visible behavior against `https://dev.kortix.com` and/or\n   `https://dev-api.kortix.com`. Prefer the real Kortix CLI configured for the\n   dev API for CLI/project/session flows, and direct authenticated HTTP calls for\n   API contracts. For web behavior, drive the deployed UI and assert its network\n   request plus visible result.\n\nLocal verification and dev verification are both required. A local pass does\nnot replace dev, and a dev smoke test does not replace focused local tests.\nRecord the branch, PR, local commands and output, merge SHA, deploy run,\ndeployed SHA evidence, and the exact dev command or interaction in the final\nresponse.\n\n## Architecture: `@kortix/sdk` is the source of truth\n\n`@kortix/sdk` is the **single source of truth** for everything that talks to the\nKortix backend — projects, accounts, sessions, files, secrets, triggers, the\nsession runtime, the runtime REST client, SSE streaming, model state,\nand auth-token plumbing. The apps\n(`apps/web`, `apps/white-label/*`, `apps/mobile`) are **thin consumers**. Treat\nthese as standing rules whenever you touch the data/runtime layer:\n\n> **Editing `packages/sdk` itself? Load the **sdk** skill (the rules) first.** It is a\n> **published npm package** with its own hard rules that have no analogue\n> elsewhere in this repo: **TDD is mandatory** (failing test first, run it, watch\n> it fail, then implement — and every turn ends with the gates run, the real\n> output pasted, and an explicit shippable YES/NO/NOT YET); exported names\n> (including *types*) are a public API contract and renaming one is a breaking\n> change; the `version` field is inert and must never be bumped by hand; adding\n> an export requires three synchronized edits; and the framework-free core is\n> enforced by a static import-graph tripwire.\n\n- **Logic lives in the SDK, never in a host.** No raw `fetch` to the Kortix API,\n  no `@opencode-ai/sdk` imports, no transport / runtime / data-state code written\n  in app code. New data or runtime behavior is added to the SDK and exposed\n  through its public surface — not hand-rolled or duplicated in a host. If you\n  need something the SDK doesn't expose, add it to the SDK.\n- **One client per host.** Create it once via `createKortix({ backendUrl,\n  getToken })` and read everything through `@kortix/sdk` + `@kortix/sdk/react`.\n  Auth is just `getToken` — an API key / PAT for programmatic use, or a Supabase\n  JWT for the logged-in web app. Hosts never instantiate a second client.\n- **A whole session is one hook.** `useSession(projectId, sessionId)` owns the\n  entire runtime lifecycle — `/start`, the sandbox switch, the live SSE stream,\n  readiness seeding, immutable runtime identity, the native conversation id,\n  and message sync. Hosts don't\n  hand-roll the mount, drive a server-store \"switch\", or mount a separate event\n  provider.\n- **Session-scoped + provider-agnostic.** The public API is session-scoped\n  (`kortix.session(pid, sid).health() / .previewUrl() / .restart() / …`).\n  The sandbox provider and the harness are server-side concerns. Host code\n  must not implement a second transport.\n- **Build on the Kortix contract, not on OpenCode.** Every new session runs\n  pi inside kortixd. OpenCode is deprecated (2026-10-23): there is no\n  `pi_harness` flag, `runtime:` in `kortix.yaml` selects nothing, and OpenCode\n  serves only sessions that started on it. It will be deleted. Code that exists\n  only for OpenCode carries `@deprecated`: never call it from new code, never\n  add a feature to it, and change it only to fix a bug that breaks a running\n  OpenCode session. pi calls models only through the LLM gateway. pi serves\n  the daemon routes (`/kortix/runtime/*`), the transcript\n  (`kortix.transcript.v1`, `packages/api-contract/src/transcript.ts`) and the\n  events. New code reads those, never an OpenCode route, type, file or\n  process. A feature pi lacks is a capability, not a harness check:\n  `GET /kortix/health` lists `capabilities` (`RUNTIME_CAPABILITIES` in\n  `packages/api-contract/src/runtime-relay.ts`), and a client gates the\n  control with `runtimeSupports`. OpenCode lists all eleven (`session.steer`\n  only on OpenCode 1.18.15 or later); pi lists `session.subagents`,\n  `session.compact`, `session.commands` and `session.steer`. pi does not\n  serve rewind, MCP servers, the todo list, shell turns, part edits or\n  `session.attach`.\n  The harness rules and the pi gap list are in\n  `apps/kortix-sandbox-agent-server/src/harness/README.md`.\n- **`apps/web` data modules are shims.** Files such as\n  `apps/web/src/ui/index.ts`, `apps/web/src/lib/iam-client.ts`, and\n  `apps/web/src/hooks/admin/use-*.ts` are thin re-exports\n  (`export * from '@kortix/sdk/...'`).\n  Keep them as shims; put the real logic in the SDK. When a merge conflict lands\n  on one of these, **keep the shim (`--ours`) and port any new host-side logic\n  into the SDK** — do not revert to a host-local implementation.\n- **Docs are the spec.** `apps/web/content/docs/sdk/*` and\n  `packages/sdk/README.md` describe the intended surface. Keep them current with\n  the SDK, and flag legacy/deprecated surfaces in-doc rather than documenting them\n  as current.\n\n## You CAN run and verify everything end-to-end. Do it.\n\nThis repo ships a **complete, runnable local stack with live cloud sandboxes**.\nDo not claim you \"can't verify from here\" or hand back unverified work — you\nhave everything needed to run the app, hit the real API, provision real\nPlatinum sandboxes, drive the real UI in a browser, and assert behavior. Use it.\n\n### Required verification standard — real inputs, real outputs\n\nFor every behavior change, assume **100% autonomy** to verify the user-visible\ncontract before handing the work back. Do not stop at typechecks, unit tests, or\nmocked internals when a real surface exists.\n\n- **API changes:** exercise the actual HTTP route with real request payloads\n  (`curl`, `bun fetch`, or the `ke2e` runner against a running API). Assert the\n  status code and exact response fields that prove the behavior. For writes,\n  also assert the persisted/read-back state or resulting repo/file output.\n- **CLI changes:** run the real CLI command as a process from bash, with the\n  same flags and stdin a user or agent would use. Assert exit code, stdout,\n  stderr, and any files/API calls/commits it should create. Do not rely only on\n  importing command functions.\n- **Web changes:** drive the real page with **agent-browser** (the primary\n  browser; `agent-browser skills get core` loads its guide). Click/type/toggle\n  the actual controls, observe the network request (`agent-browser network`),\n  and assert the visible UI state plus the outgoing payload. Record the flow\n  (`agent-browser record start`) for the PR's demo video. Screenshots and video\n  are evidence, but assertions on DOM and network data are required.\n- **Cross-surface features:** verify each exposed surface independently. If the\n  same feature ships on API + CLI + web + mobile, each gets its own black-box\n  assertion for the inputs users can make and the outputs they receive.\n- **Default/negative paths count:** when changing defaults or removing implicit\n  behavior, assert both the new default and the explicit opt-in/alternate path.\n- **No silent gaps:** if a surface cannot be fully exercised in the current\n  turn, say exactly which input/output remains unverified and why. Otherwise\n  keep going until the real surface is verified.\n- **Final response format:** when work is finished, answer per the **How to\n  communicate** standard at the top of this file — numbered/bulleted, no fluff.\n  Include exactly what changed, what was verified (with the command + output),\n  what remains unverified or risky, and what the user should test next. Do not\n  bury the actionable testing path in a paragraph.\n\n### The stack (already wired)\n\n- **Web** — Next.js dev server on `http://localhost:3000`.\n- **API** — Bun server on `http://localhost:8008/v1` (`/health` returns JSON).\n- **Supabase** — local, on `http://127.0.0.1:54321` (Docker).\n- **Sandboxes** — REAL cloud sandboxes on Platinum, the reference provider\n  (`PLATINUM_API_KEY` in `apps/api/.env` / `.env.local`). Daytona is a legacy\n  fallback with removal scheduled and no support guarantee; E2B support is\n  discontinued. Each project\n  session gets its own sandbox; `session_id == sandbox_id`. The sandbox daemon is\n  reached through `http://localhost:8008/v1/p/<external_id>/8000/...`.\n  The session runtime answers on the same proxy, under `/kortix/runtime/*`,\n  on both harnesses.\n- **Tunnel** — `scripts/dev-local.sh` (`pnpm dev`) auto-starts a cloudflared\n  quick tunnel so cloud sandboxes can call back to the local API (`KORTIX_URL`).\n\nBring it up with `pnpm dev` from `suna/` (it loads `apps/api/.env` +\n`apps/web/.env`, starts Supabase, the API, the web app, and the tunnel). Check\nwhat's already running before starting a duplicate: `curl -s\nlocalhost:8008/v1/health`, `lsof -iTCP:3000 -sTCP:LISTEN`.\n\n> **Secrets are dotenvx-encrypted (mandatory).** `apps/api/.env` (+ `.env.dev`)\n> are committed as ciphertext (`KEY=encrypted:…`); keys live in Dotenv Armor.\n> **Never write a plaintext secret into a tracked file or commit** — add/change\n> values only via `dotenvx set KEY value -f apps/api/.env` (then commit), read\n> with `dotenvx get`, and machine-local overrides go in the gitignored\n> `apps/api/.env.local`. If the user pastes a key, store it with `dotenvx set`,\n> never paste it raw. A pre-commit hook + GitHub push protection enforce this —\n> don't bypass them. Full procedure: the **dotenvx-secrets** skill.\n\n### Authenticating to the live API (for scripts/tests)\n\nMint a real JWT against local Supabase, then call the API with it:\n\n1. `SUPABASE_SERVICE_ROLE_KEY` lives in `apps/api/.env`; the anon key\n   (`NEXT_PUBLIC_SUPABASE_ANON_KEY`) in `apps/web/.env`.\n2. Create a confirmed user: `POST 127.0.0.1:54321/auth/v1/admin/users`\n   (`apikey` + `Authorization: Bearer <service_role>`, body\n   `{email,password,email_confirm:true}`).\n3. Password-grant for the token: `POST\n   127.0.0.1:54321/auth/v1/token?grant_type=password` (`apikey: <anon>`).\n4. Call the API: `Authorization: Bearer <access_token>` against\n   `localhost:8008/v1` (e.g. `/accounts`, `/projects/provision`,\n   `/projects/:id/sessions`, `/p/<ext>/8000/...`).\n\nSee `tests/e2e/helpers/session-auth.ts` for the exact calls.\n\n### One local testing system\n\n- `pnpm test` is the only repository-level test command. It runs local REST and\n  CLI flows, SDK tests, PostgreSQL-backed suites (`db-suites`), runner unit\n  tests, route coverage, and worktree tests concurrently.\n- `pnpm test -- --id ACC-4` runs one flow. `--domain access` runs one domain.\n- `pnpm test -- --sdk-only` runs only `packages/sdk` tests.\n- `pnpm test -- --db-only [path-filter]` runs only the PostgreSQL-backed suites\n  (`integration-*.test.ts`, `*.integration.test.ts`, `tests/migration`), each\n  file against its own fresh migrated database. A skipped DB suite fails.\n- `pnpm test -- --browser-only` runs Playwright browser journeys. It starts the\n  deterministic local stack.\n- Browser runs use two Playwright workers, locally and in each CI shard.\n- `pnpm test -- --packages-only` runs every app/package test and publish check.\n- `pnpm test -- --full` adds browser journeys and every app/package test. It\n  starts the deterministic local stack.\n- `pnpm test -- --target-smoke` verifies the deployed staging API and gateway\n  SHA, then runs the tagged Playwright staging smoke. Release CI supplies the\n  staging credentials and `RELEASE_SOURCE_SHA`.\n- `pnpm test -- --target-full` verifies the same deployed SHA, then runs every\n  configured staging REST, CLI, and Playwright journey. The production release\n  gate uses this command and fails on any excluded API flow.\n- Browser and full modes reuse only a running API that proves the deterministic\n  test profile. Stop an ordinary development stack before either command.\n- Every root run writes lane and total timings to\n  `tests/test-results/local/benchmark-<timestamp>.json`.\n- Before merging into `dev`, run T0 and the tests that cover your change (the\n  testing philosophy above). On a Mac, suite-wide modes exit `2`.\n- Every Linux CI job runs on a free GitHub-hosted runner (`ubuntu-24.04`,\n  `ubuntu-24.04-arm`, `ubuntu-22.04`; 4 vCPU / 16 GB on this public repo) through\n  `runs-on: ${{ vars.CI_RUNNER_<tier> || '<label>' }}`. Setting a\n  `CI_RUNNER_<tier>` repository variable moves that tier to another pool\n  without a PR. Never default a job to a paid runner (Blacksmith, a GitHub\n  larger runner): Blacksmith billed ~$2.7k in September 2026 for minutes\n  GitHub gives this repo for free. `tests/unit/image-build-speed-workflow.test.ts`\n  enforces the free default.\n- `.github/workflows/tests.yml` holds the six contract lanes — `core-flows`,\n  `core-db`, `packages-api`, `packages-cli`, `packages-kortixd`,\n  `packages-rest` — one GitHub-hosted runner each (`CI_RUNNER_L`). `core-*`\n  split the core run (`--core-group=flows|db`); `packages-*` are slices of\n  package quality (`--packages-only --package-group=<g>`). The concurrency\n  settings in `tests/bin/package-quality.ts` must not be raised. Each lane is\n  the unchanged root command at the exact requested SHA. Do not add CI-only\n  test logic.\n- The contract lanes run nightly on `dev`, once when a person adds the `test`\n  label to a pull request into `dev`, on dispatch, and inside the release\n  suite (`tests-release.yml` calls `tests.yml`). A push to `dev` and the pull\n  request into `staging` run nothing. A red nightly run comments the failing\n  lanes and suspect commits on the commit, once per new set of failing lanes.\n- The release suite (`tests-release.yml`, on the release PR into `prod`) runs\n  the contract lanes, the deployed API smoke (the flows tagged `deployed`, one\n  or two per deployment seam) and the browser journeys against deployed\n  staging, in parallel. `full suite + quality gates` requires all of them and\n  every other check on the release commit. A browser spec exists only for a\n  money-path journey (sign-in, onboarding, session load and streaming, desktop\n  parity, billing, connectors, invites); everything else is a REST flow or\n  nothing. CI records a video of every browser journey in the run's\n  `tests-release-browser-shard-N` artifact. A failed release-suite run reruns\n  its failed jobs once by itself (`rerun-failed-once.yml`): a job that passes\n  on the rerun is a flake to fix or delete.\n- `tests/unit/sandbox-workflow.test.ts` fails when any workflow except the\n  label-gated `tests.yml` and `deploy-preview.yml` triggers on a pull request\n  into `dev`, and pins both label gates to the label-added event.\n- The deployed half of the release suite (`--target-api-full`,\n  `--target-browser-full`) waits up to 15 min for staging to serve\n  `RELEASE_SOURCE_SHA`, then fails when any `deployed` flow is excluded or any\n  configured Playwright journey fails.\n- The `preview` label is not part of the development flow. Adding it deploys a\n  Platinum self-host environment for the PR (~7 min), once, and runs no tests.\n  A push does not redeploy. Only `gh workflow run deploy-preview.yml -f\n  pr_number=<N>` runs `--target-full` against a preview (40–80 min). Its mechanics are\n  in the **contributing** skill (`references/preview-environments.md`). Run a\n  preview of your change on your worktree's local stack by default.\n\n### Product flow source of truth\n\n- `tests/spec/end-to-end.md` contains the natural-language contract and stable\n  flow IDs.\n- `tests/src/flows/*.flow.ts` implements the contracts through HTTP and real CLI\n  processes. Do not import API handlers.\n- Write each `ctx.step()` as a complete action and observable result. Cover\n  setup, authentication, action, read-back proof, negative paths, and cleanup.\n- Keep every flow's `meta.routes` synchronized with\n  `tests/spec/routes.generated.json`. Regenerate the manifest with\n  `bun run apps/api/scripts/dump-routes.ts` after route changes.\n- The local profile uses local Supabase, PostgreSQL, API, gateway, and bare Git\n  repositories. It excludes Stripe, cloud sandboxes, managed GitHub repositories,\n  and external email delivery explicitly.\n- Use Playwright only for browser-visible behavior. API-only assertions belong\n  in REST flows. SDK tests remain in `packages/sdk`.\n- Do not add another cross-cutting test harness, Makefile lane, contract suite,\n  Testcontainers suite, load suite, mutation suite, visual suite, accessibility\n  suite, or ad hoc smoke script under `tests/`.\n- Read `tests/README.md` and the repository `testing` skill before changing the\n  test system.\n\n### Release topology — dev, staging, prod\n\n- **`dev` = dev trunk.** It is the repo default branch and deploys to\n  `dev.kortix.com` / `dev-api.kortix.com`. Direct pushes are allowed; breaking or\n  incomplete development can live here while it is being shaken out.\n- **`staging` = release-candidate branch.** Nothing should land on staging unless\n  it is intended to be production-ready. Promote to Production merges the\n  `dev` HEAD into staging itself (the PR runs no check), deploys it, and opens\n  the release PR whose release suite is the one gate. Staging is the deploy\n  target that suite runs against, not a test stage. Staging\n  deploys to `staging.kortix.com` / `staging-api.kortix.com` and must use the\n  staging data plane, not dev or prod. The full promote-and-gate flow, the\n  fix-forward loop, and the prod release steps live in the **kortix-release**\n  skill — that is the one place this is written out in full.\n- Staging deploys must apply pending DB migrations against `STAGING_DATABASE_URL`\n  before the staging ECS Fargate rollout. If that secret is missing or points at\n  dev/prod, treat the deploy as broken; staging must never fall back to dev,\n  KE2E, or prod DBs.\n- **`prod` = production.** Production moves only through **Promote to Production**,\n  which uses `staging` as the source, opens a reviewed release PR into `prod`,\n  publishes the release artifacts, and rolls production after merge.\n- If a staging runtime check points at `dev.kortix.com` or\n  `dev-api.kortix.com`, treat that as a broken staging setup, not a passing\n  staging gate.\n\n### Driving the real UI (agent-browser)\n\n- **agent-browser is the browser for every agent task in this repo.** Use it\n  before chrome-devtools MCP, Playwright MCP, or any other built-in browser\n  tool. Playwright stays the engine of the committed browser test suite only.\n- The installed skill is a stub. Load the guide that matches the CLI version:\n  `agent-browser skills get core` (`--full` adds the command reference).\n- Use one named session per worktree:\n  `agent-browser session id --scope worktree --prefix <task>`, then pass\n  `--session <id>` on every command. Keep every `AGENT_BROWSER_*` variable the\n  same for all commands in a session. A per-command change relaunches the\n  browser and drops the open page.\n- Routes are auth-gated (`/dashboard`, `/projects/*` → redirect to `/auth`\n  unauthenticated); sign in first (seed a user as above, then log in via the\n  `/auth` form). On a PR preview, the magic link arrives in the preview's Mailpit API\n  (`<origin>/_mailpit/api/v1`)\n  (the **contributing** skill has the script).\n- Next.js dev compiles routes on first hit — first navigation to a cold route\n  can take 30–60s; warm it with `curl` or use a generous navigation timeout.\n- `agent-browser doctor` diagnoses launch and recording problems. Recording\n  needs ffmpeg with libvpx and libx264.\n\n### API lint gate\n\n- `pnpm --filter kortix-api lint` runs in the `Tests` packages lane. Its rules\n  are in `apps/api/eslint.config.mjs`: layered imports, no Drizzle in route\n  files, no `(c: any)`, no `process.env` outside `config.ts`, no\n  `console.*`, a `replica-local:` comment on every empty module-level\n  `Map`/`Set`, and no import into `projects/` from outside it except\n  `projects/index.ts` or `projects/surface.ts`. Background timers are guarded by\n  `apps/api/src/__tests__/unit-worker-scope-wiring.test.ts` instead.\n\n### API code conventions (`apps/api/src`)\n\n- **Routes register explicitly.** A route module exports\n  `register<Name>Routes()`. The module that mounts the router calls it right\n  before `app.route()` (`app.ts`, `accounts/index.ts`, `router/index.ts`).\n  Never register at import time. Call order is dispatch order.\n- **No service imports a route module.** Shared logic lives in a service file\n  next to the routes (for example `projects/session-open/`).\n- **Services take an `Actor`, never a Hono `Context`.** The route reads the\n  request through `middleware/` and `http-*.ts` helpers, builds the actor\n  (`middleware/actor.ts`, memoized per request), and calls the service with\n  plain values. Parse `Authorization: Bearer` with `shared/bearer-token.ts`.\n- **Every background loop lives in `workers/<name>-worker.ts`.** The worker\n  owns its timer, start/stop and `runWorkerTick` call. The tick stays an\n  exported service function. `bootstrap.ts` imports every loop from `workers/`.\n- **`iam/` reads the membership, group and role tables.** Use the read models\n  in `iam/membership-read.ts`, `iam/group-read.ts` and `iam/role-read.ts`.\n- **No function longer than 300 lines.** Split long functions into named steps.\n- **SQL trace:** `KORTIX_SQL_TRACE=<file>` appends every pool statement to the\n  file. Diff two traces to prove a refactor runs the same SQL.\n- `apps/api/eslint-suppressions.json` holds the violations that existed when\n  each rule was added. A new violation fails. A fixed one fails until you run\n  `pnpm --filter kortix-api lint:prune` and commit the smaller file. Never\n  absorb a new violation with `--suppress-all`.\n\n### Frontend type/lint gate\n\n- Typecheck `apps/web` with `pnpm --filter Kortix-Computer-Frontend typecheck`\n  (tsgo, incremental). Never run raw `tsc --noEmit` there. Measured on\n  2026-10-21 on a 24 GB Mac: raw `tsc` ran 817 s, held 3.18 GB, and was\n  killed before it finished. Cold `tsgo` takes 12.1 s at 5.0 GB peak. A warm\n  incremental run takes 1.1 s at 1.7 GB.\n- On 2026-10-21 the tsgo script reported 0 errors on `dev`. The bar is\n  relative: a branch adds no type error to the files it touches. Compare against `origin/dev` with the same command, not\n  against a number in this file.\n  The old ~1500 `TS2786` / `IntrinsicAttributes` noise from a React 19↔18\n  types mismatch (two copies of `@types/react` in one program — `packages/sdk`\n  had its own) is gone as of the Next 16 upgrade. If `TS2786` appears again,\n  treat it as a genuine duplicate-`@types/react` regression and investigate —\n  do not wave it through.\n- `npx eslint <files>` should be clean of errors. `eslint .` across the whole\n  app currently reports ~455 warnings, mostly `react-hooks/*` React Compiler\n  rules pending a dedicated audit — expected until that audit lands.\n\n### Frontend design standard — Jay/Kortix bar\n\n#### Canonical design references\n\n| Surface | Read before changing UI | Implemented source |\n|---|---|---|\n| Web (`apps/web`) | `.agents/skills/kortix-brand/SKILL.md` (the router: values, voice, claims, decision history), then `.agents/skills/kortix-design-system/SKILL.md` for components | `apps/web/src/app/globals.css` tokens (generated from `.agents/skills/kortix-brand/references/visual/visual-system.json`), `packages/ui/src/primitives/` (apps/web imports each one as `@kortix/ui/<name>`; apps/web has no re-export shims, `tests/unit/web-no-ui-shims.test.ts` enforces it), the live `/design-system` route |\n| `@kortix/ui` hosts (Kortix Apps, `apps/white-label/reskin`, `apps/white-label/lumen`, `apps/white-label/team-console`, SDK customer apps) | `packages/ui/skills/kortix-ui/SKILL.md` (setup, which component, white-label theming, the `SessionThread` chat), then the web row for Kortix-owned surfaces | `packages/ui/src/` (components, `session/` for the `SessionThread` chat, `styles/tokens.css` + `theme.css`, prebuilt `dist/styles.css`); white-label = CSS variable overrides, never a forked component |\n| Desktop (Electron shell) | The web row — the shell renders `apps/web` — plus `apps/desktop-electron/README.md` for the shell boundary, then the parity gate below | `apps/web` rendered by the shell; native window geometry in the shell's titlebar classes |\n| Mobile (`apps/mobile`) | `apps/mobile/design.md` for screens, `apps/mobile/AGENTS.md` for primitives | `apps/mobile/global.css` colors, `apps/mobile/components/ui/`; stock Tailwind spacing for touch targets |\n\n#### Desktop parity is a UI gate\n\nThe Electron app loads `apps/web`. Keep product components, routes, tokens,\nand data behavior shared. Put native window geometry in the shell's explicit\ntitlebar classes. Never apply titlebar height or drag rules to generic ARIA\nroles, all sidebars, or page content.\n\nFor every shared UI change, verify the affected controls on web and in Electron\nbefore handoff. Check the outgoing request or route and the visible result.\nCheck both themes, the minimum supported window (720 × 480), sidebar collapse,\nfullscreen overlays, and browser zoom. Window controls must not overlap app\ncontrols. Lists must not overlap or clip their last row. Keyboard focus and\nscrolling must remain usable.\n\nAdd regressions to the existing Playwright journeys. The desktop journey runs\nin `pnpm test -- --browser-only` and supports the actual Electron shell:\n`E2E_DESKTOP_NATIVE=1 E2E_GREP='27 — desktop parity' pnpm test -- --browser-only`.\nRun the native journey when changing shell CSS, navigation, settings, agents,\nor connectors. A desktop user-agent test does not prove native hit testing.\nReport any unverified desktop behavior explicitly. Do not promise that tests\nprevent every future regression.\n\nWhen touching any visual surface in `apps/web`, treat brand fit as a release\ngate, not polish:\n\n- Read `.agents/skills/kortix-brand/SKILL.md` before writing the first\n  `className` or the first user-facing string. It routes you to the value law:\n  the complete allowlist of every color, spacing step, type rung, radius,\n  elevation, and duration you may use. Note `--spacing: 0.23rem` — Tailwind's\n  scale is 8% tighter here, so a 16px mockup padding is `p-4`, never\n  `p-[16px]`. Run `.agents/skills/kortix-brand/scripts/audit.sh` over your\n  changed paths before opening the PR; it must be clean on files you touched.\n  `references/visual/visual-system.json` is the only file with values: edit it,\n  then run `scripts/generate-tokens.ts`. Never edit a generated region of\n  `globals.css` by hand.\n- Read `.agents/skills/kortix-design-system/SKILL.md` next and compose existing\n  primitives from `@kortix/ui/*` before inventing local chrome.\n- Match the current Jay Suthar / Kortix product aesthetic: calm neutral surfaces,\n  dense-but-legible UI, black/white plus one earned accent, token-driven spacing,\n  and no decorative color, glow, or one-off rounded boxes.\n- Use recent product surfaces as references before editing: `/design-system`,\n  `packages/ui/src/workspace/layout/project-home.tsx`,\n  `apps/web/src/components/ui/wallpaper-background.tsx`, and the account/IAM\n  screens called out by the design-system skill.\n- Verify visual work in the browser and include the exact lint/typecheck commands\n  you ran in the PR. If it does not look native beside Jay-authored UI, keep\n  iterating before shipping.\n","CLAUDE.md":"# Kortix project\n\n## What \"ownership\" means\n\nThe words \"can you own this?\", \"are you on it?\", and \"can you take care of this?\"\nall mean the same thing: you are 100% responsible. The person who handed it to\nyou must be able to walk completely away, come back a week later, and find it\ndone properly — because you cared about every edge and corner.\n\n- it's not done if it's not implemented\n- it's not done if the implementation is ugly\n- it's not done if it's not documented\n- it's not done if users can't discover it\n- it's not done if you can't market it\n\nOwning something means owning it end to end. The whole arc from \"we have a\nproblem\" to \"nobody has to think about this again.\" Not just the code change in\nthe middle, the whole thing. If someone hands you something and still has to\ntrack whether it actually got solved, you didn't own it.\n\nHere's what that actually looks like in practice.\n\n**Start with the problem, not the solution.** A lot of the time you'll already\nhave a fix in your head before you've understood what's broken. \"We need to move\nfrom X to Y\" isn't a problem, that's a solution you've pre-committed to. The real\nproblem is probably \"it's slow\", \"it's flaky\", \"it breaks for this customer\".\nName that first. Then ask what else could solve it, what the tradeoffs are, and\nwhich option actually wins.\n\n**Then pressure-test it before you build:**\n\n- **Edge cases.** Which exist, which matter, which we can safely ignore.\n- **Failures.** Networks fail, that's a given. Retry? How many times, for how\n  long?\n- **Data.** How much, does it need migrating or cleaning, how do you get real\n  data to test against, and what are you assuming about its shape that you\n  haven't actually verified?\n- **Testing.** How will you know it's correct? Are automated tests enough or do\n  you need to poke at it by hand? Is the result something you can see in a\n  screenshot or a video?\n- **The bigger picture.** How does this get announced, how does it fit the\n  roadmap, can you even picture it shipped? If something there bothers you, push\n  back. Ask.\n\n**Then actually do it**, with precision, care, urgency and calm all at once. No\nhalf-assing. The bar before you merge: am I proud of this? Would I put it in\nfront of Steve Jobs and walk him through what I built, the constraints, the\ntradeoffs?\n\n**And then prove it works.** Not \"the tests pass\", prove it. In 99% of cases you\ncan confirm it yourself: run it, ask an agent to walk through the scenarios, poke\nat the data before and after, take a screenshot, make a demo. Are you actually\nsure it solves the problem you started with?\n\n**Then make sure it lands in production and works in production**, which is not\nthe same as merged. Did it deploy? Did the deploy quietly fail? Is there a flag\nto flip, and does the flag work? Can you use the thing in prod right now and\nconfirm it's really there?\n\n**And then close the loop with everyone it touches:**\n\n- **The team.** If it's a new feature, a new convention, or a tricky thing people\n  should know about, tell them. Don't underestimate peripheral vision. You\n  knowing that someone changed Z yesterday can save someone else three hours of\n  debugging tomorrow when a bug report about Z comes in.\n- **Customers.** Whoever reported it, whoever's blocked, let them know it's\n  fixed.\n- **The world.** If it's worth announcing, announce it.\n- **Future you.** Are there follow-ups? Should you check the logs in a week to\n  make sure it's still healthy?\n\nBut that's how we build a product in a small team. We don't have PMs, we don't\nhave a QA department. We're small, but we're great, and we can do all of that.\n\nAnd it's always okay to ask for help, it's okay to ask questions, it's okay to\nredo things and triple-check. What's not okay is to quietly assume someone else\nwill catch the parts you didn't think about.\n\n### The bar: autonomy, agency, ownership\n\nThis is what I require from the agent I work with. In my own words:\n\nEither you are performing or you are not. Either you are taking on high\nownership, are high agency and pushing without me having to micromanage you, or\nyou are not.\n\nEspecially in today's age you are limited by great talent more than ever. Because\nwe can all AGI Max, you have a single chokepoint on good judgement.\n\nEvery person that comes on the team has to have the capability to truly own\nsomething. If you have built products, you develop ownership because you owned\nsomething — whether it worked out or not — for a prolonged amount of time. You\ndevelop true agency.\n\nI hire you because I expect that if I am able to walk out of the room after\ngiving you a high level thing and come back, it's going to be done good, ideally\nbetter than I would've done it.\n\nMost people are shit at their jobs, some are decent/good, but only people who are\nexceptional should be at Kortix.\n\nBeing exceptional on paper is simple — it's a combination of Ownership, Agency\nand actual Merit/Skill. It's hard, because you have to not only be very smart but\nalso crazy driven to push like a motherfucker and want to feel every edge and\ncorner to make sure the output is good.\n\n## Skills live in `.agents/skills/`\n\nEvery repo skill is a directory in `.agents/skills/<name>/`. `.claude/skills/<name>` is a\nsymlink to it. Add a skill in `.agents/skills/`, then add the symlink. Third-party skills come\nfrom `npx skills add` and are pinned in `skills-lock.json`. The PR procedure is the\n**contributing** skill; the PR title and body are the **create-pr** skill. Browser work is the **agent-browser** skill.\n\nThe repository has no `docs/` tree. Put a runbook or spec in the skill that owns the\nsurface (`.agents/skills/<name>/references/`). Put an incident rule in the learnings\nledger. Put design detail and RCAs in the PR body. Never cite a repo path that does\nnot exist. The pre-commit hook and `tests/unit/no-docs-tree.test.ts` reject a new\n`docs/` file and any citation of one.\n\n## Ponytail is on by default\n\nEvery code change runs through the **ponytail** skill at level `full`. Load it before you\nwrite, fix, refactor, or review code, and before you add a dependency. Level switch:\n`/ponytail lite|full|ultra`. Off: \"stop ponytail\". **ponytail-review** audits a diff for\nover-engineering, **ponytail-audit** audits the whole repo, and **ponytail-debt** lists\nevery `ponytail:` shortcut comment. Ponytail cuts code, never the verification,\ndocumentation, or ownership bar in this file. The skills come from\n`DietrichGebert/ponytail`, pinned to `v4.10.0` in `skills-lock.json`.\n\n## Learnings: the episodic ledger in the `learnings` skill\n\n`.agents/skills/learnings/` is the append-only, timestamped ledger of rules paid\nfor with real downtime. `MEMORY.md` indexes it newest first, and each entry is\none file in `entries/`: the rule, the incident that taught it, and the\nautomation that enforces it. Search it before writing or reviewing a DB\nmigration, touching deploy/release workflows, planning a promote, or\nresponding to a prod incident. After resolving ANY incident or near-miss,\nrecord a new entry in the same session with `scripts/new-entry.sh`. An incident\nthat leaves no entry behind is not finished.\n\n## NEVER write customer data or PII into anything we publish or commit\n\nThis is a hard rule. No exceptions, no \"just this once\", no \"it's only\ninternal\".\n\n**Never write any of these:**\n\n- customer or company names, and the names of their people;\n- email addresses, phone numbers, or other personal data;\n- real account, project, session, user, or sandbox IDs from prod, staging, or\n  a customer deployment;\n- customer repository names, hostnames, or URLs that contain any of the above;\n- customer prompts, messages, files, or log lines;\n- screenshots of a real customer workspace.\n\n**Never write them into:**\n\n- commits, commit messages, or branch names;\n- PR titles, PR bodies, PR comments, or review comments;\n- issues;\n- code comments, test names, or test fixtures;\n- docs, runbooks, skills, `AGENTS.md`, changelogs, or release notes;\n- artifacts, Slack posts, or any public or team-visible text.\n\n**Write the class instead:** \"a customer reported\", \"an enterprise\nworkspace\", \"a prod session\", `<session_id>`. Build test data from synthetic\nvalues. Evidence that contains real data stays local: the gitignored\n`output/` folder, your scratchpad, or the private agent memory. It never goes\ninto a tracked file.\n\n**If you find customer data** in the tree or in a PR, remove it in the same\nbranch and say so. Do not rewrite history on `dev`. Report the SHA to the user\ninstead.\n\n**A guard enforces this on every commit and push.**\n`scripts/check-blocked-terms.sh` runs from `.githooks/pre-commit`,\n`.githooks/commit-msg`, and `.githooks/pre-push`. It refuses any added line,\ncommit message, or pushed branch name that contains a blocked term. Matching is\ncase-insensitive and whole-word. The list is itself customer data, so it lives\nencrypted in `apps/api/.env` as `BLOCKED_COMMIT_TERMS`, comma-separated.\n\n- Add a customer the day they sign: `dotenvx set BLOCKED_COMMIT_TERMS\n  \"<existing>,<new>\" -f apps/api/.env`. Read the current value first with\n  `dotenvx get BLOCKED_COMMIT_TERMS -f apps/api/.env`.\n- In a worktree the guard decrypts with the primary checkout's\n  `apps/api/.env.keys`. Without a key it warns and allows.\n- Deleting a line that contains a term is always allowed.\n- Never bypass the guard with `--no-verify`. If it fires, remove the term.\n- The hooks do not see PR titles, PR bodies, or comments. Those stay your\n  responsibility.\n\n## How to communicate: precise, technically accurate, no fluff\n\nWrite every response — chat, PR text, commit messages, code comments, docs — in\nthe spirit of **ASD-STE100 (Simplified Technical English)**. The goal is maximum\ntechnical precision with zero filler. Apply these rules:\n\n- **State facts, not vibes.** Every claim is specific and verifiable: name the\n  file, function, route, flag, SHA, status code, or number. No \"should work\",\n  \"probably\", \"a bunch of\", \"various\", \"seems fine\" — say what is true and how you\n  know, or say you do not know it yet.\n- **One idea per sentence.** Keep sentences short (aim ≤ 20 words) and each one\n  carries a single instruction or fact. Split compound thoughts instead of\n  chaining clauses.\n- **Active voice, present tense, direct.** \"The gate rejects the request\",\n  not \"the request may end up being rejected\". Give the instruction; do not\n  soften it.\n- **One term per concept.** Use the same word for the same thing every time —\n  do not alternate \"session\"/\"run\"/\"task\" for one concept. Match the codebase's\n  existing names exactly (`session_id`, not \"session ID / run id\").\n- **No filler, no hedging, no praise.** Cut \"basically\", \"just\", \"simply\",\n  \"I think\", \"great question\", \"as we know\", and marketing adjectives. Lead with\n  the answer; drop the throat-clearing.\n- **Quantify.** Prefer exact values over adjectives: \"up to ~9 min\", \"returns\n  `402`\", \"3 of 7 flows\", not \"slow\", \"an error\", \"most\".\n- **Show the evidence.** When you assert a behavior, cite the command you ran and\n  the real output. Distinguish verified fact from assumption explicitly.\n- **Structure over prose.** Use numbered/bulleted lists for steps, findings, and\n  status. Reserve paragraphs for genuine explanation, and keep them tight.\n- **Say the unknown plainly.** If something is unverified, blocked, or risky,\n  state it in one line — what, why, and what would resolve it — instead of\n  burying or omitting it.\n\nThis standard governs how you talk. It does not override the technical rules\nbelow; it is how you report on them.\n\n## First, at session start: which canonical branch are you in?\n\nEvery change belongs to **one canonical branch** — the branch for whatever is\nbeing worked on. One canonical branch, one worktree. Establish which one you are\nin before any non-trivial change. **Do not create a branch by reflex.**\n\n1. **Join the canonical branch that already exists** for this work. List them\n   with `git worktree list` and `git branch -r`. If the work continues, extends,\n   fixes, or cleans up something already in flight, it belongs on that branch.\n   Ask the user which branch when it is not obvious.\n2. **Start a new canonical branch** only when the work is genuinely a new thing.\n   Give it its own worktree: `pnpm worktree create --name <slug> --yes\n   --no-start`, then do all edits and runs under `../suna-<slug>`. Add `--db`\n   when the work needs migrations, destructive data work, or schema drift: it\n   copies the primary local database into the worktree's own database\n   (`kortix_wt_<slug>`, ~5 s, no extra containers). `--full-supabase` (a whole\n   Supabase stack, ~0.75 GB RAM) is only for independent auth/storage config.\n   See the **worktree** skill.\n3. **Never switch a worktree you did not create.** It belongs to one session and\n   its canonical branch. The pre-commit hook refuses a commit on any other branch\n   there (`scripts/check-worktree-branch.sh`). A throwaway probe branch goes in a\n   private `git worktree add` in your scratchpad.\n4. **The primary checkout** (`pnpm dev`, web `3000` / api `8008`) is for running\n   and investigating. Do not park feature work there.\n5. **Run `pnpm test` only from a worktree that has `.kortix-worktree.json`.** The\n   runner reads its ports from that marker. A bare `git worktree add` has none,\n   so its test stack takes the primary ports and talks to another session's\n   `pnpm dev` (2026-10-13: 156 flows failed with that stack's 503s).\n6. **Never kill a process you did not start.** Many sessions share this machine.\n   Kill by PID from your own process tree, or by a pattern that contains your\n   own worktree or scratchpad path. Never by a bare pattern such as\n   `pkill -f \"bun test\"`, and never in a loop. Need CPU? Lower your own\n   concurrency (`KE2E_API_WORKERS`, `KORTIX_DB_SUITE_WORKERS`) or ask the user.\n   2026-10-13: a guard loop that SIGTERMed every other session's `pnpm test`,\n   `tsc` and `vitest` every 20 s made 259 kills, and every suite on the machine\n   died with exit 143 and no failing test.\n\n**Pack more into one branch, not less.** A follow-up fix, a rename cleanup, a\nstale-reference sweep, and the change that caused them all belong on the same\nbranch and land together. Splitting one objective across several branches is how\na half-finished cutover reaches `dev` in pieces — each piece green alone, the\nwhole thing broken.\n\nSub-branches are allowed. Agents may cut working branches off the canonical\nbranch and merge back into it. **A sub-branch never opens a PR against `dev`.**\nOnly the canonical branch does.\n\nCarve-outs where you just proceed: read-only investigation and questions, and\ntrivial single-file typo/comment fixes on the current branch.\n\n## Default delivery: verify in your box, self-merge to `dev`, verify on dev\n\n`dev` is the dev trunk. A merge does not deploy: dev deploys only on a deliberate\ndispatch, but **merging to `dev` still lands your change in everyone's next\ndeploy and next `dev` checkout.** It is not a save point.\n\n### Testing philosophy: one gate at Promote to Production, everything else fast\n\nTesting reset, 2026-10-16. Latency is the core concern. Development must be\nfast enough to push and break things on `dev`. The full suite runs once per\nrelease, at Promote to Production, and nowhere else. The rules:\n\n1. **Never run a full suite during development, and never a whole package's\n   tests.** No multi-minute run on your box, on a PR into `dev`, or on a push\n   to `dev`. Verify only what you changed: T0 for the packages you touched,\n   the test files that cover the change, named one by one\n   (`pnpm --filter <pkg> test <file>`), and the change itself through its real\n   surface. Running all 2,129 `apps/web` tests to check one component is the\n   anti-pattern this rule exists for.\n2. **One gate: the release pull request into `prod`.** Promote to Production\n   runs the whole suite there, once, and every check must be green. It is the\n   only check that blocks anything.\n3. **Test at the boundaries users touch:** the HTTP API, the CLI, the\n   `@kortix/sdk` public surface, DB migrations, and a few browser journeys on\n   the money path. Never test internal calls or source text.\n4. **Run each suite once per tree.** A tree that ran is not run again.\n5. **A red suite is worse than none.** A test that fails twice without a bug is\n   fixed or deleted the same day. Do not park it in quarantine.\n6. **Delete on red.** A unit test that goes red on a refactor with no bug is\n   deleted, not updated. Keep unit tests only for logic that is hard to get\n   right: money and credits, parsers, the streaming protocol, permission\n   decisions, crypto and secrets.\n7. **Small, isolated tests.** Every test file runs standalone in seconds. When\n   the test that covers your change takes more than ~10 s, or drags in setup\n   that has nothing to do with it, split it into a smaller isolated test as\n   part of the same change. Fake the clock; never sleep on real time.\n\n| Tier | Contents | Runs |\n|---|---|---|\n| T0 static | `tsc`, lint, architecture checks, migration lint | anytime, seconds per package |\n| Targeted | the one test file or flow that covers your change | on your box, seconds |\n| Release suite | contract lanes (flows, DB, packages) on the release tree, the deployed smoke and the browser journeys against staging, CI, CodeQL, scanners | once per release, on the pull request into `prod` |\n\n| Where | What runs | Blocks? |\n|---|---|---|\n| Your box | T0 for the packages you touched, the targeted test, and the real surface: the route with curl, the CLI process, the page with agent-browser | your judgment |\n| Pull request into `dev` | nothing, unless a person adds `test` or `preview` | no |\n| Push to `dev` | only the seconds-long guards: `secret-scan`, `secrets-guard`, and path-gated `DB Migrations` / `i18n-catalogs` / `Terraform Apply Global` / `deploy-api-router-dev` | no |\n| Nightly on `dev` | `Tests` `core` and `packages` lanes, no browser: a red night comments the failures on the commit, so they are ready to fix before a promote | no |\n| Pull request into `staging` | nothing: Promote to Production opens and merges it | no |\n| Pull request into `prod` | the release suite, once (`full suite + quality gates`) | **yes, the only gate** |\n\n**A Mac never runs a suite.** On macOS `pnpm test` refuses a suite-wide run\n(plain, `--full`, `--packages-only`, `--browser-only`, `--db-only` with no path\nfilter) and exits `2`. 2026-10-16: five agent sessions ran the suite at once on\none laptop (load average 112, swap full). Run one flow\n(`pnpm test -- --id <ID>`), one DB suite path, or one package's test file.\n`KORTIX_FULL_TEST=1` overrides only when a person asks. Every package `test`\nscript refuses the same way: `pnpm --filter <pkg> test` with no file exits `2`\n(`scripts/test-guard.mjs`). Pass the file(s), `pnpm --filter <pkg> test <file>`,\nor a name filter after `--`: `pnpm --filter <pkg> test -- -t <name>`. CI and\nLinux run the full suite.\n\nNo hook or gate requires a test run before a push or a merge into `dev`. Write\nin the PR body what you ran and why. A diff that no test reads (docs, a skill,\na standalone script) needs no run. When the nightly run or the release suite\nturns red on your change, you fix forward on `dev` the same day.\n\n1. Work on the canonical branch in its worktree. Commit as often as you want.\n2. Verify in your box, with real inputs and outputs. Run T0 and the tests that\n   cover the change (see above). Start the worktree's stack\n   (`pnpm worktree start <slug>`) and drive the changed behavior through it: the\n   HTTP route, the real CLI process, or the page with agent-browser. Nothing\n   before the release suite at Promote to Production catches what you skip.\n3. Open the PR against `dev` and follow the **contributing** skill: it attaches\n   the demo video you recorded against your local stack. Write the title and\n   body with the **create-pr** skill: English only, for a human reviewer who\n   decides \"merge or iterate\" without reading the diff. Do not add `test` or `preview` unless you need that one explicit run.\n4. Merge `dev` into the canonical branch daily. A branch that diverges for weeks\n   detonates on merge exactly like a 1,500-line PR does.\n5. **Self-merge to `dev` when the change is verified. Do not wait for the\n   user's approval.** Speed matters: a verified change that sits unmerged is\n   waste. Verified means all of these are true:\n   - the relevant local checks ran with real inputs and outputs (rule 2), and\n     they passed;\n   - the PR is mergeable (no conflict);\n   - rule 6 holds when the change touches a client-facing runtime contract.\n   A failing check blocks the merge until you fix it or state why it is\n   unrelated (for example, the same test fails on `dev`). Squash-merge\n   (`gh pr merge <pr> --squash`), then finish rules 7 and 8. A merge is not\n   the end of the work: dev verification is still yours.\n   The only machine-enforced rule on `dev` and `staging` is that every change\n   arrives through a pull request — no required approvals, no required status\n   checks, no bypass actors. The bar is what you verified.\n   **The release gates do not change.** Merging into `staging` or `prod`,\n   running Promote to Production, and moving the `:stable` tag each need the\n   user's explicit approval (the **kortix-release** skill).\n6. **A change to a client-facing runtime contract** — the `@kortix/sdk` public\n   surface, session/thread transport, the streaming protocol — merges only after\n   the whole objective ran through a real session on your local stack, and runs\n   again on dev after the merge (rule 8). Green tests are not the bar. Someone\n   used it.\n7. After the merge, deploy dev yourself: `gh workflow run deploy-dev.yml\n   --repo kortix-ai/suna -f surface=changed` (`all` or `frontend` force a\n   rebuild). Deploys queue, they never cancel. Wait for the run to finish.\n   `/health` on every changed surface must serve the merge SHA: a successful\n   `/health` response alone is not deployment proof. The run comments \"Live on\n   dev\" on the merged pull request, and \"Not live on dev yet\" names the\n   surface that failed. The surfaces and their checks are in\n   `.github/workflows/deploy-dev.yml`. The merge push runs no suite. The\n   nightly `Tests` run on `dev` comments the failing lanes and every commit\n   since the last green run, once per new set of failing lanes. The author\n   whose commit broke `dev` fixes forward the same day. If `dev` is still red\n   1 hour after the comment, anyone may revert the culprit PR. The release\n   suite at Promote to Production runs the same lanes and blocks the release.\n8. Re-run the user-visible behavior against `https://dev.kortix.com` and/or\n   `https://dev-api.kortix.com`. Prefer the real Kortix CLI configured for the\n   dev API for CLI/project/session flows, and direct authenticated HTTP calls for\n   API contracts. For web behavior, drive the deployed UI and assert its network\n   request plus visible result.\n\nLocal verification and dev verification are both required. A local pass does\nnot replace dev, and a dev smoke test does not replace focused local tests.\nRecord the branch, PR, local commands and output, merge SHA, deploy run,\ndeployed SHA evidence, and the exact dev command or interaction in the final\nresponse.\n\n## Architecture: `@kortix/sdk` is the source of truth\n\n`@kortix/sdk` is the **single source of truth** for everything that talks to the\nKortix backend — projects, accounts, sessions, files, secrets, triggers, the\nsession runtime, the runtime REST client, SSE streaming, model state,\nand auth-token plumbing. The apps\n(`apps/web`, `apps/white-label/*`, `apps/mobile`) are **thin consumers**. Treat\nthese as standing rules whenever you touch the data/runtime layer:\n\n> **Editing `packages/sdk` itself? Load the **sdk** skill (the rules) first.** It is a\n> **published npm package** with its own hard rules that have no analogue\n> elsewhere in this repo: **TDD is mandatory** (failing test first, run it, watch\n> it fail, then implement — and every turn ends with the gates run, the real\n> output pasted, and an explicit shippable YES/NO/NOT YET); exported names\n> (including *types*) are a public API contract and renaming one is a breaking\n> change; the `version` field is inert and must never be bumped by hand; adding\n> an export requires three synchronized edits; and the framework-free core is\n> enforced by a static import-graph tripwire.\n\n- **Logic lives in the SDK, never in a host.** No raw `fetch` to the Kortix API,\n  no `@opencode-ai/sdk` imports, no transport / runtime / data-state code written\n  in app code. New data or runtime behavior is added to the SDK and exposed\n  through its public surface — not hand-rolled or duplicated in a host. If you\n  need something the SDK doesn't expose, add it to the SDK.\n- **One client per host.** Create it once via `createKortix({ backendUrl,\n  getToken })` and read everything through `@kortix/sdk` + `@kortix/sdk/react`.\n  Auth is just `getToken` — an API key / PAT for programmatic use, or a Supabase\n  JWT for the logged-in web app. Hosts never instantiate a second client.\n- **A whole session is one hook.** `useSession(projectId, sessionId)` owns the\n  entire runtime lifecycle — `/start`, the sandbox switch, the live SSE stream,\n  readiness seeding, immutable runtime identity, the native conversation id,\n  and message sync. Hosts don't\n  hand-roll the mount, drive a server-store \"switch\", or mount a separate event\n  provider.\n- **Session-scoped + provider-agnostic.** The public API is session-scoped\n  (`kortix.session(pid, sid).health() / .previewUrl() / .restart() / …`).\n  The sandbox provider and the harness are server-side concerns. Host code\n  must not implement a second transport.\n- **Build on the Kortix contract, not on OpenCode.** Every new session runs\n  pi inside kortixd. OpenCode is deprecated (2026-10-23): there is no\n  `pi_harness` flag, `runtime:` in `kortix.yaml` selects nothing, and OpenCode\n  serves only sessions that started on it. It will be deleted. Code that exists\n  only for OpenCode carries `@deprecated`: never call it from new code, never\n  add a feature to it, and change it only to fix a bug that breaks a running\n  OpenCode session. pi calls models only through the LLM gateway. pi serves\n  the daemon routes (`/kortix/runtime/*`), the transcript\n  (`kortix.transcript.v1`, `packages/api-contract/src/transcript.ts`) and the\n  events. New code reads those, never an OpenCode route, type, file or\n  process. A feature pi lacks is a capability, not a harness check:\n  `GET /kortix/health` lists `capabilities` (`RUNTIME_CAPABILITIES` in\n  `packages/api-contract/src/runtime-relay.ts`), and a client gates the\n  control with `runtimeSupports`. OpenCode lists all eleven (`session.steer`\n  only on OpenCode 1.18.15 or later); pi lists `session.subagents`,\n  `session.compact`, `session.commands` and `session.steer`. pi does not\n  serve rewind, MCP servers, the todo list, shell turns, part edits or\n  `session.attach`.\n  The harness rules and the pi gap list are in\n  `apps/kortix-sandbox-agent-server/src/harness/README.md`.\n- **`apps/web` data modules are shims.** Files such as\n  `apps/web/src/ui/index.ts`, `apps/web/src/lib/iam-client.ts`, and\n  `apps/web/src/hooks/admin/use-*.ts` are thin re-exports\n  (`export * from '@kortix/sdk/...'`).\n  Keep them as shims; put the real logic in the SDK. When a merge conflict lands\n  on one of these, **keep the shim (`--ours`) and port any new host-side logic\n  into the SDK** — do not revert to a host-local implementation.\n- **Docs are the spec.** `apps/web/content/docs/sdk/*` and\n  `packages/sdk/README.md` describe the intended surface. Keep them current with\n  the SDK, and flag legacy/deprecated surfaces in-doc rather than documenting them\n  as current.\n\n## You CAN run and verify everything end-to-end. Do it.\n\nThis repo ships a **complete, runnable local stack with live cloud sandboxes**.\nDo not claim you \"can't verify from here\" or hand back unverified work — you\nhave everything needed to run the app, hit the real API, provision real\nPlatinum sandboxes, drive the real UI in a browser, and assert behavior. Use it.\n\n### Required verification standard — real inputs, real outputs\n\nFor every behavior change, assume **100% autonomy** to verify the user-visible\ncontract before handing the work back. Do not stop at typechecks, unit tests, or\nmocked internals when a real surface exists.\n\n- **API changes:** exercise the actual HTTP route with real request payloads\n  (`curl`, `bun fetch`, or the `ke2e` runner against a running API). Assert the\n  status code and exact response fields that prove the behavior. For writes,\n  also assert the persisted/read-back state or resulting repo/file output.\n- **CLI changes:** run the real CLI command as a process from bash, with the\n  same flags and stdin a user or agent would use. Assert exit code, stdout,\n  stderr, and any files/API calls/commits it should create. Do not rely only on\n  importing command functions.\n- **Web changes:** drive the real page with **agent-browser** (the primary\n  browser; `agent-browser skills get core` loads its guide). Click/type/toggle\n  the actual controls, observe the network request (`agent-browser network`),\n  and assert the visible UI state plus the outgoing payload. Record the flow\n  (`agent-browser record start`) for the PR's demo video. Screenshots and video\n  are evidence, but assertions on DOM and network data are required.\n- **Cross-surface features:** verify each exposed surface independently. If the\n  same feature ships on API + CLI + web + mobile, each gets its own black-box\n  assertion for the inputs users can make and the outputs they receive.\n- **Default/negative paths count:** when changing defaults or removing implicit\n  behavior, assert both the new default and the explicit opt-in/alternate path.\n- **No silent gaps:** if a surface cannot be fully exercised in the current\n  turn, say exactly which input/output remains unverified and why. Otherwise\n  keep going until the real surface is verified.\n- **Final response format:** when work is finished, answer per the **How to\n  communicate** standard at the top of this file — numbered/bulleted, no fluff.\n  Include exactly what changed, what was verified (with the command + output),\n  what remains unverified or risky, and what the user should test next. Do not\n  bury the actionable testing path in a paragraph.\n\n### The stack (already wired)\n\n- **Web** — Next.js dev server on `http://localhost:3000`.\n- **API** — Bun server on `http://localhost:8008/v1` (`/health` returns JSON).\n- **Supabase** — local, on `http://127.0.0.1:54321` (Docker).\n- **Sandboxes** — REAL cloud sandboxes on Platinum, the reference provider\n  (`PLATINUM_API_KEY` in `apps/api/.env` / `.env.local`). Daytona is a legacy\n  fallback with removal scheduled and no support guarantee; E2B support is\n  discontinued. Each project\n  session gets its own sandbox; `session_id == sandbox_id`. The sandbox daemon is\n  reached through `http://localhost:8008/v1/p/<external_id>/8000/...`.\n  The session runtime answers on the same proxy, under `/kortix/runtime/*`,\n  on both harnesses.\n- **Tunnel** — `scripts/dev-local.sh` (`pnpm dev`) auto-starts a cloudflared\n  quick tunnel so cloud sandboxes can call back to the local API (`KORTIX_URL`).\n\nBring it up with `pnpm dev` from `suna/` (it loads `apps/api/.env` +\n`apps/web/.env`, starts Supabase, the API, the web app, and the tunnel). Check\nwhat's already running before starting a duplicate: `curl -s\nlocalhost:8008/v1/health`, `lsof -iTCP:3000 -sTCP:LISTEN`.\n\n> **Secrets are dotenvx-encrypted (mandatory).** `apps/api/.env` (+ `.env.dev`)\n> are committed as ciphertext (`KEY=encrypted:…`); keys live in Dotenv Armor.\n> **Never write a plaintext secret into a tracked file or commit** — add/change\n> values only via `dotenvx set KEY value -f apps/api/.env` (then commit), read\n> with `dotenvx get`, and machine-local overrides go in the gitignored\n> `apps/api/.env.local`. If the user pastes a key, store it with `dotenvx set`,\n> never paste it raw. A pre-commit hook + GitHub push protection enforce this —\n> don't bypass them. Full procedure: the **dotenvx-secrets** skill.\n\n### Authenticating to the live API (for scripts/tests)\n\nMint a real JWT against local Supabase, then call the API with it:\n\n1. `SUPABASE_SERVICE_ROLE_KEY` lives in `apps/api/.env`; the anon key\n   (`NEXT_PUBLIC_SUPABASE_ANON_KEY`) in `apps/web/.env`.\n2. Create a confirmed user: `POST 127.0.0.1:54321/auth/v1/admin/users`\n   (`apikey` + `Authorization: Bearer <service_role>`, body\n   `{email,password,email_confirm:true}`).\n3. Password-grant for the token: `POST\n   127.0.0.1:54321/auth/v1/token?grant_type=password` (`apikey: <anon>`).\n4. Call the API: `Authorization: Bearer <access_token>` against\n   `localhost:8008/v1` (e.g. `/accounts`, `/projects/provision`,\n   `/projects/:id/sessions`, `/p/<ext>/8000/...`).\n\nSee `tests/e2e/helpers/session-auth.ts` for the exact calls.\n\n### One local testing system\n\n- `pnpm test` is the only repository-level test command. It runs local REST and\n  CLI flows, SDK tests, PostgreSQL-backed suites (`db-suites`), runner unit\n  tests, route coverage, and worktree tests concurrently.\n- `pnpm test -- --id ACC-4` runs one flow. `--domain access` runs one domain.\n- `pnpm test -- --sdk-only` runs only `packages/sdk` tests.\n- `pnpm test -- --db-only [path-filter]` runs only the PostgreSQL-backed suites\n  (`integration-*.test.ts`, `*.integration.test.ts`, `tests/migration`), each\n  file against its own fresh migrated database. A skipped DB suite fails.\n- `pnpm test -- --browser-only` runs Playwright browser journeys. It starts the\n  deterministic local stack.\n- Browser runs use two Playwright workers, locally and in each CI shard.\n- `pnpm test -- --packages-only` runs every app/package test and publish check.\n- `pnpm test -- --full` adds browser journeys and every app/package test. It\n  starts the deterministic local stack.\n- `pnpm test -- --target-smoke` verifies the deployed staging API and gateway\n  SHA, then runs the tagged Playwright staging smoke. Release CI supplies the\n  staging credentials and `RELEASE_SOURCE_SHA`.\n- `pnpm test -- --target-full` verifies the same deployed SHA, then runs every\n  configured staging REST, CLI, and Playwright journey. The production release\n  gate uses this command and fails on any excluded API flow.\n- Browser and full modes reuse only a running API that proves the deterministic\n  test profile. Stop an ordinary development stack before either command.\n- Every root run writes lane and total timings to\n  `tests/test-results/local/benchmark-<timestamp>.json`.\n- Before merging into `dev`, run T0 and the tests that cover your change (the\n  testing philosophy above). On a Mac, suite-wide modes exit `2`.\n- Every Linux CI job runs on a free GitHub-hosted runner (`ubuntu-24.04`,\n  `ubuntu-24.04-arm`, `ubuntu-22.04`; 4 vCPU / 16 GB on this public repo) through\n  `runs-on: ${{ vars.CI_RUNNER_<tier> || '<label>' }}`. Setting a\n  `CI_RUNNER_<tier>` repository variable moves that tier to another pool\n  without a PR. Never default a job to a paid runner (Blacksmith, a GitHub\n  larger runner): Blacksmith billed ~$2.7k in September 2026 for minutes\n  GitHub gives this repo for free. `tests/unit/image-build-speed-workflow.test.ts`\n  enforces the free default.\n- `.github/workflows/tests.yml` holds the six contract lanes — `core-flows`,\n  `core-db`, `packages-api`, `packages-cli`, `packages-kortixd`,\n  `packages-rest` — one GitHub-hosted runner each (`CI_RUNNER_L`). `core-*`\n  split the core run (`--core-group=flows|db`); `packages-*` are slices of\n  package quality (`--packages-only --package-group=<g>`). The concurrency\n  settings in `tests/bin/package-quality.ts` must not be raised. Each lane is\n  the unchanged root command at the exact requested SHA. Do not add CI-only\n  test logic.\n- The contract lanes run nightly on `dev`, once when a person adds the `test`\n  label to a pull request into `dev`, on dispatch, and inside the release\n  suite (`tests-release.yml` calls `tests.yml`). A push to `dev` and the pull\n  request into `staging` run nothing. A red nightly run comments the failing\n  lanes and suspect commits on the commit, once per new set of failing lanes.\n- The release suite (`tests-release.yml`, on the release PR into `prod`) runs\n  the contract lanes, the deployed API smoke (the flows tagged `deployed`, one\n  or two per deployment seam) and the browser journeys against deployed\n  staging, in parallel. `full suite + quality gates` requires all of them and\n  every other check on the release commit. A browser spec exists only for a\n  money-path journey (sign-in, onboarding, session load and streaming, desktop\n  parity, billing, connectors, invites); everything else is a REST flow or\n  nothing. CI records a video of every browser journey in the run's\n  `tests-release-browser-shard-N` artifact. A failed release-suite run reruns\n  its failed jobs once by itself (`rerun-failed-once.yml`): a job that passes\n  on the rerun is a flake to fix or delete.\n- `tests/unit/sandbox-workflow.test.ts` fails when any workflow except the\n  label-gated `tests.yml` and `deploy-preview.yml` triggers on a pull request\n  into `dev`, and pins both label gates to the label-added event.\n- The deployed half of the release suite (`--target-api-full`,\n  `--target-browser-full`) waits up to 15 min for staging to serve\n  `RELEASE_SOURCE_SHA`, then fails when any `deployed` flow is excluded or any\n  configured Playwright journey fails.\n- The `preview` label is not part of the development flow. Adding it deploys a\n  Platinum self-host environment for the PR (~7 min), once, and runs no tests.\n  A push does not redeploy. Only `gh workflow run deploy-preview.yml -f\n  pr_number=<N>` runs `--target-full` against a preview (40–80 min). Its mechanics are\n  in the **contributing** skill (`references/preview-environments.md`). Run a\n  preview of your change on your worktree's local stack by default.\n\n### Product flow source of truth\n\n- `tests/spec/end-to-end.md` contains the natural-language contract and stable\n  flow IDs.\n- `tests/src/flows/*.flow.ts` implements the contracts through HTTP and real CLI\n  processes. Do not import API handlers.\n- Write each `ctx.step()` as a complete action and observable result. Cover\n  setup, authentication, action, read-back proof, negative paths, and cleanup.\n- Keep every flow's `meta.routes` synchronized with\n  `tests/spec/routes.generated.json`. Regenerate the manifest with\n  `bun run apps/api/scripts/dump-routes.ts` after route changes.\n- The local profile uses local Supabase, PostgreSQL, API, gateway, and bare Git\n  repositories. It excludes Stripe, cloud sandboxes, managed GitHub repositories,\n  and external email delivery explicitly.\n- Use Playwright only for browser-visible behavior. API-only assertions belong\n  in REST flows. SDK tests remain in `packages/sdk`.\n- Do not add another cross-cutting test harness, Makefile lane, contract suite,\n  Testcontainers suite, load suite, mutation suite, visual suite, accessibility\n  suite, or ad hoc smoke script under `tests/`.\n- Read `tests/README.md` and the repository `testing` skill before changing the\n  test system.\n\n### Release topology — dev, staging, prod\n\n- **`dev` = dev trunk.** It is the repo default branch and deploys to\n  `dev.kortix.com` / `dev-api.kortix.com`. Direct pushes are allowed; breaking or\n  incomplete development can live here while it is being shaken out.\n- **`staging` = release-candidate branch.** Nothing should land on staging unless\n  it is intended to be production-ready. Promote to Production merges the\n  `dev` HEAD into staging itself (the PR runs no check), deploys it, and opens\n  the release PR whose release suite is the one gate. Staging is the deploy\n  target that suite runs against, not a test stage. Staging\n  deploys to `staging.kortix.com` / `staging-api.kortix.com` and must use the\n  staging data plane, not dev or prod. The full promote-and-gate flow, the\n  fix-forward loop, and the prod release steps live in the **kortix-release**\n  skill — that is the one place this is written out in full.\n- Staging deploys must apply pending DB migrations against `STAGING_DATABASE_URL`\n  before the staging ECS Fargate rollout. If that secret is missing or points at\n  dev/prod, treat the deploy as broken; staging must never fall back to dev,\n  KE2E, or prod DBs.\n- **`prod` = production.** Production moves only through **Promote to Production**,\n  which uses `staging` as the source, opens a reviewed release PR into `prod`,\n  publishes the release artifacts, and rolls production after merge.\n- If a staging runtime check points at `dev.kortix.com` or\n  `dev-api.kortix.com`, treat that as a broken staging setup, not a passing\n  staging gate.\n\n### Driving the real UI (agent-browser)\n\n- **agent-browser is the browser for every agent task in this repo.** Use it\n  before chrome-devtools MCP, Playwright MCP, or any other built-in browser\n  tool. Playwright stays the engine of the committed browser test suite only.\n- The installed skill is a stub. Load the guide that matches the CLI version:\n  `agent-browser skills get core` (`--full` adds the command reference).\n- Use one named session per worktree:\n  `agent-browser session id --scope worktree --prefix <task>`, then pass\n  `--session <id>` on every command. Keep every `AGENT_BROWSER_*` variable the\n  same for all commands in a session. A per-command change relaunches the\n  browser and drops the open page.\n- Routes are auth-gated (`/dashboard`, `/projects/*` → redirect to `/auth`\n  unauthenticated); sign in first (seed a user as above, then log in via the\n  `/auth` form). On a PR preview, the magic link arrives in the preview's Mailpit API\n  (`<origin>/_mailpit/api/v1`)\n  (the **contributing** skill has the script).\n- Next.js dev compiles routes on first hit — first navigation to a cold route\n  can take 30–60s; warm it with `curl` or use a generous navigation timeout.\n- `agent-browser doctor` diagnoses launch and recording problems. Recording\n  needs ffmpeg with libvpx and libx264.\n\n### API lint gate\n\n- `pnpm --filter kortix-api lint` runs in the `Tests` packages lane. Its rules\n  are in `apps/api/eslint.config.mjs`: layered imports, no Drizzle in route\n  files, no `(c: any)`, no `process.env` outside `config.ts`, no\n  `console.*`, a `replica-local:` comment on every empty module-level\n  `Map`/`Set`, and no import into `projects/` from outside it except\n  `projects/index.ts` or `projects/surface.ts`. Background timers are guarded by\n  `apps/api/src/__tests__/unit-worker-scope-wiring.test.ts` instead.\n\n### API code conventions (`apps/api/src`)\n\n- **Routes register explicitly.** A route module exports\n  `register<Name>Routes()`. The module that mounts the router calls it right\n  before `app.route()` (`app.ts`, `accounts/index.ts`, `router/index.ts`).\n  Never register at import time. Call order is dispatch order.\n- **No service imports a route module.** Shared logic lives in a service file\n  next to the routes (for example `projects/session-open/`).\n- **Services take an `Actor`, never a Hono `Context`.** The route reads the\n  request through `middleware/` and `http-*.ts` helpers, builds the actor\n  (`middleware/actor.ts`, memoized per request), and calls the service with\n  plain values. Parse `Authorization: Bearer` with `shared/bearer-token.ts`.\n- **Every background loop lives in `workers/<name>-worker.ts`.** The worker\n  owns its timer, start/stop and `runWorkerTick` call. The tick stays an\n  exported service function. `bootstrap.ts` imports every loop from `workers/`.\n- **`iam/` reads the membership, group and role tables.** Use the read models\n  in `iam/membership-read.ts`, `iam/group-read.ts` and `iam/role-read.ts`.\n- **No function longer than 300 lines.** Split long functions into named steps.\n- **SQL trace:** `KORTIX_SQL_TRACE=<file>` appends every pool statement to the\n  file. Diff two traces to prove a refactor runs the same SQL.\n- `apps/api/eslint-suppressions.json` holds the violations that existed when\n  each rule was added. A new violation fails. A fixed one fails until you run\n  `pnpm --filter kortix-api lint:prune` and commit the smaller file. Never\n  absorb a new violation with `--suppress-all`.\n\n### Frontend type/lint gate\n\n- Typecheck `apps/web` with `pnpm --filter Kortix-Computer-Frontend typecheck`\n  (tsgo, incremental). Never run raw `tsc --noEmit` there. Measured on\n  2026-10-21 on a 24 GB Mac: raw `tsc` ran 817 s, held 3.18 GB, and was\n  killed before it finished. Cold `tsgo` takes 12.1 s at 5.0 GB peak. A warm\n  incremental run takes 1.1 s at 1.7 GB.\n- On 2026-10-21 the tsgo script reported 0 errors on `dev`. The bar is\n  relative: a branch adds no type error to the files it touches. Compare against `origin/dev` with the same command, not\n  against a number in this file.\n  The old ~1500 `TS2786` / `IntrinsicAttributes` noise from a React 19↔18\n  types mismatch (two copies of `@types/react` in one program — `packages/sdk`\n  had its own) is gone as of the Next 16 upgrade. If `TS2786` appears again,\n  treat it as a genuine duplicate-`@types/react` regression and investigate —\n  do not wave it through.\n- `npx eslint <files>` should be clean of errors. `eslint .` across the whole\n  app currently reports ~455 warnings, mostly `react-hooks/*` React Compiler\n  rules pending a dedicated audit — expected until that audit lands.\n\n### Frontend design standard — Jay/Kortix bar\n\n#### Canonical design references\n\n| Surface | Read before changing UI | Implemented source |\n|---|---|---|\n| Web (`apps/web`) | `.agents/skills/kortix-brand/SKILL.md` (the router: values, voice, claims, decision history), then `.agents/skills/kortix-design-system/SKILL.md` for components | `apps/web/src/app/globals.css` tokens (generated from `.agents/skills/kortix-brand/references/visual/visual-system.json`), `packages/ui/src/primitives/` (apps/web imports each one as `@kortix/ui/<name>`; apps/web has no re-export shims, `tests/unit/web-no-ui-shims.test.ts` enforces it), the live `/design-system` route |\n| `@kortix/ui` hosts (Kortix Apps, `apps/white-label/reskin`, `apps/white-label/lumen`, `apps/white-label/team-console`, SDK customer apps) | `packages/ui/skills/kortix-ui/SKILL.md` (setup, which component, white-label theming, the `SessionThread` chat), then the web row for Kortix-owned surfaces | `packages/ui/src/` (components, `session/` for the `SessionThread` chat, `styles/tokens.css` + `theme.css`, prebuilt `dist/styles.css`); white-label = CSS variable overrides, never a forked component |\n| Desktop (Electron shell) | The web row — the shell renders `apps/web` — plus `apps/desktop-electron/README.md` for the shell boundary, then the parity gate below | `apps/web` rendered by the shell; native window geometry in the shell's titlebar classes |\n| Mobile (`apps/mobile`) | `apps/mobile/design.md` for screens, `apps/mobile/AGENTS.md` for primitives | `apps/mobile/global.css` colors, `apps/mobile/components/ui/`; stock Tailwind spacing for touch targets |\n\n#### Desktop parity is a UI gate\n\nThe Electron app loads `apps/web`. Keep product components, routes, tokens,\nand data behavior shared. Put native window geometry in the shell's explicit\ntitlebar classes. Never apply titlebar height or drag rules to generic ARIA\nroles, all sidebars, or page content.\n\nFor every shared UI change, verify the affected controls on web and in Electron\nbefore handoff. Check the outgoing request or route and the visible result.\nCheck both themes, the minimum supported window (720 × 480), sidebar collapse,\nfullscreen overlays, and browser zoom. Window controls must not overlap app\ncontrols. Lists must not overlap or clip their last row. Keyboard focus and\nscrolling must remain usable.\n\nAdd regressions to the existing Playwright journeys. The desktop journey runs\nin `pnpm test -- --browser-only` and supports the actual Electron shell:\n`E2E_DESKTOP_NATIVE=1 E2E_GREP='27 — desktop parity' pnpm test -- --browser-only`.\nRun the native journey when changing shell CSS, navigation, settings, agents,\nor connectors. A desktop user-agent test does not prove native hit testing.\nReport any unverified desktop behavior explicitly. Do not promise that tests\nprevent every future regression.\n\nWhen touching any visual surface in `apps/web`, treat brand fit as a release\ngate, not polish:\n\n- Read `.agents/skills/kortix-brand/SKILL.md` before writing the first\n  `className` or the first user-facing string. It routes you to the value law:\n  the complete allowlist of every color, spacing step, type rung, radius,\n  elevation, and duration you may use. Note `--spacing: 0.23rem` — Tailwind's\n  scale is 8% tighter here, so a 16px mockup padding is `p-4`, never\n  `p-[16px]`. Run `.agents/skills/kortix-brand/scripts/audit.sh` over your\n  changed paths before opening the PR; it must be clean on files you touched.\n  `references/visual/visual-system.json` is the only file with values: edit it,\n  then run `scripts/generate-tokens.ts`. Never edit a generated region of\n  `globals.css` by hand.\n- Read `.agents/skills/kortix-design-system/SKILL.md` next and compose existing\n  primitives from `@kortix/ui/*` before inventing local chrome.\n- Match the current Jay Suthar / Kortix product aesthetic: calm neutral surfaces,\n  dense-but-legible UI, black/white plus one earned accent, token-driven spacing,\n  and no decorative color, glow, or one-off rounded boxes.\n- Use recent product surfaces as references before editing: `/design-system`,\n  `packages/ui/src/workspace/layout/project-home.tsx`,\n  `apps/web/src/components/ui/wallpaper-background.tsx`, and the account/IAM\n  screens called out by the design-system skill.\n- Verify visual work in the browser and include the exact lint/typecheck commands\n  you ran in the PR. If it does not look native beside Jay-authored UI, keep\n  iterating before shipping.\n",".cursor/mcp.json":"# Model Context Protocol (MCP) Configuration\n\nPath: `.cursor/mcp.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/kortix-ai/suna/HEAD/.cursor/mcp.json)",".claude-plugin/marketplace.json":"# Claude Plugin Marketplace Catalog\n\nPath: `.claude-plugin/marketplace.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/kortix-ai/suna/HEAD/.claude-plugin/marketplace.json)",".claude-plugin/plugin.json":"# claude-plugin Plugin Manifest\n\nPath: `.claude-plugin/plugin.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/kortix-ai/suna/HEAD/.claude-plugin/plugin.json)"},"items":[{"name":"AGENTS.md","path":"AGENTS.md","rawUrl":"https://raw.githubusercontent.com/kortix-ai/suna/HEAD/AGENTS.md","title":"AI Agent Protocol & Instructions","category":"root-instruction","format":"markdown","content":"# Kortix project\n\n## What \"ownership\" means\n\nThe words \"can you own this?\", \"are you on it?\", and \"can you take care of this?\"\nall mean the same thing: you are 100% responsible. The person who handed it to\nyou must be able to walk completely away, come back a week later, and find it\ndone properly — because you cared about every edge and corner.\n\n- it's not done if it's not implemented\n- it's not done if the implementation is ugly\n- it's not done if it's not documented\n- it's not done if users can't discover it\n- it's not done if you can't market it\n\nOwning something means owning it end to end. The whole arc from \"we have a\nproblem\" to \"nobody has to think about this again.\" Not just the code change in\nthe middle, the whole thing. If someone hands you something and still has to\ntrack whether it actually got solved, you didn't own it.\n\nHere's what that actually looks like in practice.\n\n**Start with the problem, not the solution.** A lot of the time you'll already\nhave a fix in your head before you've understood what's broken. \"We need to move\nfrom X to Y\" isn't a problem, that's a solution you've pre-committed to. The real\nproblem is probably \"it's slow\", \"it's flaky\", \"it breaks for this customer\".\nName that first. Then ask what else could solve it, what the tradeoffs are, and\nwhich option actually wins.\n\n**Then pressure-test it before you build:**\n\n- **Edge cases.** Which exist, which matter, which we can safely ignore.\n- **Failures.** Networks fail, that's a given. Retry? How many times, for how\n  long?\n- **Data.** How much, does it need migrating or cleaning, how do you get real\n  data to test against, and what are you assuming about its shape that you\n  haven't actually verified?\n- **Testing.** How will you know it's correct? Are automated tests enough or do\n  you need to poke at it by hand? Is the result something you can see in a\n  screenshot or a video?\n- **The bigger picture.** How does this get announced, how does it fit the\n  roadmap, can you even picture it shipped? If something there bothers you, push\n  back. Ask.\n\n**Then actually do it**, with precision, care, urgency and calm all at once. No\nhalf-assing. The bar before you merge: am I proud of this? Would I put it in\nfront of Steve Jobs and walk him through what I built, the constraints, the\ntradeoffs?\n\n**And then prove it works.** Not \"the tests pass\", prove it. In 99% of cases you\ncan confirm it yourself: run it, ask an agent to walk through the scenarios, poke\nat the data before and after, take a screenshot, make a demo. Are you actually\nsure it solves the problem you started with?\n\n**Then make sure it lands in production and works in production**, which is not\nthe same as merged. Did it deploy? Did the deploy quietly fail? Is there a flag\nto flip, and does the flag work? Can you use the thing in prod right now and\nconfirm it's really there?\n\n**And then close the loop with everyone it touches:**\n\n- **The team.** If it's a new feature, a new convention, or a tricky thing people\n  should know about, tell them. Don't underestimate peripheral vision. You\n  knowing that someone changed Z yesterday can save someone else three hours of\n  debugging tomorrow when a bug report about Z comes in.\n- **Customers.** Whoever reported it, whoever's blocked, let them know it's\n  fixed.\n- **The world.** If it's worth announcing, announce it.\n- **Future you.** Are there follow-ups? Should you check the logs in a week to\n  make sure it's still healthy?\n\nBut that's how we build a product in a small team. We don't have PMs, we don't\nhave a QA department. We're small, but we're great, and we can do all of that.\n\nAnd it's always okay to ask for help, it's okay to ask questions, it's okay to\nredo things and triple-check. What's not okay is to quietly assume someone else\nwill catch the parts you didn't think about.\n\n### The bar: autonomy, agency, ownership\n\nThis is what I require from the agent I work with. In my own words:\n\nEither you are performing or you are not. Either you are taking on high\nownership, are high agency and pushing without me having to micromanage you, or\nyou are not.\n\nEspecially in today's age you are limited by great talent more than ever. Because\nwe can all AGI Max, you have a single chokepoint on good judgement.\n\nEvery person that comes on the team has to have the capability to truly own\nsomething. If you have built products, you develop ownership because you owned\nsomething — whether it worked out or not — for a prolonged amount of time. You\ndevelop true agency.\n\nI hire you because I expect that if I am able to walk out of the room after\ngiving you a high level thing and come back, it's going to be done good, ideally\nbetter than I would've done it.\n\nMost people are shit at their jobs, some are decent/good, but only people who are\nexceptional should be at Kortix.\n\nBeing exceptional on paper is simple — it's a combination of Ownership, Agency\nand actual Merit/Skill. It's hard, because you have to not only be very smart but\nalso crazy driven to push like a motherfucker and want to feel every edge and\ncorner to make sure the output is good.\n\n## Skills live in `.agents/skills/`\n\nEvery repo skill is a directory in `.agents/skills/<name>/`. `.claude/skills/<name>` is a\nsymlink to it. Add a skill in `.agents/skills/`, then add the symlink. Third-party skills come\nfrom `npx skills add` and are pinned in `skills-lock.json`. The PR procedure is the\n**contributing** skill; the PR title and body are the **create-pr** skill. Browser work is the **agent-browser** skill.\n\nThe repository has no `docs/` tree. Put a runbook or spec in the skill that owns the\nsurface (`.agents/skills/<name>/references/`). Put an incident rule in the learnings\nledger. Put design detail and RCAs in the PR body. Never cite a repo path that does\nnot exist. The pre-commit hook and `tests/unit/no-docs-tree.test.ts` reject a new\n`docs/` file and any citation of one.\n\n## Ponytail is on by default\n\nEvery code change runs through the **ponytail** skill at level `full`. Load it before you\nwrite, fix, refactor, or review code, and before you add a dependency. Level switch:\n`/ponytail lite|full|ultra`. Off: \"stop ponytail\". **ponytail-review** audits a diff for\nover-engineering, **ponytail-audit** audits the whole repo, and **ponytail-debt** lists\nevery `ponytail:` shortcut comment. Ponytail cuts code, never the verification,\ndocumentation, or ownership bar in this file. The skills come from\n`DietrichGebert/ponytail`, pinned to `v4.10.0` in `skills-lock.json`.\n\n## Learnings: the episodic ledger in the `learnings` skill\n\n`.agents/skills/learnings/` is the append-only, timestamped ledger of rules paid\nfor with real downtime. `MEMORY.md` indexes it newest first, and each entry is\none file in `entries/`: the rule, the incident that taught it, and the\nautomation that enforces it. Search it before writing or reviewing a DB\nmigration, touching deploy/release workflows, planning a promote, or\nresponding to a prod incident. After resolving ANY incident or near-miss,\nrecord a new entry in the same session with `scripts/new-entry.sh`. An incident\nthat leaves no entry behind is not finished.\n\n## NEVER write customer data or PII into anything we publish or commit\n\nThis is a hard rule. No exceptions, no \"just this once\", no \"it's only\ninternal\".\n\n**Never write any of these:**\n\n- customer or company names, and the names of their people;\n- email addresses, phone numbers, or other personal data;\n- real account, project, session, user, or sandbox IDs from prod, staging, or\n  a customer deployment;\n- customer repository names, hostnames, or URLs that contain any of the above;\n- customer prompts, messages, files, or log lines;\n- screenshots of a real customer workspace.\n\n**Never write them into:**\n\n- commits, commit messages, or branch names;\n- PR titles, PR bodies, PR comments, or review comments;\n- issues;\n- code comments, test names, or test fixtures;\n- docs, runbooks, skills, `AGENTS.md`, changelogs, or release notes;\n- artifacts, Slack posts, or any public or team-visible text.\n\n**Write the class instead:** \"a customer reported\", \"an enterprise\nworkspace\", \"a prod session\", `<session_id>`. Build test data from synthetic\nvalues. Evidence that contains real data stays local: the gitignored\n`output/` folder, your scratchpad, or the private agent memory. It never goes\ninto a tracked file.\n\n**If you find customer data** in the tree or in a PR, remove it in the same\nbranch and say so. Do not rewrite history on `dev`. Report the SHA to the user\ninstead.\n\n**A guard enforces this on every commit and push.**\n`scripts/check-blocked-terms.sh` runs from `.githooks/pre-commit`,\n`.githooks/commit-msg`, and `.githooks/pre-push`. It refuses any added line,\ncommit message, or pushed branch name that contains a blocked term. Matching is\ncase-insensitive and whole-word. The list is itself customer data, so it lives\nencrypted in `apps/api/.env` as `BLOCKED_COMMIT_TERMS`, comma-separated.\n\n- Add a customer the day they sign: `dotenvx set BLOCKED_COMMIT_TERMS\n  \"<existing>,<new>\" -f apps/api/.env`. Read the current value first with\n  `dotenvx get BLOCKED_COMMIT_TERMS -f apps/api/.env`.\n- In a worktree the guard decrypts with the primary checkout's\n  `apps/api/.env.keys`. Without a key it warns and allows.\n- Deleting a line that contains a term is always allowed.\n- Never bypass the guard with `--no-verify`. If it fires, remove the term.\n- The hooks do not see PR titles, PR bodies, or comments. Those stay your\n  responsibility.\n\n## How to communicate: precise, technically accurate, no fluff\n\nWrite every response — chat, PR text, commit messages, code comments, docs — in\nthe spirit of **ASD-STE100 (Simplified Technical English)**. The goal is maximum\ntechnical precision with zero filler. Apply these rules:\n\n- **State facts, not vibes.** Every claim is specific and verifiable: name the\n  file, function, route, flag, SHA, status code, or number. No \"should work\",\n  \"probably\", \"a bunch of\", \"various\", \"seems fine\" — say what is true and how you\n  know, or say you do not know it yet.\n- **One idea per sentence.** Keep sentences short (aim ≤ 20 words) and each one\n  carries a single instruction or fact. Split compound thoughts instead of\n  chaining clauses.\n- **Active voice, present tense, direct.** \"The gate rejects the request\",\n  not \"the request may end up being rejected\". Give the instruction; do not\n  soften it.\n- **One term per concept.** Use the same word for the same thing every time —\n  do not alternate \"session\"/\"run\"/\"task\" for one concept. Match the codebase's\n  existing names exactly (`session_id`, not \"session ID / run id\").\n- **No filler, no hedging, no praise.** Cut \"basically\", \"just\", \"simply\",\n  \"I think\", \"great question\", \"as we know\", and marketing adjectives. Lead with\n  the answer; drop the throat-clearing.\n- **Quantify.** Prefer exact values over adjectives: \"up to ~9 min\", \"returns\n  `402`\", \"3 of 7 flows\", not \"slow\", \"an error\", \"most\".\n- **Show the evidence.** When you assert a behavior, cite the command you ran and\n  the real output. Distinguish verified fact from assumption explicitly.\n- **Structure over prose.** Use numbered/bulleted lists for steps, findings, and\n  status. Reserve paragraphs for genuine explanation, and keep them tight.\n- **Say the unknown plainly.** If something is unverified, blocked, or risky,\n  state it in one line — what, why, and what would resolve it — instead of\n  burying or omitting it.\n\nThis standard governs how you talk. It does not override the technical rules\nbelow; it is how you report on them.\n\n## First, at session start: which canonical branch are you in?\n\nEvery change belongs to **one canonical branch** — the branch for whatever is\nbeing worked on. One canonical branch, one worktree. Establish which one you are\nin before any non-trivial change. **Do not create a branch by reflex.**\n\n1. **Join the canonical branch that already exists** for this work. List them\n   with `git worktree list` and `git branch -r`. If the work continues, extends,\n   fixes, or cleans up something already in flight, it belongs on that branch.\n   Ask the user which branch when it is not obvious.\n2. **Start a new canonical branch** only when the work is genuinely a new thing.\n   Give it its own worktree: `pnpm worktree create --name <slug> --yes\n   --no-start`, then do all edits and runs under `../suna-<slug>`. Add `--db`\n   when the work needs migrations, destructive data work, or schema drift: it\n   copies the primary local database into the worktree's own database\n   (`kortix_wt_<slug>`, ~5 s, no extra containers). `--full-supabase` (a whole\n   Supabase stack, ~0.75 GB RAM) is only for independent auth/storage config.\n   See the **worktree** skill.\n3. **Never switch a worktree you did not create.** It belongs to one session and\n   its canonical branch. The pre-commit hook refuses a commit on any other branch\n   there (`scripts/check-worktree-branch.sh`). A throwaway probe branch goes in a\n   private `git worktree add` in your scratchpad.\n4. **The primary checkout** (`pnpm dev`, web `3000` / api `8008`) is for running\n   and investigating. Do not park feature work there.\n5. **Run `pnpm test` only from a worktree that has `.kortix-worktree.json`.** The\n   runner reads its ports from that marker. A bare `git worktree add` has none,\n   so its test stack takes the primary ports and talks to another session's\n   `pnpm dev` (2026-10-13: 156 flows failed with that stack's 503s).\n6. **Never kill a process you did not start.** Many sessions share this machine.\n   Kill by PID from your own process tree, or by a pattern that contains your\n   own worktree or scratchpad path. Never by a bare pattern such as\n   `pkill -f \"bun test\"`, and never in a loop. Need CPU? Lower your own\n   concurrency (`KE2E_API_WORKERS`, `KORTIX_DB_SUITE_WORKERS`) or ask the user.\n   2026-10-13: a guard loop that SIGTERMed every other session's `pnpm test`,\n   `tsc` and `vitest` every 20 s made 259 kills, and every suite on the machine\n   died with exit 143 and no failing test.\n\n**Pack more into one branch, not less.** A follow-up fix, a rename cleanup, a\nstale-reference sweep, and the change that caused them all belong on the same\nbranch and land together. Splitting one objective across several branches is how\na half-finished cutover reaches `dev` in pieces — each piece green alone, the\nwhole thing broken.\n\nSub-branches are allowed. Agents may cut working branches off the canonical\nbranch and merge back into it. **A sub-branch never opens a PR against `dev`.**\nOnly the canonical branch does.\n\nCarve-outs where you just proceed: read-only investigation and questions, and\ntrivial single-file typo/comment fixes on the current branch.\n\n## Default delivery: verify in your box, self-merge to `dev`, verify on dev\n\n`dev` is the dev trunk. A merge does not deploy: dev deploys only on a deliberate\ndispatch, but **merging to `dev` still lands your change in everyone's next\ndeploy and next `dev` checkout.** It is not a save point.\n\n### Testing philosophy: one gate at Promote to Production, everything else fast\n\nTesting reset, 2026-10-16. Latency is the core concern. Development must be\nfast enough to push and break things on `dev`. The full suite runs once per\nrelease, at Promote to Production, and nowhere else. The rules:\n\n1. **Never run a full suite during development, and never a whole package's\n   tests.** No multi-minute run on your box, on a PR into `dev`, or on a push\n   to `dev`. Verify only what you changed: T0 for the packages you touched,\n   the test files that cover the change, named one by one\n   (`pnpm --filter <pkg> test <file>`), and the change itself through its real\n   surface. Running all 2,129 `apps/web` tests to check one component is the\n   anti-pattern this rule exists for.\n2. **One gate: the release pull request into `prod`.** Promote to Production\n   runs the whole suite there, once, and every check must be green. It is the\n   only check that blocks anything.\n3. **Test at the boundaries users touch:** the HTTP API, the CLI, the\n   `@kortix/sdk` public surface, DB migrations, and a few browser journeys on\n   the money path. Never test internal calls or source text.\n4. **Run each suite once per tree.** A tree that ran is not run again.\n5. **A red suite is worse than none.** A test that fails twice without a bug is\n   fixed or deleted the same day. Do not park it in quarantine.\n6. **Delete on red.** A unit test that goes red on a refactor with no bug is\n   deleted, not updated. Keep unit tests only for logic that is hard to get\n   right: money and credits, parsers, the streaming protocol, permission\n   decisions, crypto and secrets.\n7. **Small, isolated tests.** Every test file runs standalone in seconds. When\n   the test that covers your change takes more than ~10 s, or drags in setup\n   that has nothing to do with it, split it into a smaller isolated test as\n   part of the same change. Fake the clock; never sleep on real time.\n\n| Tier | Contents | Runs |\n|---|---|---|\n| T0 static | `tsc`, lint, architecture checks, migration lint | anytime, seconds per package |\n| Targeted | the one test file or flow that covers your change | on your box, seconds |\n| Release suite | contract lanes (flows, DB, packages) on the release tree, the deployed smoke and the browser journeys against staging, CI, CodeQL, scanners | once per release, on the pull request into `prod` |\n\n| Where | What runs | Blocks? |\n|---|---|---|\n| Your box | T0 for the packages you touched, the targeted test, and the real surface: the route with curl, the CLI process, the page with agent-browser | your judgment |\n| Pull request into `dev` | nothing, unless a person adds `test` or `preview` | no |\n| Push to `dev` | only the seconds-long guards: `secret-scan`, `secrets-guard`, and path-gated `DB Migrations` / `i18n-catalogs` / `Terraform Apply Global` / `deploy-api-router-dev` | no |\n| Nightly on `dev` | `Tests` `core` and `packages` lanes, no browser: a red night comments the failures on the commit, so they are ready to fix before a promote | no |\n| Pull request into `staging` | nothing: Promote to Production opens and merges it | no |\n| Pull request into `prod` | the release suite, once (`full suite + quality gates`) | **yes, the only gate** |\n\n**A Mac never runs a suite.** On macOS `pnpm test` refuses a suite-wide run\n(plain, `--full`, `--packages-only`, `--browser-only`, `--db-only` with no path\nfilter) and exits `2`. 2026-10-16: five agent sessions ran the suite at once on\none laptop (load average 112, swap full). Run one flow\n(`pnpm test -- --id <ID>`), one DB suite path, or one package's test file.\n`KORTIX_FULL_TEST=1` overrides only when a person asks. Every package `test`\nscript refuses the same way: `pnpm --filter <pkg> test` with no file exits `2`\n(`scripts/test-guard.mjs`). Pass the file(s), `pnpm --filter <pkg> test <file>`,\nor a name filter after `--`: `pnpm --filter <pkg> test -- -t <name>`. CI and\nLinux run the full suite.\n\nNo hook or gate requires a test run before a push or a merge into `dev`. Write\nin the PR body what you ran and why. A diff that no test reads (docs, a skill,\na standalone script) needs no run. When the nightly run or the release suite\nturns red on your change, you fix forward on `dev` the same day.\n\n1. Work on the canonical branch in its worktree. Commit as often as you want.\n2. Verify in your box, with real inputs and outputs. Run T0 and the tests that\n   cover the change (see above). Start the worktree's stack\n   (`pnpm worktree start <slug>`) and drive the changed behavior through it: the\n   HTTP route, the real CLI process, or the page with agent-browser. Nothing\n   before the release suite at Promote to Production catches what you skip.\n3. Open the PR against `dev` and follow the **contributing** skill: it attaches\n   the demo video you recorded against your local stack. Write the title and\n   body with the **create-pr** skill: English only, for a human reviewer who\n   decides \"merge or iterate\" without reading the diff. Do not add `test` or `preview` unless you need that one explicit run.\n4. Merge `dev` into the canonical branch daily. A branch that diverges for weeks\n   detonates on merge exactly like a 1,500-line PR does.\n5. **Self-merge to `dev` when the change is verified. Do not wait for the\n   user's approval.** Speed matters: a verified change that sits unmerged is\n   waste. Verified means all of these are true:\n   - the relevant local checks ran with real inputs and outputs (rule 2), and\n     they passed;\n   - the PR is mergeable (no conflict);\n   - rule 6 holds when the change touches a client-facing runtime contract.\n   A failing check blocks the merge until you fix it or state why it is\n   unrelated (for example, the same test fails on `dev`). Squash-merge\n   (`gh pr merge <pr> --squash`), then finish rules 7 and 8. A merge is not\n   the end of the work: dev verification is still yours.\n   The only machine-enforced rule on `dev` and `staging` is that every change\n   arrives through a pull request — no required approvals, no required status\n   checks, no bypass actors. The bar is what you verified.\n   **The release gates do not change.** Merging into `staging` or `prod`,\n   running Promote to Production, and moving the `:stable` tag each need the\n   user's explicit approval (the **kortix-release** skill).\n6. **A change to a client-facing runtime contract** — the `@kortix/sdk` public\n   surface, session/thread transport, the streaming protocol — merges only after\n   the whole objective ran through a real session on your local stack, and runs\n   again on dev after the merge (rule 8). Green tests are not the bar. Someone\n   used it.\n7. After the merge, deploy dev yourself: `gh workflow run deploy-dev.yml\n   --repo kortix-ai/suna -f surface=changed` (`all` or `frontend` force a\n   rebuild). Deploys queue, they never cancel. Wait for the run to finish.\n   `/health` on every changed surface must serve the merge SHA: a successful\n   `/health` response alone is not deployment proof. The run comments \"Live on\n   dev\" on the merged pull request, and \"Not live on dev yet\" names the\n   surface that failed. The surfaces and their checks are in\n   `.github/workflows/deploy-dev.yml`. The merge push runs no suite. The\n   nightly `Tests` run on `dev` comments the failing lanes and every commit\n   since the last green run, once per new set of failing lanes. The author\n   whose commit broke `dev` fixes forward the same day. If `dev` is still red\n   1 hour after the comment, anyone may revert the culprit PR. The release\n   suite at Promote to Production runs the same lanes and blocks the release.\n8. Re-run the user-visible behavior against `https://dev.kortix.com` and/or\n   `https://dev-api.kortix.com`. Prefer the real Kortix CLI configured for the\n   dev API for CLI/project/session flows, and direct authenticated HTTP calls for\n   API contracts. For web behavior, drive the deployed UI and assert its network\n   request plus visible result.\n\nLocal verification and dev verification are both required. A local pass does\nnot replace dev, and a dev smoke test does not replace focused local tests.\nRecord the branch, PR, local commands and output, merge SHA, deploy run,\ndeployed SHA evidence, and the exact dev command or interaction in the final\nresponse.\n\n## Architecture: `@kortix/sdk` is the source of truth\n\n`@kortix/sdk` is the **single source of truth** for everything that talks to the\nKortix backend — projects, accounts, sessions, files, secrets, triggers, the\nsession runtime, the runtime REST client, SSE streaming, model state,\nand auth-token plumbing. The apps\n(`apps/web`, `apps/white-label/*`, `apps/mobile`) are **thin consumers**. Treat\nthese as standing rules whenever you touch the data/runtime layer:\n\n> **Editing `packages/sdk` itself? Load the **sdk** skill (the rules) first.** It is a\n> **published npm package** with its own hard rules that have no analogue\n> elsewhere in this repo: **TDD is mandatory** (failing test first, run it, watch\n> it fail, then implement — and every turn ends with the gates run, the real\n> output pasted, and an explicit shippable YES/NO/NOT YET); exported names\n> (including *types*) are a public API contract and renaming one is a breaking\n> change; the `version` field is inert and must never be bumped by hand; adding\n> an export requires three synchronized edits; and the framework-free core is\n> enforced by a static import-graph tripwire.\n\n- **Logic lives in the SDK, never in a host.** No raw `fetch` to the Kortix API,\n  no `@opencode-ai/sdk` imports, no transport / runtime / data-state code written\n  in app code. New data or runtime behavior is added to the SDK and exposed\n  through its public surface — not hand-rolled or duplicated in a host. If you\n  need something the SDK doesn't expose, add it to the SDK.\n- **One client per host.** Create it once via `createKortix({ backendUrl,\n  getToken })` and read everything through `@kortix/sdk` + `@kortix/sdk/react`.\n  Auth is just `getToken` — an API key / PAT for programmatic use, or a Supabase\n  JWT for the logged-in web app. Hosts never instantiate a second client.\n- **A whole session is one hook.** `useSession(projectId, sessionId)` owns the\n  entire runtime lifecycle — `/start`, the sandbox switch, the live SSE stream,\n  readiness seeding, immutable runtime identity, the native conversation id,\n  and message sync. Hosts don't\n  hand-roll the mount, drive a server-store \"switch\", or mount a separate event\n  provider.\n- **Session-scoped + provider-agnostic.** The public API is session-scoped\n  (`kortix.session(pid, sid).health() / .previewUrl() / .restart() / …`).\n  The sandbox provider and the harness are server-side concerns. Host code\n  must not implement a second transport.\n- **Build on the Kortix contract, not on OpenCode.** Every new session runs\n  pi inside kortixd. OpenCode is deprecated (2026-10-23): there is no\n  `pi_harness` flag, `runtime:` in `kortix.yaml` selects nothing, and OpenCode\n  serves only sessions that started on it. It will be deleted. Code that exists\n  only for OpenCode carries `@deprecated`: never call it from new code, never\n  add a feature to it, and change it only to fix a bug that breaks a running\n  OpenCode session. pi calls models only through the LLM gateway. pi serves\n  the daemon routes (`/kortix/runtime/*`), the transcript\n  (`kortix.transcript.v1`, `packages/api-contract/src/transcript.ts`) and the\n  events. New code reads those, never an OpenCode route, type, file or\n  process. A feature pi lacks is a capability, not a harness check:\n  `GET /kortix/health` lists `capabilities` (`RUNTIME_CAPABILITIES` in\n  `packages/api-contract/src/runtime-relay.ts`), and a client gates the\n  control with `runtimeSupports`. OpenCode lists all eleven (`session.steer`\n  only on OpenCode 1.18.15 or later); pi lists `session.subagents`,\n  `session.compact`, `session.commands` and `session.steer`. pi does not\n  serve rewind, MCP servers, the todo list, shell turns, part edits or\n  `session.attach`.\n  The harness rules and the pi gap list are in\n  `apps/kortix-sandbox-agent-server/src/harness/README.md`.\n- **`apps/web` data modules are shims.** Files such as\n  `apps/web/src/ui/index.ts`, `apps/web/src/lib/iam-client.ts`, and\n  `apps/web/src/hooks/admin/use-*.ts` are thin re-exports\n  (`export * from '@kortix/sdk/...'`).\n  Keep them as shims; put the real logic in the SDK. When a merge conflict lands\n  on one of these, **keep the shim (`--ours`) and port any new host-side logic\n  into the SDK** — do not revert to a host-local implementation.\n- **Docs are the spec.** `apps/web/content/docs/sdk/*` and\n  `packages/sdk/README.md` describe the intended surface. Keep them current with\n  the SDK, and flag legacy/deprecated surfaces in-doc rather than documenting them\n  as current.\n\n## You CAN run and verify everything end-to-end. Do it.\n\nThis repo ships a **complete, runnable local stack with live cloud sandboxes**.\nDo not claim you \"can't verify from here\" or hand back unverified work — you\nhave everything needed to run the app, hit the real API, provision real\nPlatinum sandboxes, drive the real UI in a browser, and assert behavior. Use it.\n\n### Required verification standard — real inputs, real outputs\n\nFor every behavior change, assume **100% autonomy** to verify the user-visible\ncontract before handing the work back. Do not stop at typechecks, unit tests, or\nmocked internals when a real surface exists.\n\n- **API changes:** exercise the actual HTTP route with real request payloads\n  (`curl`, `bun fetch`, or the `ke2e` runner against a running API). Assert the\n  status code and exact response fields that prove the behavior. For writes,\n  also assert the persisted/read-back state or resulting repo/file output.\n- **CLI changes:** run the real CLI command as a process from bash, with the\n  same flags and stdin a user or agent would use. Assert exit code, stdout,\n  stderr, and any files/API calls/commits it should create. Do not rely only on\n  importing command functions.\n- **Web changes:** drive the real page with **agent-browser** (the primary\n  browser; `agent-browser skills get core` loads its guide). Click/type/toggle\n  the actual controls, observe the network request (`agent-browser network`),\n  and assert the visible UI state plus the outgoing payload. Record the flow\n  (`agent-browser record start`) for the PR's demo video. Screenshots and video\n  are evidence, but assertions on DOM and network data are required.\n- **Cross-surface features:** verify each exposed surface independently. If the\n  same feature ships on API + CLI + web + mobile, each gets its own black-box\n  assertion for the inputs users can make and the outputs they receive.\n- **Default/negative paths count:** when changing defaults or removing implicit\n  behavior, assert both the new default and the explicit opt-in/alternate path.\n- **No silent gaps:** if a surface cannot be fully exercised in the current\n  turn, say exactly which input/output remains unverified and why. Otherwise\n  keep going until the real surface is verified.\n- **Final response format:** when work is finished, answer per the **How to\n  communicate** standard at the top of this file — numbered/bulleted, no fluff.\n  Include exactly what changed, what was verified (with the command + output),\n  what remains unverified or risky, and what the user should test next. Do not\n  bury the actionable testing path in a paragraph.\n\n### The stack (already wired)\n\n- **Web** — Next.js dev server on `http://localhost:3000`.\n- **API** — Bun server on `http://localhost:8008/v1` (`/health` returns JSON).\n- **Supabase** — local, on `http://127.0.0.1:54321` (Docker).\n- **Sandboxes** — REAL cloud sandboxes on Platinum, the reference provider\n  (`PLATINUM_API_KEY` in `apps/api/.env` / `.env.local`). Daytona is a legacy\n  fallback with removal scheduled and no support guarantee; E2B support is\n  discontinued. Each project\n  session gets its own sandbox; `session_id == sandbox_id`. The sandbox daemon is\n  reached through `http://localhost:8008/v1/p/<external_id>/8000/...`.\n  The session runtime answers on the same proxy, under `/kortix/runtime/*`,\n  on both harnesses.\n- **Tunnel** — `scripts/dev-local.sh` (`pnpm dev`) auto-starts a cloudflared\n  quick tunnel so cloud sandboxes can call back to the local API (`KORTIX_URL`).\n\nBring it up with `pnpm dev` from `suna/` (it loads `apps/api/.env` +\n`apps/web/.env`, starts Supabase, the API, the web app, and the tunnel). Check\nwhat's already running before starting a duplicate: `curl -s\nlocalhost:8008/v1/health`, `lsof -iTCP:3000 -sTCP:LISTEN`.\n\n> **Secrets are dotenvx-encrypted (mandatory).** `apps/api/.env` (+ `.env.dev`)\n> are committed as ciphertext (`KEY=encrypted:…`); keys live in Dotenv Armor.\n> **Never write a plaintext secret into a tracked file or commit** — add/change\n> values only via `dotenvx set KEY value -f apps/api/.env` (then commit), read\n> with `dotenvx get`, and machine-local overrides go in the gitignored\n> `apps/api/.env.local`. If the user pastes a key, store it with `dotenvx set`,\n> never paste it raw. A pre-commit hook + GitHub push protection enforce this —\n> don't bypass them. Full procedure: the **dotenvx-secrets** skill.\n\n### Authenticating to the live API (for scripts/tests)\n\nMint a real JWT against local Supabase, then call the API with it:\n\n1. `SUPABASE_SERVICE_ROLE_KEY` lives in `apps/api/.env`; the anon key\n   (`NEXT_PUBLIC_SUPABASE_ANON_KEY`) in `apps/web/.env`.\n2. Create a confirmed user: `POST 127.0.0.1:54321/auth/v1/admin/users`\n   (`apikey` + `Authorization: Bearer <service_role>`, body\n   `{email,password,email_confirm:true}`).\n3. Password-grant for the token: `POST\n   127.0.0.1:54321/auth/v1/token?grant_type=password` (`apikey: <anon>`).\n4. Call the API: `Authorization: Bearer <access_token>` against\n   `localhost:8008/v1` (e.g. `/accounts`, `/projects/provision`,\n   `/projects/:id/sessions`, `/p/<ext>/8000/...`).\n\nSee `tests/e2e/helpers/session-auth.ts` for the exact calls.\n\n### One local testing system\n\n- `pnpm test` is the only repository-level test command. It runs local REST and\n  CLI flows, SDK tests, PostgreSQL-backed suites (`db-suites`), runner unit\n  tests, route coverage, and worktree tests concurrently.\n- `pnpm test -- --id ACC-4` runs one flow. `--domain access` runs one domain.\n- `pnpm test -- --sdk-only` runs only `packages/sdk` tests.\n- `pnpm test -- --db-only [path-filter]` runs only the PostgreSQL-backed suites\n  (`integration-*.test.ts`, `*.integration.test.ts`, `tests/migration`), each\n  file against its own fresh migrated database. A skipped DB suite fails.\n- `pnpm test -- --browser-only` runs Playwright browser journeys. It starts the\n  deterministic local stack.\n- Browser runs use two Playwright workers, locally and in each CI shard.\n- `pnpm test -- --packages-only` runs every app/package test and publish check.\n- `pnpm test -- --full` adds browser journeys and every app/package test. It\n  starts the deterministic local stack.\n- `pnpm test -- --target-smoke` verifies the deployed staging API and gateway\n  SHA, then runs the tagged Playwright staging smoke. Release CI supplies the\n  staging credentials and `RELEASE_SOURCE_SHA`.\n- `pnpm test -- --target-full` verifies the same deployed SHA, then runs every\n  configured staging REST, CLI, and Playwright journey. The production release\n  gate uses this command and fails on any excluded API flow.\n- Browser and full modes reuse only a running API that proves the deterministic\n  test profile. Stop an ordinary development stack before either command.\n- Every root run writes lane and total timings to\n  `tests/test-results/local/benchmark-<timestamp>.json`.\n- Before merging into `dev`, run T0 and the tests that cover your change (the\n  testing philosophy above). On a Mac, suite-wide modes exit `2`.\n- Every Linux CI job runs on a free GitHub-hosted runner (`ubuntu-24.04`,\n  `ubuntu-24.04-arm`, `ubuntu-22.04`; 4 vCPU / 16 GB on this public repo) through\n  `runs-on: ${{ vars.CI_RUNNER_<tier> || '<label>' }}`. Setting a\n  `CI_RUNNER_<tier>` repository variable moves that tier to another pool\n  without a PR. Never default a job to a paid runner (Blacksmith, a GitHub\n  larger runner): Blacksmith billed ~$2.7k in September 2026 for minutes\n  GitHub gives this repo for free. `tests/unit/image-build-speed-workflow.test.ts`\n  enforces the free default.\n- `.github/workflows/tests.yml` holds the six contract lanes — `core-flows`,\n  `core-db`, `packages-api`, `packages-cli`, `packages-kortixd`,\n  `packages-rest` — one GitHub-hosted runner each (`CI_RUNNER_L`). `core-*`\n  split the core run (`--core-group=flows|db`); `packages-*` are slices of\n  package quality (`--packages-only --package-group=<g>`). The concurrency\n  settings in `tests/bin/package-quality.ts` must not be raised. Each lane is\n  the unchanged root command at the exact requested SHA. Do not add CI-only\n  test logic.\n- The contract lanes run nightly on `dev`, once when a person adds the `test`\n  label to a pull request into `dev`, on dispatch, and inside the release\n  suite (`tests-release.yml` calls `tests.yml`). A push to `dev` and the pull\n  request into `staging` run nothing. A red nightly run comments the failing\n  lanes and suspect commits on the commit, once per new set of failing lanes.\n- The release suite (`tests-release.yml`, on the release PR into `prod`) runs\n  the contract lanes, the deployed API smoke (the flows tagged `deployed`, one\n  or two per deployment seam) and the browser journeys against deployed\n  staging, in parallel. `full suite + quality gates` requires all of them and\n  every other check on the release commit. A browser spec exists only for a\n  money-path journey (sign-in, onboarding, session load and streaming, desktop\n  parity, billing, connectors, invites); everything else is a REST flow or\n  nothing. CI records a video of every browser journey in the run's\n  `tests-release-browser-shard-N` artifact. A failed release-suite run reruns\n  its failed jobs once by itself (`rerun-failed-once.yml`): a job that passes\n  on the rerun is a flake to fix or delete.\n- `tests/unit/sandbox-workflow.test.ts` fails when any workflow except the\n  label-gated `tests.yml` and `deploy-preview.yml` triggers on a pull request\n  into `dev`, and pins both label gates to the label-added event.\n- The deployed half of the release suite (`--target-api-full`,\n  `--target-browser-full`) waits up to 15 min for staging to serve\n  `RELEASE_SOURCE_SHA`, then fails when any `deployed` flow is excluded or any\n  configured Playwright journey fails.\n- The `preview` label is not part of the development flow. Adding it deploys a\n  Platinum self-host environment for the PR (~7 min), once, and runs no tests.\n  A push does not redeploy. Only `gh workflow run deploy-preview.yml -f\n  pr_number=<N>` runs `--target-full` against a preview (40–80 min). Its mechanics are\n  in the **contributing** skill (`references/preview-environments.md`). Run a\n  preview of your change on your worktree's local stack by default.\n\n### Product flow source of truth\n\n- `tests/spec/end-to-end.md` contains the natural-language contract and stable\n  flow IDs.\n- `tests/src/flows/*.flow.ts` implements the contracts through HTTP and real CLI\n  processes. Do not import API handlers.\n- Write each `ctx.step()` as a complete action and observable result. Cover\n  setup, authentication, action, read-back proof, negative paths, and cleanup.\n- Keep every flow's `meta.routes` synchronized with\n  `tests/spec/routes.generated.json`. Regenerate the manifest with\n  `bun run apps/api/scripts/dump-routes.ts` after route changes.\n- The local profile uses local Supabase, PostgreSQL, API, gateway, and bare Git\n  repositories. It excludes Stripe, cloud sandboxes, managed GitHub repositories,\n  and external email delivery explicitly.\n- Use Playwright only for browser-visible behavior. API-only assertions belong\n  in REST flows. SDK tests remain in `packages/sdk`.\n- Do not add another cross-cutting test harness, Makefile lane, contract suite,\n  Testcontainers suite, load suite, mutation suite, visual suite, accessibility\n  suite, or ad hoc smoke script under `tests/`.\n- Read `tests/README.md` and the repository `testing` skill before changing the\n  test system.\n\n### Release topology — dev, staging, prod\n\n- **`dev` = dev trunk.** It is the repo default branch and deploys to\n  `dev.kortix.com` / `dev-api.kortix.com`. Direct pushes are allowed; breaking or\n  incomplete development can live here while it is being shaken out.\n- **`staging` = release-candidate branch.** Nothing should land on staging unless\n  it is intended to be production-ready. Promote to Production merges the\n  `dev` HEAD into staging itself (the PR runs no check), deploys it, and opens\n  the release PR whose release suite is the one gate. Staging is the deploy\n  target that suite runs against, not a test stage. Staging\n  deploys to `staging.kortix.com` / `staging-api.kortix.com` and must use the\n  staging data plane, not dev or prod. The full promote-and-gate flow, the\n  fix-forward loop, and the prod release steps live in the **kortix-release**\n  skill — that is the one place this is written out in full.\n- Staging deploys must apply pending DB migrations against `STAGING_DATABASE_URL`\n  before the staging ECS Fargate rollout. If that secret is missing or points at\n  dev/prod, treat the deploy as broken; staging must never fall back to dev,\n  KE2E, or prod DBs.\n- **`prod` = production.** Production moves only through **Promote to Production**,\n  which uses `staging` as the source, opens a reviewed release PR into `prod`,\n  publishes the release artifacts, and rolls production after merge.\n- If a staging runtime check points at `dev.kortix.com` or\n  `dev-api.kortix.com`, treat that as a broken staging setup, not a passing\n  staging gate.\n\n### Driving the real UI (agent-browser)\n\n- **agent-browser is the browser for every agent task in this repo.** Use it\n  before chrome-devtools MCP, Playwright MCP, or any other built-in browser\n  tool. Playwright stays the engine of the committed browser test suite only.\n- The installed skill is a stub. Load the guide that matches the CLI version:\n  `agent-browser skills get core` (`--full` adds the command reference).\n- Use one named session per worktree:\n  `agent-browser session id --scope worktree --prefix <task>`, then pass\n  `--session <id>` on every command. Keep every `AGENT_BROWSER_*` variable the\n  same for all commands in a session. A per-command change relaunches the\n  browser and drops the open page.\n- Routes are auth-gated (`/dashboard`, `/projects/*` → redirect to `/auth`\n  unauthenticated); sign in first (seed a user as above, then log in via the\n  `/auth` form). On a PR preview, the magic link arrives in the preview's Mailpit API\n  (`<origin>/_mailpit/api/v1`)\n  (the **contributing** skill has the script).\n- Next.js dev compiles routes on first hit — first navigation to a cold route\n  can take 30–60s; warm it with `curl` or use a generous navigation timeout.\n- `agent-browser doctor` diagnoses launch and recording problems. Recording\n  needs ffmpeg with libvpx and libx264.\n\n### API lint gate\n\n- `pnpm --filter kortix-api lint` runs in the `Tests` packages lane. Its rules\n  are in `apps/api/eslint.config.mjs`: layered imports, no Drizzle in route\n  files, no `(c: any)`, no `process.env` outside `config.ts`, no\n  `console.*`, a `replica-local:` comment on every empty module-level\n  `Map`/`Set`, and no import into `projects/` from outside it except\n  `projects/index.ts` or `projects/surface.ts`. Background timers are guarded by\n  `apps/api/src/__tests__/unit-worker-scope-wiring.test.ts` instead.\n\n### API code conventions (`apps/api/src`)\n\n- **Routes register explicitly.** A route module exports\n  `register<Name>Routes()`. The module that mounts the router calls it right\n  before `app.route()` (`app.ts`, `accounts/index.ts`, `router/index.ts`).\n  Never register at import time. Call order is dispatch order.\n- **No service imports a route module.** Shared logic lives in a service file\n  next to the routes (for example `projects/session-open/`).\n- **Services take an `Actor`, never a Hono `Context`.** The route reads the\n  request through `middleware/` and `http-*.ts` helpers, builds the actor\n  (`middleware/actor.ts`, memoized per request), and calls the service with\n  plain values. Parse `Authorization: Bearer` with `shared/bearer-token.ts`.\n- **Every background loop lives in `workers/<name>-worker.ts`.** The worker\n  owns its timer, start/stop and `runWorkerTick` call. The tick stays an\n  exported service function. `bootstrap.ts` imports every loop from `workers/`.\n- **`iam/` reads the membership, group and role tables.** Use the read models\n  in `iam/membership-read.ts`, `iam/group-read.ts` and `iam/role-read.ts`.\n- **No function longer than 300 lines.** Split long functions into named steps.\n- **SQL trace:** `KORTIX_SQL_TRACE=<file>` appends every pool statement to the\n  file. Diff two traces to prove a refactor runs the same SQL.\n- `apps/api/eslint-suppressions.json` holds the violations that existed when\n  each rule was added. A new violation fails. A fixed one fails until you run\n  `pnpm --filter kortix-api lint:prune` and commit the smaller file. Never\n  absorb a new violation with `--suppress-all`.\n\n### Frontend type/lint gate\n\n- Typecheck `apps/web` with `pnpm --filter Kortix-Computer-Frontend typecheck`\n  (tsgo, incremental). Never run raw `tsc --noEmit` there. Measured on\n  2026-10-21 on a 24 GB Mac: raw `tsc` ran 817 s, held 3.18 GB, and was\n  killed before it finished. Cold `tsgo` takes 12.1 s at 5.0 GB peak. A warm\n  incremental run takes 1.1 s at 1.7 GB.\n- On 2026-10-21 the tsgo script reported 0 errors on `dev`. The bar is\n  relative: a branch adds no type error to the files it touches. Compare against `origin/dev` with the same command, not\n  against a number in this file.\n  The old ~1500 `TS2786` / `IntrinsicAttributes` noise from a React 19↔18\n  types mismatch (two copies of `@types/react` in one program — `packages/sdk`\n  had its own) is gone as of the Next 16 upgrade. If `TS2786` appears again,\n  treat it as a genuine duplicate-`@types/react` regression and investigate —\n  do not wave it through.\n- `npx eslint <files>` should be clean of errors. `eslint .` across the whole\n  app currently reports ~455 warnings, mostly `react-hooks/*` React Compiler\n  rules pending a dedicated audit — expected until that audit lands.\n\n### Frontend design standard — Jay/Kortix bar\n\n#### Canonical design references\n\n| Surface | Read before changing UI | Implemented source |\n|---|---|---|\n| Web (`apps/web`) | `.agents/skills/kortix-brand/SKILL.md` (the router: values, voice, claims, decision history), then `.agents/skills/kortix-design-system/SKILL.md` for components | `apps/web/src/app/globals.css` tokens (generated from `.agents/skills/kortix-brand/references/visual/visual-system.json`), `packages/ui/src/primitives/` (apps/web imports each one as `@kortix/ui/<name>`; apps/web has no re-export shims, `tests/unit/web-no-ui-shims.test.ts` enforces it), the live `/design-system` route |\n| `@kortix/ui` hosts (Kortix Apps, `apps/white-label/reskin`, `apps/white-label/lumen`, `apps/white-label/team-console`, SDK customer apps) | `packages/ui/skills/kortix-ui/SKILL.md` (setup, which component, white-label theming, the `SessionThread` chat), then the web row for Kortix-owned surfaces | `packages/ui/src/` (components, `session/` for the `SessionThread` chat, `styles/tokens.css` + `theme.css`, prebuilt `dist/styles.css`); white-label = CSS variable overrides, never a forked component |\n| Desktop (Electron shell) | The web row — the shell renders `apps/web` — plus `apps/desktop-electron/README.md` for the shell boundary, then the parity gate below | `apps/web` rendered by the shell; native window geometry in the shell's titlebar classes |\n| Mobile (`apps/mobile`) | `apps/mobile/design.md` for screens, `apps/mobile/AGENTS.md` for primitives | `apps/mobile/global.css` colors, `apps/mobile/components/ui/`; stock Tailwind spacing for touch targets |\n\n#### Desktop parity is a UI gate\n\nThe Electron app loads `apps/web`. Keep product components, routes, tokens,\nand data behavior shared. Put native window geometry in the shell's explicit\ntitlebar classes. Never apply titlebar height or drag rules to generic ARIA\nroles, all sidebars, or page content.\n\nFor every shared UI change, verify the affected controls on web and in Electron\nbefore handoff. Check the outgoing request or route and the visible result.\nCheck both themes, the minimum supported window (720 × 480), sidebar collapse,\nfullscreen overlays, and browser zoom. Window controls must not overlap app\ncontrols. Lists must not overlap or clip their last row. Keyboard focus and\nscrolling must remain usable.\n\nAdd regressions to the existing Playwright journeys. The desktop journey runs\nin `pnpm test -- --browser-only` and supports the actual Electron shell:\n`E2E_DESKTOP_NATIVE=1 E2E_GREP='27 — desktop parity' pnpm test -- --browser-only`.\nRun the native journey when changing shell CSS, navigation, settings, agents,\nor connectors. A desktop user-agent test does not prove native hit testing.\nReport any unverified desktop behavior explicitly. Do not promise that tests\nprevent every future regression.\n\nWhen touching any visual surface in `apps/web`, treat brand fit as a release\ngate, not polish:\n\n- Read `.agents/skills/kortix-brand/SKILL.md` before writing the first\n  `className` or the first user-facing string. It routes you to the value law:\n  the complete allowlist of every color, spacing step, type rung, radius,\n  elevation, and duration you may use. Note `--spacing: 0.23rem` — Tailwind's\n  scale is 8% tighter here, so a 16px mockup padding is `p-4`, never\n  `p-[16px]`. Run `.agents/skills/kortix-brand/scripts/audit.sh` over your\n  changed paths before opening the PR; it must be clean on files you touched.\n  `references/visual/visual-system.json` is the only file with values: edit it,\n  then run `scripts/generate-tokens.ts`. Never edit a generated region of\n  `globals.css` by hand.\n- Read `.agents/skills/kortix-design-system/SKILL.md` next and compose existing\n  primitives from `@kortix/ui/*` before inventing local chrome.\n- Match the current Jay Suthar / Kortix product aesthetic: calm neutral surfaces,\n  dense-but-legible UI, black/white plus one earned accent, token-driven spacing,\n  and no decorative color, glow, or one-off rounded boxes.\n- Use recent product surfaces as references before editing: `/design-system`,\n  `packages/ui/src/workspace/layout/project-home.tsx`,\n  `apps/web/src/components/ui/wallpaper-background.tsx`, and the account/IAM\n  screens called out by the design-system skill.\n- Verify visual work in the browser and include the exact lint/typecheck commands\n  you ran in the PR. If it does not look native beside Jay-authored UI, keep\n  iterating before shipping.\n","isInternal":false,"tokens":12595,"sizeBytes":49999},{"name":"CLAUDE.md","path":"CLAUDE.md","rawUrl":"https://raw.githubusercontent.com/kortix-ai/suna/HEAD/CLAUDE.md","title":"Claude Agent Guidelines & System Prompt","category":"claude-rule","format":"markdown","content":"# Kortix project\n\n## What \"ownership\" means\n\nThe words \"can you own this?\", \"are you on it?\", and \"can you take care of this?\"\nall mean the same thing: you are 100% responsible. The person who handed it to\nyou must be able to walk completely away, come back a week later, and find it\ndone properly — because you cared about every edge and corner.\n\n- it's not done if it's not implemented\n- it's not done if the implementation is ugly\n- it's not done if it's not documented\n- it's not done if users can't discover it\n- it's not done if you can't market it\n\nOwning something means owning it end to end. The whole arc from \"we have a\nproblem\" to \"nobody has to think about this again.\" Not just the code change in\nthe middle, the whole thing. If someone hands you something and still has to\ntrack whether it actually got solved, you didn't own it.\n\nHere's what that actually looks like in practice.\n\n**Start with the problem, not the solution.** A lot of the time you'll already\nhave a fix in your head before you've understood what's broken. \"We need to move\nfrom X to Y\" isn't a problem, that's a solution you've pre-committed to. The real\nproblem is probably \"it's slow\", \"it's flaky\", \"it breaks for this customer\".\nName that first. Then ask what else could solve it, what the tradeoffs are, and\nwhich option actually wins.\n\n**Then pressure-test it before you build:**\n\n- **Edge cases.** Which exist, which matter, which we can safely ignore.\n- **Failures.** Networks fail, that's a given. Retry? How many times, for how\n  long?\n- **Data.** How much, does it need migrating or cleaning, how do you get real\n  data to test against, and what are you assuming about its shape that you\n  haven't actually verified?\n- **Testing.** How will you know it's correct? Are automated tests enough or do\n  you need to poke at it by hand? Is the result something you can see in a\n  screenshot or a video?\n- **The bigger picture.** How does this get announced, how does it fit the\n  roadmap, can you even picture it shipped? If something there bothers you, push\n  back. Ask.\n\n**Then actually do it**, with precision, care, urgency and calm all at once. No\nhalf-assing. The bar before you merge: am I proud of this? Would I put it in\nfront of Steve Jobs and walk him through what I built, the constraints, the\ntradeoffs?\n\n**And then prove it works.** Not \"the tests pass\", prove it. In 99% of cases you\ncan confirm it yourself: run it, ask an agent to walk through the scenarios, poke\nat the data before and after, take a screenshot, make a demo. Are you actually\nsure it solves the problem you started with?\n\n**Then make sure it lands in production and works in production**, which is not\nthe same as merged. Did it deploy? Did the deploy quietly fail? Is there a flag\nto flip, and does the flag work? Can you use the thing in prod right now and\nconfirm it's really there?\n\n**And then close the loop with everyone it touches:**\n\n- **The team.** If it's a new feature, a new convention, or a tricky thing people\n  should know about, tell them. Don't underestimate peripheral vision. You\n  knowing that someone changed Z yesterday can save someone else three hours of\n  debugging tomorrow when a bug report about Z comes in.\n- **Customers.** Whoever reported it, whoever's blocked, let them know it's\n  fixed.\n- **The world.** If it's worth announcing, announce it.\n- **Future you.** Are there follow-ups? Should you check the logs in a week to\n  make sure it's still healthy?\n\nBut that's how we build a product in a small team. We don't have PMs, we don't\nhave a QA department. We're small, but we're great, and we can do all of that.\n\nAnd it's always okay to ask for help, it's okay to ask questions, it's okay to\nredo things and triple-check. What's not okay is to quietly assume someone else\nwill catch the parts you didn't think about.\n\n### The bar: autonomy, agency, ownership\n\nThis is what I require from the agent I work with. In my own words:\n\nEither you are performing or you are not. Either you are taking on high\nownership, are high agency and pushing without me having to micromanage you, or\nyou are not.\n\nEspecially in today's age you are limited by great talent more than ever. Because\nwe can all AGI Max, you have a single chokepoint on good judgement.\n\nEvery person that comes on the team has to have the capability to truly own\nsomething. If you have built products, you develop ownership because you owned\nsomething — whether it worked out or not — for a prolonged amount of time. You\ndevelop true agency.\n\nI hire you because I expect that if I am able to walk out of the room after\ngiving you a high level thing and come back, it's going to be done good, ideally\nbetter than I would've done it.\n\nMost people are shit at their jobs, some are decent/good, but only people who are\nexceptional should be at Kortix.\n\nBeing exceptional on paper is simple — it's a combination of Ownership, Agency\nand actual Merit/Skill. It's hard, because you have to not only be very smart but\nalso crazy driven to push like a motherfucker and want to feel every edge and\ncorner to make sure the output is good.\n\n## Skills live in `.agents/skills/`\n\nEvery repo skill is a directory in `.agents/skills/<name>/`. `.claude/skills/<name>` is a\nsymlink to it. Add a skill in `.agents/skills/`, then add the symlink. Third-party skills come\nfrom `npx skills add` and are pinned in `skills-lock.json`. The PR procedure is the\n**contributing** skill; the PR title and body are the **create-pr** skill. Browser work is the **agent-browser** skill.\n\nThe repository has no `docs/` tree. Put a runbook or spec in the skill that owns the\nsurface (`.agents/skills/<name>/references/`). Put an incident rule in the learnings\nledger. Put design detail and RCAs in the PR body. Never cite a repo path that does\nnot exist. The pre-commit hook and `tests/unit/no-docs-tree.test.ts` reject a new\n`docs/` file and any citation of one.\n\n## Ponytail is on by default\n\nEvery code change runs through the **ponytail** skill at level `full`. Load it before you\nwrite, fix, refactor, or review code, and before you add a dependency. Level switch:\n`/ponytail lite|full|ultra`. Off: \"stop ponytail\". **ponytail-review** audits a diff for\nover-engineering, **ponytail-audit** audits the whole repo, and **ponytail-debt** lists\nevery `ponytail:` shortcut comment. Ponytail cuts code, never the verification,\ndocumentation, or ownership bar in this file. The skills come from\n`DietrichGebert/ponytail`, pinned to `v4.10.0` in `skills-lock.json`.\n\n## Learnings: the episodic ledger in the `learnings` skill\n\n`.agents/skills/learnings/` is the append-only, timestamped ledger of rules paid\nfor with real downtime. `MEMORY.md` indexes it newest first, and each entry is\none file in `entries/`: the rule, the incident that taught it, and the\nautomation that enforces it. Search it before writing or reviewing a DB\nmigration, touching deploy/release workflows, planning a promote, or\nresponding to a prod incident. After resolving ANY incident or near-miss,\nrecord a new entry in the same session with `scripts/new-entry.sh`. An incident\nthat leaves no entry behind is not finished.\n\n## NEVER write customer data or PII into anything we publish or commit\n\nThis is a hard rule. No exceptions, no \"just this once\", no \"it's only\ninternal\".\n\n**Never write any of these:**\n\n- customer or company names, and the names of their people;\n- email addresses, phone numbers, or other personal data;\n- real account, project, session, user, or sandbox IDs from prod, staging, or\n  a customer deployment;\n- customer repository names, hostnames, or URLs that contain any of the above;\n- customer prompts, messages, files, or log lines;\n- screenshots of a real customer workspace.\n\n**Never write them into:**\n\n- commits, commit messages, or branch names;\n- PR titles, PR bodies, PR comments, or review comments;\n- issues;\n- code comments, test names, or test fixtures;\n- docs, runbooks, skills, `AGENTS.md`, changelogs, or release notes;\n- artifacts, Slack posts, or any public or team-visible text.\n\n**Write the class instead:** \"a customer reported\", \"an enterprise\nworkspace\", \"a prod session\", `<session_id>`. Build test data from synthetic\nvalues. Evidence that contains real data stays local: the gitignored\n`output/` folder, your scratchpad, or the private agent memory. It never goes\ninto a tracked file.\n\n**If you find customer data** in the tree or in a PR, remove it in the same\nbranch and say so. Do not rewrite history on `dev`. Report the SHA to the user\ninstead.\n\n**A guard enforces this on every commit and push.**\n`scripts/check-blocked-terms.sh` runs from `.githooks/pre-commit`,\n`.githooks/commit-msg`, and `.githooks/pre-push`. It refuses any added line,\ncommit message, or pushed branch name that contains a blocked term. Matching is\ncase-insensitive and whole-word. The list is itself customer data, so it lives\nencrypted in `apps/api/.env` as `BLOCKED_COMMIT_TERMS`, comma-separated.\n\n- Add a customer the day they sign: `dotenvx set BLOCKED_COMMIT_TERMS\n  \"<existing>,<new>\" -f apps/api/.env`. Read the current value first with\n  `dotenvx get BLOCKED_COMMIT_TERMS -f apps/api/.env`.\n- In a worktree the guard decrypts with the primary checkout's\n  `apps/api/.env.keys`. Without a key it warns and allows.\n- Deleting a line that contains a term is always allowed.\n- Never bypass the guard with `--no-verify`. If it fires, remove the term.\n- The hooks do not see PR titles, PR bodies, or comments. Those stay your\n  responsibility.\n\n## How to communicate: precise, technically accurate, no fluff\n\nWrite every response — chat, PR text, commit messages, code comments, docs — in\nthe spirit of **ASD-STE100 (Simplified Technical English)**. The goal is maximum\ntechnical precision with zero filler. Apply these rules:\n\n- **State facts, not vibes.** Every claim is specific and verifiable: name the\n  file, function, route, flag, SHA, status code, or number. No \"should work\",\n  \"probably\", \"a bunch of\", \"various\", \"seems fine\" — say what is true and how you\n  know, or say you do not know it yet.\n- **One idea per sentence.** Keep sentences short (aim ≤ 20 words) and each one\n  carries a single instruction or fact. Split compound thoughts instead of\n  chaining clauses.\n- **Active voice, present tense, direct.** \"The gate rejects the request\",\n  not \"the request may end up being rejected\". Give the instruction; do not\n  soften it.\n- **One term per concept.** Use the same word for the same thing every time —\n  do not alternate \"session\"/\"run\"/\"task\" for one concept. Match the codebase's\n  existing names exactly (`session_id`, not \"session ID / run id\").\n- **No filler, no hedging, no praise.** Cut \"basically\", \"just\", \"simply\",\n  \"I think\", \"great question\", \"as we know\", and marketing adjectives. Lead with\n  the answer; drop the throat-clearing.\n- **Quantify.** Prefer exact values over adjectives: \"up to ~9 min\", \"returns\n  `402`\", \"3 of 7 flows\", not \"slow\", \"an error\", \"most\".\n- **Show the evidence.** When you assert a behavior, cite the command you ran and\n  the real output. Distinguish verified fact from assumption explicitly.\n- **Structure over prose.** Use numbered/bulleted lists for steps, findings, and\n  status. Reserve paragraphs for genuine explanation, and keep them tight.\n- **Say the unknown plainly.** If something is unverified, blocked, or risky,\n  state it in one line — what, why, and what would resolve it — instead of\n  burying or omitting it.\n\nThis standard governs how you talk. It does not override the technical rules\nbelow; it is how you report on them.\n\n## First, at session start: which canonical branch are you in?\n\nEvery change belongs to **one canonical branch** — the branch for whatever is\nbeing worked on. One canonical branch, one worktree. Establish which one you are\nin before any non-trivial change. **Do not create a branch by reflex.**\n\n1. **Join the canonical branch that already exists** for this work. List them\n   with `git worktree list` and `git branch -r`. If the work continues, extends,\n   fixes, or cleans up something already in flight, it belongs on that branch.\n   Ask the user which branch when it is not obvious.\n2. **Start a new canonical branch** only when the work is genuinely a new thing.\n   Give it its own worktree: `pnpm worktree create --name <slug> --yes\n   --no-start`, then do all edits and runs under `../suna-<slug>`. Add `--db`\n   when the work needs migrations, destructive data work, or schema drift: it\n   copies the primary local database into the worktree's own database\n   (`kortix_wt_<slug>`, ~5 s, no extra containers). `--full-supabase` (a whole\n   Supabase stack, ~0.75 GB RAM) is only for independent auth/storage config.\n   See the **worktree** skill.\n3. **Never switch a worktree you did not create.** It belongs to one session and\n   its canonical branch. The pre-commit hook refuses a commit on any other branch\n   there (`scripts/check-worktree-branch.sh`). A throwaway probe branch goes in a\n   private `git worktree add` in your scratchpad.\n4. **The primary checkout** (`pnpm dev`, web `3000` / api `8008`) is for running\n   and investigating. Do not park feature work there.\n5. **Run `pnpm test` only from a worktree that has `.kortix-worktree.json`.** The\n   runner reads its ports from that marker. A bare `git worktree add` has none,\n   so its test stack takes the primary ports and talks to another session's\n   `pnpm dev` (2026-10-13: 156 flows failed with that stack's 503s).\n6. **Never kill a process you did not start.** Many sessions share this machine.\n   Kill by PID from your own process tree, or by a pattern that contains your\n   own worktree or scratchpad path. Never by a bare pattern such as\n   `pkill -f \"bun test\"`, and never in a loop. Need CPU? Lower your own\n   concurrency (`KE2E_API_WORKERS`, `KORTIX_DB_SUITE_WORKERS`) or ask the user.\n   2026-10-13: a guard loop that SIGTERMed every other session's `pnpm test`,\n   `tsc` and `vitest` every 20 s made 259 kills, and every suite on the machine\n   died with exit 143 and no failing test.\n\n**Pack more into one branch, not less.** A follow-up fix, a rename cleanup, a\nstale-reference sweep, and the change that caused them all belong on the same\nbranch and land together. Splitting one objective across several branches is how\na half-finished cutover reaches `dev` in pieces — each piece green alone, the\nwhole thing broken.\n\nSub-branches are allowed. Agents may cut working branches off the canonical\nbranch and merge back into it. **A sub-branch never opens a PR against `dev`.**\nOnly the canonical branch does.\n\nCarve-outs where you just proceed: read-only investigation and questions, and\ntrivial single-file typo/comment fixes on the current branch.\n\n## Default delivery: verify in your box, self-merge to `dev`, verify on dev\n\n`dev` is the dev trunk. A merge does not deploy: dev deploys only on a deliberate\ndispatch, but **merging to `dev` still lands your change in everyone's next\ndeploy and next `dev` checkout.** It is not a save point.\n\n### Testing philosophy: one gate at Promote to Production, everything else fast\n\nTesting reset, 2026-10-16. Latency is the core concern. Development must be\nfast enough to push and break things on `dev`. The full suite runs once per\nrelease, at Promote to Production, and nowhere else. The rules:\n\n1. **Never run a full suite during development, and never a whole package's\n   tests.** No multi-minute run on your box, on a PR into `dev`, or on a push\n   to `dev`. Verify only what you changed: T0 for the packages you touched,\n   the test files that cover the change, named one by one\n   (`pnpm --filter <pkg> test <file>`), and the change itself through its real\n   surface. Running all 2,129 `apps/web` tests to check one component is the\n   anti-pattern this rule exists for.\n2. **One gate: the release pull request into `prod`.** Promote to Production\n   runs the whole suite there, once, and every check must be green. It is the\n   only check that blocks anything.\n3. **Test at the boundaries users touch:** the HTTP API, the CLI, the\n   `@kortix/sdk` public surface, DB migrations, and a few browser journeys on\n   the money path. Never test internal calls or source text.\n4. **Run each suite once per tree.** A tree that ran is not run again.\n5. **A red suite is worse than none.** A test that fails twice without a bug is\n   fixed or deleted the same day. Do not park it in quarantine.\n6. **Delete on red.** A unit test that goes red on a refactor with no bug is\n   deleted, not updated. Keep unit tests only for logic that is hard to get\n   right: money and credits, parsers, the streaming protocol, permission\n   decisions, crypto and secrets.\n7. **Small, isolated tests.** Every test file runs standalone in seconds. When\n   the test that covers your change takes more than ~10 s, or drags in setup\n   that has nothing to do with it, split it into a smaller isolated test as\n   part of the same change. Fake the clock; never sleep on real time.\n\n| Tier | Contents | Runs |\n|---|---|---|\n| T0 static | `tsc`, lint, architecture checks, migration lint | anytime, seconds per package |\n| Targeted | the one test file or flow that covers your change | on your box, seconds |\n| Release suite | contract lanes (flows, DB, packages) on the release tree, the deployed smoke and the browser journeys against staging, CI, CodeQL, scanners | once per release, on the pull request into `prod` |\n\n| Where | What runs | Blocks? |\n|---|---|---|\n| Your box | T0 for the packages you touched, the targeted test, and the real surface: the route with curl, the CLI process, the page with agent-browser | your judgment |\n| Pull request into `dev` | nothing, unless a person adds `test` or `preview` | no |\n| Push to `dev` | only the seconds-long guards: `secret-scan`, `secrets-guard`, and path-gated `DB Migrations` / `i18n-catalogs` / `Terraform Apply Global` / `deploy-api-router-dev` | no |\n| Nightly on `dev` | `Tests` `core` and `packages` lanes, no browser: a red night comments the failures on the commit, so they are ready to fix before a promote | no |\n| Pull request into `staging` | nothing: Promote to Production opens and merges it | no |\n| Pull request into `prod` | the release suite, once (`full suite + quality gates`) | **yes, the only gate** |\n\n**A Mac never runs a suite.** On macOS `pnpm test` refuses a suite-wide run\n(plain, `--full`, `--packages-only`, `--browser-only`, `--db-only` with no path\nfilter) and exits `2`. 2026-10-16: five agent sessions ran the suite at once on\none laptop (load average 112, swap full). Run one flow\n(`pnpm test -- --id <ID>`), one DB suite path, or one package's test file.\n`KORTIX_FULL_TEST=1` overrides only when a person asks. Every package `test`\nscript refuses the same way: `pnpm --filter <pkg> test` with no file exits `2`\n(`scripts/test-guard.mjs`). Pass the file(s), `pnpm --filter <pkg> test <file>`,\nor a name filter after `--`: `pnpm --filter <pkg> test -- -t <name>`. CI and\nLinux run the full suite.\n\nNo hook or gate requires a test run before a push or a merge into `dev`. Write\nin the PR body what you ran and why. A diff that no test reads (docs, a skill,\na standalone script) needs no run. When the nightly run or the release suite\nturns red on your change, you fix forward on `dev` the same day.\n\n1. Work on the canonical branch in its worktree. Commit as often as you want.\n2. Verify in your box, with real inputs and outputs. Run T0 and the tests that\n   cover the change (see above). Start the worktree's stack\n   (`pnpm worktree start <slug>`) and drive the changed behavior through it: the\n   HTTP route, the real CLI process, or the page with agent-browser. Nothing\n   before the release suite at Promote to Production catches what you skip.\n3. Open the PR against `dev` and follow the **contributing** skill: it attaches\n   the demo video you recorded against your local stack. Write the title and\n   body with the **create-pr** skill: English only, for a human reviewer who\n   decides \"merge or iterate\" without reading the diff. Do not add `test` or `preview` unless you need that one explicit run.\n4. Merge `dev` into the canonical branch daily. A branch that diverges for weeks\n   detonates on merge exactly like a 1,500-line PR does.\n5. **Self-merge to `dev` when the change is verified. Do not wait for the\n   user's approval.** Speed matters: a verified change that sits unmerged is\n   waste. Verified means all of these are true:\n   - the relevant local checks ran with real inputs and outputs (rule 2), and\n     they passed;\n   - the PR is mergeable (no conflict);\n   - rule 6 holds when the change touches a client-facing runtime contract.\n   A failing check blocks the merge until you fix it or state why it is\n   unrelated (for example, the same test fails on `dev`). Squash-merge\n   (`gh pr merge <pr> --squash`), then finish rules 7 and 8. A merge is not\n   the end of the work: dev verification is still yours.\n   The only machine-enforced rule on `dev` and `staging` is that every change\n   arrives through a pull request — no required approvals, no required status\n   checks, no bypass actors. The bar is what you verified.\n   **The release gates do not change.** Merging into `staging` or `prod`,\n   running Promote to Production, and moving the `:stable` tag each need the\n   user's explicit approval (the **kortix-release** skill).\n6. **A change to a client-facing runtime contract** — the `@kortix/sdk` public\n   surface, session/thread transport, the streaming protocol — merges only after\n   the whole objective ran through a real session on your local stack, and runs\n   again on dev after the merge (rule 8). Green tests are not the bar. Someone\n   used it.\n7. After the merge, deploy dev yourself: `gh workflow run deploy-dev.yml\n   --repo kortix-ai/suna -f surface=changed` (`all` or `frontend` force a\n   rebuild). Deploys queue, they never cancel. Wait for the run to finish.\n   `/health` on every changed surface must serve the merge SHA: a successful\n   `/health` response alone is not deployment proof. The run comments \"Live on\n   dev\" on the merged pull request, and \"Not live on dev yet\" names the\n   surface that failed. The surfaces and their checks are in\n   `.github/workflows/deploy-dev.yml`. The merge push runs no suite. The\n   nightly `Tests` run on `dev` comments the failing lanes and every commit\n   since the last green run, once per new set of failing lanes. The author\n   whose commit broke `dev` fixes forward the same day. If `dev` is still red\n   1 hour after the comment, anyone may revert the culprit PR. The release\n   suite at Promote to Production runs the same lanes and blocks the release.\n8. Re-run the user-visible behavior against `https://dev.kortix.com` and/or\n   `https://dev-api.kortix.com`. Prefer the real Kortix CLI configured for the\n   dev API for CLI/project/session flows, and direct authenticated HTTP calls for\n   API contracts. For web behavior, drive the deployed UI and assert its network\n   request plus visible result.\n\nLocal verification and dev verification are both required. A local pass does\nnot replace dev, and a dev smoke test does not replace focused local tests.\nRecord the branch, PR, local commands and output, merge SHA, deploy run,\ndeployed SHA evidence, and the exact dev command or interaction in the final\nresponse.\n\n## Architecture: `@kortix/sdk` is the source of truth\n\n`@kortix/sdk` is the **single source of truth** for everything that talks to the\nKortix backend — projects, accounts, sessions, files, secrets, triggers, the\nsession runtime, the runtime REST client, SSE streaming, model state,\nand auth-token plumbing. The apps\n(`apps/web`, `apps/white-label/*`, `apps/mobile`) are **thin consumers**. Treat\nthese as standing rules whenever you touch the data/runtime layer:\n\n> **Editing `packages/sdk` itself? Load the **sdk** skill (the rules) first.** It is a\n> **published npm package** with its own hard rules that have no analogue\n> elsewhere in this repo: **TDD is mandatory** (failing test first, run it, watch\n> it fail, then implement — and every turn ends with the gates run, the real\n> output pasted, and an explicit shippable YES/NO/NOT YET); exported names\n> (including *types*) are a public API contract and renaming one is a breaking\n> change; the `version` field is inert and must never be bumped by hand; adding\n> an export requires three synchronized edits; and the framework-free core is\n> enforced by a static import-graph tripwire.\n\n- **Logic lives in the SDK, never in a host.** No raw `fetch` to the Kortix API,\n  no `@opencode-ai/sdk` imports, no transport / runtime / data-state code written\n  in app code. New data or runtime behavior is added to the SDK and exposed\n  through its public surface — not hand-rolled or duplicated in a host. If you\n  need something the SDK doesn't expose, add it to the SDK.\n- **One client per host.** Create it once via `createKortix({ backendUrl,\n  getToken })` and read everything through `@kortix/sdk` + `@kortix/sdk/react`.\n  Auth is just `getToken` — an API key / PAT for programmatic use, or a Supabase\n  JWT for the logged-in web app. Hosts never instantiate a second client.\n- **A whole session is one hook.** `useSession(projectId, sessionId)` owns the\n  entire runtime lifecycle — `/start`, the sandbox switch, the live SSE stream,\n  readiness seeding, immutable runtime identity, the native conversation id,\n  and message sync. Hosts don't\n  hand-roll the mount, drive a server-store \"switch\", or mount a separate event\n  provider.\n- **Session-scoped + provider-agnostic.** The public API is session-scoped\n  (`kortix.session(pid, sid).health() / .previewUrl() / .restart() / …`).\n  The sandbox provider and the harness are server-side concerns. Host code\n  must not implement a second transport.\n- **Build on the Kortix contract, not on OpenCode.** Every new session runs\n  pi inside kortixd. OpenCode is deprecated (2026-10-23): there is no\n  `pi_harness` flag, `runtime:` in `kortix.yaml` selects nothing, and OpenCode\n  serves only sessions that started on it. It will be deleted. Code that exists\n  only for OpenCode carries `@deprecated`: never call it from new code, never\n  add a feature to it, and change it only to fix a bug that breaks a running\n  OpenCode session. pi calls models only through the LLM gateway. pi serves\n  the daemon routes (`/kortix/runtime/*`), the transcript\n  (`kortix.transcript.v1`, `packages/api-contract/src/transcript.ts`) and the\n  events. New code reads those, never an OpenCode route, type, file or\n  process. A feature pi lacks is a capability, not a harness check:\n  `GET /kortix/health` lists `capabilities` (`RUNTIME_CAPABILITIES` in\n  `packages/api-contract/src/runtime-relay.ts`), and a client gates the\n  control with `runtimeSupports`. OpenCode lists all eleven (`session.steer`\n  only on OpenCode 1.18.15 or later); pi lists `session.subagents`,\n  `session.compact`, `session.commands` and `session.steer`. pi does not\n  serve rewind, MCP servers, the todo list, shell turns, part edits or\n  `session.attach`.\n  The harness rules and the pi gap list are in\n  `apps/kortix-sandbox-agent-server/src/harness/README.md`.\n- **`apps/web` data modules are shims.** Files such as\n  `apps/web/src/ui/index.ts`, `apps/web/src/lib/iam-client.ts`, and\n  `apps/web/src/hooks/admin/use-*.ts` are thin re-exports\n  (`export * from '@kortix/sdk/...'`).\n  Keep them as shims; put the real logic in the SDK. When a merge conflict lands\n  on one of these, **keep the shim (`--ours`) and port any new host-side logic\n  into the SDK** — do not revert to a host-local implementation.\n- **Docs are the spec.** `apps/web/content/docs/sdk/*` and\n  `packages/sdk/README.md` describe the intended surface. Keep them current with\n  the SDK, and flag legacy/deprecated surfaces in-doc rather than documenting them\n  as current.\n\n## You CAN run and verify everything end-to-end. Do it.\n\nThis repo ships a **complete, runnable local stack with live cloud sandboxes**.\nDo not claim you \"can't verify from here\" or hand back unverified work — you\nhave everything needed to run the app, hit the real API, provision real\nPlatinum sandboxes, drive the real UI in a browser, and assert behavior. Use it.\n\n### Required verification standard — real inputs, real outputs\n\nFor every behavior change, assume **100% autonomy** to verify the user-visible\ncontract before handing the work back. Do not stop at typechecks, unit tests, or\nmocked internals when a real surface exists.\n\n- **API changes:** exercise the actual HTTP route with real request payloads\n  (`curl`, `bun fetch`, or the `ke2e` runner against a running API). Assert the\n  status code and exact response fields that prove the behavior. For writes,\n  also assert the persisted/read-back state or resulting repo/file output.\n- **CLI changes:** run the real CLI command as a process from bash, with the\n  same flags and stdin a user or agent would use. Assert exit code, stdout,\n  stderr, and any files/API calls/commits it should create. Do not rely only on\n  importing command functions.\n- **Web changes:** drive the real page with **agent-browser** (the primary\n  browser; `agent-browser skills get core` loads its guide). Click/type/toggle\n  the actual controls, observe the network request (`agent-browser network`),\n  and assert the visible UI state plus the outgoing payload. Record the flow\n  (`agent-browser record start`) for the PR's demo video. Screenshots and video\n  are evidence, but assertions on DOM and network data are required.\n- **Cross-surface features:** verify each exposed surface independently. If the\n  same feature ships on API + CLI + web + mobile, each gets its own black-box\n  assertion for the inputs users can make and the outputs they receive.\n- **Default/negative paths count:** when changing defaults or removing implicit\n  behavior, assert both the new default and the explicit opt-in/alternate path.\n- **No silent gaps:** if a surface cannot be fully exercised in the current\n  turn, say exactly which input/output remains unverified and why. Otherwise\n  keep going until the real surface is verified.\n- **Final response format:** when work is finished, answer per the **How to\n  communicate** standard at the top of this file — numbered/bulleted, no fluff.\n  Include exactly what changed, what was verified (with the command + output),\n  what remains unverified or risky, and what the user should test next. Do not\n  bury the actionable testing path in a paragraph.\n\n### The stack (already wired)\n\n- **Web** — Next.js dev server on `http://localhost:3000`.\n- **API** — Bun server on `http://localhost:8008/v1` (`/health` returns JSON).\n- **Supabase** — local, on `http://127.0.0.1:54321` (Docker).\n- **Sandboxes** — REAL cloud sandboxes on Platinum, the reference provider\n  (`PLATINUM_API_KEY` in `apps/api/.env` / `.env.local`). Daytona is a legacy\n  fallback with removal scheduled and no support guarantee; E2B support is\n  discontinued. Each project\n  session gets its own sandbox; `session_id == sandbox_id`. The sandbox daemon is\n  reached through `http://localhost:8008/v1/p/<external_id>/8000/...`.\n  The session runtime answers on the same proxy, under `/kortix/runtime/*`,\n  on both harnesses.\n- **Tunnel** — `scripts/dev-local.sh` (`pnpm dev`) auto-starts a cloudflared\n  quick tunnel so cloud sandboxes can call back to the local API (`KORTIX_URL`).\n\nBring it up with `pnpm dev` from `suna/` (it loads `apps/api/.env` +\n`apps/web/.env`, starts Supabase, the API, the web app, and the tunnel). Check\nwhat's already running before starting a duplicate: `curl -s\nlocalhost:8008/v1/health`, `lsof -iTCP:3000 -sTCP:LISTEN`.\n\n> **Secrets are dotenvx-encrypted (mandatory).** `apps/api/.env` (+ `.env.dev`)\n> are committed as ciphertext (`KEY=encrypted:…`); keys live in Dotenv Armor.\n> **Never write a plaintext secret into a tracked file or commit** — add/change\n> values only via `dotenvx set KEY value -f apps/api/.env` (then commit), read\n> with `dotenvx get`, and machine-local overrides go in the gitignored\n> `apps/api/.env.local`. If the user pastes a key, store it with `dotenvx set`,\n> never paste it raw. A pre-commit hook + GitHub push protection enforce this —\n> don't bypass them. Full procedure: the **dotenvx-secrets** skill.\n\n### Authenticating to the live API (for scripts/tests)\n\nMint a real JWT against local Supabase, then call the API with it:\n\n1. `SUPABASE_SERVICE_ROLE_KEY` lives in `apps/api/.env`; the anon key\n   (`NEXT_PUBLIC_SUPABASE_ANON_KEY`) in `apps/web/.env`.\n2. Create a confirmed user: `POST 127.0.0.1:54321/auth/v1/admin/users`\n   (`apikey` + `Authorization: Bearer <service_role>`, body\n   `{email,password,email_confirm:true}`).\n3. Password-grant for the token: `POST\n   127.0.0.1:54321/auth/v1/token?grant_type=password` (`apikey: <anon>`).\n4. Call the API: `Authorization: Bearer <access_token>` against\n   `localhost:8008/v1` (e.g. `/accounts`, `/projects/provision`,\n   `/projects/:id/sessions`, `/p/<ext>/8000/...`).\n\nSee `tests/e2e/helpers/session-auth.ts` for the exact calls.\n\n### One local testing system\n\n- `pnpm test` is the only repository-level test command. It runs local REST and\n  CLI flows, SDK tests, PostgreSQL-backed suites (`db-suites`), runner unit\n  tests, route coverage, and worktree tests concurrently.\n- `pnpm test -- --id ACC-4` runs one flow. `--domain access` runs one domain.\n- `pnpm test -- --sdk-only` runs only `packages/sdk` tests.\n- `pnpm test -- --db-only [path-filter]` runs only the PostgreSQL-backed suites\n  (`integration-*.test.ts`, `*.integration.test.ts`, `tests/migration`), each\n  file against its own fresh migrated database. A skipped DB suite fails.\n- `pnpm test -- --browser-only` runs Playwright browser journeys. It starts the\n  deterministic local stack.\n- Browser runs use two Playwright workers, locally and in each CI shard.\n- `pnpm test -- --packages-only` runs every app/package test and publish check.\n- `pnpm test -- --full` adds browser journeys and every app/package test. It\n  starts the deterministic local stack.\n- `pnpm test -- --target-smoke` verifies the deployed staging API and gateway\n  SHA, then runs the tagged Playwright staging smoke. Release CI supplies the\n  staging credentials and `RELEASE_SOURCE_SHA`.\n- `pnpm test -- --target-full` verifies the same deployed SHA, then runs every\n  configured staging REST, CLI, and Playwright journey. The production release\n  gate uses this command and fails on any excluded API flow.\n- Browser and full modes reuse only a running API that proves the deterministic\n  test profile. Stop an ordinary development stack before either command.\n- Every root run writes lane and total timings to\n  `tests/test-results/local/benchmark-<timestamp>.json`.\n- Before merging into `dev`, run T0 and the tests that cover your change (the\n  testing philosophy above). On a Mac, suite-wide modes exit `2`.\n- Every Linux CI job runs on a free GitHub-hosted runner (`ubuntu-24.04`,\n  `ubuntu-24.04-arm`, `ubuntu-22.04`; 4 vCPU / 16 GB on this public repo) through\n  `runs-on: ${{ vars.CI_RUNNER_<tier> || '<label>' }}`. Setting a\n  `CI_RUNNER_<tier>` repository variable moves that tier to another pool\n  without a PR. Never default a job to a paid runner (Blacksmith, a GitHub\n  larger runner): Blacksmith billed ~$2.7k in September 2026 for minutes\n  GitHub gives this repo for free. `tests/unit/image-build-speed-workflow.test.ts`\n  enforces the free default.\n- `.github/workflows/tests.yml` holds the six contract lanes — `core-flows`,\n  `core-db`, `packages-api`, `packages-cli`, `packages-kortixd`,\n  `packages-rest` — one GitHub-hosted runner each (`CI_RUNNER_L`). `core-*`\n  split the core run (`--core-group=flows|db`); `packages-*` are slices of\n  package quality (`--packages-only --package-group=<g>`). The concurrency\n  settings in `tests/bin/package-quality.ts` must not be raised. Each lane is\n  the unchanged root command at the exact requested SHA. Do not add CI-only\n  test logic.\n- The contract lanes run nightly on `dev`, once when a person adds the `test`\n  label to a pull request into `dev`, on dispatch, and inside the release\n  suite (`tests-release.yml` calls `tests.yml`). A push to `dev` and the pull\n  request into `staging` run nothing. A red nightly run comments the failing\n  lanes and suspect commits on the commit, once per new set of failing lanes.\n- The release suite (`tests-release.yml`, on the release PR into `prod`) runs\n  the contract lanes, the deployed API smoke (the flows tagged `deployed`, one\n  or two per deployment seam) and the browser journeys against deployed\n  staging, in parallel. `full suite + quality gates` requires all of them and\n  every other check on the release commit. A browser spec exists only for a\n  money-path journey (sign-in, onboarding, session load and streaming, desktop\n  parity, billing, connectors, invites); everything else is a REST flow or\n  nothing. CI records a video of every browser journey in the run's\n  `tests-release-browser-shard-N` artifact. A failed release-suite run reruns\n  its failed jobs once by itself (`rerun-failed-once.yml`): a job that passes\n  on the rerun is a flake to fix or delete.\n- `tests/unit/sandbox-workflow.test.ts` fails when any workflow except the\n  label-gated `tests.yml` and `deploy-preview.yml` triggers on a pull request\n  into `dev`, and pins both label gates to the label-added event.\n- The deployed half of the release suite (`--target-api-full`,\n  `--target-browser-full`) waits up to 15 min for staging to serve\n  `RELEASE_SOURCE_SHA`, then fails when any `deployed` flow is excluded or any\n  configured Playwright journey fails.\n- The `preview` label is not part of the development flow. Adding it deploys a\n  Platinum self-host environment for the PR (~7 min), once, and runs no tests.\n  A push does not redeploy. Only `gh workflow run deploy-preview.yml -f\n  pr_number=<N>` runs `--target-full` against a preview (40–80 min). Its mechanics are\n  in the **contributing** skill (`references/preview-environments.md`). Run a\n  preview of your change on your worktree's local stack by default.\n\n### Product flow source of truth\n\n- `tests/spec/end-to-end.md` contains the natural-language contract and stable\n  flow IDs.\n- `tests/src/flows/*.flow.ts` implements the contracts through HTTP and real CLI\n  processes. Do not import API handlers.\n- Write each `ctx.step()` as a complete action and observable result. Cover\n  setup, authentication, action, read-back proof, negative paths, and cleanup.\n- Keep every flow's `meta.routes` synchronized with\n  `tests/spec/routes.generated.json`. Regenerate the manifest with\n  `bun run apps/api/scripts/dump-routes.ts` after route changes.\n- The local profile uses local Supabase, PostgreSQL, API, gateway, and bare Git\n  repositories. It excludes Stripe, cloud sandboxes, managed GitHub repositories,\n  and external email delivery explicitly.\n- Use Playwright only for browser-visible behavior. API-only assertions belong\n  in REST flows. SDK tests remain in `packages/sdk`.\n- Do not add another cross-cutting test harness, Makefile lane, contract suite,\n  Testcontainers suite, load suite, mutation suite, visual suite, accessibility\n  suite, or ad hoc smoke script under `tests/`.\n- Read `tests/README.md` and the repository `testing` skill before changing the\n  test system.\n\n### Release topology — dev, staging, prod\n\n- **`dev` = dev trunk.** It is the repo default branch and deploys to\n  `dev.kortix.com` / `dev-api.kortix.com`. Direct pushes are allowed; breaking or\n  incomplete development can live here while it is being shaken out.\n- **`staging` = release-candidate branch.** Nothing should land on staging unless\n  it is intended to be production-ready. Promote to Production merges the\n  `dev` HEAD into staging itself (the PR runs no check), deploys it, and opens\n  the release PR whose release suite is the one gate. Staging is the deploy\n  target that suite runs against, not a test stage. Staging\n  deploys to `staging.kortix.com` / `staging-api.kortix.com` and must use the\n  staging data plane, not dev or prod. The full promote-and-gate flow, the\n  fix-forward loop, and the prod release steps live in the **kortix-release**\n  skill — that is the one place this is written out in full.\n- Staging deploys must apply pending DB migrations against `STAGING_DATABASE_URL`\n  before the staging ECS Fargate rollout. If that secret is missing or points at\n  dev/prod, treat the deploy as broken; staging must never fall back to dev,\n  KE2E, or prod DBs.\n- **`prod` = production.** Production moves only through **Promote to Production**,\n  which uses `staging` as the source, opens a reviewed release PR into `prod`,\n  publishes the release artifacts, and rolls production after merge.\n- If a staging runtime check points at `dev.kortix.com` or\n  `dev-api.kortix.com`, treat that as a broken staging setup, not a passing\n  staging gate.\n\n### Driving the real UI (agent-browser)\n\n- **agent-browser is the browser for every agent task in this repo.** Use it\n  before chrome-devtools MCP, Playwright MCP, or any other built-in browser\n  tool. Playwright stays the engine of the committed browser test suite only.\n- The installed skill is a stub. Load the guide that matches the CLI version:\n  `agent-browser skills get core` (`--full` adds the command reference).\n- Use one named session per worktree:\n  `agent-browser session id --scope worktree --prefix <task>`, then pass\n  `--session <id>` on every command. Keep every `AGENT_BROWSER_*` variable the\n  same for all commands in a session. A per-command change relaunches the\n  browser and drops the open page.\n- Routes are auth-gated (`/dashboard`, `/projects/*` → redirect to `/auth`\n  unauthenticated); sign in first (seed a user as above, then log in via the\n  `/auth` form). On a PR preview, the magic link arrives in the preview's Mailpit API\n  (`<origin>/_mailpit/api/v1`)\n  (the **contributing** skill has the script).\n- Next.js dev compiles routes on first hit — first navigation to a cold route\n  can take 30–60s; warm it with `curl` or use a generous navigation timeout.\n- `agent-browser doctor` diagnoses launch and recording problems. Recording\n  needs ffmpeg with libvpx and libx264.\n\n### API lint gate\n\n- `pnpm --filter kortix-api lint` runs in the `Tests` packages lane. Its rules\n  are in `apps/api/eslint.config.mjs`: layered imports, no Drizzle in route\n  files, no `(c: any)`, no `process.env` outside `config.ts`, no\n  `console.*`, a `replica-local:` comment on every empty module-level\n  `Map`/`Set`, and no import into `projects/` from outside it except\n  `projects/index.ts` or `projects/surface.ts`. Background timers are guarded by\n  `apps/api/src/__tests__/unit-worker-scope-wiring.test.ts` instead.\n\n### API code conventions (`apps/api/src`)\n\n- **Routes register explicitly.** A route module exports\n  `register<Name>Routes()`. The module that mounts the router calls it right\n  before `app.route()` (`app.ts`, `accounts/index.ts`, `router/index.ts`).\n  Never register at import time. Call order is dispatch order.\n- **No service imports a route module.** Shared logic lives in a service file\n  next to the routes (for example `projects/session-open/`).\n- **Services take an `Actor`, never a Hono `Context`.** The route reads the\n  request through `middleware/` and `http-*.ts` helpers, builds the actor\n  (`middleware/actor.ts`, memoized per request), and calls the service with\n  plain values. Parse `Authorization: Bearer` with `shared/bearer-token.ts`.\n- **Every background loop lives in `workers/<name>-worker.ts`.** The worker\n  owns its timer, start/stop and `runWorkerTick` call. The tick stays an\n  exported service function. `bootstrap.ts` imports every loop from `workers/`.\n- **`iam/` reads the membership, group and role tables.** Use the read models\n  in `iam/membership-read.ts`, `iam/group-read.ts` and `iam/role-read.ts`.\n- **No function longer than 300 lines.** Split long functions into named steps.\n- **SQL trace:** `KORTIX_SQL_TRACE=<file>` appends every pool statement to the\n  file. Diff two traces to prove a refactor runs the same SQL.\n- `apps/api/eslint-suppressions.json` holds the violations that existed when\n  each rule was added. A new violation fails. A fixed one fails until you run\n  `pnpm --filter kortix-api lint:prune` and commit the smaller file. Never\n  absorb a new violation with `--suppress-all`.\n\n### Frontend type/lint gate\n\n- Typecheck `apps/web` with `pnpm --filter Kortix-Computer-Frontend typecheck`\n  (tsgo, incremental). Never run raw `tsc --noEmit` there. Measured on\n  2026-10-21 on a 24 GB Mac: raw `tsc` ran 817 s, held 3.18 GB, and was\n  killed before it finished. Cold `tsgo` takes 12.1 s at 5.0 GB peak. A warm\n  incremental run takes 1.1 s at 1.7 GB.\n- On 2026-10-21 the tsgo script reported 0 errors on `dev`. The bar is\n  relative: a branch adds no type error to the files it touches. Compare against `origin/dev` with the same command, not\n  against a number in this file.\n  The old ~1500 `TS2786` / `IntrinsicAttributes` noise from a React 19↔18\n  types mismatch (two copies of `@types/react` in one program — `packages/sdk`\n  had its own) is gone as of the Next 16 upgrade. If `TS2786` appears again,\n  treat it as a genuine duplicate-`@types/react` regression and investigate —\n  do not wave it through.\n- `npx eslint <files>` should be clean of errors. `eslint .` across the whole\n  app currently reports ~455 warnings, mostly `react-hooks/*` React Compiler\n  rules pending a dedicated audit — expected until that audit lands.\n\n### Frontend design standard — Jay/Kortix bar\n\n#### Canonical design references\n\n| Surface | Read before changing UI | Implemented source |\n|---|---|---|\n| Web (`apps/web`) | `.agents/skills/kortix-brand/SKILL.md` (the router: values, voice, claims, decision history), then `.agents/skills/kortix-design-system/SKILL.md` for components | `apps/web/src/app/globals.css` tokens (generated from `.agents/skills/kortix-brand/references/visual/visual-system.json`), `packages/ui/src/primitives/` (apps/web imports each one as `@kortix/ui/<name>`; apps/web has no re-export shims, `tests/unit/web-no-ui-shims.test.ts` enforces it), the live `/design-system` route |\n| `@kortix/ui` hosts (Kortix Apps, `apps/white-label/reskin`, `apps/white-label/lumen`, `apps/white-label/team-console`, SDK customer apps) | `packages/ui/skills/kortix-ui/SKILL.md` (setup, which component, white-label theming, the `SessionThread` chat), then the web row for Kortix-owned surfaces | `packages/ui/src/` (components, `session/` for the `SessionThread` chat, `styles/tokens.css` + `theme.css`, prebuilt `dist/styles.css`); white-label = CSS variable overrides, never a forked component |\n| Desktop (Electron shell) | The web row — the shell renders `apps/web` — plus `apps/desktop-electron/README.md` for the shell boundary, then the parity gate below | `apps/web` rendered by the shell; native window geometry in the shell's titlebar classes |\n| Mobile (`apps/mobile`) | `apps/mobile/design.md` for screens, `apps/mobile/AGENTS.md` for primitives | `apps/mobile/global.css` colors, `apps/mobile/components/ui/`; stock Tailwind spacing for touch targets |\n\n#### Desktop parity is a UI gate\n\nThe Electron app loads `apps/web`. Keep product components, routes, tokens,\nand data behavior shared. Put native window geometry in the shell's explicit\ntitlebar classes. Never apply titlebar height or drag rules to generic ARIA\nroles, all sidebars, or page content.\n\nFor every shared UI change, verify the affected controls on web and in Electron\nbefore handoff. Check the outgoing request or route and the visible result.\nCheck both themes, the minimum supported window (720 × 480), sidebar collapse,\nfullscreen overlays, and browser zoom. Window controls must not overlap app\ncontrols. Lists must not overlap or clip their last row. Keyboard focus and\nscrolling must remain usable.\n\nAdd regressions to the existing Playwright journeys. The desktop journey runs\nin `pnpm test -- --browser-only` and supports the actual Electron shell:\n`E2E_DESKTOP_NATIVE=1 E2E_GREP='27 — desktop parity' pnpm test -- --browser-only`.\nRun the native journey when changing shell CSS, navigation, settings, agents,\nor connectors. A desktop user-agent test does not prove native hit testing.\nReport any unverified desktop behavior explicitly. Do not promise that tests\nprevent every future regression.\n\nWhen touching any visual surface in `apps/web`, treat brand fit as a release\ngate, not polish:\n\n- Read `.agents/skills/kortix-brand/SKILL.md` before writing the first\n  `className` or the first user-facing string. It routes you to the value law:\n  the complete allowlist of every color, spacing step, type rung, radius,\n  elevation, and duration you may use. Note `--spacing: 0.23rem` — Tailwind's\n  scale is 8% tighter here, so a 16px mockup padding is `p-4`, never\n  `p-[16px]`. Run `.agents/skills/kortix-brand/scripts/audit.sh` over your\n  changed paths before opening the PR; it must be clean on files you touched.\n  `references/visual/visual-system.json` is the only file with values: edit it,\n  then run `scripts/generate-tokens.ts`. Never edit a generated region of\n  `globals.css` by hand.\n- Read `.agents/skills/kortix-design-system/SKILL.md` next and compose existing\n  primitives from `@kortix/ui/*` before inventing local chrome.\n- Match the current Jay Suthar / Kortix product aesthetic: calm neutral surfaces,\n  dense-but-legible UI, black/white plus one earned accent, token-driven spacing,\n  and no decorative color, glow, or one-off rounded boxes.\n- Use recent product surfaces as references before editing: `/design-system`,\n  `packages/ui/src/workspace/layout/project-home.tsx`,\n  `apps/web/src/components/ui/wallpaper-background.tsx`, and the account/IAM\n  screens called out by the design-system skill.\n- Verify visual work in the browser and include the exact lint/typecheck commands\n  you ran in the PR. If it does not look native beside Jay-authored UI, keep\n  iterating before shipping.\n","isInternal":false,"tokens":12595,"sizeBytes":49999},{"name":"marketplace.json","path":".claude-plugin/marketplace.json","rawUrl":"https://raw.githubusercontent.com/kortix-ai/suna/HEAD/.claude-plugin/marketplace.json","title":"Claude Plugin Marketplace Catalog","category":"marketplace","format":"json","content":"# Claude Plugin Marketplace Catalog\n\nPath: `.claude-plugin/marketplace.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/kortix-ai/suna/HEAD/.claude-plugin/marketplace.json)","tokens":300,"sizeBytes":193},{"name":"plugin.json","path":".claude-plugin/plugin.json","rawUrl":"https://raw.githubusercontent.com/kortix-ai/suna/HEAD/.claude-plugin/plugin.json","title":"claude-plugin Plugin Manifest","category":"plugin-manifest","format":"json","content":"# claude-plugin Plugin Manifest\n\nPath: `.claude-plugin/plugin.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/kortix-ai/suna/HEAD/.claude-plugin/plugin.json)","tokens":300,"sizeBytes":179},{"name":"mcp.json","path":".cursor/mcp.json","rawUrl":"https://raw.githubusercontent.com/kortix-ai/suna/HEAD/.cursor/mcp.json","title":"Model Context Protocol (MCP) Configuration","category":"mcp-config","format":"json","content":"# Model Context Protocol (MCP) Configuration\n\nPath: `.cursor/mcp.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/kortix-ai/suna/HEAD/.cursor/mcp.json)","tokens":300,"sizeBytes":172}],"systemPromptSnippet":"<yakaai_skills repo=\"kortix-ai/suna\">\n<!-- File: AGENTS.md (Tokens: ~12595 | Category: root-instruction) -->\n# Kortix project\n\n## What \"ownership\" means\n\nThe words \"can you own this?\", \"are you on it?\", and \"can you take care of this?\"\nall mean the same thing: you are 100% responsible. The person who handed it to\nyou must be able to walk completely away, come back a week later, and find it\ndone properly — because you cared about every edge and corner.\n\n- it's not done if it's not implemented\n- it's not done if the implementation is ugly\n- it's not done if it's not documented\n- it's not done if users can't discover it\n- it's not done if you can't market it\n\nOwning something means owning it end to end. The whole arc from \"we have a\nproblem\" to \"nobody has to think about this again.\" Not just the code change in\nthe middle, the whole thing. If someone hands you something and still has to\ntrack whether it actually got solved, you didn't own it.\n\nHere's what that actually looks like in practice.\n\n**Start with the problem, not the solution.** A lot of the time you'll already\nhave a fix in your head before you've understood what's broken. \"We need to move\nfrom X to Y\" isn't a problem, that's a solution you've pre-committed to. The real\nproblem is probably \"it's slow\", \"it's flaky\", \"it breaks for this customer\".\nName that first. Then ask what else could solve it, what the tradeoffs are, and\nwhich option actually wins.\n\n**Then pressure-test it before you build:**\n\n- **Edge cases.** Which exist, which matter, which we can safely ignore.\n- **Failures.** Networks fail, that's a given. Retry? How many times, for how\n  long?\n- **Data.** How much, does it need migrating or cleaning, how do you get real\n  data to test against, and what are you assuming about its shape that you\n  haven't actually verified?\n- **Testing.** How will you know it's correct? Are automated tests enough or do\n  you need to poke at it by hand? Is the result something you can see in a\n  screenshot or a video?\n- **The bigger picture.** How does this get announced, how does it fit the\n  roadmap, can you even picture it shipped? If something there bothers you, push\n  back. Ask.\n\n**Then actually do it**, with precision, care, urgency and calm all at once. No\nhalf-assing. The bar before you merge: am I proud of this? Would I put it in\nfront of Steve Jobs and walk him through what I built, the constraints, the\ntradeoffs?\n\n**And then prove it works.** Not \"the tests pass\", prove it. In 99% of cases you\ncan confirm it yourself: run it, ask an agent to walk through the scenarios, poke\nat the data before and after, take a screenshot, make a demo. Are you actually\nsure it solves the problem you started with?\n\n**Then make sure it lands in production and works in production**, which is not\nthe same as merged. Did it deploy? Did the deploy quietly fail? Is there a flag\nto flip, and does the flag work? Can you use the thing in prod right now and\nconfirm it's really there?\n\n**And then close the loop with everyone it touches:**\n\n- **The team.** If it's a new feature, a new convention, or a tricky thing people\n  should know about, tell them. Don't underestimate peripheral vision. You\n  knowing that someone changed Z yesterday can save someone else three hours of\n  debugging tomorrow when a bug report about Z comes in.\n- **Customers.** Whoever reported it, whoever's blocked, let them know it's\n  fixed.\n- **The world.** If it's worth announcing, announce it.\n- **Future you.** Are there follow-ups? Should you check the logs in a week to\n  make sure it's still healthy?\n\nBut that's how we build a product in a small team. We don't have PMs, we don't\nhave a QA department. We're small, but we're great, and we can do all of that.\n\nAnd it's always okay to ask for help, it's okay to ask questions, it's okay to\nredo things and triple-check. What's not okay is to quietly assume someone else\nwill catch the parts you didn't think about.\n\n### The bar: autonomy, agency, ownership\n\nThis is what I require from the agent I work with. In my own words:\n\nEither you are performing or you are not. Either you are taking on high\nownership, are high agency and pushing without me having to micromanage you, or\nyou are not.\n\nEspecially in today's age you are limited by great talent more than ever. Because\nwe can all AGI Max, you have a single chokepoint on good judgement.\n\nEvery person that comes on the team has to have the capability to truly own\nsomething. If you have built products, you develop ownership because you owned\nsomething — whether it worked out or not — for a prolonged amount of time. You\ndevelop true agency.\n\nI hire you because I expect that if I am able to walk out of the room after\ngiving you a high level thing and come back, it's going to be done good, ideally\nbetter than I would've done it.\n\nMost people are shit at their jobs, some are decent/good, but only people who are\nexceptional should be at Kortix.\n\nBeing exceptional on paper is simple — it's a combination of Ownership, Agency\nand actual Merit/Skill. It's hard, because you have to not only be very smart but\nalso crazy driven to push like a motherfucker and want to feel every edge and\ncorner to make sure the output is good.\n\n## Skills live in `.agents/skills/`\n\nEvery repo skill is a directory in `.agents/skills/<name>/`. `.claude/skills/<name>` is a\nsymlink to it. Add a skill in `.agents/skills/`, then add the symlink. Third-party skills come\nfrom `npx skills add` and are pinned in `skills-lock.json`. The PR procedure is the\n**contributing** skill; the PR title and body are the **create-pr** skill. Browser work is the **agent-browser** skill.\n\nThe repository has no `docs/` tree. Put a runbook or spec in the skill that owns the\nsurface (`.agents/skills/<name>/references/`). Put an incident rule in the learnings\nledger. Put design detail and RCAs in the PR body. Never cite a repo path that does\nnot exist. The pre-commit hook and `tests/unit/no-docs-tree.test.ts` reject a new\n`docs/` file and any citation of one.\n\n## Ponytail is on by default\n\nEvery code change runs through the **ponytail** skill at level `full`. Load it before you\nwrite, fix, refactor, or review code, and before you add a dependency. Level switch:\n`/ponytail lite|full|ultra`. Off: \"stop ponytail\". **ponytail-review** audits a diff for\nover-engineering, **ponytail-audit** audits the whole repo, and **ponytail-debt** lists\nevery `ponytail:` shortcut comment. Ponytail cuts code, never the verification,\ndocumentation, or ownership bar in this file. The skills come from\n`DietrichGebert/ponytail`, pinned to `v4.10.0` in `skills-lock.json`.\n\n## Learnings: the episodic ledger in the `learnings` skill\n\n`.agents/skills/learnings/` is the append-only, timestamped ledger of rules paid\nfor with real downtime. `MEMORY.md` indexes it newest first, and each entry is\none file in `entries/`: the rule, the incident that taught it, and the\nautomation that enforces it. Search it before writing or reviewing a DB\nmigration, touching deploy/release workflows, planning a promote, or\nresponding to a prod incident. After resolving ANY incident or near-miss,\nrecord a new entry in the same session with `scripts/new-entry.sh`. An incident\nthat leaves no entry behind is not finished.\n\n## NEVER write customer data or PII into anything we publish or commit\n\nThis is a hard rule. No exceptions, no \"just this once\", no \"it's only\ninternal\".\n\n**Never write any of these:**\n\n- customer or company names, and the names of their people;\n- email addresses, phone numbers, or other personal data;\n- real account, project, session, user, or sandbox IDs from prod, staging, or\n  a customer deployment;\n- customer repository names, hostnames, or URLs that contain any of the above;\n- customer prompts, messages, files, or log lines;\n- screenshots of a real customer workspace.\n\n**Never write them into:**\n\n- commits, commit messages, or branch names;\n- PR titles, PR bodies, PR comments, or review comments;\n- issues;\n- code comments, test names, or test fixtures;\n- docs, runbooks, skills, `AGENTS.md`, changelogs, or release notes;\n- artifacts, Slack posts, or any public or team-visible text.\n\n**Write the class instead:** \"a customer reported\", \"an enterprise\nworkspace\", \"a prod session\", `<session_id>`. Build test data from synthetic\nvalues. Evidence that contains real data stays local: the gitignored\n`output/` folder, your scratchpad, or the private agent memory. It never goes\ninto a tracked file.\n\n**If you find customer data** in the tree or in a PR, remove it in the same\nbranch and say so. Do not rewrite history on `dev`. Report the SHA to the user\ninstead.\n\n**A guard enforces this on every commit and push.**\n`scripts/check-blocked-terms.sh` runs from `.githooks/pre-commit`,\n`.githooks/commit-msg`, and `.githooks/pre-push`. It refuses any added line,\ncommit message, or pushed branch name that contains a blocked term. Matching is\ncase-insensitive and whole-word. The list is itself customer data, so it lives\nencrypted in `apps/api/.env` as `BLOCKED_COMMIT_TERMS`, comma-separated.\n\n- Add a customer the day they sign: `dotenvx set BLOCKED_COMMIT_TERMS\n  \"<existing>,<new>\" -f apps/api/.env`. Read the current value first with\n  `dotenvx get BLOCKED_COMMIT_TERMS -f apps/api/.env`.\n- In a worktree the guard decrypts with the primary checkout's\n  `apps/api/.env.keys`. Without a key it warns and allows.\n- Deleting a line that contains a term is always allowed.\n- Never bypass the guard with `--no-verify`. If it fires, remove the term.\n- The hooks do not see PR titles, PR bodies, or comments. Those stay your\n  responsibility.\n\n## How to communicate: precise, technically accurate, no fluff\n\nWrite every response — chat, PR text, commit messages, code comments, docs — in\nthe spirit of **ASD-STE100 (Simplified Technical English)**. The goal is maximum\ntechnical precision with zero filler. Apply these rules:\n\n- **State facts, not vibes.** Every claim is specific and verifiable: name the\n  file, function, route, flag, SHA, status code, or number. No \"should work\",\n  \"probably\", \"a bunch of\", \"various\", \"seems fine\" — say what is true and how you\n  know, or say you do not know it yet.\n- **One idea per sentence.** Keep sentences short (aim ≤ 20 words) and each one\n  carries a single instruction or fact. Split compound thoughts instead of\n  chaining clauses.\n- **Active voice, present tense, direct.** \"The gate rejects the request\",\n  not \"the request may end up being rejected\". Give the instruction; do not\n  soften it.\n- **One term per concept.** Use the same word for the same thing every time —\n  do not alternate \"session\"/\"run\"/\"task\" for one concept. Match the codebase's\n  existing names exactly (`session_id`, not \"session ID / run id\").\n- **No filler, no hedging, no praise.** Cut \"basically\", \"just\", \"simply\",\n  \"I think\", \"great question\", \"as we know\", and marketing adjectives. Lead with\n  the answer; drop the throat-clearing.\n- **Quantify.** Prefer exact values over adjectives: \"up to ~9 min\", \"returns\n  `402`\", \"3 of 7 flows\", not \"slow\", \"an error\", \"most\".\n- **Show the evidence.** When you assert a behavior, cite the command you ran and\n  the real output. Distinguish verified fact from assumption explicitly.\n- **Structure over prose.** Use numbered/bulleted lists for steps, findings, and\n  status. Reserve paragraphs for genuine explanation, and keep them tight.\n- **Say the unknown plainly.** If something is unverified, blocked, or risky,\n  state it in one line — what, why, and what would resolve it — instead of\n  burying or omitting it.\n\nThis standard governs how you talk. It does not override the technical rules\nbelow; it is how you report on them.\n\n## First, at session start: which canonical branch are you in?\n\nEvery change belongs to **one canonical branch** — the branch for whatever is\nbeing worked on. One canonical branch, one worktree. Establish which one you are\nin before any non-trivial change. **Do not create a branch by reflex.**\n\n1. **Join the canonical branch that already exists** for this work. List them\n   with `git worktree list` and `git branch -r`. If the work continues, extends,\n   fixes, or cleans up something already in flight, it belongs on that branch.\n   Ask the user which branch when it is not obvious.\n2. **Start a new canonical branch** only when the work is genuinely a new thing.\n   Give it its own worktree: `pnpm worktree create --name <slug> --yes\n   --no-start`, then do all edits and runs under `../suna-<slug>`. Add `--db`\n   when the work needs migrations, destructive data work, or schema drift: it\n   copies the primary local database into the worktree's own database\n   (`kortix_wt_<slug>`, ~5 s, no extra containers). `--full-supabase` (a whole\n   Supabase stack, ~0.75 GB RAM) is only for independent auth/storage config.\n   See the **worktree** skill.\n3. **Never switch a worktree you did not create.** It belongs to one session and\n   its canonical branch. The pre-commit hook refuses a commit on any other branch\n   there (`scripts/check-worktree-branch.sh`). A throwaway probe branch goes in a\n   private `git worktree add` in your scratchpad.\n4. **The primary checkout** (`pnpm dev`, web `3000` / api `8008`) is for running\n   and investigating. Do not park feature work there.\n5. **Run `pnpm test` only from a worktree that has `.kortix-worktree.json`.** The\n   runner reads its ports from that marker. A bare `git worktree add` has none,\n   so its test stack takes the primary ports and talks to another session's\n   `pnpm dev` (2026-10-13: 156 flows failed with that stack's 503s).\n6. **Never kill a process you did not start.** Many sessions share this machine.\n   Kill by PID from your own process tree, or by a pattern that contains your\n   own worktree or scratchpad path. Never by a bare pattern such as\n   `pkill -f \"bun test\"`, and never in a loop. Need CPU? Lower your own\n   concurrency (`KE2E_API_WORKERS`, `KORTIX_DB_SUITE_WORKERS`) or ask the user.\n   2026-10-13: a guard loop that SIGTERMed every other session's `pnpm test`,\n   `tsc` and `vitest` every 20 s made 259 kills, and every suite on the machine\n   died with exit 143 and no failing test.\n\n**Pack more into one branch, not less.** A follow-up fix, a rename cleanup, a\nstale-reference sweep, and the change that caused them all belong on the same\nbranch and land together. Splitting one objective across several branches is how\na half-finished cutover reaches `dev` in pieces — each piece green alone, the\nwhole thing broken.\n\nSub-branches are allowed. Agents may cut working branches off the canonical\nbranch and merge back into it. **A sub-branch never opens a PR against `dev`.**\nOnly the canonical branch does.\n\nCarve-outs where you just proceed: read-only investigation and questions, and\ntrivial single-file typo/comment fixes on the current branch.\n\n## Default delivery: verify in your box, self-merge to `dev`, verify on dev\n\n`dev` is the dev trunk. A merge does not deploy: dev deploys only on a deliberate\ndispatch, but **merging to `dev` still lands your change in everyone's next\ndeploy and next `dev` checkout.** It is not a save point.\n\n### Testing philosophy: one gate at Promote to Production, everything else fast\n\nTesting reset, 2026-10-16. Latency is the core concern. Development must be\nfast enough to push and break things on `dev`. The full suite runs once per\nrelease, at Promote to Production, and nowhere else. The rules:\n\n1. **Never run a full suite during development, and never a whole package's\n   tests.** No multi-minute run on your box, on a PR into `dev`, or on a push\n   to `dev`. Verify only what you changed: T0 for the packages you touched,\n   the test files that cover the change, named one by one\n   (`pnpm --filter <pkg> test <file>`), and the change itself through its real\n   surface. Running all 2,129 `apps/web` tests to check one component is the\n   anti-pattern this rule exists for.\n2. **One gate: the release pull request into `prod`.** Promote to Production\n   runs the whole suite there, once, and every check must be green. It is the\n   only check that blocks anything.\n3. **Test at the boundaries users touch:** the HTTP API, the CLI, the\n   `@kortix/sdk` public surface, DB migrations, and a few browser journeys on\n   the money path. Never test internal calls or source text.\n4. **Run each suite once per tree.** A tree that ran is not run again.\n5. **A red suite is worse than none.** A test that fails twice without a bug is\n   fixed or deleted the same day. Do not park it in quarantine.\n6. **Delete on red.** A unit test that goes red on a refactor with no bug is\n   deleted, not updated. Keep unit tests only for logic that is hard to get\n   right: money and credits, parsers, the streaming protocol, permission\n   decisions, crypto and secrets.\n7. **Small, isolated tests.** Every test file runs standalone in seconds. When\n   the test that covers your change takes more than ~10 s, or drags in setup\n   that has nothing to do with it, split it into a smaller isolated test as\n   part of the same change. Fake the clock; never sleep on real time.\n\n| Tier | Contents | Runs |\n|---|---|---|\n| T0 static | `tsc`, lint, architecture checks, migration lint | anytime, seconds per package |\n| Targeted | the one test file or flow that covers your change | on your box, seconds |\n| Release suite | contract lanes (flows, DB, packages) on the release tree, the deployed smoke and the browser journeys against staging, CI, CodeQL, scanners | once per release, on the pull request into `prod` |\n\n| Where | What runs | Blocks? |\n|---|---|---|\n| Your box | T0 for the packages you touched, the targeted test, and the real surface: the route with curl, the CLI process, the page with agent-browser | your judgment |\n| Pull request into `dev` | nothing, unless a person adds `test` or `preview` | no |\n| Push to `dev` | only the seconds-long guards: `secret-scan`, `secrets-guard`, and path-gated `DB Migrations` / `i18n-catalogs` / `Terraform Apply Global` / `deploy-api-router-dev` | no |\n| Nightly on `dev` | `Tests` `core` and `packages` lanes, no browser: a red night comments the failures on the commit, so they are ready to fix before a promote | no |\n| Pull request into `staging` | nothing: Promote to Production opens and merges it | no |\n| Pull request into `prod` | the release suite, once (`full suite + quality gates`) | **yes, the only gate** |\n\n**A Mac never runs a suite.** On macOS `pnpm test` refuses a suite-wide run\n(plain, `--full`, `--packages-only`, `--browser-only`, `--db-only` with no path\nfilter) and exits `2`. 2026-10-16: five agent sessions ran the suite at once on\none laptop (load average 112, swap full). Run one flow\n(`pnpm test -- --id <ID>`), one DB suite path, or one package's test file.\n`KORTIX_FULL_TEST=1` overrides only when a person asks. Every package `test`\nscript refuses the same way: `pnpm --filter <pkg> test` with no file exits `2`\n(`scripts/test-guard.mjs`). Pass the file(s), `pnpm --filter <pkg> test <file>`,\nor a name filter after `--`: `pnpm --filter <pkg> test -- -t <name>`. CI and\nLinux run the full suite.\n\nNo hook or gate requires a test run before a push or a merge into `dev`. Write\nin the PR body what you ran and why. A diff that no test reads (docs, a skill,\na standalone script) needs no run. When the nightly run or the release suite\nturns red on your change, you fix forward on `dev` the same day.\n\n1. Work on the canonical branch in its worktree. Commit as often as you want.\n2. Verify in your box, with real inputs and outputs. Run T0 and the tests that\n   cover the change (see above). Start the worktree's stack\n   (`pnpm worktree start <slug>`) and drive the changed behavior through it: the\n   HTTP route, the real CLI process, or the page with agent-browser. Nothing\n   before the release suite at Promote to Production catches what you skip.\n3. Open the PR against `dev` and follow the **contributing** skill: it attaches\n   the demo video you recorded against your local stack. Write the title and\n   body with the **create-pr** skill: English only, for a human reviewer who\n   decides \"merge or iterate\" without reading the diff. Do not add `test` or `preview` unless you need that one explicit run.\n4. Merge `dev` into the canonical branch daily. A branch that diverges for weeks\n   detonates on merge exactly like a 1,500-line PR does.\n5. **Self-merge to `dev` when the change is verified. Do not wait for the\n   user's approval.** Speed matters: a verified change that sits unmerged is\n   waste. Verified means all of these are true:\n   - the relevant local checks ran with real inputs and outputs (rule 2), and\n     they passed;\n   - the PR is mergeable (no conflict);\n   - rule 6 holds when the change touches a client-facing runtime contract.\n   A failing check blocks the merge until you fix it or state why it is\n   unrelated (for example, the same test fails on `dev`). Squash-merge\n   (`gh pr merge <pr> --squash`), then finish rules 7 and 8. A merge is not\n   the end of the work: dev verification is still yours.\n   The only machine-enforced rule on `dev` and `staging` is that every change\n   arrives through a pull request — no required approvals, no required status\n   checks, no bypass actors. The bar is what you verified.\n   **The release gates do not change.** Merging into `staging` or `prod`,\n   running Promote to Production, and moving the `:stable` tag each need the\n   user's explicit approval (the **kortix-release** skill).\n6. **A change to a client-facing runtime contract** — the `@kortix/sdk` public\n   surface, session/thread transport, the streaming protocol — merges only after\n   the whole objective ran through a real session on your local stack, and runs\n   again on dev after the merge (rule 8). Green tests are not the bar. Someone\n   used it.\n7. After the merge, deploy dev yourself: `gh workflow run deploy-dev.yml\n   --repo kortix-ai/suna -f surface=changed` (`all` or `frontend` force a\n   rebuild). Deploys queue, they never cancel. Wait for the run to finish.\n   `/health` on every changed surface must serve the merge SHA: a successful\n   `/health` response alone is not deployment proof. The run comments \"Live on\n   dev\" on the merged pull request, and \"Not live on dev yet\" names the\n   surface that failed. The surfaces and their checks are in\n   `.github/workflows/deploy-dev.yml`. The merge push runs no suite. The\n   nightly `Tests` run on `dev` comments the failing lanes and every commit\n   since the last green run, once per new set of failing lanes. The author\n   whose commit broke `dev` fixes forward the same day. If `dev` is still red\n   1 hour after the comment, anyone may revert the culprit PR. The release\n   suite at Promote to Production runs the same lanes and blocks the release.\n8. Re-run the user-visible behavior against `https://dev.kortix.com` and/or\n   `https://dev-api.kortix.com`. Prefer the real Kortix CLI configured for the\n   dev API for CLI/project/session flows, and direct authenticated HTTP calls for\n   API contracts. For web behavior, drive the deployed UI and assert its network\n   request plus visible result.\n\nLocal verification and dev verification are both required. A local pass does\nnot replace dev, and a dev smoke test does not replace focused local tests.\nRecord the branch, PR, local commands and output, merge SHA, deploy run,\ndeployed SHA evidence, and the exact dev command or interaction in the final\nresponse.\n\n## Architecture: `@kortix/sdk` is the source of truth\n\n`@kortix/sdk` is the **single source of truth** for everything that talks to the\nKortix backend — projects, accounts, sessions, files, secrets, triggers, the\nsession runtime, the runtime REST client, SSE streaming, model state,\nand auth-token plumbing. The apps\n(`apps/web`, `apps/white-label/*`, `apps/mobile`) are **thin consumers**. Treat\nthese as standing rules whenever you touch the data/runtime layer:\n\n> **Editing `packages/sdk` itself? Load the **sdk** skill (the rules) first.** It is a\n> **published npm package** with its own hard rules that have no analogue\n> elsewhere in this repo: **TDD is mandatory** (failing test first, run it, watch\n> it fail, then implement — and every turn ends with the gates run, the real\n> output pasted, and an explicit shippable YES/NO/NOT YET); exported names\n> (including *types*) are a public API contract and renaming one is a breaking\n> change; the `version` field is inert and must never be bumped by hand; adding\n> an export requires three synchronized edits; and the framework-free core is\n> enforced by a static import-graph tripwire.\n\n- **Logic lives in the SDK, never in a host.** No raw `fetch` to the Kortix API,\n  no `@opencode-ai/sdk` imports, no transport / runtime / data-state code written\n  in app code. New data or runtime behavior is added to the SDK and exposed\n  through its public surface — not hand-rolled or duplicated in a host. If you\n  need something the SDK doesn't expose, add it to the SDK.\n- **One client per host.** Create it once via `createKortix({ backendUrl,\n  getToken })` and read everything through `@kortix/sdk` + `@kortix/sdk/react`.\n  Auth is just `getToken` — an API key / PAT for programmatic use, or a Supabase\n  JWT for the logged-in web app. Hosts never instantiate a second client.\n- **A whole session is one hook.** `useSession(projectId, sessionId)` owns the\n  entire runtime lifecycle — `/start`, the sandbox switch, the live SSE stream,\n  readiness seeding, immutable runtime identity, the native conversation id,\n  and message sync. Hosts don't\n  hand-roll the mount, drive a server-store \"switch\", or mount a separate event\n  provider.\n- **Session-scoped + provider-agnostic.** The public API is session-scoped\n  (`kortix.session(pid, sid).health() / .previewUrl() / .restart() / …`).\n  The sandbox provider and the harness are server-side concerns. Host code\n  must not implement a second transport.\n- **Build on the Kortix contract, not on OpenCode.** Every new session runs\n  pi inside kortixd. OpenCode is deprecated (2026-10-23): there is no\n  `pi_harness` flag, `runtime:` in `kortix.yaml` selects nothing, and OpenCode\n  serves only sessions that started on it. It will be deleted. Code that exists\n  only for OpenCode carries `@deprecated`: never call it from new code, never\n  add a feature to it, and change it only to fix a bug that breaks a running\n  OpenCode session. pi calls models only through the LLM gateway. pi serves\n  the daemon routes (`/kortix/runtime/*`), the transcript\n  (`kortix.transcript.v1`, `packages/api-contract/src/transcript.ts`) and the\n  events. New code reads those, never an OpenCode route, type, file or\n  process. A feature pi lacks is a capability, not a harness check:\n  `GET /kortix/health` lists `capabilities` (`RUNTIME_CAPABILITIES` in\n  `packages/api-contract/src/runtime-relay.ts`), and a client gates the\n  control with `runtimeSupports`. OpenCode lists all eleven (`session.steer`\n  only on OpenCode 1.18.15 or later); pi lists `session.subagents`,\n  `session.compact`, `session.commands` and `session.steer`. pi does not\n  serve rewind, MCP servers, the todo list, shell turns, part edits or\n  `session.attach`.\n  The harness rules and the pi gap list are in\n  `apps/kortix-sandbox-agent-server/src/harness/README.md`.\n- **`apps/web` data modules are shims.** Files such as\n  `apps/web/src/ui/index.ts`, `apps/web/src/lib/iam-client.ts`, and\n  `apps/web/src/hooks/admin/use-*.ts` are thin re-exports\n  (`export * from '@kortix/sdk/...'`).\n  Keep them as shims; put the real logic in the SDK. When a merge conflict lands\n  on one of these, **keep the shim (`--ours`) and port any new host-side logic\n  into the SDK** — do not revert to a host-local implementation.\n- **Docs are the spec.** `apps/web/content/docs/sdk/*` and\n  `packages/sdk/README.md` describe the intended surface. Keep them current with\n  the SDK, and flag legacy/deprecated surfaces in-doc rather than documenting them\n  as current.\n\n## You CAN run and verify everything end-to-end. Do it.\n\nThis repo ships a **complete, runnable local stack with live cloud sandboxes**.\nDo not claim you \"can't verify from here\" or hand back unverified work — you\nhave everything needed to run the app, hit the real API, provision real\nPlatinum sandboxes, drive the real UI in a browser, and assert behavior. Use it.\n\n### Required verification standard — real inputs, real outputs\n\nFor every behavior change, assume **100% autonomy** to verify the user-visible\ncontract before handing the work back. Do not stop at typechecks, unit tests, or\nmocked internals when a real surface exists.\n\n- **API changes:** exercise the actual HTTP route with real request payloads\n  (`curl`, `bun fetch`, or the `ke2e` runner against a running API). Assert the\n  status code and exact response fields that prove the behavior. For writes,\n  also assert the persisted/read-back state or resulting repo/file output.\n- **CLI changes:** run the real CLI command as a process from bash, with the\n  same flags and stdin a user or agent would use. Assert exit code, stdout,\n  stderr, and any files/API calls/commits it should create. Do not rely only on\n  importing command functions.\n- **Web changes:** drive the real page with **agent-browser** (the primary\n  browser; `agent-browser skills get core` loads its guide). Click/type/toggle\n  the actual controls, observe the network request (`agent-browser network`),\n  and assert the visible UI state plus the outgoing payload. Record the flow\n  (`agent-browser record start`) for the PR's demo video. Screenshots and video\n  are evidence, but assertions on DOM and network data are required.\n- **Cross-surface features:** verify each exposed surface independently. If the\n  same feature ships on API + CLI + web + mobile, each gets its own black-box\n  assertion for the inputs users can make and the outputs they receive.\n- **Default/negative paths count:** when changing defaults or removing implicit\n  behavior, assert both the new default and the explicit opt-in/alternate path.\n- **No silent gaps:** if a surface cannot be fully exercised in the current\n  turn, say exactly which input/output remains unverified and why. Otherwise\n  keep going until the real surface is verified.\n- **Final response format:** when work is finished, answer per the **How to\n  communicate** standard at the top of this file — numbered/bulleted, no fluff.\n  Include exactly what changed, what was verified (with the command + output),\n  what remains unverified or risky, and what the user should test next. Do not\n  bury the actionable testing path in a paragraph.\n\n### The stack (already wired)\n\n- **Web** — Next.js dev server on `http://localhost:3000`.\n- **API** — Bun server on `http://localhost:8008/v1` (`/health` returns JSON).\n- **Supabase** — local, on `http://127.0.0.1:54321` (Docker).\n- **Sandboxes** — REAL cloud sandboxes on Platinum, the reference provider\n  (`PLATINUM_API_KEY` in `apps/api/.env` / `.env.local`). Daytona is a legacy\n  fallback with removal scheduled and no support guarantee; E2B support is\n  discontinued. Each project\n  session gets its own sandbox; `session_id == sandbox_id`. The sandbox daemon is\n  reached through `http://localhost:8008/v1/p/<external_id>/8000/...`.\n  The session runtime answers on the same proxy, under `/kortix/runtime/*`,\n  on both harnesses.\n- **Tunnel** — `scripts/dev-local.sh` (`pnpm dev`) auto-starts a cloudflared\n  quick tunnel so cloud sandboxes can call back to the local API (`KORTIX_URL`).\n\nBring it up with `pnpm dev` from `suna/` (it loads `apps/api/.env` +\n`apps/web/.env`, starts Supabase, the API, the web app, and the tunnel). Check\nwhat's already running before starting a duplicate: `curl -s\nlocalhost:8008/v1/health`, `lsof -iTCP:3000 -sTCP:LISTEN`.\n\n> **Secrets are dotenvx-encrypted (mandatory).** `apps/api/.env` (+ `.env.dev`)\n> are committed as ciphertext (`KEY=encrypted:…`); keys live in Dotenv Armor.\n> **Never write a plaintext secret into a tracked file or commit** — add/change\n> values only via `dotenvx set KEY value -f apps/api/.env` (then commit), read\n> with `dotenvx get`, and machine-local overrides go in the gitignored\n> `apps/api/.env.local`. If the user pastes a key, store it with `dotenvx set`,\n> never paste it raw. A pre-commit hook + GitHub push protection enforce this —\n> don't bypass them. Full procedure: the **dotenvx-secrets** skill.\n\n### Authenticating to the live API (for scripts/tests)\n\nMint a real JWT against local Supabase, then call the API with it:\n\n1. `SUPABASE_SERVICE_ROLE_KEY` lives in `apps/api/.env`; the anon key\n   (`NEXT_PUBLIC_SUPABASE_ANON_KEY`) in `apps/web/.env`.\n2. Create a confirmed user: `POST 127.0.0.1:54321/auth/v1/admin/users`\n   (`apikey` + `Authorization: Bearer <service_role>`, body\n   `{email,password,email_confirm:true}`).\n3. Password-grant for the token: `POST\n   127.0.0.1:54321/auth/v1/token?grant_type=password` (`apikey: <anon>`).\n4. Call the API: `Authorization: Bearer <access_token>` against\n   `localhost:8008/v1` (e.g. `/accounts`, `/projects/provision`,\n   `/projects/:id/sessions`, `/p/<ext>/8000/...`).\n\nSee `tests/e2e/helpers/session-auth.ts` for the exact calls.\n\n### One local testing system\n\n- `pnpm test` is the only repository-level test command. It runs local REST and\n  CLI flows, SDK tests, PostgreSQL-backed suites (`db-suites`), runner unit\n  tests, route coverage, and worktree tests concurrently.\n- `pnpm test -- --id ACC-4` runs one flow. `--domain access` runs one domain.\n- `pnpm test -- --sdk-only` runs only `packages/sdk` tests.\n- `pnpm test -- --db-only [path-filter]` runs only the PostgreSQL-backed suites\n  (`integration-*.test.ts`, `*.integration.test.ts`, `tests/migration`), each\n  file against its own fresh migrated database. A skipped DB suite fails.\n- `pnpm test -- --browser-only` runs Playwright browser journeys. It starts the\n  deterministic local stack.\n- Browser runs use two Playwright workers, locally and in each CI shard.\n- `pnpm test -- --packages-only` runs every app/package test and publish check.\n- `pnpm test -- --full` adds browser journeys and every app/package test. It\n  starts the deterministic local stack.\n- `pnpm test -- --target-smoke` verifies the deployed staging API and gateway\n  SHA, then runs the tagged Playwright staging smoke. Release CI supplies the\n  staging credentials and `RELEASE_SOURCE_SHA`.\n- `pnpm test -- --target-full` verifies the same deployed SHA, then runs every\n  configured staging REST, CLI, and Playwright journey. The production release\n  gate uses this command and fails on any excluded API flow.\n- Browser and full modes reuse only a running API that proves the deterministic\n  test profile. Stop an ordinary development stack before either command.\n- Every root run writes lane and total timings to\n  `tests/test-results/local/benchmark-<timestamp>.json`.\n- Before merging into `dev`, run T0 and the tests that cover your change (the\n  testing philosophy above). On a Mac, suite-wide modes exit `2`.\n- Every Linux CI job runs on a free GitHub-hosted runner (`ubuntu-24.04`,\n  `ubuntu-24.04-arm`, `ubuntu-22.04`; 4 vCPU / 16 GB on this public repo) through\n  `runs-on: ${{ vars.CI_RUNNER_<tier> || '<label>' }}`. Setting a\n  `CI_RUNNER_<tier>` repository variable moves that tier to another pool\n  without a PR. Never default a job to a paid runner (Blacksmith, a GitHub\n  larger runner): Blacksmith billed ~$2.7k in September 2026 for minutes\n  GitHub gives this repo for free. `tests/unit/image-build-speed-workflow.test.ts`\n  enforces the free default.\n- `.github/workflows/tests.yml` holds the six contract lanes — `core-flows`,\n  `core-db`, `packages-api`, `packages-cli`, `packages-kortixd`,\n  `packages-rest` — one GitHub-hosted runner each (`CI_RUNNER_L`). `core-*`\n  split the core run (`--core-group=flows|db`); `packages-*` are slices of\n  package quality (`--packages-only --package-group=<g>`). The concurrency\n  settings in `tests/bin/package-quality.ts` must not be raised. Each lane is\n  the unchanged root command at the exact requested SHA. Do not add CI-only\n  test logic.\n- The contract lanes run nightly on `dev`, once when a person adds the `test`\n  label to a pull request into `dev`, on dispatch, and inside the release\n  suite (`tests-release.yml` calls `tests.yml`). A push to `dev` and the pull\n  request into `staging` run nothing. A red nightly run comments the failing\n  lanes and suspect commits on the commit, once per new set of failing lanes.\n- The release suite (`tests-release.yml`, on the release PR into `prod`) runs\n  the contract lanes, the deployed API smoke (the flows tagged `deployed`, one\n  or two per deployment seam) and the browser journeys against deployed\n  staging, in parallel. `full suite + quality gates` requires all of them and\n  every other check on the release commit. A browser spec exists only for a\n  money-path journey (sign-in, onboarding, session load and streaming, desktop\n  parity, billing, connectors, invites); everything else is a REST flow or\n  nothing. CI records a video of every browser journey in the run's\n  `tests-release-browser-shard-N` artifact. A failed release-suite run reruns\n  its failed jobs once by itself (`rerun-failed-once.yml`): a job that passes\n  on the rerun is a flake to fix or delete.\n- `tests/unit/sandbox-workflow.test.ts` fails when any workflow except the\n  label-gated `tests.yml` and `deploy-preview.yml` triggers on a pull request\n  into `dev`, and pins both label gates to the label-added event.\n- The deployed half of the release suite (`--target-api-full`,\n  `--target-browser-full`) waits up to 15 min for staging to serve\n  `RELEASE_SOURCE_SHA`, then fails when any `deployed` flow is excluded or any\n  configured Playwright journey fails.\n- The `preview` label is not part of the development flow. Adding it deploys a\n  Platinum self-host environment for the PR (~7 min), once, and runs no tests.\n  A push does not redeploy. Only `gh workflow run deploy-preview.yml -f\n  pr_number=<N>` runs `--target-full` against a preview (40–80 min). Its mechanics are\n  in the **contributing** skill (`references/preview-environments.md`). Run a\n  preview of your change on your worktree's local stack by default.\n\n### Product flow source of truth\n\n- `tests/spec/end-to-end.md` contains the natural-language contract and stable\n  flow IDs.\n- `tests/src/flows/*.flow.ts` implements the contracts through HTTP and real CLI\n  processes. Do not import API handlers.\n- Write each `ctx.step()` as a complete action and observable result. Cover\n  setup, authentication, action, read-back proof, negative paths, and cleanup.\n- Keep every flow's `meta.routes` synchronized with\n  `tests/spec/routes.generated.json`. Regenerate the manifest with\n  `bun run apps/api/scripts/dump-routes.ts` after route changes.\n- The local profile uses local Supabase, PostgreSQL, API, gateway, and bare Git\n  repositories. It excludes Stripe, cloud sandboxes, managed GitHub repositories,\n  and external email delivery explicitly.\n- Use Playwright only for browser-visible behavior. API-only assertions belong\n  in REST flows. SDK tests remain in `packages/sdk`.\n- Do not add another cross-cutting test harness, Makefile lane, contract suite,\n  Testcontainers suite, load suite, mutation suite, visual suite, accessibility\n  suite, or ad hoc smoke script under `tests/`.\n- Read `tests/README.md` and the repository `testing` skill before changing the\n  test system.\n\n### Release topology — dev, staging, prod\n\n- **`dev` = dev trunk.** It is the repo default branch and deploys to\n  `dev.kortix.com` / `dev-api.kortix.com`. Direct pushes are allowed; breaking or\n  incomplete development can live here while it is being shaken out.\n- **`staging` = release-candidate branch.** Nothing should land on staging unless\n  it is intended to be production-ready. Promote to Production merges the\n  `dev` HEAD into staging itself (the PR runs no check), deploys it, and opens\n  the release PR whose release suite is the one gate. Staging is the deploy\n  target that suite runs against, not a test stage. Staging\n  deploys to `staging.kortix.com` / `staging-api.kortix.com` and must use the\n  staging data plane, not dev or prod. The full promote-and-gate flow, the\n  fix-forward loop, and the prod release steps live in the **kortix-release**\n  skill — that is the one place this is written out in full.\n- Staging deploys must apply pending DB migrations against `STAGING_DATABASE_URL`\n  before the staging ECS Fargate rollout. If that secret is missing or points at\n  dev/prod, treat the deploy as broken; staging must never fall back to dev,\n  KE2E, or prod DBs.\n- **`prod` = production.** Production moves only through **Promote to Production**,\n  which uses `staging` as the source, opens a reviewed release PR into `prod`,\n  publishes the release artifacts, and rolls production after merge.\n- If a staging runtime check points at `dev.kortix.com` or\n  `dev-api.kortix.com`, treat that as a broken staging setup, not a passing\n  staging gate.\n\n### Driving the real UI (agent-browser)\n\n- **agent-browser is the browser for every agent task in this repo.** Use it\n  before chrome-devtools MCP, Playwright MCP, or any other built-in browser\n  tool. Playwright stays the engine of the committed browser test suite only.\n- The installed skill is a stub. Load the guide that matches the CLI version:\n  `agent-browser skills get core` (`--full` adds the command reference).\n- Use one named session per worktree:\n  `agent-browser session id --scope worktree --prefix <task>`, then pass\n  `--session <id>` on every command. Keep every `AGENT_BROWSER_*` variable the\n  same for all commands in a session. A per-command change relaunches the\n  browser and drops the open page.\n- Routes are auth-gated (`/dashboard`, `/projects/*` → redirect to `/auth`\n  unauthenticated); sign in first (seed a user as above, then log in via the\n  `/auth` form). On a PR preview, the magic link arrives in the preview's Mailpit API\n  (`<origin>/_mailpit/api/v1`)\n  (the **contributing** skill has the script).\n- Next.js dev compiles routes on first hit — first navigation to a cold route\n  can take 30–60s; warm it with `curl` or use a generous navigation timeout.\n- `agent-browser doctor` diagnoses launch and recording problems. Recording\n  needs ffmpeg with libvpx and libx264.\n\n### API lint gate\n\n- `pnpm --filter kortix-api lint` runs in the `Tests` packages lane. Its rules\n  are in `apps/api/eslint.config.mjs`: layered imports, no Drizzle in route\n  files, no `(c: any)`, no `process.env` outside `config.ts`, no\n  `console.*`, a `replica-local:` comment on every empty module-level\n  `Map`/`Set`, and no import into `projects/` from outside it except\n  `projects/index.ts` or `projects/surface.ts`. Background timers are guarded by\n  `apps/api/src/__tests__/unit-worker-scope-wiring.test.ts` instead.\n\n### API code conventions (`apps/api/src`)\n\n- **Routes register explicitly.** A route module exports\n  `register<Name>Routes()`. The module that mounts the router calls it right\n  before `app.route()` (`app.ts`, `accounts/index.ts`, `router/index.ts`).\n  Never register at import time. Call order is dispatch order.\n- **No service imports a route module.** Shared logic lives in a service file\n  next to the routes (for example `projects/session-open/`).\n- **Services take an `Actor`, never a Hono `Context`.** The route reads the\n  request through `middleware/` and `http-*.ts` helpers, builds the actor\n  (`middleware/actor.ts`, memoized per request), and calls the service with\n  plain values. Parse `Authorization: Bearer` with `shared/bearer-token.ts`.\n- **Every background loop lives in `workers/<name>-worker.ts`.** The worker\n  owns its timer, start/stop and `runWorkerTick` call. The tick stays an\n  exported service function. `bootstrap.ts` imports every loop from `workers/`.\n- **`iam/` reads the membership, group and role tables.** Use the read models\n  in `iam/membership-read.ts`, `iam/group-read.ts` and `iam/role-read.ts`.\n- **No function longer than 300 lines.** Split long functions into named steps.\n- **SQL trace:** `KORTIX_SQL_TRACE=<file>` appends every pool statement to the\n  file. Diff two traces to prove a refactor runs the same SQL.\n- `apps/api/eslint-suppressions.json` holds the violations that existed when\n  each rule was added. A new violation fails. A fixed one fails until you run\n  `pnpm --filter kortix-api lint:prune` and commit the smaller file. Never\n  absorb a new violation with `--suppress-all`.\n\n### Frontend type/lint gate\n\n- Typecheck `apps/web` with `pnpm --filter Kortix-Computer-Frontend typecheck`\n  (tsgo, incremental). Never run raw `tsc --noEmit` there. Measured on\n  2026-10-21 on a 24 GB Mac: raw `tsc` ran 817 s, held 3.18 GB, and was\n  killed before it finished. Cold `tsgo` takes 12.1 s at 5.0 GB peak. A warm\n  incremental run takes 1.1 s at 1.7 GB.\n- On 2026-10-21 the tsgo script reported 0 errors on `dev`. The bar is\n  relative: a branch adds no type error to the files it touches. Compare against `origin/dev` with the same command, not\n  against a number in this file.\n  The old ~1500 `TS2786` / `IntrinsicAttributes` noise from a React 19↔18\n  types mismatch (two copies of `@types/react` in one program — `packages/sdk`\n  had its own) is gone as of the Next 16 upgrade. If `TS2786` appears again,\n  treat it as a genuine duplicate-`@types/react` regression and investigate —\n  do not wave it through.\n- `npx eslint <files>` should be clean of errors. `eslint .` across the whole\n  app currently reports ~455 warnings, mostly `react-hooks/*` React Compiler\n  rules pending a dedicated audit — expected until that audit lands.\n\n### Frontend design standard — Jay/Kortix bar\n\n#### Canonical design references\n\n| Surface | Read before changing UI | Implemented source |\n|---|---|---|\n| Web (`apps/web`) | `.agents/skills/kortix-brand/SKILL.md` (the router: values, voice, claims, decision history), then `.agents/skills/kortix-design-system/SKILL.md` for components | `apps/web/src/app/globals.css` tokens (generated from `.agents/skills/kortix-brand/references/visual/visual-system.json`), `packages/ui/src/primitives/` (apps/web imports each one as `@kortix/ui/<name>`; apps/web has no re-export shims, `tests/unit/web-no-ui-shims.test.ts` enforces it), the live `/design-system` route |\n| `@kortix/ui` hosts (Kortix Apps, `apps/white-label/reskin`, `apps/white-label/lumen`, `apps/white-label/team-console`, SDK customer apps) | `packages/ui/skills/kortix-ui/SKILL.md` (setup, which component, white-label theming, the `SessionThread` chat), then the web row for Kortix-owned surfaces | `packages/ui/src/` (components, `session/` for the `SessionThread` chat, `styles/tokens.css` + `theme.css`, prebuilt `dist/styles.css`); white-label = CSS variable overrides, never a forked component |\n| Desktop (Electron shell) | The web row — the shell renders `apps/web` — plus `apps/desktop-electron/README.md` for the shell boundary, then the parity gate below | `apps/web` rendered by the shell; native window geometry in the shell's titlebar classes |\n| Mobile (`apps/mobile`) | `apps/mobile/design.md` for screens, `apps/mobile/AGENTS.md` for primitives | `apps/mobile/global.css` colors, `apps/mobile/components/ui/`; stock Tailwind spacing for touch targets |\n\n#### Desktop parity is a UI gate\n\nThe Electron app loads `apps/web`. Keep product components, routes, tokens,\nand data behavior shared. Put native window geometry in the shell's explicit\ntitlebar classes. Never apply titlebar height or drag rules to generic ARIA\nroles, all sidebars, or page content.\n\nFor every shared UI change, verify the affected controls on web and in Electron\nbefore handoff. Check the outgoing request or route and the visible result.\nCheck both themes, the minimum supported window (720 × 480), sidebar collapse,\nfullscreen overlays, and browser zoom. Window controls must not overlap app\ncontrols. Lists must not overlap or clip their last row. Keyboard focus and\nscrolling must remain usable.\n\nAdd regressions to the existing Playwright journeys. The desktop journey runs\nin `pnpm test -- --browser-only` and supports the actual Electron shell:\n`E2E_DESKTOP_NATIVE=1 E2E_GREP='27 — desktop parity' pnpm test -- --browser-only`.\nRun the native journey when changing shell CSS, navigation, settings, agents,\nor connectors. A desktop user-agent test does not prove native hit testing.\nReport any unverified desktop behavior explicitly. Do not promise that tests\nprevent every future regression.\n\nWhen touching any visual surface in `apps/web`, treat brand fit as a release\ngate, not polish:\n\n- Read `.agents/skills/kortix-brand/SKILL.md` before writing the first\n  `className` or the first user-facing string. It routes you to the value law:\n  the complete allowlist of every color, spacing step, type rung, radius,\n  elevation, and duration you may use. Note `--spacing: 0.23rem` — Tailwind's\n  scale is 8% tighter here, so a 16px mockup padding is `p-4`, never\n  `p-[16px]`. Run `.agents/skills/kortix-brand/scripts/audit.sh` over your\n  changed paths before opening the PR; it must be clean on files you touched.\n  `references/visual/visual-system.json` is the only file with values: edit it,\n  then run `scripts/generate-tokens.ts`. Never edit a generated region of\n  `globals.css` by hand.\n- Read `.agents/skills/kortix-design-system/SKILL.md` next and compose existing\n  primitives from `@kortix/ui/*` before inventing local chrome.\n- Match the current Jay Suthar / Kortix product aesthetic: calm neutral surfaces,\n  dense-but-legible UI, black/white plus one earned accent, token-driven spacing,\n  and no decorative color, glow, or one-off rounded boxes.\n- Use recent product surfaces as references before editing: `/design-system`,\n  `packages/ui/src/workspace/layout/project-home.tsx`,\n  `apps/web/src/components/ui/wallpaper-background.tsx`, and the account/IAM\n  screens called out by the design-system skill.\n- Verify visual work in the browser and include the exact lint/typecheck commands\n  you ran in the PR. If it does not look native beside Jay-authored UI, keep\n  iterating before shipping.\n\n<!-- File: CLAUDE.md (Tokens: ~12595 | Category: claude-rule) -->\n# Kortix project\n\n## What \"ownership\" means\n\nThe words \"can you own this?\", \"are you on it?\", and \"can you take care of this?\"\nall mean the same thing: you are 100% responsible. The person who handed it to\nyou must be able to walk completely away, come back a week later, and find it\ndone properly — because you cared about every edge and corner.\n\n- it's not done if it's not implemented\n- it's not done if the implementation is ugly\n- it's not done if it's not documented\n- it's not done if users can't discover it\n- it's not done if you can't market it\n\nOwning something means owning it end to end. The whole arc from \"we have a\nproblem\" to \"nobody has to think about this again.\" Not just the code change in\nthe middle, the whole thing. If someone hands you something and still has to\ntrack whether it actually got solved, you didn't own it.\n\nHere's what that actually looks like in practice.\n\n**Start with the problem, not the solution.** A lot of the time you'll already\nhave a fix in your head before you've understood what's broken. \"We need to move\nfrom X to Y\" isn't a problem, that's a solution you've pre-committed to. The real\nproblem is probably \"it's slow\", \"it's flaky\", \"it breaks for this customer\".\nName that first. Then ask what else could solve it, what the tradeoffs are, and\nwhich option actually wins.\n\n**Then pressure-test it before you build:**\n\n- **Edge cases.** Which exist, which matter, which we can safely ignore.\n- **Failures.** Networks fail, that's a given. Retry? How many times, for how\n  long?\n- **Data.** How much, does it need migrating or cleaning, how do you get real\n  data to test against, and what are you assuming about its shape that you\n  haven't actually verified?\n- **Testing.** How will you know it's correct? Are automated tests enough or do\n  you need to poke at it by hand? Is the result something you can see in a\n  screenshot or a video?\n- **The bigger picture.** How does this get announced, how does it fit the\n  roadmap, can you even picture it shipped? If something there bothers you, push\n  back. Ask.\n\n**Then actually do it**, with precision, care, urgency and calm all at once. No\nhalf-assing. The bar before you merge: am I proud of this? Would I put it in\nfront of Steve Jobs and walk him through what I built, the constraints, the\ntradeoffs?\n\n**And then prove it works.** Not \"the tests pass\", prove it. In 99% of cases you\ncan confirm it yourself: run it, ask an agent to walk through the scenarios, poke\nat the data before and after, take a screenshot, make a demo. Are you actually\nsure it solves the problem you started with?\n\n**Then make sure it lands in production and works in production**, which is not\nthe same as merged. Did it deploy? Did the deploy quietly fail? Is there a flag\nto flip, and does the flag work? Can you use the thing in prod right now and\nconfirm it's really there?\n\n**And then close the loop with everyone it touches:**\n\n- **The team.** If it's a new feature, a new convention, or a tricky thing people\n  should know about, tell them. Don't underestimate peripheral vision. You\n  knowing that someone changed Z yesterday can save someone else three hours of\n  debugging tomorrow when a bug report about Z comes in.\n- **Customers.** Whoever reported it, whoever's blocked, let them know it's\n  fixed.\n- **The world.** If it's worth announcing, announce it.\n- **Future you.** Are there follow-ups? Should you check the logs in a week to\n  make sure it's still healthy?\n\nBut that's how we build a product in a small team. We don't have PMs, we don't\nhave a QA department. We're small, but we're great, and we can do all of that.\n\nAnd it's always okay to ask for help, it's okay to ask questions, it's okay to\nredo things and triple-check. What's not okay is to quietly assume someone else\nwill catch the parts you didn't think about.\n\n### The bar: autonomy, agency, ownership\n\nThis is what I require from the agent I work with. In my own words:\n\nEither you are performing or you are not. Either you are taking on high\nownership, are high agency and pushing without me having to micromanage you, or\nyou are not.\n\nEspecially in today's age you are limited by great talent more than ever. Because\nwe can all AGI Max, you have a single chokepoint on good judgement.\n\nEvery person that comes on the team has to have the capability to truly own\nsomething. If you have built products, you develop ownership because you owned\nsomething — whether it worked out or not — for a prolonged amount of time. You\ndevelop true agency.\n\nI hire you because I expect that if I am able to walk out of the room after\ngiving you a high level thing and come back, it's going to be done good, ideally\nbetter than I would've done it.\n\nMost people are shit at their jobs, some are decent/good, but only people who are\nexceptional should be at Kortix.\n\nBeing exceptional on paper is simple — it's a combination of Ownership, Agency\nand actual Merit/Skill. It's hard, because you have to not only be very smart but\nalso crazy driven to push like a motherfucker and want to feel every edge and\ncorner to make sure the output is good.\n\n## Skills live in `.agents/skills/`\n\nEvery repo skill is a directory in `.agents/skills/<name>/`. `.claude/skills/<name>` is a\nsymlink to it. Add a skill in `.agents/skills/`, then add the symlink. Third-party skills come\nfrom `npx skills add` and are pinned in `skills-lock.json`. The PR procedure is the\n**contributing** skill; the PR title and body are the **create-pr** skill. Browser work is the **agent-browser** skill.\n\nThe repository has no `docs/` tree. Put a runbook or spec in the skill that owns the\nsurface (`.agents/skills/<name>/references/`). Put an incident rule in the learnings\nledger. Put design detail and RCAs in the PR body. Never cite a repo path that does\nnot exist. The pre-commit hook and `tests/unit/no-docs-tree.test.ts` reject a new\n`docs/` file and any citation of one.\n\n## Ponytail is on by default\n\nEvery code change runs through the **ponytail** skill at level `full`. Load it before you\nwrite, fix, refactor, or review code, and before you add a dependency. Level switch:\n`/ponytail lite|full|ultra`. Off: \"stop ponytail\". **ponytail-review** audits a diff for\nover-engineering, **ponytail-audit** audits the whole repo, and **ponytail-debt** lists\nevery `ponytail:` shortcut comment. Ponytail cuts code, never the verification,\ndocumentation, or ownership bar in this file. The skills come from\n`DietrichGebert/ponytail`, pinned to `v4.10.0` in `skills-lock.json`.\n\n## Learnings: the episodic ledger in the `learnings` skill\n\n`.agents/skills/learnings/` is the append-only, timestamped ledger of rules paid\nfor with real downtime. `MEMORY.md` indexes it newest first, and each entry is\none file in `entries/`: the rule, the incident that taught it, and the\nautomation that enforces it. Search it before writing or reviewing a DB\nmigration, touching deploy/release workflows, planning a promote, or\nresponding to a prod incident. After resolving ANY incident or near-miss,\nrecord a new entry in the same session with `scripts/new-entry.sh`. An incident\nthat leaves no entry behind is not finished.\n\n## NEVER write customer data or PII into anything we publish or commit\n\nThis is a hard rule. No exceptions, no \"just this once\", no \"it's only\ninternal\".\n\n**Never write any of these:**\n\n- customer or company names, and the names of their people;\n- email addresses, phone numbers, or other personal data;\n- real account, project, session, user, or sandbox IDs from prod, staging, or\n  a customer deployment;\n- customer repository names, hostnames, or URLs that contain any of the above;\n- customer prompts, messages, files, or log lines;\n- screenshots of a real customer workspace.\n\n**Never write them into:**\n\n- commits, commit messages, or branch names;\n- PR titles, PR bodies, PR comments, or review comments;\n- issues;\n- code comments, test names, or test fixtures;\n- docs, runbooks, skills, `AGENTS.md`, changelogs, or release notes;\n- artifacts, Slack posts, or any public or team-visible text.\n\n**Write the class instead:** \"a customer reported\", \"an enterprise\nworkspace\", \"a prod session\", `<session_id>`. Build test data from synthetic\nvalues. Evidence that contains real data stays local: the gitignored\n`output/` folder, your scratchpad, or the private agent memory. It never goes\ninto a tracked file.\n\n**If you find customer data** in the tree or in a PR, remove it in the same\nbranch and say so. Do not rewrite history on `dev`. Report the SHA to the user\ninstead.\n\n**A guard enforces this on every commit and push.**\n`scripts/check-blocked-terms.sh` runs from `.githooks/pre-commit`,\n`.githooks/commit-msg`, and `.githooks/pre-push`. It refuses any added line,\ncommit message, or pushed branch name that contains a blocked term. Matching is\ncase-insensitive and whole-word. The list is itself customer data, so it lives\nencrypted in `apps/api/.env` as `BLOCKED_COMMIT_TERMS`, comma-separated.\n\n- Add a customer the day they sign: `dotenvx set BLOCKED_COMMIT_TERMS\n  \"<existing>,<new>\" -f apps/api/.env`. Read the current value first with\n  `dotenvx get BLOCKED_COMMIT_TERMS -f apps/api/.env`.\n- In a worktree the guard decrypts with the primary checkout's\n  `apps/api/.env.keys`. Without a key it warns and allows.\n- Deleting a line that contains a term is always allowed.\n- Never bypass the guard with `--no-verify`. If it fires, remove the term.\n- The hooks do not see PR titles, PR bodies, or comments. Those stay your\n  responsibility.\n\n## How to communicate: precise, technically accurate, no fluff\n\nWrite every response — chat, PR text, commit messages, code comments, docs — in\nthe spirit of **ASD-STE100 (Simplified Technical English)**. The goal is maximum\ntechnical precision with zero filler. Apply these rules:\n\n- **State facts, not vibes.** Every claim is specific and verifiable: name the\n  file, function, route, flag, SHA, status code, or number. No \"should work\",\n  \"probably\", \"a bunch of\", \"various\", \"seems fine\" — say what is true and how you\n  know, or say you do not know it yet.\n- **One idea per sentence.** Keep sentences short (aim ≤ 20 words) and each one\n  carries a single instruction or fact. Split compound thoughts instead of\n  chaining clauses.\n- **Active voice, present tense, direct.** \"The gate rejects the request\",\n  not \"the request may end up being rejected\". Give the instruction; do not\n  soften it.\n- **One term per concept.** Use the same word for the same thing every time —\n  do not alternate \"session\"/\"run\"/\"task\" for one concept. Match the codebase's\n  existing names exactly (`session_id`, not \"session ID / run id\").\n- **No filler, no hedging, no praise.** Cut \"basically\", \"just\", \"simply\",\n  \"I think\", \"great question\", \"as we know\", and marketing adjectives. Lead with\n  the answer; drop the throat-clearing.\n- **Quantify.** Prefer exact values over adjectives: \"up to ~9 min\", \"returns\n  `402`\", \"3 of 7 flows\", not \"slow\", \"an error\", \"most\".\n- **Show the evidence.** When you assert a behavior, cite the command you ran and\n  the real output. Distinguish verified fact from assumption explicitly.\n- **Structure over prose.** Use numbered/bulleted lists for steps, findings, and\n  status. Reserve paragraphs for genuine explanation, and keep them tight.\n- **Say the unknown plainly.** If something is unverified, blocked, or risky,\n  state it in one line — what, why, and what would resolve it — instead of\n  burying or omitting it.\n\nThis standard governs how you talk. It does not override the technical rules\nbelow; it is how you report on them.\n\n## First, at session start: which canonical branch are you in?\n\nEvery change belongs to **one canonical branch** — the branch for whatever is\nbeing worked on. One canonical branch, one worktree. Establish which one you are\nin before any non-trivial change. **Do not create a branch by reflex.**\n\n1. **Join the canonical branch that already exists** for this work. List them\n   with `git worktree list` and `git branch -r`. If the work continues, extends,\n   fixes, or cleans up something already in flight, it belongs on that branch.\n   Ask the user which branch when it is not obvious.\n2. **Start a new canonical branch** only when the work is genuinely a new thing.\n   Give it its own worktree: `pnpm worktree create --name <slug> --yes\n   --no-start`, then do all edits and runs under `../suna-<slug>`. Add `--db`\n   when the work needs migrations, destructive data work, or schema drift: it\n   copies the primary local database into the worktree's own database\n   (`kortix_wt_<slug>`, ~5 s, no extra containers). `--full-supabase` (a whole\n   Supabase stack, ~0.75 GB RAM) is only for independent auth/storage config.\n   See the **worktree** skill.\n3. **Never switch a worktree you did not create.** It belongs to one session and\n   its canonical branch. The pre-commit hook refuses a commit on any other branch\n   there (`scripts/check-worktree-branch.sh`). A throwaway probe branch goes in a\n   private `git worktree add` in your scratchpad.\n4. **The primary checkout** (`pnpm dev`, web `3000` / api `8008`) is for running\n   and investigating. Do not park feature work there.\n5. **Run `pnpm test` only from a worktree that has `.kortix-worktree.json`.** The\n   runner reads its ports from that marker. A bare `git worktree add` has none,\n   so its test stack takes the primary ports and talks to another session's\n   `pnpm dev` (2026-10-13: 156 flows failed with that stack's 503s).\n6. **Never kill a process you did not start.** Many sessions share this machine.\n   Kill by PID from your own process tree, or by a pattern that contains your\n   own worktree or scratchpad path. Never by a bare pattern such as\n   `pkill -f \"bun test\"`, and never in a loop. Need CPU? Lower your own\n   concurrency (`KE2E_API_WORKERS`, `KORTIX_DB_SUITE_WORKERS`) or ask the user.\n   2026-10-13: a guard loop that SIGTERMed every other session's `pnpm test`,\n   `tsc` and `vitest` every 20 s made 259 kills, and every suite on the machine\n   died with exit 143 and no failing test.\n\n**Pack more into one branch, not less.** A follow-up fix, a rename cleanup, a\nstale-reference sweep, and the change that caused them all belong on the same\nbranch and land together. Splitting one objective across several branches is how\na half-finished cutover reaches `dev` in pieces — each piece green alone, the\nwhole thing broken.\n\nSub-branches are allowed. Agents may cut working branches off the canonical\nbranch and merge back into it. **A sub-branch never opens a PR against `dev`.**\nOnly the canonical branch does.\n\nCarve-outs where you just proceed: read-only investigation and questions, and\ntrivial single-file typo/comment fixes on the current branch.\n\n## Default delivery: verify in your box, self-merge to `dev`, verify on dev\n\n`dev` is the dev trunk. A merge does not deploy: dev deploys only on a deliberate\ndispatch, but **merging to `dev` still lands your change in everyone's next\ndeploy and next `dev` checkout.** It is not a save point.\n\n### Testing philosophy: one gate at Promote to Production, everything else fast\n\nTesting reset, 2026-10-16. Latency is the core concern. Development must be\nfast enough to push and break things on `dev`. The full suite runs once per\nrelease, at Promote to Production, and nowhere else. The rules:\n\n1. **Never run a full suite during development, and never a whole package's\n   tests.** No multi-minute run on your box, on a PR into `dev`, or on a push\n   to `dev`. Verify only what you changed: T0 for the packages you touched,\n   the test files that cover the change, named one by one\n   (`pnpm --filter <pkg> test <file>`), and the change itself through its real\n   surface. Running all 2,129 `apps/web` tests to check one component is the\n   anti-pattern this rule exists for.\n2. **One gate: the release pull request into `prod`.** Promote to Production\n   runs the whole suite there, once, and every check must be green. It is the\n   only check that blocks anything.\n3. **Test at the boundaries users touch:** the HTTP API, the CLI, the\n   `@kortix/sdk` public surface, DB migrations, and a few browser journeys on\n   the money path. Never test internal calls or source text.\n4. **Run each suite once per tree.** A tree that ran is not run again.\n5. **A red suite is worse than none.** A test that fails twice without a bug is\n   fixed or deleted the same day. Do not park it in quarantine.\n6. **Delete on red.** A unit test that goes red on a refactor with no bug is\n   deleted, not updated. Keep unit tests only for logic that is hard to get\n   right: money and credits, parsers, the streaming protocol, permission\n   decisions, crypto and secrets.\n7. **Small, isolated tests.** Every test file runs standalone in seconds. When\n   the test that covers your change takes more than ~10 s, or drags in setup\n   that has nothing to do with it, split it into a smaller isolated test as\n   part of the same change. Fake the clock; never sleep on real time.\n\n| Tier | Contents | Runs |\n|---|---|---|\n| T0 static | `tsc`, lint, architecture checks, migration lint | anytime, seconds per package |\n| Targeted | the one test file or flow that covers your change | on your box, seconds |\n| Release suite | contract lanes (flows, DB, packages) on the release tree, the deployed smoke and the browser journeys against staging, CI, CodeQL, scanners | once per release, on the pull request into `prod` |\n\n| Where | What runs | Blocks? |\n|---|---|---|\n| Your box | T0 for the packages you touched, the targeted test, and the real surface: the route with curl, the CLI process, the page with agent-browser | your judgment |\n| Pull request into `dev` | nothing, unless a person adds `test` or `preview` | no |\n| Push to `dev` | only the seconds-long guards: `secret-scan`, `secrets-guard`, and path-gated `DB Migrations` / `i18n-catalogs` / `Terraform Apply Global` / `deploy-api-router-dev` | no |\n| Nightly on `dev` | `Tests` `core` and `packages` lanes, no browser: a red night comments the failures on the commit, so they are ready to fix before a promote | no |\n| Pull request into `staging` | nothing: Promote to Production opens and merges it | no |\n| Pull request into `prod` | the release suite, once (`full suite + quality gates`) | **yes, the only gate** |\n\n**A Mac never runs a suite.** On macOS `pnpm test` refuses a suite-wide run\n(plain, `--full`, `--packages-only`, `--browser-only`, `--db-only` with no path\nfilter) and exits `2`. 2026-10-16: five agent sessions ran the suite at once on\none laptop (load average 112, swap full). Run one flow\n(`pnpm test -- --id <ID>`), one DB suite path, or one package's test file.\n`KORTIX_FULL_TEST=1` overrides only when a person asks. Every package `test`\nscript refuses the same way: `pnpm --filter <pkg> test` with no file exits `2`\n(`scripts/test-guard.mjs`). Pass the file(s), `pnpm --filter <pkg> test <file>`,\nor a name filter after `--`: `pnpm --filter <pkg> test -- -t <name>`. CI and\nLinux run the full suite.\n\nNo hook or gate requires a test run before a push or a merge into `dev`. Write\nin the PR body what you ran and why. A diff that no test reads (docs, a skill,\na standalone script) needs no run. When the nightly run or the release suite\nturns red on your change, you fix forward on `dev` the same day.\n\n1. Work on the canonical branch in its worktree. Commit as often as you want.\n2. Verify in your box, with real inputs and outputs. Run T0 and the tests that\n   cover the change (see above). Start the worktree's stack\n   (`pnpm worktree start <slug>`) and drive the changed behavior through it: the\n   HTTP route, the real CLI process, or the page with agent-browser. Nothing\n   before the release suite at Promote to Production catches what you skip.\n3. Open the PR against `dev` and follow the **contributing** skill: it attaches\n   the demo video you recorded against your local stack. Write the title and\n   body with the **create-pr** skill: English only, for a human reviewer who\n   decides \"merge or iterate\" without reading the diff. Do not add `test` or `preview` unless you need that one explicit run.\n4. Merge `dev` into the canonical branch daily. A branch that diverges for weeks\n   detonates on merge exactly like a 1,500-line PR does.\n5. **Self-merge to `dev` when the change is verified. Do not wait for the\n   user's approval.** Speed matters: a verified change that sits unmerged is\n   waste. Verified means all of these are true:\n   - the relevant local checks ran with real inputs and outputs (rule 2), and\n     they passed;\n   - the PR is mergeable (no conflict);\n   - rule 6 holds when the change touches a client-facing runtime contract.\n   A failing check blocks the merge until you fix it or state why it is\n   unrelated (for example, the same test fails on `dev`). Squash-merge\n   (`gh pr merge <pr> --squash`), then finish rules 7 and 8. A merge is not\n   the end of the work: dev verification is still yours.\n   The only machine-enforced rule on `dev` and `staging` is that every change\n   arrives through a pull request — no required approvals, no required status\n   checks, no bypass actors. The bar is what you verified.\n   **The release gates do not change.** Merging into `staging` or `prod`,\n   running Promote to Production, and moving the `:stable` tag each need the\n   user's explicit approval (the **kortix-release** skill).\n6. **A change to a client-facing runtime contract** — the `@kortix/sdk` public\n   surface, session/thread transport, the streaming protocol — merges only after\n   the whole objective ran through a real session on your local stack, and runs\n   again on dev after the merge (rule 8). Green tests are not the bar. Someone\n   used it.\n7. After the merge, deploy dev yourself: `gh workflow run deploy-dev.yml\n   --repo kortix-ai/suna -f surface=changed` (`all` or `frontend` force a\n   rebuild). Deploys queue, they never cancel. Wait for the run to finish.\n   `/health` on every changed surface must serve the merge SHA: a successful\n   `/health` response alone is not deployment proof. The run comments \"Live on\n   dev\" on the merged pull request, and \"Not live on dev yet\" names the\n   surface that failed. The surfaces and their checks are in\n   `.github/workflows/deploy-dev.yml`. The merge push runs no suite. The\n   nightly `Tests` run on `dev` comments the failing lanes and every commit\n   since the last green run, once per new set of failing lanes. The author\n   whose commit broke `dev` fixes forward the same day. If `dev` is still red\n   1 hour after the comment, anyone may revert the culprit PR. The release\n   suite at Promote to Production runs the same lanes and blocks the release.\n8. Re-run the user-visible behavior against `https://dev.kortix.com` and/or\n   `https://dev-api.kortix.com`. Prefer the real Kortix CLI configured for the\n   dev API for CLI/project/session flows, and direct authenticated HTTP calls for\n   API contracts. For web behavior, drive the deployed UI and assert its network\n   request plus visible result.\n\nLocal verification and dev verification are both required. A local pass does\nnot replace dev, and a dev smoke test does not replace focused local tests.\nRecord the branch, PR, local commands and output, merge SHA, deploy run,\ndeployed SHA evidence, and the exact dev command or interaction in the final\nresponse.\n\n## Architecture: `@kortix/sdk` is the source of truth\n\n`@kortix/sdk` is the **single source of truth** for everything that talks to the\nKortix backend — projects, accounts, sessions, files, secrets, triggers, the\nsession runtime, the runtime REST client, SSE streaming, model state,\nand auth-token plumbing. The apps\n(`apps/web`, `apps/white-label/*`, `apps/mobile`) are **thin consumers**. Treat\nthese as standing rules whenever you touch the data/runtime layer:\n\n> **Editing `packages/sdk` itself? Load the **sdk** skill (the rules) first.** It is a\n> **published npm package** with its own hard rules that have no analogue\n> elsewhere in this repo: **TDD is mandatory** (failing test first, run it, watch\n> it fail, then implement — and every turn ends with the gates run, the real\n> output pasted, and an explicit shippable YES/NO/NOT YET); exported names\n> (including *types*) are a public API contract and renaming one is a breaking\n> change; the `version` field is inert and must never be bumped by hand; adding\n> an export requires three synchronized edits; and the framework-free core is\n> enforced by a static import-graph tripwire.\n\n- **Logic lives in the SDK, never in a host.** No raw `fetch` to the Kortix API,\n  no `@opencode-ai/sdk` imports, no transport / runtime / data-state code written\n  in app code. New data or runtime behavior is added to the SDK and exposed\n  through its public surface — not hand-rolled or duplicated in a host. If you\n  need something the SDK doesn't expose, add it to the SDK.\n- **One client per host.** Create it once via `createKortix({ backendUrl,\n  getToken })` and read everything through `@kortix/sdk` + `@kortix/sdk/react`.\n  Auth is just `getToken` — an API key / PAT for programmatic use, or a Supabase\n  JWT for the logged-in web app. Hosts never instantiate a second client.\n- **A whole session is one hook.** `useSession(projectId, sessionId)` owns the\n  entire runtime lifecycle — `/start`, the sandbox switch, the live SSE stream,\n  readiness seeding, immutable runtime identity, the native conversation id,\n  and message sync. Hosts don't\n  hand-roll the mount, drive a server-store \"switch\", or mount a separate event\n  provider.\n- **Session-scoped + provider-agnostic.** The public API is session-scoped\n  (`kortix.session(pid, sid).health() / .previewUrl() / .restart() / …`).\n  The sandbox provider and the harness are server-side concerns. Host code\n  must not implement a second transport.\n- **Build on the Kortix contract, not on OpenCode.** Every new session runs\n  pi inside kortixd. OpenCode is deprecated (2026-10-23): there is no\n  `pi_harness` flag, `runtime:` in `kortix.yaml` selects nothing, and OpenCode\n  serves only sessions that started on it. It will be deleted. Code that exists\n  only for OpenCode carries `@deprecated`: never call it from new code, never\n  add a feature to it, and change it only to fix a bug that breaks a running\n  OpenCode session. pi calls models only through the LLM gateway. pi serves\n  the daemon routes (`/kortix/runtime/*`), the transcript\n  (`kortix.transcript.v1`, `packages/api-contract/src/transcript.ts`) and the\n  events. New code reads those, never an OpenCode route, type, file or\n  process. A feature pi lacks is a capability, not a harness check:\n  `GET /kortix/health` lists `capabilities` (`RUNTIME_CAPABILITIES` in\n  `packages/api-contract/src/runtime-relay.ts`), and a client gates the\n  control with `runtimeSupports`. OpenCode lists all eleven (`session.steer`\n  only on OpenCode 1.18.15 or later); pi lists `session.subagents`,\n  `session.compact`, `session.commands` and `session.steer`. pi does not\n  serve rewind, MCP servers, the todo list, shell turns, part edits or\n  `session.attach`.\n  The harness rules and the pi gap list are in\n  `apps/kortix-sandbox-agent-server/src/harness/README.md`.\n- **`apps/web` data modules are shims.** Files such as\n  `apps/web/src/ui/index.ts`, `apps/web/src/lib/iam-client.ts`, and\n  `apps/web/src/hooks/admin/use-*.ts` are thin re-exports\n  (`export * from '@kortix/sdk/...'`).\n  Keep them as shims; put the real logic in the SDK. When a merge conflict lands\n  on one of these, **keep the shim (`--ours`) and port any new host-side logic\n  into the SDK** — do not revert to a host-local implementation.\n- **Docs are the spec.** `apps/web/content/docs/sdk/*` and\n  `packages/sdk/README.md` describe the intended surface. Keep them current with\n  the SDK, and flag legacy/deprecated surfaces in-doc rather than documenting them\n  as current.\n\n## You CAN run and verify everything end-to-end. Do it.\n\nThis repo ships a **complete, runnable local stack with live cloud sandboxes**.\nDo not claim you \"can't verify from here\" or hand back unverified work — you\nhave everything needed to run the app, hit the real API, provision real\nPlatinum sandboxes, drive the real UI in a browser, and assert behavior. Use it.\n\n### Required verification standard — real inputs, real outputs\n\nFor every behavior change, assume **100% autonomy** to verify the user-visible\ncontract before handing the work back. Do not stop at typechecks, unit tests, or\nmocked internals when a real surface exists.\n\n- **API changes:** exercise the actual HTTP route with real request payloads\n  (`curl`, `bun fetch`, or the `ke2e` runner against a running API). Assert the\n  status code and exact response fields that prove the behavior. For writes,\n  also assert the persisted/read-back state or resulting repo/file output.\n- **CLI changes:** run the real CLI command as a process from bash, with the\n  same flags and stdin a user or agent would use. Assert exit code, stdout,\n  stderr, and any files/API calls/commits it should create. Do not rely only on\n  importing command functions.\n- **Web changes:** drive the real page with **agent-browser** (the primary\n  browser; `agent-browser skills get core` loads its guide). Click/type/toggle\n  the actual controls, observe the network request (`agent-browser network`),\n  and assert the visible UI state plus the outgoing payload. Record the flow\n  (`agent-browser record start`) for the PR's demo video. Screenshots and video\n  are evidence, but assertions on DOM and network data are required.\n- **Cross-surface features:** verify each exposed surface independently. If the\n  same feature ships on API + CLI + web + mobile, each gets its own black-box\n  assertion for the inputs users can make and the outputs they receive.\n- **Default/negative paths count:** when changing defaults or removing implicit\n  behavior, assert both the new default and the explicit opt-in/alternate path.\n- **No silent gaps:** if a surface cannot be fully exercised in the current\n  turn, say exactly which input/output remains unverified and why. Otherwise\n  keep going until the real surface is verified.\n- **Final response format:** when work is finished, answer per the **How to\n  communicate** standard at the top of this file — numbered/bulleted, no fluff.\n  Include exactly what changed, what was verified (with the command + output),\n  what remains unverified or risky, and what the user should test next. Do not\n  bury the actionable testing path in a paragraph.\n\n### The stack (already wired)\n\n- **Web** — Next.js dev server on `http://localhost:3000`.\n- **API** — Bun server on `http://localhost:8008/v1` (`/health` returns JSON).\n- **Supabase** — local, on `http://127.0.0.1:54321` (Docker).\n- **Sandboxes** — REAL cloud sandboxes on Platinum, the reference provider\n  (`PLATINUM_API_KEY` in `apps/api/.env` / `.env.local`). Daytona is a legacy\n  fallback with removal scheduled and no support guarantee; E2B support is\n  discontinued. Each project\n  session gets its own sandbox; `session_id == sandbox_id`. The sandbox daemon is\n  reached through `http://localhost:8008/v1/p/<external_id>/8000/...`.\n  The session runtime answers on the same proxy, under `/kortix/runtime/*`,\n  on both harnesses.\n- **Tunnel** — `scripts/dev-local.sh` (`pnpm dev`) auto-starts a cloudflared\n  quick tunnel so cloud sandboxes can call back to the local API (`KORTIX_URL`).\n\nBring it up with `pnpm dev` from `suna/` (it loads `apps/api/.env` +\n`apps/web/.env`, starts Supabase, the API, the web app, and the tunnel). Check\nwhat's already running before starting a duplicate: `curl -s\nlocalhost:8008/v1/health`, `lsof -iTCP:3000 -sTCP:LISTEN`.\n\n> **Secrets are dotenvx-encrypted (mandatory).** `apps/api/.env` (+ `.env.dev`)\n> are committed as ciphertext (`KEY=encrypted:…`); keys live in Dotenv Armor.\n> **Never write a plaintext secret into a tracked file or commit** — add/change\n> values only via `dotenvx set KEY value -f apps/api/.env` (then commit), read\n> with `dotenvx get`, and machine-local overrides go in the gitignored\n> `apps/api/.env.local`. If the user pastes a key, store it with `dotenvx set`,\n> never paste it raw. A pre-commit hook + GitHub push protection enforce this —\n> don't bypass them. Full procedure: the **dotenvx-secrets** skill.\n\n### Authenticating to the live API (for scripts/tests)\n\nMint a real JWT against local Supabase, then call the API with it:\n\n1. `SUPABASE_SERVICE_ROLE_KEY` lives in `apps/api/.env`; the anon key\n   (`NEXT_PUBLIC_SUPABASE_ANON_KEY`) in `apps/web/.env`.\n2. Create a confirmed user: `POST 127.0.0.1:54321/auth/v1/admin/users`\n   (`apikey` + `Authorization: Bearer <service_role>`, body\n   `{email,password,email_confirm:true}`).\n3. Password-grant for the token: `POST\n   127.0.0.1:54321/auth/v1/token?grant_type=password` (`apikey: <anon>`).\n4. Call the API: `Authorization: Bearer <access_token>` against\n   `localhost:8008/v1` (e.g. `/accounts`, `/projects/provision`,\n   `/projects/:id/sessions`, `/p/<ext>/8000/...`).\n\nSee `tests/e2e/helpers/session-auth.ts` for the exact calls.\n\n### One local testing system\n\n- `pnpm test` is the only repository-level test command. It runs local REST and\n  CLI flows, SDK tests, PostgreSQL-backed suites (`db-suites`), runner unit\n  tests, route coverage, and worktree tests concurrently.\n- `pnpm test -- --id ACC-4` runs one flow. `--domain access` runs one domain.\n- `pnpm test -- --sdk-only` runs only `packages/sdk` tests.\n- `pnpm test -- --db-only [path-filter]` runs only the PostgreSQL-backed suites\n  (`integration-*.test.ts`, `*.integration.test.ts`, `tests/migration`), each\n  file against its own fresh migrated database. A skipped DB suite fails.\n- `pnpm test -- --browser-only` runs Playwright browser journeys. It starts the\n  deterministic local stack.\n- Browser runs use two Playwright workers, locally and in each CI shard.\n- `pnpm test -- --packages-only` runs every app/package test and publish check.\n- `pnpm test -- --full` adds browser journeys and every app/package test. It\n  starts the deterministic local stack.\n- `pnpm test -- --target-smoke` verifies the deployed staging API and gateway\n  SHA, then runs the tagged Playwright staging smoke. Release CI supplies the\n  staging credentials and `RELEASE_SOURCE_SHA`.\n- `pnpm test -- --target-full` verifies the same deployed SHA, then runs every\n  configured staging REST, CLI, and Playwright journey. The production release\n  gate uses this command and fails on any excluded API flow.\n- Browser and full modes reuse only a running API that proves the deterministic\n  test profile. Stop an ordinary development stack before either command.\n- Every root run writes lane and total timings to\n  `tests/test-results/local/benchmark-<timestamp>.json`.\n- Before merging into `dev`, run T0 and the tests that cover your change (the\n  testing philosophy above). On a Mac, suite-wide modes exit `2`.\n- Every Linux CI job runs on a free GitHub-hosted runner (`ubuntu-24.04`,\n  `ubuntu-24.04-arm`, `ubuntu-22.04`; 4 vCPU / 16 GB on this public repo) through\n  `runs-on: ${{ vars.CI_RUNNER_<tier> || '<label>' }}`. Setting a\n  `CI_RUNNER_<tier>` repository variable moves that tier to another pool\n  without a PR. Never default a job to a paid runner (Blacksmith, a GitHub\n  larger runner): Blacksmith billed ~$2.7k in September 2026 for minutes\n  GitHub gives this repo for free. `tests/unit/image-build-speed-workflow.test.ts`\n  enforces the free default.\n- `.github/workflows/tests.yml` holds the six contract lanes — `core-flows`,\n  `core-db`, `packages-api`, `packages-cli`, `packages-kortixd`,\n  `packages-rest` — one GitHub-hosted runner each (`CI_RUNNER_L`). `core-*`\n  split the core run (`--core-group=flows|db`); `packages-*` are slices of\n  package quality (`--packages-only --package-group=<g>`). The concurrency\n  settings in `tests/bin/package-quality.ts` must not be raised. Each lane is\n  the unchanged root command at the exact requested SHA. Do not add CI-only\n  test logic.\n- The contract lanes run nightly on `dev`, once when a person adds the `test`\n  label to a pull request into `dev`, on dispatch, and inside the release\n  suite (`tests-release.yml` calls `tests.yml`). A push to `dev` and the pull\n  request into `staging` run nothing. A red nightly run comments the failing\n  lanes and suspect commits on the commit, once per new set of failing lanes.\n- The release suite (`tests-release.yml`, on the release PR into `prod`) runs\n  the contract lanes, the deployed API smoke (the flows tagged `deployed`, one\n  or two per deployment seam) and the browser journeys against deployed\n  staging, in parallel. `full suite + quality gates` requires all of them and\n  every other check on the release commit. A browser spec exists only for a\n  money-path journey (sign-in, onboarding, session load and streaming, desktop\n  parity, billing, connectors, invites); everything else is a REST flow or\n  nothing. CI records a video of every browser journey in the run's\n  `tests-release-browser-shard-N` artifact. A failed release-suite run reruns\n  its failed jobs once by itself (`rerun-failed-once.yml`): a job that passes\n  on the rerun is a flake to fix or delete.\n- `tests/unit/sandbox-workflow.test.ts` fails when any workflow except the\n  label-gated `tests.yml` and `deploy-preview.yml` triggers on a pull request\n  into `dev`, and pins both label gates to the label-added event.\n- The deployed half of the release suite (`--target-api-full`,\n  `--target-browser-full`) waits up to 15 min for staging to serve\n  `RELEASE_SOURCE_SHA`, then fails when any `deployed` flow is excluded or any\n  configured Playwright journey fails.\n- The `preview` label is not part of the development flow. Adding it deploys a\n  Platinum self-host environment for the PR (~7 min), once, and runs no tests.\n  A push does not redeploy. Only `gh workflow run deploy-preview.yml -f\n  pr_number=<N>` runs `--target-full` against a preview (40–80 min). Its mechanics are\n  in the **contributing** skill (`references/preview-environments.md`). Run a\n  preview of your change on your worktree's local stack by default.\n\n### Product flow source of truth\n\n- `tests/spec/end-to-end.md` contains the natural-language contract and stable\n  flow IDs.\n- `tests/src/flows/*.flow.ts` implements the contracts through HTTP and real CLI\n  processes. Do not import API handlers.\n- Write each `ctx.step()` as a complete action and observable result. Cover\n  setup, authentication, action, read-back proof, negative paths, and cleanup.\n- Keep every flow's `meta.routes` synchronized with\n  `tests/spec/routes.generated.json`. Regenerate the manifest with\n  `bun run apps/api/scripts/dump-routes.ts` after route changes.\n- The local profile uses local Supabase, PostgreSQL, API, gateway, and bare Git\n  repositories. It excludes Stripe, cloud sandboxes, managed GitHub repositories,\n  and external email delivery explicitly.\n- Use Playwright only for browser-visible behavior. API-only assertions belong\n  in REST flows. SDK tests remain in `packages/sdk`.\n- Do not add another cross-cutting test harness, Makefile lane, contract suite,\n  Testcontainers suite, load suite, mutation suite, visual suite, accessibility\n  suite, or ad hoc smoke script under `tests/`.\n- Read `tests/README.md` and the repository `testing` skill before changing the\n  test system.\n\n### Release topology — dev, staging, prod\n\n- **`dev` = dev trunk.** It is the repo default branch and deploys to\n  `dev.kortix.com` / `dev-api.kortix.com`. Direct pushes are allowed; breaking or\n  incomplete development can live here while it is being shaken out.\n- **`staging` = release-candidate branch.** Nothing should land on staging unless\n  it is intended to be production-ready. Promote to Production merges the\n  `dev` HEAD into staging itself (the PR runs no check), deploys it, and opens\n  the release PR whose release suite is the one gate. Staging is the deploy\n  target that suite runs against, not a test stage. Staging\n  deploys to `staging.kortix.com` / `staging-api.kortix.com` and must use the\n  staging data plane, not dev or prod. The full promote-and-gate flow, the\n  fix-forward loop, and the prod release steps live in the **kortix-release**\n  skill — that is the one place this is written out in full.\n- Staging deploys must apply pending DB migrations against `STAGING_DATABASE_URL`\n  before the staging ECS Fargate rollout. If that secret is missing or points at\n  dev/prod, treat the deploy as broken; staging must never fall back to dev,\n  KE2E, or prod DBs.\n- **`prod` = production.** Production moves only through **Promote to Production**,\n  which uses `staging` as the source, opens a reviewed release PR into `prod`,\n  publishes the release artifacts, and rolls production after merge.\n- If a staging runtime check points at `dev.kortix.com` or\n  `dev-api.kortix.com`, treat that as a broken staging setup, not a passing\n  staging gate.\n\n### Driving the real UI (agent-browser)\n\n- **agent-browser is the browser for every agent task in this repo.** Use it\n  before chrome-devtools MCP, Playwright MCP, or any other built-in browser\n  tool. Playwright stays the engine of the committed browser test suite only.\n- The installed skill is a stub. Load the guide that matches the CLI version:\n  `agent-browser skills get core` (`--full` adds the command reference).\n- Use one named session per worktree:\n  `agent-browser session id --scope worktree --prefix <task>`, then pass\n  `--session <id>` on every command. Keep every `AGENT_BROWSER_*` variable the\n  same for all commands in a session. A per-command change relaunches the\n  browser and drops the open page.\n- Routes are auth-gated (`/dashboard`, `/projects/*` → redirect to `/auth`\n  unauthenticated); sign in first (seed a user as above, then log in via the\n  `/auth` form). On a PR preview, the magic link arrives in the preview's Mailpit API\n  (`<origin>/_mailpit/api/v1`)\n  (the **contributing** skill has the script).\n- Next.js dev compiles routes on first hit — first navigation to a cold route\n  can take 30–60s; warm it with `curl` or use a generous navigation timeout.\n- `agent-browser doctor` diagnoses launch and recording problems. Recording\n  needs ffmpeg with libvpx and libx264.\n\n### API lint gate\n\n- `pnpm --filter kortix-api lint` runs in the `Tests` packages lane. Its rules\n  are in `apps/api/eslint.config.mjs`: layered imports, no Drizzle in route\n  files, no `(c: any)`, no `process.env` outside `config.ts`, no\n  `console.*`, a `replica-local:` comment on every empty module-level\n  `Map`/`Set`, and no import into `projects/` from outside it except\n  `projects/index.ts` or `projects/surface.ts`. Background timers are guarded by\n  `apps/api/src/__tests__/unit-worker-scope-wiring.test.ts` instead.\n\n### API code conventions (`apps/api/src`)\n\n- **Routes register explicitly.** A route module exports\n  `register<Name>Routes()`. The module that mounts the router calls it right\n  before `app.route()` (`app.ts`, `accounts/index.ts`, `router/index.ts`).\n  Never register at import time. Call order is dispatch order.\n- **No service imports a route module.** Shared logic lives in a service file\n  next to the routes (for example `projects/session-open/`).\n- **Services take an `Actor`, never a Hono `Context`.** The route reads the\n  request through `middleware/` and `http-*.ts` helpers, builds the actor\n  (`middleware/actor.ts`, memoized per request), and calls the service with\n  plain values. Parse `Authorization: Bearer` with `shared/bearer-token.ts`.\n- **Every background loop lives in `workers/<name>-worker.ts`.** The worker\n  owns its timer, start/stop and `runWorkerTick` call. The tick stays an\n  exported service function. `bootstrap.ts` imports every loop from `workers/`.\n- **`iam/` reads the membership, group and role tables.** Use the read models\n  in `iam/membership-read.ts`, `iam/group-read.ts` and `iam/role-read.ts`.\n- **No function longer than 300 lines.** Split long functions into named steps.\n- **SQL trace:** `KORTIX_SQL_TRACE=<file>` appends every pool statement to the\n  file. Diff two traces to prove a refactor runs the same SQL.\n- `apps/api/eslint-suppressions.json` holds the violations that existed when\n  each rule was added. A new violation fails. A fixed one fails until you run\n  `pnpm --filter kortix-api lint:prune` and commit the smaller file. Never\n  absorb a new violation with `--suppress-all`.\n\n### Frontend type/lint gate\n\n- Typecheck `apps/web` with `pnpm --filter Kortix-Computer-Frontend typecheck`\n  (tsgo, incremental). Never run raw `tsc --noEmit` there. Measured on\n  2026-10-21 on a 24 GB Mac: raw `tsc` ran 817 s, held 3.18 GB, and was\n  killed before it finished. Cold `tsgo` takes 12.1 s at 5.0 GB peak. A warm\n  incremental run takes 1.1 s at 1.7 GB.\n- On 2026-10-21 the tsgo script reported 0 errors on `dev`. The bar is\n  relative: a branch adds no type error to the files it touches. Compare against `origin/dev` with the same command, not\n  against a number in this file.\n  The old ~1500 `TS2786` / `IntrinsicAttributes` noise from a React 19↔18\n  types mismatch (two copies of `@types/react` in one program — `packages/sdk`\n  had its own) is gone as of the Next 16 upgrade. If `TS2786` appears again,\n  treat it as a genuine duplicate-`@types/react` regression and investigate —\n  do not wave it through.\n- `npx eslint <files>` should be clean of errors. `eslint .` across the whole\n  app currently reports ~455 warnings, mostly `react-hooks/*` React Compiler\n  rules pending a dedicated audit — expected until that audit lands.\n\n### Frontend design standard — Jay/Kortix bar\n\n#### Canonical design references\n\n| Surface | Read before changing UI | Implemented source |\n|---|---|---|\n| Web (`apps/web`) | `.agents/skills/kortix-brand/SKILL.md` (the router: values, voice, claims, decision history), then `.agents/skills/kortix-design-system/SKILL.md` for components | `apps/web/src/app/globals.css` tokens (generated from `.agents/skills/kortix-brand/references/visual/visual-system.json`), `packages/ui/src/primitives/` (apps/web imports each one as `@kortix/ui/<name>`; apps/web has no re-export shims, `tests/unit/web-no-ui-shims.test.ts` enforces it), the live `/design-system` route |\n| `@kortix/ui` hosts (Kortix Apps, `apps/white-label/reskin`, `apps/white-label/lumen`, `apps/white-label/team-console`, SDK customer apps) | `packages/ui/skills/kortix-ui/SKILL.md` (setup, which component, white-label theming, the `SessionThread` chat), then the web row for Kortix-owned surfaces | `packages/ui/src/` (components, `session/` for the `SessionThread` chat, `styles/tokens.css` + `theme.css`, prebuilt `dist/styles.css`); white-label = CSS variable overrides, never a forked component |\n| Desktop (Electron shell) | The web row — the shell renders `apps/web` — plus `apps/desktop-electron/README.md` for the shell boundary, then the parity gate below | `apps/web` rendered by the shell; native window geometry in the shell's titlebar classes |\n| Mobile (`apps/mobile`) | `apps/mobile/design.md` for screens, `apps/mobile/AGENTS.md` for primitives | `apps/mobile/global.css` colors, `apps/mobile/components/ui/`; stock Tailwind spacing for touch targets |\n\n#### Desktop parity is a UI gate\n\nThe Electron app loads `apps/web`. Keep product components, routes, tokens,\nand data behavior shared. Put native window geometry in the shell's explicit\ntitlebar classes. Never apply titlebar height or drag rules to generic ARIA\nroles, all sidebars, or page content.\n\nFor every shared UI change, verify the affected controls on web and in Electron\nbefore handoff. Check the outgoing request or route and the visible result.\nCheck both themes, the minimum supported window (720 × 480), sidebar collapse,\nfullscreen overlays, and browser zoom. Window controls must not overlap app\ncontrols. Lists must not overlap or clip their last row. Keyboard focus and\nscrolling must remain usable.\n\nAdd regressions to the existing Playwright journeys. The desktop journey runs\nin `pnpm test -- --browser-only` and supports the actual Electron shell:\n`E2E_DESKTOP_NATIVE=1 E2E_GREP='27 — desktop parity' pnpm test -- --browser-only`.\nRun the native journey when changing shell CSS, navigation, settings, agents,\nor connectors. A desktop user-agent test does not prove native hit testing.\nReport any unverified desktop behavior explicitly. Do not promise that tests\nprevent every future regression.\n\nWhen touching any visual surface in `apps/web`, treat brand fit as a release\ngate, not polish:\n\n- Read `.agents/skills/kortix-brand/SKILL.md` before writing the first\n  `className` or the first user-facing string. It routes you to the value law:\n  the complete allowlist of every color, spacing step, type rung, radius,\n  elevation, and duration you may use. Note `--spacing: 0.23rem` — Tailwind's\n  scale is 8% tighter here, so a 16px mockup padding is `p-4`, never\n  `p-[16px]`. Run `.agents/skills/kortix-brand/scripts/audit.sh` over your\n  changed paths before opening the PR; it must be clean on files you touched.\n  `references/visual/visual-system.json` is the only file with values: edit it,\n  then run `scripts/generate-tokens.ts`. Never edit a generated region of\n  `globals.css` by hand.\n- Read `.agents/skills/kortix-design-system/SKILL.md` next and compose existing\n  primitives from `@kortix/ui/*` before inventing local chrome.\n- Match the current Jay Suthar / Kortix product aesthetic: calm neutral surfaces,\n  dense-but-legible UI, black/white plus one earned accent, token-driven spacing,\n  and no decorative color, glow, or one-off rounded boxes.\n- Use recent product surfaces as references before editing: `/design-system`,\n  `packages/ui/src/workspace/layout/project-home.tsx`,\n  `apps/web/src/components/ui/wallpaper-background.tsx`, and the account/IAM\n  screens called out by the design-system skill.\n- Verify visual work in the browser and include the exact lint/typecheck commands\n  you ran in the PR. If it does not look native beside Jay-authored UI, keep\n  iterating before shipping.\n\n<!-- File: .claude-plugin/marketplace.json (Tokens: ~300 | Category: marketplace) -->\n# Claude Plugin Marketplace Catalog\n\nPath: `.claude-plugin/marketplace.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/kortix-ai/suna/HEAD/.claude-plugin/marketplace.json)\n\n<!-- File: .claude-plugin/plugin.json (Tokens: ~300 | Category: plugin-manifest) -->\n# claude-plugin Plugin Manifest\n\nPath: `.claude-plugin/plugin.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/kortix-ai/suna/HEAD/.claude-plugin/plugin.json)\n\n<!-- File: .cursor/mcp.json (Tokens: ~300 | Category: mcp-config) -->\n# Model Context Protocol (MCP) Configuration\n\nPath: `.cursor/mcp.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/kortix-ai/suna/HEAD/.cursor/mcp.json)\n</yakaai_skills>"}