Persistent project workspace for Koder Kli (Flow-backed, ephemeral-default)
1. Decision
Koder Kli sessions stay ephemeral by default (throwaway sandbox), and gain an opt-in persistent project workspace whose durability comes from a Koder Flow git repository — NOT from persisting the container or inventing a workspace store. The sandbox clones the repo at session start; the AI works and commits + pushes back to Flow; the diff/PR surface (#011) reads git in the session.
This is the /k-arch option (c) hybrid Flow-backed (Quality > Speed, Rule 13): covers both the scratch and the real-project use-cases, distributes complexity only where there's value, and reuses Flow as the repo SSOT (self-hosted-first, reuse-first) instead of a new store. The container is still ephemeral (cheap, secure); persistence = git.
2. Why not the alternatives
- Ephemeral-only (status quo): caps kli as a scratch runner; the diff viewer
is cosmetic; no project continuity. Low ceiling.
- Persistent-always (durable volume per session): pays storageGCquota on
every run, including throwaway smoke-tests, and reinvents a project store that Flow already is. Correct-but-redundant and mal-distributed.
3. Model
A session is created with an optional project:
POST /v1/sessions
{ "adapter_id": "claude",
"project": { "repo": "Koder/koder", "branch": "main", "base": "main" } } // optional- No
project→ today's ephemeral session (unchanged). - With
project→ klid resolves a per-tenant git credential (§5), thesandbox clones
https://flow.koder.dev/{repo}.git(branch) into the session working dir BEFORE the adapter is attached, and the adapter runs *inside the checkout. The AI's edits are durable the moment they are *committed + pushed*
Durability boundary: the git repo on Flow, not the container. A session that ends with un-pushed work loses only the un-pushed delta — surfaced honestly in the UI ("N uncommitted changes"). A work branch (kli/<session> or user-named) keeps the AI's commits off main until reviewed.
4. Surfaces (slices)
| Slice | Ticket | What |
|---|---|---|
| 1 | kli#017 | Backend workspace mount — Spec.Workspace; klid clones the Flow repo into the session before spawn; project on session-create + on the Session model. |
| 2 | kli#018 | Commit / push — klid endpoints to git add/commit + push the work branch (per-tenant cred); surfaced in the UI. |
| 3 | kli#019 (= #011) | Diff / changes viewer — git status/git diff via session exec (or fswatch), per-file diff side panel. Now real (a repo exists). |
| 4 | kli#020 | Project picker UI — choose repo+branch when launching an AI; show the active project + branch + dirty state in the workspace. |
| 5 | kli#021 | Open PR — push branch + create a Flow pull request from the session. |
Slices ship independently; 1 is the foundation. #011 is reframed as slice 3.
5. Security & tenancy
- Per-tenant git credential. Extends the existing
internal/credential.Resolver(already resolves per-tenant provider secrets, KLI-001 1f) with a git token role. For the owner/v1: a configured Flow PAT (gitignored,
--provider-secrets-style). For multi-tenant prod: the user's Koder ID → a scoped Flow token (future, ties to Flow's token API). The credential never rides the shared image (SANDBOX-028 per-session injection only). - Tenant isolation unchanged (
multi-tenancy/contract.kmd): a session can onlyclone repos the session's tenant/user may read on Flow — enforced by Flow's own authz on the clone, not re-implemented in kli.
- Branch hygiene: AI commits land on a work branch, never directly on
base;merge to
baseis an explicit PR (slice 5).
6. Non-goals
- Persisting the container or a raw durable volume (rejected — §2).
- A kli-native VCS or merge engine (Flow/git owns that).
- Real-time multi-writer on one workspace (one session = one checkout; collaboration
is via branches/PRs, the git model).
7. Status
Shipped 2026-06-24 — all slices (#017018011020021) DONE + proven on prod. The loop "AI works in a Flow repo → diff → commit/push → open PR" is live on kli.koder.dev. Slice 1 (kli#017) is the foundation and starts immediately.