| 1 | # g1t: agent-native architecture review |
| 2 | |
| 3 | Status: proposal, not built. Written 2026-10-12 from the code at `bba76b3`. |
| 4 | Nothing here is documented behaviour; `apps/docs` remains the description of |
| 5 | what g1t does. |
| 6 | |
| 7 | Labels used throughout: |
| 8 | |
| 9 | | Label | Meaning | |
| 10 | | --- | --- | |
| 11 | | **[built]** | Implemented and reachable on g1t.sh today | |
| 12 | | **[partial]** | Implemented but incomplete, heuristic or narrow | |
| 13 | | **[stub]** | Types, flags or UI exist; behaviour does not | |
| 14 | | **[claimed]** | Stated in README/docs/marketing; not supported by the code | |
| 15 | | **[proposed]** | My recommendation | |
| 16 | |
| 17 | Provider facts (Claude Code, Codex, Cursor, Gemini CLI, Copilot) were |
| 18 | checked against vendor docs and, for Codex and Gemini CLI, against source at |
| 19 | recent commits. Cursor, the Cloudflare post and Cloudflare limits come from |
| 20 | secondary sources because this environment's network policy blocked those |
| 21 | hosts (and `g1t.sh` itself; see §13). Treat those rows as needing a |
| 22 | re-check before you build on them. |
| 23 | |
| 24 | --- |
| 25 | |
| 26 | ## Part I — Executive verdict |
| 27 | |
| 28 | **g1t should be an agent-native Git forge (C) whose one genuinely new |
| 29 | primitive is the *session*: a first-class, provider-neutral record of a |
| 30 | unit of work by a human-sponsored agent, linked to commits by Git itself and |
| 31 | observed by deterministic client hooks. Coordination (B) is a thin layer of |
| 32 | that forge, not a product of its own. Managed execution (A) is just another |
| 33 | client of the session protocol that also happens to own compute.** |
| 34 | |
| 35 | That is your hypothesis D in shape, but I disagree with it in three places: |
| 36 | |
| 37 | 1. **B is not a co-equal core.** As a separate "coordination platform" |
| 38 | (claims, interface contracts, agent-to-agent messaging, semantic conflict |
| 39 | detection), B is mostly premature and partly unsound: what can be |
| 40 | detected deterministically is file overlap, Git conflicts and test |
| 41 | failures, and the forge already computes the latter two. The coordination |
| 42 | that is worth building now is *visibility of in-progress work before it |
| 43 | becomes a PR*, delivered into the agent's context by hooks. That is a |
| 44 | forge feature (an "Active work" view plus a per-repository lease table), |
| 45 | not a platform. |
| 46 | 2. **The biggest architectural problem is not missing coordination; it is |
| 47 | that g1t is currently two products.** About a third of the backend and |
| 48 | most of the positioning (README: "where a company's people and agents |
| 49 | work together", chat, artifacts, agents hired into roles, an org chart, |
| 50 | `@g1t` the orchestrator) is an *AI workforce/Slack* product. The other |
| 51 | ~60% is a good, unusually rigorous Git forge. Your prompt describes the |
| 52 | second. The code and homepage lead with the first. Pick the forge. |
| 53 | 3. **Managed execution is already closer to "just a client" than you |
| 54 | think.** g1t's hosted coding agent does not have its own agent loop: it |
| 55 | runs headless Claude Code in a Cloudflare Container that talks to g1t |
| 56 | only through the public API, MCP and Git |
| 57 | (`crates/runner/src/harness.rs:169`, `run_claude`). The coupling that |
| 58 | makes it special lives in the *forge's data model* (`pulls.agent`, |
| 59 | `pulls.runtime`, `pulls.managed`, `pulls.stage`, `made_by_g1t()` in |
| 60 | `services/work/src/lifecycle.rs:121`), not in the runtime. Unwinding that |
| 61 | is a refactor, not a rewrite. |
| 62 | |
| 63 | **What g1t should explicitly avoid becoming:** |
| 64 | |
| 65 | - A chat/workforce platform with a forge attached (Slack + Devin + GitHub). |
| 66 | - An agent orchestrator that schedules and plans for other people's agents |
| 67 | (`plans`, `hand_off`, `@g1t` routing). |
| 68 | - A transcript warehouse. Sessions should keep *summaries, provenance edges |
| 69 | and bounded observed facts*, not full conversations. |
| 70 | - A proprietary memory system competing with `AGENTS.md`/`CLAUDE.md`. |
| 71 | - A coordinator on the hot path of every edit. Coordination must be |
| 72 | advisory, cached locally and fail-open. |
| 73 | |
| 74 | **Guiding principle for the rebuild:** |
| 75 | |
| 76 | > **The commit is the unit of truth, the session is the unit of |
| 77 | > participation, the pull request is the unit of approval.** |
| 78 | > g1t trusts what it can verify (Git objects, check results, credentials), |
| 79 | > records what clients observe (hook events) with a trust label, and treats |
| 80 | > what models say as claims. Every agent, hosted or not, participates |
| 81 | > through the same session protocol. |
| 82 | |
| 83 | --- |
| 84 | |
| 85 | ## Part II — Current-state audit |
| 86 | |
| 87 | ### II.1 Shape of the system |
| 88 | |
| 89 | - About 700k lines across 22 services and 8 apps. Rust for the core |
| 90 | (identity, repos, work, events, billing, actions, security, packages, |
| 91 | api). TypeScript for web, runner, agents, chat, artifacts, context and |
| 92 | models. |
| 93 | - Every service is a Worker with its own D1 database. Services call each |
| 94 | other through service bindings and fan events out through Queues. |
| 95 | - Self-host runs the same Workers under workerd/Miniflare |
| 96 | (`deploy/self-host`). Runner-dependent features are off there: agents, |
| 97 | merge-queue builds and hosted Actions jobs. |
| 98 | |
| 99 | | Area | Status | Where | |
| 100 | | --- | --- | --- | |
| 101 | | Git storage on Cloudflare Artifacts, sharded namespaces, R2 pack cache, nightly bundles, cold fallback | [built] | `services/repos/src/{store,shards,pack_cache,backups,fallback}.rs` | |
| 102 | | Smart HTTP (proxied to Artifacts), push checks: workflow gate, rulesets, secret push protection | [built] | `services/repos/src/{git_http,push_checks,workflow_gate,rules,secret_scan}.rs` | |
| 103 | | Repository size: 950 MB repo / 32 MB object / 100 MB push | [built] (hard limits) | `services/repos/src/pack_limits.rs` | |
| 104 | | Landing: fast-forward only; merge commits made earlier by catch-up | [built] | `services/repos/src/land.rs:209`, `catch_up.rs` | |
| 105 | | Squash / rebase merges | [stub] (enum + validation; `merge_gate` hard-codes `Merge`) | `services/work/src/rulesets.rs:851` | |
| 106 | | Issues, PRs, comments-as-reviews, labels, milestones, CODEOWNERS | [built] | `services/work/migrations/*` | |
| 107 | | GitHub-compatible checks/statuses | [built] | `services/work/migrations/0029_commit_checks.sql` | |
| 108 | | Rulesets incl. `applies_to: Everyone/Agents/People`, `count_agent_approvals`, `confidence_threshold`, `cost_cap`, `path_review`, `merge_window` | [built] | `crates/rules`, `crates/contracts/src/rules.rs:304,830` | |
| 109 | | Speculative merge queue (batches of ≤4, `g1t-queue/<id>`, in-order landing, eject + retest) | [built] | `services/work/src/queue.rs`, `0010_merge_queue.sql` | |
| 110 | | Actions engine (GitHub-compatible), self-hosted runner under Sysbox | [built] | `services/actions`, `crates/actions`, `crates/runner` | |
| 111 | | Event bus: Queue → D1 append log → 16 subscriber queues; at-least-once, DLQ | [built] | `services/events/src/lib.rs`, `crates/contracts/src/subscribers.rs` | |
| 112 | | Webhooks with per-hook dedupe and retries over ~7.5 h | [built] | `services/webhooks/src/deliver.rs` | |
| 113 | | Event stream or replay cursor for API clients | absent | (log is queryable via `list_events`, no cursor/SSE) | |
| 114 | | PR file overlap ("overlaps" on `pull_request get`) | [partial] (file paths, PR↔PR only, after push) | `services/work/src/reviews.rs:95` | |
| 115 | | Mergeability precompute (disjoint-files fast path, sandbox probe) | [built] | `services/work/src/mergeability.rs` | |
| 116 | |
| 117 | ### II.2 External agent integration (the part that matters most) |
| 118 | |
| 119 | | Capability | Status | Where / notes | |
| 120 | | --- | --- | --- | |
| 121 | | MCP server, streamable HTTP but stateless JSON replies; OAuth 2.1 + PKCE + dynamic client registration; PAT | [built] | `apps/api/src/{mcp,oauth}.rs` | |
| 122 | | 20 MCP tools, ~380 actions, generated from the same `Op` table as REST/OpenAPI, so permissions/audit/billing are shared | [built]. This is a strong design worth keeping. | `apps/api/src/{tools,operations,rest,openapi}.rs` | |
| 123 | | MCP resources/prompts | absent | capabilities = tools only | |
| 124 | | `record_session` / `read_session` | [built] but **append-only log keyed to a PR**: no session id, actor, client, start/end | `services/work/migrations/0001_init.sql` `session_entries(pull_id, seq, kind, text, tool, commit, at)` | |
| 125 | | Only the PR owner may record | [built] | `services/work/src/lib.rs:2432`, `Pull::is_owned_by` | |
| 126 | | Claude Code hook installer (`UserPromptSubmit`, `PostToolUse *`, `Stop`) | [partial] | `apps/web/public/install/{claude.sh,setup.mjs,hook.mjs}` | |
| 127 | | ↳ records nothing until an open PR exists for the branch; caches a miss **permanently** (`~/.g1t/pulls.json`), so a PR opened later is never picked up | bug | `hook.mjs` `pullFor` | |
| 128 | | ↳ blocking network call on every tool use (10 s timeout), no offline spool, no idempotency key | design flaw | `hook.mjs` `main` | |
| 129 | | ↳ sends raw `tool_response` (clipped to 4 kB) **without secret redaction**; installed into global `~/.claude/settings.json` so it fires in every repo | security flaw | `hook.mjs`, `setup.mjs` | |
| 130 | | ↳ never sets `commit`, so the server stamps whatever head the PR had | accuracy flaw | `hook.mjs` | |
| 131 | | why-blame | [partial]: heuristic window from "Pushed <sha>" notes that only hosted runs write; for external sessions it pulls in all earlier entries | `apps/web/app/routes/repo/why.ts`, `apps/web/app/lib/provenance.server.ts` | |
| 132 | | Coding-agent recognition from commit email / Co-authored-by | [built], display only | `crates/contracts/src/coding_agents.rs`, `apps/web/app/lib/commit-people.ts` | |
| 133 | | `g1t` CLI | [built] **image push only**: no login, sessions or hooks | `crates/g1t/src/main.rs` | |
| 134 | | Codex / Cursor / Gemini / Copilot adapters | absent | — | |
| 135 | | "Any coding agent over MCP, recording its session onto its pull requests" (README) | [claimed] in its general form: any agent *can* call `record_session`, but only voluntarily, only after a PR exists, and only Claude Code has a hook | README.md | |
| 136 | |
| 137 | ### II.3 Two internal agent runtimes that don't share a model |
| 138 | |
| 139 | **(A) The g1t coding agent ("assign an issue to g1t")** [built] |
| 140 | |
| 141 | - `Op::AssignIssue` → `services/runner` `startImplement` → `work.openPull({agent:"g1t", runtime:"hosted"})`. |
| 142 | - `runCredential(onBehalfOf: actor)` then mints the token, and `AttemptSandbox` (Cloudflare Containers, not Sandbox SDK) runs `g1t-runner`. |
| 143 | - `g1t-runner` spawns `claude --print … --output-format stream-json --dangerously-skip-permissions` with g1t's MCP server, guard hooks (`PreToolUse`, `crates/runner/src/guard.rs`) and steer hooks (`PostToolUse`/`Stop` inject PR messages, `crates/runner/src/steer.rs`). |
| 144 | - `services/work/src/lifecycle.rs` `decide()` drives draft → review (second agent) → revise → catch-up → queue. |
| 145 | - Runs are recorded in **work's** D1 (`agent_runs`, `agent_messages`, `agent_waits`, `memories`, `plans`, `run_confidence`, `guardrails`). |
| 146 | |
| 147 | **(B) Workspace agents ("hired" teammates, `@g1t` orchestrator)** [built] |
| 148 | |
| 149 | - `services/agents`: a hand-written Anthropic Messages tool loop (`services/agents/src/turn.ts`) in a `Desk` Durable Object per agent, driven by chat. |
| 150 | - Its own sessions (`agent_sessions` with tree/payer/inbox/context), its own memory (`agent_memories`), routines, skills, marketplaces, plugins, connectors and budgets. |
| 151 | - It **cannot open PRs or start coding runs**: abilities `g1t:pulls`, `g1t:merge` and `g1t:deploy` have `tools: []` [stub] (`packages/contracts/src/abilities.ts`). |
| 152 | |
| 153 | So "session" means three unrelated things: `agent_sessions` (chat-born work |
| 154 | of workspace agents), `agent_runs` (sandboxes) and `session_entries` (a PR's |
| 155 | log). None of them can represent *the founder's Claude Code session that |
| 156 | hasn't produced a PR*. |
| 157 | |
| 158 | ### II.4 Identity and authority: one significant hole |
| 159 | |
| 160 | - g1t's own agent and workspace agents are well modelled. |
| 161 | - `User.acting = Acting { agent, on_behalf_of, scope: AgentScope }` (`crates/contracts/src/credentials.rs`). |
| 162 | - Run credentials are intersected with the sponsor's live rights, capped at 6 h and revoked at stop (`services/identity/src/run_credentials.rs`). |
| 163 | - Audit records `ActorKind::Agent` with `on_behalf_of`. |
| 164 | - **External agents are indistinguishable from their human.** |
| 165 | - An OAuth grant from Claude Code, or a PAT, never sets `acting` (`services/identity/src/oauth.rs`, `tokens.rs`), so `rules::is_agent` returns false (`crates/contracts/src/rules.rs:1329`). |
| 166 | - A review is only treated as an agent's if `reviewer_id == AGENT_ID` (`services/work/src/rulesets.rs:184`). |
| 167 | - Consequence: **a local Claude Code session holding the founder's MCP grant can approve a colleague's protected PR, and that approval counts as the founder's.** |
| 168 | - `agent_change()` falls back on the *self-reported* free-text `pulls.agent` field (`rulesets.rs:176-181`). |
| 169 | - This is the most important correctness issue in the audit, and it gets worse the more you succeed at bringing external agents in. |
| 170 | |
| 171 | ### II.5 Memory and context: three overlapping systems |
| 172 | |
| 173 | | System | What it holds | Where | |
| 174 | | --- | --- | --- | |
| 175 | | Work `memories` | Per repo or workspace; candidates/kept; confidence; fingerprints; sources | `0015`, `0019_memory_capture.sql` | |
| 176 | | Agents `agent_memories` | Workspace/channel/person scopes | `services/agents/src/memory.ts` | |
| 177 | | `services/context` | Catalog, Vectorize search, scorecards, `runcontext.ts` injected into hosted runs | `services/context` | |
| 178 | |
| 179 | - The repository itself has **no `AGENTS.md` or `CLAUDE.md`**, even though the docs recommend one (`guides/bring-your-own-agent.mdx`). |
| 180 | - That is the clearest dogfooding signal in the repo: the knowledge your Claude Code sessions use to build g1t lives in commit messages and in your head, not in any of g1t's three memory systems. |
| 181 | |
| 182 | ### II.6 Dogfooding reality, from this very session |
| 183 | |
| 184 | - This checkout's `origin` is `github.com/flagon-io/g1t`. |
| 185 | - 43 of the last 127 commits are authored by Claude, with `Claude-Session:` trailers pointing at claude.ai cloud sessions. |
| 186 | - Claude Code cloud sessions can only clone from and push to GitHub. Other hosts get a read-only bundle. |
| 187 | - So today your cloud agents **cannot push to g1t at all**. g1t sees their work only through the GitHub mirror (`services/repos/src/mirror.rs`, integrations' takeover/hand-back). |
| 188 | - The commit trailers your agents already write (`Claude-Session: https://claude.ai/code/session_…`) are a provenance channel g1t ignores today. |
| 189 | - The sandbox network policy used for this review could not reach `g1t.sh`, `api.g1t.sh` or `docs.g1t.sh`. A cloud agent with a default network policy cannot call g1t's MCP server or hooks unless the environment allowlists g1t. |
| 190 | |
| 191 | **Implication: Phase 1 must work for a GitHub-mirrored repository and for |
| 192 | agents that reach g1t only intermittently.** "Push to g1t" cannot be a |
| 193 | prerequisite for g1t being useful. |
| 194 | |
| 195 | ### II.7 Accidental complexity and coupling |
| 196 | |
| 197 | 1. Agent lifecycle is baked into `pulls`: `agent`, `runtime`, `managed`, `stage`, `stage_detail`, `revisions`, `working_on`, `working_until`, `stalled`. This makes "g1t-made" PRs a different species from human/external PRs. |
| 198 | 2. Agent run state lives in `services/work` (forge D1), workspace-agent state in `services/agents`, and `services/agents` imports source files from `services/runner` (`model-env.ts`, `hosted.ts`). Routing config is duplicated across two wrangler files. |
| 199 | 3. Billing is deeply agent-aware (`attribution.rs` stamps agent handle on ledger lines). That is fine for managed runs, but must never be needed for external sessions. |
| 200 | 4. Three memory systems, three session concepts, two agent identity models (`Acting` vs. `AskerAccess`). |
| 201 | 5. `apps/web` is 152k lines, larger than any backend, with WebSockets for chat, notify and artifacts but polling for agent sessions. |
| 202 | |
| 203 | --- |
| 204 | |
| 205 | ## Part III — Recommended architecture |
| 206 | |
| 207 | ### III.1 The four options, judged |
| 208 | |
| 209 | | Option | Verdict | Why | |
| 210 | | --- | --- | --- | |
| 211 | | **A. Execution platform** | Reject as the core; keep as an optional adapter | Commodity and capital-intensive. Anthropic, OpenAI, Cursor, GitHub and Cloudflare all sell sandboxes plus agents. Your own use proves the agent of choice lives elsewhere. | |
| 212 | | **B. Coordination platform (standalone)** | Reject as a separate product | Without the repository, PRs, checks and approvals, coordination has nothing authoritative to coordinate against. It becomes a chat bus or a task tracker (Linear, Jira and Copilot's Agents tab already exist). | |
| 213 | | **C. Agent-native forge** | **Core** | The forge already owns the things agents cannot fake: refs, diffs, checks, rulesets, approvals and the merge queue. Making sessions and agent identity native to it is new, defensible, and useful with zero hosted agents. | |
| 214 | | **D. Combination** | Yes, as **C ⊃ B-lite, A as a client** | Not three products with three architectures: one forge, one session protocol, and an execution adapter that speaks it. | |
| 215 | |
| 216 | ### III.2 Where "separate the agent from the platform" breaks down |
| 217 | |
| 218 | The principle is right. It breaks in four specific places, and the design |
| 219 | should name them rather than paper over them: |
| 220 | |
| 221 | 1. **Control.** g1t can stop a managed run (kill the container, revoke the credential). For an external session it can only *revoke the session credential* and *ask*, through hook-injected context. The protocol must expose `capabilities` per session (`can_cancel`, `can_steer`) rather than pretend. |
| 222 | 2. **Observation fidelity.** |
| 223 | - Managed runs give g1t the full stream-json and exact cost. |
| 224 | - Local Claude Code gives hook events that an honest client observed: tool names, file paths, Bash commands and `session_id`. |
| 225 | - Codex and Cursor give somewhat less. |
| 226 | - An MCP-only client gives only what the model chooses to say. |
| 227 | - The data model needs a `source` and `trust` on every observed fact. |
| 228 | 3. **Credentials.** A managed run gets a short-lived credential g1t mints. A local agent runs as the developer's OS user and can read `~/.g1t/config.json` and the Git credential helper. g1t can distinguish *credentials*, never *processes*. Authority rules must therefore rest on credential type plus step-up for human-only acts (§III.6), not on trusting the client to say it is an agent. |
| 229 | 4. **Lifecycle automation** (auto-revise on failing checks, auto-review, catch-up) is a policy that needs something to *execute*. For a managed run g1t executes it. For an external session the same policy can only *notify* the session's client, or start a managed run. Put the policy on the PR and the executor behind an interface. |
| 230 | |
| 231 | ### III.3 System diagram |
| 232 | |
| 233 | ```mermaid |
| 234 | flowchart LR |
| 235 | subgraph Clients["Clients (untrusted)"] |
| 236 | CC["Claude Code (local / cloud)<br/>hooks → g1t hook"] |
| 237 | CX["Codex CLI<br/>hooks → g1t hook"] |
| 238 | CU["Cursor<br/>hooks → g1t hook"] |
| 239 | MCPC["Any MCP client<br/>(voluntary)"] |
| 240 | H["Humans: browser, desktop, mobile"] |
| 241 | MR["Managed runner (g1t-runner in Containers)<br/>same g1t hook + run control"] |
| 242 | end |
| 243 | |
| 244 | subgraph Edge["Edge / API (apps/api)"] |
| 245 | REST["REST /v1 (Op table)"] |
| 246 | MCP["MCP (same Op table)"] |
| 247 | GIT["Git smart HTTP"] |
| 248 | FEED["Event feed: cursor long-poll; SSE later"] |
| 249 | end |
| 250 | |
| 251 | subgraph Control["Control plane"] |
| 252 | ID["identity: principals, grants,<br/>session credentials, step-up"] |
| 253 | WORK["work: issues, PRs, reviews, checks,<br/>rulesets, queue, sessions, provenance"] |
| 254 | COORD["RepoCoordinator DO (per repo):<br/>presence, leases, overlap index"] |
| 255 | EV["events: append log + fan-out"] |
| 256 | EXEC["execution (services/runner):<br/>runs, scheduling, cost"] |
| 257 | end |
| 258 | |
| 259 | subgraph Data["Data plane"] |
| 260 | ART["Cloudflare Artifacts (git)"] |
| 261 | D1[("D1 per service")] |
| 262 | R2[("R2: packs, backups, logs")] |
| 263 | CT["Containers: CI, queue builds, managed runs"] |
| 264 | end |
| 265 | |
| 266 | CC & CX & CU --> REST |
| 267 | MCPC --> MCP |
| 268 | CC & CX & CU & MR --> GIT |
| 269 | H --> REST |
| 270 | MR --> REST |
| 271 | REST & MCP --> ID & WORK & COORD & EXEC |
| 272 | GIT --> ART |
| 273 | GIT -- git.push --> EV |
| 274 | WORK --> EV |
| 275 | COORD --> EV |
| 276 | EV --> WORK & EXEC & FEED |
| 277 | EXEC --> CT |
| 278 | CT --> GIT |
| 279 | WORK --> D1 |
| 280 | COORD --> D1 |
| 281 | ``` |
| 282 | |
| 283 | ### III.4 Responsibilities and data ownership |
| 284 | |
| 285 | | Component | Owns (authoritative) | Does not own | |
| 286 | | --- | --- | --- | |
| 287 | | **Git / Artifacts** | Source, refs, commit graph, trailers | Anything about who *ran* what | |
| 288 | | **identity** | Principals, grants, session credentials, client registrations, step-up state | Session content | |
| 289 | | **work** | Issues, PRs, reviews, checks, rulesets, merge queue, **sessions, session events (bounded), provenance edges** | Live leases (it reads them) | |
| 290 | | **RepoCoordinator DO** [proposed] | Live presence and leases for one repo, plus the overlap index of in-flight paths. Snapshotted to D1. | Durable history (events do that) | |
| 291 | | **events** | Ordered append log per stream; fan-out; replay cursor | Business state | |
| 292 | | **execution** (`services/runner`) | Runs: compute, credentials it minted, cancellation, cost | Sessions (it *creates* them like any client) | |
| 293 | | **agents / chat / artifacts** | Frozen (Part VIII) | — | |
| 294 | |
| 295 | Why sessions live in `work` and not a new service: |
| 296 | |
| 297 | - They join to PRs, issues and commits constantly. |
| 298 | - `work` already owns `session_entries` and `agent_runs`. |
| 299 | - A new service would add a network round trip to every PR page. |
| 300 | |
| 301 | Split them out later only if volume demands it. |
| 302 | |
| 303 | ### III.5 Control plane vs. data plane |
| 304 | |
| 305 | - **Data plane:** Git objects (Artifacts), CI and run containers, logs and packs (R2). High volume, eventually consistent from the control plane's point of view, never trusted for authorization. |
| 306 | - **Control plane:** small transactional records in D1 plus per-repo Durable Objects for the few things that need linearizability (leases, presence, the queue's head if it ever needs it). Every control-plane write is idempotent by client-supplied key. |
| 307 | |
| 308 | ### III.6 Identity and permission model |
| 309 | |
| 310 | Four distinct concepts, which the code currently conflates for external |
| 311 | agents: |
| 312 | |
| 313 | | Concept | Definition | Representation | |
| 314 | | --- | --- | --- | |
| 315 | | **Principal** | Who is accountable | A human `User` or a workspace (service account). Never an agent. | |
| 316 | | **Agent client** | What software is acting | `{provider, product, version}` on the grant and the session. Descriptive and self-declared, so not a security boundary. | |
| 317 | | **Delegation** | A principal letting a client act within limits | A *session credential* [proposed]: `Acting { agent: client, on_behalf_of: principal, scope }`. This is the type that already exists in `crates/contracts/src/credentials.rs`, extended to external clients. | |
| 318 | | **Authorization** | What a request may do | Intersection of principal rights × credential scope × rule constraints for agent actors. | |
| 319 | |
| 320 | Rules [proposed]: |
| 321 | |
| 322 | 1. Every OAuth grant issued to an MCP client or to the `g1t` CLI's agent flow is an **agent grant**. Its tokens resolve to `User { acting: Some(..) }`, so `is_agent()` is true and rulesets with `applies_to: Agents` apply. That one change makes external agents real participants under existing rules. |
| 323 | - PATs stay human by default. |
| 324 | - A PAT used through the hook or MCP is still a human credential. That is a known gap, closed by rule 2, not by detection. |
| 325 | 2. **Human-only acts require an interactive human credential:** |
| 326 | - approving a PR (counting toward required approvals or CODEOWNERS), |
| 327 | - bypassing a rule, |
| 328 | - changing rulesets or branch protection, |
| 329 | - creating or reading secrets, |
| 330 | - production deploy approval, |
| 331 | - minting tokens. |
| 332 | |
| 333 | "Interactive human credential" means a browser or desktop session, or a PAT marked `human_interactive` used with a recent WebAuthn/TOTP step-up (e.g. within 12 h). A compromised laptop agent can still click through a browser it controls. Nothing at the forge prevents that, and the review should say so. |
| 334 | 3. Agent approvals are recorded, shown and *never* count unless the ruleset sets `count_agent_approvals` (existing flag), and never for the PR's own sponsor. |
| 335 | 4. **Self-approval is forbidden by principal, not by client.** An agent acting for Alice cannot approve Alice's PR, and Alice can't approve a PR whose sessions she sponsored if the ruleset requires an independent reviewer. |
| 336 | 5. Session credentials are scoped to one workspace and an explicit repo set, expire (default 12 h, renewable while active) and are revocable from the session page. |
| 337 | |
| 338 | ### III.7 Consistency guarantees |
| 339 | |
| 340 | | Data | Guarantee | |
| 341 | | --- | --- | |
| 342 | | Refs, merges, queue landing | Linearizable per ref (Artifacts compare-and-swap, fast-forward land). Unchanged. | |
| 343 | | Session creation, close, link | Idempotent upsert by `(principal, client_session_id)`. Read-your-writes per request. | |
| 344 | | Session events | At-least-once from client; deduped by `(session_id, client_seq)`. Ordered per session by `client_seq`, not across sessions. | |
| 345 | | Leases | Linearizable per repo (one DO). TTL expiry is authoritative only at the DO. Clients see eventually consistent copies (≤ a few seconds). | |
| 346 | | Overlap / conflict hints | Eventually consistent, advisory, may be stale. Never block a push or merge on them. | |
| 347 | | Provenance edges from trailers | Derived from Git at push time. Recomputable from Git alone. | |
| 348 | | Event feed | At-least-once, per-repo ordering by log id, replayable for retention window (proposed 7 days). | |
| 349 | |
| 350 | --- |
| 351 | |
| 352 | ## Part IV — Domain model |
| 353 | |
| 354 | ### IV.1 Which concepts get entities |
| 355 | |
| 356 | | Concept | Entity? | Decision | |
| 357 | | --- | --- | --- | |
| 358 | | **Commit** | Git | Never duplicate. Store only edges *to* SHAs. | |
| 359 | | **Changeset** | No | A branch + head SHA + base SHA is the changeset. Git has it. | |
| 360 | | **Pull request** | Yes (exists) | Remains *the* approval and integration artifact. Strip the agent-lifecycle columns into a separate table. | |
| 361 | | **Issue** | Yes (exists) | Is the intent *and* the task. Sub-issues and `blocked_by` (exists) cover decomposition. | |
| 362 | | **Intent** | No | An issue, or a session's `goal` text. A separate entity adds a concept nobody maintains. | |
| 363 | | **Task** | No | An issue. If you need "a task an agent claimed", that is a lease on an issue. | |
| 364 | | **Session** | **Yes, new** | The unit of participation; may never produce a commit. | |
| 365 | | **Run** | Yes (rename `agent_runs`) | Exists only for managed execution: compute g1t controls and bills. A run *serves* a session. | |
| 366 | | **Session event** | Yes, bounded | Observed or reported facts with source and trust. Not a transcript. | |
| 367 | | **Lease (claim)** | Yes, lives in the DO | Advisory, TTL'd, owned by a session. | |
| 368 | | **Provenance edge** | Yes, link tables | `session_commits`, `session_links` (issue/PR), `pull_heads`. | |
| 369 | | **Contract / decision** | No | Files in the repo (OpenAPI, `.proto`, ADRs) guarded by CODEOWNERS/path rules. | |
| 370 | | **Integration frontier** | No | Merge queue + speculative batches (exists). | |
| 371 | |
| 372 | ### IV.2 TypeScript interfaces |
| 373 | |
| 374 | These mirror into Rust contracts in `crates/contracts/src/work.rs` and |
| 375 | `packages/contracts/src/work.ts`. |
| 376 | |
| 377 | ```ts |
| 378 | // ---------- identity ---------- |
| 379 | type PrincipalRef = { kind: "user" | "workspace"; id: string; name: string }; |
| 380 | |
| 381 | /** Self-declared; descriptive only. */ |
| 382 | interface AgentClient { |
| 383 | provider: "anthropic" | "openai" | "cursor" | "google" | "github" | "g1t" | "other"; |
| 384 | product: string; // "claude-code", "codex-cli", "cursor", "gemini-cli", "g1t-runner" |
| 385 | version?: string; |
| 386 | adapter?: string; // "g1t-hook/0.3.0" – our code, so slightly more trustworthy |
| 387 | } |
| 388 | |
| 389 | interface SessionCredential { // extends existing Acting |
| 390 | id: string; // cred_… |
| 391 | principal: PrincipalRef; // on_behalf_of |
| 392 | client: AgentClient; |
| 393 | workspace: string; |
| 394 | repos: string[]; // explicit; [] = none |
| 395 | scopes: string[]; // ⊆ grant scopes ⊆ principal rights |
| 396 | sessionId?: string; // bound once the session is opened |
| 397 | expiresAt: string; |
| 398 | revokedAt?: string; |
| 399 | } |
| 400 | |
| 401 | // ---------- sessions ---------- |
| 402 | type Execution = |
| 403 | | { kind: "local"; host?: string /* hashed hostname, optional */ } |
| 404 | | { kind: "remote"; vendor: string; url?: string /* e.g. claude.ai session URL */ } |
| 405 | | { kind: "managed"; runId: string }; |
| 406 | |
| 407 | type SessionState = |
| 408 | | "active" // event within idle window |
| 409 | | "idle" // no event for > 15 min |
| 410 | | "ended" // SessionEnd / explicit end |
| 411 | | "abandoned" // no event for > lease max (default 4 h); leases released |
| 412 | | "handed_off"; // ended with a successor session |
| 413 | |
| 414 | interface Session { |
| 415 | id: string; // ses_… (ULID) |
| 416 | workspace: string; |
| 417 | repoId: string | null; // null allowed: investigation outside any g1t repo isn't recorded at all |
| 418 | principal: PrincipalRef; // AUTHORITATIVE (from credential) |
| 419 | client: AgentClient; // SELF-REPORTED |
| 420 | clientSessionId: string; // provider's id (Claude session_id, Codex thread id). SELF-REPORTED, unique per principal |
| 421 | execution: Execution; // managed: AUTHORITATIVE; else SELF-REPORTED |
| 422 | parentId?: string; // subagent or handoff source |
| 423 | state: SessionState; // DERIVED (server) |
| 424 | title?: string; // REPORTED (model/human) |
| 425 | goal?: string; // REPORTED |
| 426 | summary?: string; // REPORTED, shown with "agent's account" label |
| 427 | // Git position: last OBSERVED by the client adapter (git rev-parse), verified when SHA appears in a push |
| 428 | branch?: string; |
| 429 | baseSha?: string; // merge-base with default branch when started / last seen |
| 430 | headSha?: string; |
| 431 | touchedPaths: string[]; // OBSERVED by client hooks (Edit/Write/patch), capped 500, repo-relative |
| 432 | capabilities: { cancel: boolean; steer: boolean; costExact: boolean }; // DERIVED from execution+adapter |
| 433 | usage?: { inputTokens?: number; outputTokens?: number; costUsd?: number; exact: boolean }; // managed: exact |
| 434 | visibility: "repo" | "private"; // private: only principal + admins (investigations) |
| 435 | startedAt: string; |
| 436 | lastEventAt: string; |
| 437 | endedAt?: string; |
| 438 | } |
| 439 | |
| 440 | /** Edges. Rows, not a graph DB. */ |
| 441 | interface SessionLink { |
| 442 | sessionId: string; |
| 443 | target: { kind: "issue" | "pull"; repoId: string; number: number }; |
| 444 | source: "explicit" | "branch" | "commit_trailer" | "managed"; |
| 445 | createdAt: string; |
| 446 | } |
| 447 | |
| 448 | interface SessionCommit { |
| 449 | sessionId: string; |
| 450 | repoId: string; |
| 451 | sha: string; |
| 452 | source: "trailer" | "hook_observed" | "managed"; // trailer = verifiable in git |
| 453 | observedAt: string; |
| 454 | } |
| 455 | |
| 456 | /** Every head a PR has ever had: survives force-push and rebase. */ |
| 457 | interface PullHead { pullId: string; sha: string; pushedBy: string; pushedAt: string; forced: boolean } |
| 458 | |
| 459 | // ---------- session events (bounded, typed, not a transcript) ---------- |
| 460 | type Trust = "verified" | "observed" | "reported"; |
| 461 | // verified: g1t checked it (a SHA exists, a check passed) |
| 462 | // observed: an adapter saw it deterministically (hook) – honest-client assumption |
| 463 | // reported: model/user text |
| 464 | |
| 465 | type SessionEventKind = |
| 466 | | "lifecycle" // start, resume, compact, end, stop_failure |
| 467 | | "prompt" // OFF by default; opt-in per repo, redacted |
| 468 | | "tool" // {tool, target (path/cmd head), ok, durationMs} – no outputs |
| 469 | | "files" // {paths[], op: edit|write|delete} |
| 470 | | "git" // {op: commit|push|checkout|rebase, sha?, branch?} |
| 471 | | "note" // model or human text: decisions, blockers, summary |
| 472 | | "lease" // acquired/renewed/released (mirrors DO) |
| 473 | | "warning"; // server → session: overlap, behind, failing check (echoed for audit) |
| 474 | |
| 475 | interface SessionEvent { |
| 476 | sessionId: string; |
| 477 | seq: number; // client-assigned, monotonic per session; dedupe key |
| 478 | kind: SessionEventKind; |
| 479 | trust: Trust; |
| 480 | source: "hook" | "mcp" | "api" | "runner" | "server"; |
| 481 | at: string; // client clock; server stores receivedAt too |
| 482 | data: Record<string, unknown>; // ≤ 4 KB after redaction |
| 483 | } |
| 484 | |
| 485 | // ---------- coordination ---------- |
| 486 | type LeaseTarget = |
| 487 | | { kind: "issue"; number: number } |
| 488 | | { kind: "paths"; globs: string[] }; // e.g. ["services/work/src/queue.rs", "crates/rules/**"] |
| 489 | |
| 490 | interface Lease { |
| 491 | id: string; // lse_… |
| 492 | repoId: string; |
| 493 | sessionId: string; |
| 494 | principal: PrincipalRef; |
| 495 | target: LeaseTarget; |
| 496 | mode: "advisory"; // exclusive is NOT offered (see Part VII) |
| 497 | note?: string; // "changing merge_gate signature" |
| 498 | acquiredAt: string; |
| 499 | expiresAt: string; // ≤ 30 min; renewed by any session event |
| 500 | } |
| 501 | |
| 502 | interface Overlap { // computed, never stored as truth |
| 503 | withSession?: string; |
| 504 | withPull?: number; |
| 505 | paths: string[]; |
| 506 | kind: "lease" | "touched" | "pr_diff"; |
| 507 | freshness: string; // when computed |
| 508 | } |
| 509 | |
| 510 | // ---------- managed execution ---------- |
| 511 | type RunState = "queued" | "starting" | "running" | "succeeded" | "failed" | "cancelled" | "timed_out"; |
| 512 | interface Run { |
| 513 | id: string; // run_… |
| 514 | sessionId: string; // every run serves exactly one session; a session may have many runs (retries) |
| 515 | workspace: string; |
| 516 | repoId: string; |
| 517 | kind: "implement" | "revise" | "review" | "update" | "answer" | "plan"; |
| 518 | executor: { image: string; agent: AgentClient; model?: string; size: string; where: "cloudflare" | "self_hosted" }; |
| 519 | state: RunState; |
| 520 | attempt: number; |
| 521 | timeoutAt: string; |
| 522 | costUsd?: number; // exact, from model proxy |
| 523 | sandboxSeconds?: number; |
| 524 | error?: string; |
| 525 | createdAt: string; startedAt?: string; finishedAt?: string; |
| 526 | } |
| 527 | |
| 528 | // ---------- PR automation, moved off `pulls` ---------- |
| 529 | interface PullAutomation { // replaces pulls.managed/stage/revisions/working_*/stalled |
| 530 | pullId: string; |
| 531 | policy: "none" | "notify_session" | "managed_revise"; |
| 532 | stage?: string; |
| 533 | revisions: number; |
| 534 | workingRunId?: string; |
| 535 | stalled?: string; |
| 536 | } |
| 537 | ``` |
| 538 | |
| 539 | ### IV.3 SQL (D1, `g1t-work`): additive migrations only |
| 540 | |
| 541 | ```sql |
| 542 | CREATE TABLE sessions ( |
| 543 | id TEXT PRIMARY KEY, workspace TEXT NOT NULL, repo_id TEXT, |
| 544 | principal_id TEXT NOT NULL, principal_name TEXT NOT NULL, |
| 545 | client TEXT NOT NULL, -- JSON AgentClient |
| 546 | client_session_id TEXT NOT NULL, |
| 547 | execution TEXT NOT NULL, -- JSON |
| 548 | parent_id TEXT, state TEXT NOT NULL, title TEXT, goal TEXT, summary TEXT, |
| 549 | branch TEXT, base_sha TEXT, head_sha TEXT, |
| 550 | touched_paths TEXT NOT NULL DEFAULT '[]', |
| 551 | usage TEXT, visibility TEXT NOT NULL DEFAULT 'repo', |
| 552 | started_at TEXT NOT NULL, last_event_at TEXT NOT NULL, ended_at TEXT, |
| 553 | UNIQUE (principal_id, client_session_id) |
| 554 | ); |
| 555 | CREATE INDEX sessions_active ON sessions (repo_id, state, last_event_at); |
| 556 | CREATE INDEX sessions_branch ON sessions (repo_id, branch); |
| 557 | |
| 558 | CREATE TABLE session_events ( |
| 559 | session_id TEXT NOT NULL, seq INTEGER NOT NULL, kind TEXT NOT NULL, |
| 560 | trust TEXT NOT NULL, source TEXT NOT NULL, at TEXT NOT NULL, |
| 561 | received_at TEXT NOT NULL, data TEXT NOT NULL, |
| 562 | PRIMARY KEY (session_id, seq) |
| 563 | ); -- retention: 90 days default; summaries/links kept |
| 564 | |
| 565 | CREATE TABLE session_links ( |
| 566 | session_id TEXT NOT NULL, kind TEXT NOT NULL, repo_id TEXT NOT NULL, number INTEGER NOT NULL, |
| 567 | source TEXT NOT NULL, created_at TEXT NOT NULL, |
| 568 | PRIMARY KEY (session_id, kind, repo_id, number) |
| 569 | ); |
| 570 | CREATE INDEX session_links_target ON session_links (repo_id, kind, number); |
| 571 | |
| 572 | CREATE TABLE session_commits ( |
| 573 | session_id TEXT NOT NULL, repo_id TEXT NOT NULL, sha TEXT NOT NULL, |
| 574 | source TEXT NOT NULL, observed_at TEXT NOT NULL, |
| 575 | PRIMARY KEY (repo_id, sha, session_id) |
| 576 | ); |
| 577 | |
| 578 | CREATE TABLE pull_heads ( |
| 579 | pull_id TEXT NOT NULL, sha TEXT NOT NULL, pushed_by TEXT, pushed_at TEXT NOT NULL, |
| 580 | forced INTEGER NOT NULL DEFAULT 0, PRIMARY KEY (pull_id, sha) |
| 581 | ); |
| 582 | ``` |
| 583 | |
| 584 | - `session_entries` stays read-only for old PRs. |
| 585 | - `record_session` becomes a shim: it finds or creates the session linked to the PR and appends `note` events. |
| 586 | |
| 587 | ### IV.4 Lifecycles |
| 588 | |
| 589 | ```mermaid |
| 590 | stateDiagram-v2 |
| 591 | [*] --> active: open (SessionStart / first event / run start) |
| 592 | active --> idle: no event 15m |
| 593 | idle --> active: any event |
| 594 | active --> ended: SessionEnd / end call / run finished |
| 595 | idle --> abandoned: no event 4h (leases released) |
| 596 | active --> handed_off: end with successor |
| 597 | abandoned --> active: resume with same client_session_id |
| 598 | ended --> active: resume (Claude --resume, Codex thread resume) |
| 599 | ended --> [*] |
| 600 | handed_off --> [*] |
| 601 | ``` |
| 602 | |
| 603 | ```mermaid |
| 604 | stateDiagram-v2 |
| 605 | [*] --> queued |
| 606 | queued --> starting: slot admitted |
| 607 | starting --> running: container up, session opened |
| 608 | running --> succeeded |
| 609 | running --> failed |
| 610 | running --> cancelled: user/system cancel (g1t controls compute) |
| 611 | running --> timed_out |
| 612 | failed --> queued: retry (attempt+1, same session) |
| 613 | ``` |
| 614 | |
| 615 | A PR's lifecycle is unchanged. Sessions attach to it; they never gate it. |
| 616 | |
| 617 | --- |
| 618 | |
| 619 | ## Part V — External agent protocol |
| 620 | |
| 621 | ### V.1 Layering |
| 622 | |
| 623 | | Layer | Role | Primary or adapter? | |
| 624 | | --- | --- | --- | |
| 625 | | **Git** | Code transport, branches, commits, **trailers as provenance** (`G1t-Session: ses_…`) | Primary, unchanged | |
| 626 | | **REST `/v1/sessions…`** | Deterministic integration surface for adapters, daemons and runners. No model involvement. | **Primary** | |
| 627 | | **`g1t` CLI** | Auth, repo detection, hook adapter for every provider, local spool, Git hook installer | **Essential.** Hooks need a fast, local, offline-tolerant executable; a script piping to `curl` isn't one. | |
| 628 | | **MCP** | Discovery, context reads, voluntary structured reports (notes, leases, links), platform actions | **Convenience adapter** over the same `Op`s. Never the only path for anything that must be reliable. | |
| 629 | | **Event feed** | Clients and integrations learn what changed | Small: cursor long-poll | |
| 630 | |
| 631 | Why MCP is not primary: |
| 632 | |
| 633 | - Every MCP call depends on the model deciding to make it. The current docs admit it: "it has to remember to." |
| 634 | - Session open and close, file touches, commits and pushes must be captured by hooks. |
| 635 | - MCP remains excellent for *reading* (context, active work, PR state) and for *intentional* acts (claim this issue, record this decision, open this PR). |
| 636 | |
| 637 | ### V.2 Authentication |
| 638 | |
| 639 | 1. `g1t login` runs the device flow (exists: `/device/code`, `/device/token`). It produces an **agent grant** (refresh token in the OS keychain, falling back to `~/.g1t/credentials` at mode 0600). |
| 640 | 2. On session open, the CLI exchanges the grant for a **session credential** bound to `(workspace, repos, session)`, valid for 12 h and renewable. Hooks and MCP use it. |
| 641 | 3. For managed runs, `services/runner` mints the session credential directly (today's `runCredential`). |
| 642 | 4. For cloud agents (Claude Code on the web, Codex cloud): |
| 643 | - set `G1T_TOKEN` as an environment secret holding a **workspace-scoped agent token** limited to named repos, |
| 644 | - allowlist `api.g1t.sh` in the environment's network policy, |
| 645 | - document both steps. |
| 646 | |
| 647 | ### V.3 Endpoints |
| 648 | |
| 649 | All take `Idempotency-Key`, all return `ETag` where meaningful, and all are |
| 650 | mirrored as MCP actions on a new `session` tool. |
| 651 | |
| 652 | ```http |
| 653 | POST /v1/sessions open or resume (upsert by client_session_id) |
| 654 | POST /v1/sessions/{id}/events batch append (≤200), dedupe by seq |
| 655 | POST /v1/sessions/{id}/links link issue/PR |
| 656 | POST /v1/sessions/{id}/end end (optional successor for handoff) |
| 657 | GET /v1/sessions/{id} read |
| 658 | GET /v1/repos/{o}/{r}/activity active sessions + leases + overlaps for this repo (the "board") |
| 659 | POST /v1/repos/{o}/{r}/leases acquire/renew advisory lease |
| 660 | DELETE /v1/leases/{id} release |
| 661 | GET /v1/repos/{o}/{r}/events?after=… cursor feed (long-poll ≤25 s) |
| 662 | GET /v1/repos/{o}/{r}/context what an agent should know at start (§X) |
| 663 | ``` |
| 664 | |
| 665 | **Open or resume:** |
| 666 | |
| 667 | ```http |
| 668 | POST /v1/sessions |
| 669 | Authorization: Bearer g1s_… (agent grant or session credential) |
| 670 | Idempotency-Key: claude:4f1c…:open |
| 671 | Content-Type: application/json |
| 672 | |
| 673 | { |
| 674 | "client": { "provider": "anthropic", "product": "claude-code", "version": "2.3.1", "adapter": "g1t-hook/0.1.0" }, |
| 675 | "client_session_id": "4f1c9e2a-…", |
| 676 | "execution": { "kind": "local" }, |
| 677 | "repo": { "remote": "https://github.com/flagon-io/g1t.git" }, // resolved to the g1t mirror server-side |
| 678 | "git": { "branch": "claude/bold-sagan", "head": "bba76b3…", "base": "bba76b3…" }, |
| 679 | "parent_client_session_id": null |
| 680 | } |
| 681 | ``` |
| 682 | |
| 683 | ```json |
| 684 | { |
| 685 | "session": { "id": "ses_01JA…", "state": "active", "repo": "flagon-io/g1t", |
| 686 | "capabilities": { "cancel": false, "steer": true, "costExact": false } }, |
| 687 | "credential": { "token": "g1s_…", "expires_at": "2026-10-13T02:00:00Z" }, |
| 688 | "trailer": "G1t-Session: ses_01JA…", |
| 689 | "context": { |
| 690 | "active": [ |
| 691 | { "session": "ses_01J9…", "who": "syntaqx · codex-cli", "branch": "fix/queue-retest", |
| 692 | "touched": ["services/work/src/queue.rs"], "issue": 412, "last": "2m" } |
| 693 | ], |
| 694 | "open_pulls_touching_hot_paths": [ { "number": 418, "paths": ["crates/rules/src/merge.rs"] } ], |
| 695 | "default_branch_failing": [], |
| 696 | "instructions": ["AGENTS.md"] |
| 697 | }, |
| 698 | "feed_cursor": "evt_01JA…" |
| 699 | } |
| 700 | ``` |
| 701 | |
| 702 | - `repo.remote` may point at GitHub. The server resolves it to the g1t repository whose mirror config names that remote. If none exists, it answers `404 repo_not_on_g1t` and the adapter stays silent. |
| 703 | - An unauthorised repo returns the same 404, so the response does not reveal that the repository exists. |
| 704 | |
| 705 | **Append events:** |
| 706 | |
| 707 | ```http |
| 708 | POST /v1/sessions/ses_01JA…/events |
| 709 | Idempotency-Key: ses_01JA…:17-24 |
| 710 | |
| 711 | { "events": [ |
| 712 | { "seq": 17, "kind": "files", "trust": "observed", "source": "hook", "at": "…", |
| 713 | "data": { "op": "edit", "paths": ["services/work/src/queue.rs"] } }, |
| 714 | { "seq": 18, "kind": "tool", "trust": "observed", "source": "hook", "at": "…", |
| 715 | "data": { "tool": "Bash", "target": "cargo test -p g1t-work", "ok": false, "durationMs": 48211 } }, |
| 716 | { "seq": 19, "kind": "git", "trust": "observed", "source": "hook", "at": "…", |
| 717 | "data": { "op": "commit", "sha": "9c2e…", "branch": "claude/bold-sagan" } } |
| 718 | ]} |
| 719 | ``` |
| 720 | |
| 721 | ```json |
| 722 | { "accepted": [17,18,19], "duplicates": [], "next_seq": 20, |
| 723 | "advice": [ |
| 724 | { "kind": "overlap", "paths": ["services/work/src/queue.rs"], |
| 725 | "with": { "session": "ses_01J9…", "who": "syntaqx · codex-cli", "lease_note": "changing retest ordering" }, |
| 726 | "freshness": "2026-10-12T15:02:11Z" } |
| 727 | ] } |
| 728 | ``` |
| 729 | |
| 730 | - `advice` is how the server talks back without a socket. The adapter turns it into hook output (`additionalContext`) on the next hook call. |
| 731 | - Clients never block on it. |
| 732 | |
| 733 | **Lease:** |
| 734 | |
| 735 | ```json |
| 736 | POST /v1/repos/flagon-io/g1t/leases |
| 737 | { "session": "ses_01JA…", "target": { "kind": "paths", "globs": ["services/work/src/queue.rs"] }, |
| 738 | "note": "changing retest ordering", "ttl_seconds": 1800 } |
| 739 | → 201 { "lease": { "id": "lse_…", "expires_at": "…" }, |
| 740 | "conflicts": [ { "lease": "lse_…", "session": "ses_01J9…", "globs": ["services/work/**"] } ] } |
| 741 | ``` |
| 742 | |
| 743 | The lease is granted even when it conflicts, because it is advisory. The |
| 744 | caller is told about the conflict. |
| 745 | |
| 746 | ### V.4 Failure handling and reconnection |
| 747 | |
| 748 | | Failure | Behaviour | |
| 749 | | --- | --- | |
| 750 | | Network down | The CLI appends to `~/.g1t/spool/<session>.ndjson` and returns immediately (hooks are never slower than ~20 ms locally). A detached flusher retries with backoff. Advice comes from the last cached `activity` snapshot, marked stale. | |
| 751 | | Duplicate delivery | Server dedupes on `(session_id, seq)`; open dedupes on `(principal, client_session_id)`. | |
| 752 | | Clock skew | `at` is the client's; ordering uses `seq`; `received_at` is the server's. | |
| 753 | | Laptop sleeps / process killed | No `SessionEnd`. The server derives `idle` at 15 min and `abandoned` at 4 h, and leases expire at their TTL (≤ 30 min unless renewed by events). Resume with the same `client_session_id` reattaches. | |
| 754 | | Credential expired mid-session | The CLI silently re-exchanges the grant. If the grant was revoked, the spool is discarded after 24 h and the user is told on next `g1t status`. | |
| 755 | | Malicious client | Can only lie about its *own* sessions' reported/observed fields, inside its principal's repos. It cannot affect approvals (§III.6). Its overlap claims are labelled with who made them. Rate limits apply per credential (events ≤ 20/s, leases ≤ 50 active). | |
| 756 | | Secrets in events | Redaction runs **client-side** (the existing `crates/scan` patterns compiled into the CLI) and **again server-side**. Tool outputs are never sent; only tool name, target head and status. Prompt capture is opt-in per repository and per user. | |
| 757 | |
| 758 | ### V.5 Events feed (smallest useful) |
| 759 | |
| 760 | - **Keep webhooks** as they are. |
| 761 | - **Add:** `GET /v1/repos/{o}/{r}/events?after=<id>&types=…`. |
| 762 | - It reads the existing D1 `events` log, whose ids are already time-sortable. |
| 763 | - It long-polls up to 25 s, returns ≤ 500 events and a `next` cursor, and keeps 7 days. |
| 764 | - This gives replay, reconnection and at-least-once delivery for free from the existing log. |
| 765 | - **Later, only if needed:** SSE served by the RepoCoordinator DO for the web "Active work" panel. No WebSockets for agents: agents don't hold sockets, and hooks are request/response. |
| 766 | |
| 767 | --- |
| 768 | |
| 769 | ## Part VI — Claude Code integration |
| 770 | |
| 771 | ### VI.1 What Claude Code actually offers (relevant subset) |
| 772 | |
| 773 | - **Hooks** (deterministic): |
| 774 | - `SessionStart` (source: startup/resume/clear/compact) and `SessionEnd` |
| 775 | - `UserPromptSubmit` |
| 776 | - `PreToolUse` (can deny/ask and rewrite input), `PostToolUse` (receives `tool_input` and `tool_response`), `PostToolUseFailure` |
| 777 | - `Stop` (receives `last_assistant_message`), `StopFailure` |
| 778 | - `SubagentStart` and `SubagentStop` |
| 779 | - `PreCompact`, `CwdChanged`, `WorktreeCreate` |
| 780 | - **Common hook input:** `session_id`, `transcript_path` and `cwd`. |
| 781 | - **Context injection:** `SessionStart`, `UserPromptSubmit` and `PostToolUse` can return `additionalContext`. |
| 782 | - **Handler types:** `command` and `http`. |
| 783 | - **Plugins** bundle hooks, MCP config, skills and commands, and can be installed from a marketplace. |
| 784 | - **MCP** over HTTP with OAuth discovery. |
| 785 | - **`CLAUDE.md`**, which can `@import` `AGENTS.md`. |
| 786 | - **`attribution` settings** for commit and PR text. |
| 787 | - **OTel export** of tokens and cost. |
| 788 | - **Cloud sessions** that require GitHub for clone and push. |
| 789 | |
| 790 | Hook names and payloads should be pinned to a tested Claude Code version in |
| 791 | the adapter's test suite, because the hook surface has grown quickly. |
| 792 | |
| 793 | ### VI.2 Responsibilities |
| 794 | |
| 795 | | Concern | Hook | CLI (`g1t hook claude <event>`) | MCP | Backend | |
| 796 | | --- | --- | --- | --- | --- | |
| 797 | | Open/resume session | `SessionStart` | Read `session_id`, `cwd`, git branch/head/base; open via REST; write `~/.g1t/sessions/<claude id>.json`; print `additionalContext` with active work + instructions pointer | — | Upsert session; compute context | |
| 798 | | Bind commits | `PostToolUse` (Bash with `git commit`/`git push`) + Git `prepare-commit-msg` hook | Git hook adds `G1t-Session:` trailer from the session file matching `cwd`; PostToolUse reads `git rev-parse HEAD` | — | Parse trailers on `git.push` (own and mirrored repos) | |
| 799 | | File touches | `PostToolUse` matcher `Edit\|Write\|MultiEdit\|NotebookEdit` | Collect `tool_input.file_path`, relativize, spool | — | Update `touched_paths`, recompute overlaps | |
| 800 | | Overlap warning | `PostToolUse` on Edit/Write | If cached advice mentions the path, return `additionalContext`: "ses_… (Codex, syntaqx) holds a lease on this file: 'changing retest ordering'. Consider coordinating before diverging." | — | Advice in events response | |
| 801 | | Optional soft gate | `PreToolUse` on Edit/Write | Only if repo setting `coordination.ask_on_leased_paths`: return `permissionDecision: "ask"` with reason. **Never `deny`.** | — | Repo setting | |
| 802 | | Tool activity | `PostToolUse` (all) | Name + target head + ok + duration. **No `tool_response` content.** | — | Store bounded events | |
| 803 | | Prompts | `UserPromptSubmit` | Only if opted in; redacted; ≤ 2 kB | — | Store | |
| 804 | | Turn summary | `Stop` | `last_assistant_message`, ≤ 2 kB, as `note` (trust: reported), debounced to the last per 5 min | — | Shown as "agent's account" | |
| 805 | | End | `SessionEnd` | End via REST; flush spool | — | State → ended; release leases | |
| 806 | | Subagents | `SubagentStart/Stop` | Child session with `parentId` (only if it edits files) | — | — | |
| 807 | | Link issue/PR | — | `g1t link 412` (human) | `session.link` (model) | Also auto-link: branch matches a PR's head, or commits with trailer land in a PR | |
| 808 | | Claim | — | `g1t claim services/work/**` | `session.lease` | DO | |
| 809 | | Read context | `SessionStart` injection | `g1t status` | `session.activity`, `repo.context` resources | — | |
| 810 | | Cost | — | Optional: parse final `result`/OTel if the user enables OTel → `usage` (exact: false unless from OTel) | — | — | |
| 811 | |
| 812 | ### VI.3 Installation |
| 813 | |
| 814 | - **Ship a Claude Code plugin** (`g1t`, in a g1t-hosted marketplace) containing: |
| 815 | - `hooks/hooks.json` calling `g1t hook claude <event>`, |
| 816 | - `.mcp.json` pointing at `https://mcp.g1t.sh`, |
| 817 | - a skill telling the model *when* to call `session.lease`, `session.link` and `session.note`. |
| 818 | - **Install at project scope** (`.claude/settings.json` committed in the repo): |
| 819 | - this makes it fire in cloud sessions too, |
| 820 | - it fires only where the repo opts in, unlike today's global install, |
| 821 | - the binary no-ops if `g1t` isn't installed or the remote isn't on g1t. |
| 822 | - **For cloud sessions**, the environment setup script installs the `g1t` binary (one static Rust binary from `crates/g1t`), and `G1T_TOKEN` comes from environment secrets. |
| 823 | |
| 824 | ### VI.4 Local session lifecycle |
| 825 | |
| 826 | ```mermaid |
| 827 | sequenceDiagram |
| 828 | participant Dev as Developer |
| 829 | participant CC as Claude Code |
| 830 | participant CLI as g1t (hook adapter) |
| 831 | participant Git as git |
| 832 | participant API as g1t API |
| 833 | participant DO as RepoCoordinator |
| 834 | |
| 835 | Dev->>CC: claude (in g1t checkout) |
| 836 | CC->>CLI: SessionStart {session_id, cwd, source} |
| 837 | CLI->>Git: branch, HEAD, merge-base |
| 838 | CLI->>API: POST /v1/sessions (idempotent) |
| 839 | API->>DO: register presence |
| 840 | API-->>CLI: session, credential, context(active work, hot PRs, CI) |
| 841 | CLI-->>CC: additionalContext ("2 others active; ses_… editing queue.rs") |
| 842 | Dev->>CC: "why does the queue retest everything?" |
| 843 | CC->>CLI: PostToolUse Read/Grep (spooled, async flush) |
| 844 | CC->>CLI: PostToolUse Edit services/work/src/queue.rs |
| 845 | CLI->>API: events [files] (async) |
| 846 | API->>DO: touch paths → overlap with ses_J9 lease |
| 847 | API-->>CLI: advice: overlap |
| 848 | CLI-->>CC: additionalContext on next PostToolUse |
| 849 | CC->>Git: git commit (prepare-commit-msg adds G1t-Session trailer) |
| 850 | CC->>CLI: PostToolUse Bash(git commit) → git event with sha |
| 851 | CC->>Git: git push (to GitHub or g1t) |
| 852 | Git-->>API: git.push / mirror sync → parse trailers → session_commits |
| 853 | Dev->>CC: /exit |
| 854 | CC->>CLI: SessionEnd |
| 855 | CLI->>API: POST /end ; DO releases leases |
| 856 | ``` |
| 857 | |
| 858 | ### VI.5 Unreliable or unavailable observability |
| 859 | |
| 860 | | Signal | Reliability | |
| 861 | | --- | --- | |
| 862 | | **Commits made outside Claude** (e.g., in a terminal) | Captured only by the Git `prepare-commit-msg` hook, if installed and a session file matches `cwd`. | |
| 863 | | **File edits via Bash** (`sed -i`, codegen) | Not visible as Edit events. Mitigation: at `Stop` and on `git commit`, the CLI runs `git diff --name-only <base>` and reports those paths with `trust: observed`. This is more reliable than tool events anyway. | |
| 864 | | **Cost** | Not in hook payloads. Only via OTel (user opt-in) or headless `result`. Treat as approximate for local sessions; exact only for managed runs through the model proxy. | |
| 865 | | **"Why"** | Only `last_assistant_message` and explicit notes. Model-generated, so always labelled. | |
| 866 | | **Liveness while idle** | Unknown. No events means "idle", not "dead". | |
| 867 | | **Cloud sessions** | Same hooks, but only if the environment can reach g1t and has the binary. Plan for the failure case: commit trailers still carry provenance when the session pushes to GitHub and g1t mirrors it. | |
| 868 | |
| 869 | --- |
| 870 | |
| 871 | ## Part VII — Multi-agent coordination |
| 872 | |
| 873 | ### VII.1 Scenario baseline |
| 874 | |
| 875 | Three humans run Claude Code, plus two remote autonomous agents, in one |
| 876 | repo. What can actually go wrong, and what g1t can know: |
| 877 | |
| 878 | | Conflict type | Detectable how | Deterministic? | g1t action | |
| 879 | | --- | --- | --- | --- | |
| 880 | | Duplicate work (same issue) | Two sessions linked to / leasing the same issue | Yes (if linked) | Warn both at link/lease time | |
| 881 | | File overlap, in progress | `touched_paths` ∩ across active sessions, ∩ open PR diffs | Yes (honest clients) | Advice to both sessions; badge on board | |
| 882 | | File overlap, pushed | PR diffs (exists: `overlaps`) | Yes | Existing; extend to session ↔ PR | |
| 883 | | Git merge conflict | Trial merge (exists: mergeability) | Yes | Existing; add *pairwise* trial merge between overlapping PRs on push, not just vs. base | |
| 884 | | Symbol overlap | Tree-sitter diff of changed symbols per language | Mostly (per supported language) | **Defer.** Useful but language-by-language work; file overlap gets 80% | |
| 885 | | Interface incompatibility | Contract files changed (OpenAPI, proto, `crates/contracts`, exported types); typecheck of the combined state | Partly: "contract file changed" yes; "incompatible" only by compiling/testing the combination | Path rule: changes to declared contract paths notify sessions touching dependents; the merge queue's combined build is the real check | |
| 886 | | Behavioural incompatibility | Tests on the combined state | Only as good as the tests | Speculative merge queue (exists) | |
| 887 | | Dependency conflicts | Lockfile/manifest diffs; resolution on combined state | Yes for "both changed lockfile"; resolution needs a build | Treat lockfiles as always-overlapping; queue catches the rest | |
| 888 | | Contradictory architecture decisions | Not detectable reliably | No (LLM judgment) | Don't build a detector. Make decisions *files* (ADRs) so they show up as file overlap and review | |
| 889 | |
| 890 | **What not to oversell:** |
| 891 | |
| 892 | - "Semantic conflict detection" in practice is (a) file and lease overlap early, (b) pairwise trial merges, and (c) building and testing the combination in the merge queue. |
| 893 | - An LLM "these two changes look incompatible" reviewer can be an *optional* advisory check later, labelled as such. It must never gate anything. |
| 894 | |
| 895 | ### VII.2 Leases (claims) |
| 896 | |
| 897 | - **Advisory only.** Exclusive locks across laptops that sleep, crash or go offline produce either stuck locks or forced breaks. Both train people to ignore them, and Git already resolves real conflicts. The forge's strong guarantees remain the merge queue and branch rules. |
| 898 | - **Targets:** an issue, or path globs. Not symbols (defer). Not directories as a special type: a glob covers it. |
| 899 | - **Created** explicitly by a human (`g1t claim`), by the model via MCP `session.lease`, or implicitly when a session links to an issue (lease on the issue). |
| 900 | - **Not created implicitly from touched files.** Touches are signals, leases are intentions. Keep them separate so a broad `grep -l | xargs sed` doesn't "claim" half the repo. |
| 901 | - **TTL** ≤ 30 min, renewed by any event from the owning session. Released on `SessionEnd`; expired on `abandoned`. |
| 902 | - **Disconnected clients:** the DO is the clock. A laptop that comes back after expiry gets `lease_expired` in advice and may re-acquire. |
| 903 | - **Storage:** one `RepoCoordinator` Durable Object per repository (SQLite inside), snapshotting to D1 for the board and history. It is per repo because coordination is meaningless across repos, and a DO handles ~1k req/s, orders of magnitude above one repo's session traffic. |
| 904 | |
| 905 | ### VII.3 Presence and the overlap index |
| 906 | |
| 907 | - The DO holds, per active session: principal, client, branch, base, `touched_paths`, leases and last event. |
| 908 | - On every touch or lease, it computes overlaps against other sessions and the open-PR file sets (pushed from `work` when `pulls.files` changes). |
| 909 | - Overlaps are returned as advice and published as `coordination.overlap` events (deduped per pair per 10 min). That avoids notification storms. |
| 910 | - Base drift: when `git.push` moves the default branch, the DO marks sessions whose `baseSha` is behind and whose `touched_paths` intersect the newly landed paths. Those sessions get advice: "main changed `crates/rules/src/merge.rs` since you started." |
| 911 | - This is the single most useful signal for agents working from different base commits. |
| 912 | - It is fully deterministic. |
| 913 | |
| 914 | ### VII.4 Dependencies |
| 915 | |
| 916 | - Issue → issue: `blocked_by` (exists). Expose it in MCP and the board. |
| 917 | - PR → PR: stacked PRs via `base_branch` (exists) are the Git-native way to depend on unmerged work. Make stacks first-class in the UI before inventing anything else. |
| 918 | - **Contract-first:** |
| 919 | - An agent that wants to publish an interface before implementing it opens a small PR changing only the contract file (OpenAPI, a Rust trait in `crates/contracts`, a `.proto`) and gets it merged or stacked. |
| 920 | - Dependents stack on it. |
| 921 | - A change to the contract invalidates dependents through the normal mechanism: their PRs go `behind`, the queue retests, and the base-drift advice fires. |
| 922 | - No contract entity is needed. Declare "contract paths" in repo settings so their changes notify sessions touching dependents. |
| 923 | |
| 924 | ### VII.5 Communication |
| 925 | |
| 926 | - **No agent-to-agent chat.** The existing `agent_messages` exchange (question/handoff between PR agents, `0012_exchanges.sql`) is a step toward exactly the failure mode you named: agents messaging instead of converging. |
| 927 | - Communication happens through **durable artifacts**: lease notes, session notes (decisions, blockers), PR comments and review threads, issues, and the files in the repo. |
| 928 | - A human can always read all of it, and an agent reads it via the board/context at `SessionStart` and in advice. |
| 929 | - The only "message to a session" is a human steering a session that declared `capabilities.steer` (the managed runner via `steer.rs`, local Claude Code via injected `additionalContext` on its next hook). That is one-directional, rate-limited and human-originated. |
| 930 | |
| 931 | ### VII.6 Coordination boundary |
| 932 | |
| 933 | | Scope | Role | |
| 934 | | --- | --- | |
| 935 | | **Repository** | The unit for presence, leases, overlap and the event feed. | |
| 936 | | **Workspace** | Aggregates for Home ("what's running"), never for locking. | |
| 937 | | **Integration branch** | The merge queue's unit (exists: default branch only; extend to any protected branch later). | |
| 938 | |
| 939 | There is no central coordinator: each repo's DO is independent, and the |
| 940 | forge's authoritative state stays in D1 and Git. |
| 941 | |
| 942 | --- |
| 943 | |
| 944 | ## Part VIII — Pull requests, integration and HITL |
| 945 | |
| 946 | 1. **The PR remains the approval artifact.** It is what humans already know how to review, what branch protection binds to, and what mirrors back to GitHub. Sessions *decorate* the PR: |
| 947 | - a "Work" tab listing the sessions that produced its commits (via `session_commits`), each with principal, client, summary (labelled as the agent's account), touched files and verified checks, |
| 948 | - the diff annotated by session. |
| 949 | 2. **The integration pipeline is what exists, made agent-aware:** |
| 950 | - push → push checks (rulesets, secrets), |
| 951 | - PR → required checks (Actions, external statuses), |
| 952 | - confidence/cost/path rules for agent changes (exist), |
| 953 | - human approval (CODEOWNERS), |
| 954 | - merge queue speculative batches (exist) → fast-forward land. |
| 955 | 3. **Integration frontier: reject for now.** |
| 956 | - The speculative merge queue already promotes a validated prefix of compatible changes and ejects failures. |
| 957 | - A "frontier" (any mutually compatible subset, promoted independently) adds combinatorial building, non-linear history semantics and a UI nobody understands, all for throughput gains that only matter at hundreds of merges a day. |
| 958 | - Revisit if queue wait time becomes your bottleneck. Two cheaper improvements come first: |
| 959 | - **independent lanes** for PRs with disjoint path sets (the mergeability disjoint-files logic already exists), |
| 960 | - **pairwise trial merges** between overlapping open PRs. |
| 961 | 4. **Merge methods:** |
| 962 | - Implement squash before anything agent-specific. Agent PRs are commit-noisy, and squash is table stakes. |
| 963 | - Provenance survives because `pull_heads` and `session_commits` keep every pre-squash SHA, and the squash commit message aggregates `G1t-Session:` trailers. |
| 964 | - Rebase merge rewrites SHAs, so record the old → new SHA map at land time. |
| 965 | 5. **Human authority:** as §III.6, enforced in `crates/rules`. Additions: |
| 966 | - `required_human_approvals` (people only, interactive credential) distinct from `required_approvals`, |
| 967 | - "sponsor cannot approve own sessions' work", |
| 968 | - rule edits and bypasses require step-up, |
| 969 | - agent credentials can never hold `secrets:*`, `rulesets:write`, `admin` or production-environment approval, whatever the grant asks for. |
| 970 | 6. **Automated promotion:** |
| 971 | - `agent_auto_merge` (exists) stays, but is gated by human-approved policy and only for low-risk paths (e.g., dependency bumps with green checks). |
| 972 | - It is never available to a credential that also authored the change. |
| 973 | |
| 974 | --- |
| 975 | |
| 976 | ## Part IX — Provenance and explainability |
| 977 | |
| 978 | **Chain:** Human → (Issue) → Session → Commit → PR → Checks → Approval → Merge. |
| 979 | |
| 980 | | Edge | How it is established | Trust | |
| 981 | | --- | --- | --- | |
| 982 | | Human → Session | Credential principal | Verified | |
| 983 | | Session → Issue/PR | Explicit link, branch match, trailer-in-PR | Verified (explicit by principal) / derived | |
| 984 | | Session → Commit | `G1t-Session:` trailer in the commit | Verified as "the committer's tooling said so". Strong because it is in Git and survives rebase, cherry-pick and mirroring. | |
| 985 | | Session → Commit (fallback) | Hook observed `git commit` → sha | Observed | |
| 986 | | Commit → PR | `pull_heads` (every pushed head) + ancestry | Verified | |
| 987 | | PR → Checks/Approval/Merge | Exists | Verified | |
| 988 | | "Why" | Session notes, `Stop` summaries, PR body | **Reported.** Always shown as the agent's account, never as fact. | |
| 989 | |
| 990 | Design rules: |
| 991 | |
| 992 | - **Relational rows**, not a graph store. Every query you need is two or three joins: blame line → commit → session → PR → issue/reviews. |
| 993 | - **Recognise existing trailers too:** `Claude-Session: https://claude.ai/code/session_…` (your commits already have them), `Co-Authored-By`, Codex/Copilot bot identities (`crates/contracts/src/coding_agents.rs`). These give provenance with **no g1t client installed at all**: a session row with `execution: remote`, `client: claude-code`, `principal` = pusher, `trust: reported`. That is a big, cheap win on day one, and it covers your current GitHub-first workflow. |
| 994 | - **Multiple agents on one commit:** multiple `G1t-Session` trailers are allowed (`session_commits` is many-to-many). Attribution is "contributed", not "authored". |
| 995 | - **Force-push:** `pull_heads.forced` keeps the replaced head. Mirror sync already keeps replaced refs under `refs/g1t/replaced/`. Sessions keep their commit edges even when the SHAs become unreachable, shown as "superseded". |
| 996 | - **Failed integrations:** queue entries and check runs already reference SHAs. Ejected entries keep their session edges, so "which session's change broke the batch" is answerable. |
| 997 | - **why-blame** becomes: blame → sha → `session_commits` → session summary + notes near that commit's `git` event (by `seq`) + PR + checks + reviews. Replace the "Pushed <sha>" heuristic in `apps/web/app/routes/repo/why.ts`. |
| 998 | - **Storage bound:** |
| 999 | - events are ≤ 4 kB, prompts are opt-in, tool outputs are never stored, and events are kept 90 days by default (configurable per workspace); |
| 1000 | - sessions, links and commit edges are kept forever (they are small); |
| 1001 | - no transcripts. Managed runs may keep the stream-json in R2 for 30 days for debugging, because g1t paid for it and owns it. |
| 1002 | |
| 1003 | --- |
| 1004 | |
| 1005 | ## Part X — Project context and agent memory |
| 1006 | |
| 1007 | - **Source of truth: files in the repository.** |
| 1008 | - `AGENTS.md` is canonical. |
| 1009 | - `CLAUDE.md` contains `@AGENTS.md`. |
| 1010 | - `docs/decisions/NNNN-*.md` holds ADRs. |
| 1011 | - Existing docs stay where they are. |
| 1012 | - These are portable across every provider, reviewed like code, versioned, and readable by humans. g1t should add **no** competing authoritative store. |
| 1013 | - **g1t's role is distribution and freshness, not storage:** |
| 1014 | 1. `GET /v1/repos/{o}/{r}/context` and MCP resources (`g1t://repo/{o}/{r}/context`) return pointers plus *live state*: active sessions and leases, open PRs touching hot paths, default-branch CI failures, recent merges into the session's touched paths, and blocked issues. Live state is not memory, so it should not be stored as memory. |
| 1015 | 2. `SessionStart` injects a ≤ 1.5 kB digest of that, deterministically. |
| 1016 | 3. **Memory capture becomes suggestion PRs.** |
| 1017 | - The existing `memories` candidate pipeline (`0019_memory_capture.sql`: review corrections, merge summaries, agent reports, with fingerprints and multi-source confirmation) is good machinery pointed at the wrong sink. |
| 1018 | - Point it at "proposed edits to `AGENTS.md` / an ADR", batched weekly as a PR a human merges. |
| 1019 | - The DB keeps candidates and provenance; the repo keeps what's true. |
| 1020 | - **Provider memory** (Claude's auto-memory, Cursor's memories) stays local and private. g1t doesn't sync it. If a developer wants something shared, it goes in `AGENTS.md`. |
| 1021 | - **`services/context`'s catalog and search:** useful for search inside g1t's UI; keep it off the critical path. Its `runcontext.ts` injection for hosted runs should be replaced by the same `/context` endpoint every client uses. |
| 1022 | - **Immediate action:** add an `AGENTS.md` to this repository. Its absence costs you every session today. |
| 1023 | |
| 1024 | --- |
| 1025 | |
| 1026 | ## Part XI — Agent runtime and orchestration |
| 1027 | |
| 1028 | **Should g1t orchestrate agents directly?** No, not as a core competency. |
| 1029 | |
| 1030 | - External orchestrators (Claude Code subagents and agent teams, Codex cloud, Copilot, Cursor background agents, LangGraph-style frameworks) should use g1t as the substrate: sessions, leases, PRs and the queue. |
| 1031 | - g1t's managed execution should be **one more orchestrator**, with no private APIs. |
| 1032 | |
| 1033 | Both coexist without two architectures if, and only if, the managed runner |
| 1034 | creates sessions, takes leases, links issues and opens PRs **through the |
| 1035 | same `/v1` endpoints** with a session credential, which is nearly what it |
| 1036 | does today. |
| 1037 | |
| 1038 | | Current piece | Disposition | |
| 1039 | | --- | --- | |
| 1040 | | `crates/runner` running headless Claude Code with MCP + hooks | **Keep.** Add a `codex exec --json` harness behind the same `Harness` trait. Install the same `g1t hook` adapter inside the container so session events come through the identical path. | |
| 1041 | | `services/runner` containers, admission (`ComputeGate`), credentials, egress allowlist, time caps | **Keep**, as the execution service. | |
| 1042 | | `work` lifecycle (`lifecycle.rs` decide → review/revise/catch-up) | **Refactor.** Move to a `PullAutomation` policy executed by the execution service. Today it keys on `made_by_g1t` (`runtime == Hosted && agent == "g1t"`); key it on `pull_automation.policy`. External PRs can opt into "managed revise on failing checks", which is a strong upsell. | |
| 1043 | | Run records in work D1 (`agent_runs`) | **Move** to the execution service's own D1 as `runs`, FK `session_id`. | |
| 1044 | | Model proxy, per-run spend cap (`services/models`, `RunSpend` DO) | **Keep.** It is the only place exact cost exists. | |
| 1045 | | `agent_messages` exchanges, `plans`, `@g1t` hand-off routing | **Remove/freeze** (§VII.5; planning belongs to the human or their orchestrator). | |
| 1046 | |
| 1047 | Execution abstraction for later: |
| 1048 | |
| 1049 | ```ts |
| 1050 | interface Executor { |
| 1051 | start(spec: { |
| 1052 | session: Session; kind: Run["kind"]; harness: "claude-code" | "codex" | string; |
| 1053 | model?: string; size: string; timeoutS: number; egress: string[]; credential: SessionCredential; |
| 1054 | workspaceSecrets: string[]; // names only; resolved inside execution, never visible to coordination |
| 1055 | }): Promise<Run>; |
| 1056 | cancel(runId: string, reason: string): Promise<void>; // real for managed only |
| 1057 | status(runId: string): Promise<Run>; |
| 1058 | } |
| 1059 | // Implementations: CloudflareContainers (exists), SelfHostedRunner (exists via actions queue), later: vendor clouds. |
| 1060 | ``` |
| 1061 | |
| 1062 | g1t never claims `cancel`, `pause` or billing for sessions whose |
| 1063 | `execution.kind != "managed"`. |
| 1064 | |
| 1065 | --- |
| 1066 | |
| 1067 | ## Part XII — Cloudflare fit |
| 1068 | |
| 1069 | The system is already on Cloudflare and largely fits. Don't migrate |
| 1070 | anything for the competition, and don't migrate anything off it either. |
| 1071 | |
| 1072 | On the competition itself: per secondary sources it is due 2026-10-14 |
| 1073 | and requires showing multiple agents changing code concurrently. The |
| 1074 | smallest vertical slice in Part XVI (two or more sessions from different |
| 1075 | providers, live overlap advice, trailers binding commits to sessions) is |
| 1076 | also the most honest demo of that requirement. It needs no architecture |
| 1077 | built only for the contest. |
| 1078 | |
| 1079 | | Need | Fit | Notes | |
| 1080 | | --- | --- | --- | |
| 1081 | | Repository storage | Good | Artifacts is native Git with CoW forks (used for PR forks). Constraints: control-plane rate limits per namespace (already handled by sharding), repo size (g1t caps at 950 MB; Artifacts' documented limit may now be higher, so re-check), no inbound TCP, so no SSH yet (`crates/sshd` waits). Large monorepos are not a near-term market. | |
| 1082 | | Coordination (leases, presence) | **Excellent** | A Durable Object per repo with SQLite is exactly a linearizable lease table with alarms for expiry. This is the strongest leverage point and the forge doesn't use DOs today. | |
| 1083 | | Durable control-plane state | Fine | D1 per service. Watch write contention on hot tables (`session_events`): batch writes, or buffer in the DO and flush. | |
| 1084 | | Event processing | Fine | Queues with at-least-once delivery and a DLQ, already built. Adding a cursor feed over the D1 log is cheap. | |
| 1085 | | Idempotency | Fine | D1 unique keys; DO single-threaded writes. | |
| 1086 | | Long-running executions | Adequate | Containers (up to standard-4) for runs and CI. Self-hosted runner for bigger or GPU work, or for macOS/Windows. Workflows could replace bits of runner state machines, but the existing alarm/queue design works; no need. | |
| 1087 | | CI at scale | Cost risk | Containers are billed per active time at cost +20%. Large CI fleets will want the self-hosted runner (exists). | |
| 1088 | | Tenant isolation | Good | Containers per run, egress allowlist and credential stripping exist. Keep coordination per repo DO, and never place two workspaces in one DO. | |
| 1089 | | High concurrency | Good | Per-repo DOs scale horizontally; Workers scale. | |
| 1090 | | Filesystem/network limits | Real | Workers can't run git binaries, so trial merges and catch-up need sandboxes (exists). Pairwise trial merges should reuse the disjoint-file fast path and batch sandbox probes. | |
| 1091 | | Self-host | OK | workerd/Miniflare emulate D1 and DOs. A RepoCoordinator DO works there too. | |
| 1092 | |
| 1093 | --- |
| 1094 | |
| 1095 | ## Part XIII — The dogfooding workflow |
| 1096 | |
| 1097 | Assumptions: the repo is on g1t (or mirrored from GitHub); you use Claude |
| 1098 | Code locally and in the cloud, and sometimes Codex; `g1t` CLI + plugin |
| 1099 | installed. |
| 1100 | |
| 1101 | | # | Step | Local | g1t | Protocol | Auto/explicit | Offline | Recovery | Visible to team | |
| 1102 | |---|---|---|---|---|---|---|---|---| |
| 1103 | | 1 | Clone | `git clone https://g1t.sh/flagon-io/g1t` (or GitHub; mirror) | Nothing new | Git | — | n/a | Git | — | |
| 1104 | | 2 | Open Claude Code | `claude` loads project plugin | — | — | Auto | — | — | — | |
| 1105 | | 3 | Session associates | `SessionStart` → `g1t hook claude` | Session upserted, presence in DO | REST | **Auto** | Spooled; context from cache, marked stale | Flushed later with original `seq` | "syntaqx · Claude Code · active · main" on the repo's Active work | |
| 1106 | | 4 | Discover context | Injected digest; `AGENTS.md` | `/context` composed | Hook output, MCP resource | Auto | Cached digest | — | — | |
| 1107 | | 5 | Investigate (no issue/PR) | Reads/greps | Tool events, no files | REST (async) | Auto | Spool | — | Visible as "investigating", or private if `visibility: private` | |
| 1108 | | 6 | Change code | Edits | `touched_paths`, overlap computed | REST | Auto | Spool; overlap unknown | — | Touched files on the board | |
| 1109 | | 7 | Associate with issue | "this is #412" → model calls `session.link`, or `g1t link 412` | `session_links`, issue lease | MCP or CLI | Explicit | Spooled link | Idempotent | Issue shows "being worked on by …" | |
| 1110 | | 8 | Another agent overlaps | Codex session edits `queue.rs` | DO detects overlap | — | Auto | — | — | Both sessions flagged | |
| 1111 | | 9 | Receive conflict info | Next `PostToolUse` injects advice; base-drift advice if main moved | advice in event response | Hook output | Auto | Stale cache; warn on reconnect | — | Overlap badge | |
| 1112 | | 10 | Complete | Tests run, commit (trailer added) | `git` event | Git hook + REST | Auto | Commit works offline | Trailer still binds later | — | |
| 1113 | | 11 | Push | `git push` | `git.push` (or mirror sync) parses trailers → `session_commits` | Git | Auto | Push when online | — | Commits show session badge | |
| 1114 | | 12 | Create/update PR | `g1t pr` / MCP `pull_request create` / GitHub PR mirrored | Sessions auto-linked by trailer + branch | REST/MCP | Explicit | — | — | PR "Work" tab | |
| 1115 | | 13 | Link evidence | — | Checks and queue results already join by SHA | — | Auto | — | — | — | |
| 1116 | | 14 | Review in g1t | Browser | Diff by session, agent's account vs. verified checks, overlaps, behind | Web | Human | — | — | Reviewers | |
| 1117 | | 15 | Merge | Approve with an interactive credential → queue | Speculative batch → land | Web | Human | — | Queue retests | Everyone | |
| 1118 | |
| 1119 | **Minimum that makes this valuable:** steps 3, 6, 8–9 and 11–12. That means: |
| 1120 | |
| 1121 | - seeing your other sessions and agents live with their touched files, |
| 1122 | - being warned in-context about overlap and base drift, |
| 1123 | - every PR showing which sessions produced it. |
| 1124 | |
| 1125 | Everything else can wait. |
| 1126 | |
| 1127 | --- |
| 1128 | |
| 1129 | ## Part XIV — Differentiation |
| 1130 | |
| 1131 | | Compared with | What they have | What g1t adds | |
| 1132 | | --- | --- | --- | |
| 1133 | | GitHub + Copilot/Claude/Codex agents | Forge, Agents tab for *their* cloud agents, session logs for Copilot, hooks in `.github/hooks` | Provider-neutral sessions including **local** ones; in-progress overlap before PRs; agent credentials distinct from humans in branch rules; provenance via trailers across providers. GitHub can copy any one of these. It is unlikely to prioritise local, non-Copilot agents as first-class. | |
| 1134 | | GitLab | Forge, Duo agents | Same as above | |
| 1135 | | Claude Code / Codex / Cursor | The agent, local and cloud, plus hooks and MCP | Nothing about the agent. g1t is where their output meets other people's output under rules. | |
| 1136 | | Orchestration frameworks | Plan/execute graphs | Not competing; they become clients | |
| 1137 | | Agent task boards (Linear agents, etc.) | Assignment and status | Status grounded in Git and hooks, not self-report | |
| 1138 | |
| 1139 | **What's commodity:** |
| 1140 | |
| 1141 | - hosting Git, CI minutes, sandboxes, model access, |
| 1142 | - an MCP server over a REST API, |
| 1143 | - chat, docs, "AI teammates". |
| 1144 | |
| 1145 | **What's new:** |
| 1146 | |
| 1147 | - a *neutral* session layer that sees local and remote agents of any vendor, |
| 1148 | - in-context coordination by deterministic hooks, |
| 1149 | - identity rules that separate agent credentials from human authority. |
| 1150 | |
| 1151 | **Network effects / switching costs:** |
| 1152 | |
| 1153 | - weak per user, real per team: the board is only valuable when everyone's sessions show up, |
| 1154 | - provenance history accrues on g1t, |
| 1155 | - branch rules and the queue are sticky. |
| 1156 | |
| 1157 | **What people pay for:** |
| 1158 | |
| 1159 | - managed runs, CI and deploy minutes (already metered), |
| 1160 | - per-workspace governance: agent rules, audit, provenance retention, SSO, |
| 1161 | - not the dashboard. |
| 1162 | |
| 1163 | **Not being a middleman:** |
| 1164 | |
| 1165 | - g1t is never in the path of code transport (plain Git) or model calls (for external agents). |
| 1166 | - If g1t is down, agents keep working: hooks spool, Git pushes queue. |
| 1167 | |
| 1168 | **Interface:** the unique UI is not "another dashboard". It is three places: |
| 1169 | |
| 1170 | 1. the repo's **Active work** panel, |
| 1171 | 2. the PR's **Work** tab, |
| 1172 | 3. **why-blame**. |
| 1173 | |
| 1174 | The agent side of the interface is *inside the developer's agent* (injected |
| 1175 | context), which is the only place developers reliably look. |
| 1176 | |
| 1177 | --- |
| 1178 | |
| 1179 | ## Part XV — Existing code disposition |
| 1180 | |
| 1181 | | Subsystem | Decision | Reason | |
| 1182 | | --- | --- | --- | |
| 1183 | | `services/repos` (Artifacts, smart HTTP, push checks, landing, mirror) | **Keep** | Core, solid | |
| 1184 | | `services/work` issues/PRs/reviews/checks/rulesets/queue | **Keep**; add squash/rebase | Core | |
| 1185 | | `crates/rules` | **Keep**; extend for human-only approvals and agent credentials | Already models `applies_to` | |
| 1186 | | `apps/api` Op table → REST/MCP/OpenAPI | **Keep**; add `session` tool, MCP resources | Best-designed part of the integration surface | |
| 1187 | | `services/identity` | **Retain & refactor**: agent grants set `acting`; step-up; session credentials | Closes the approval hole | |
| 1188 | | `session_entries`, `record_session` | **Replace** with sessions; keep shim | PR-bound, no identity | |
| 1189 | | `apps/web/public/install/*` hook | **Replace** with `g1t hook` + plugin | Fixes PR-only, caching, latency, redaction, global install | |
| 1190 | | `crates/g1t` CLI | **Retain & extend** (login, hook, link, claim, status, pr) | Static Rust binary is the right vehicle | |
| 1191 | | why-blame heuristic | **Replace** with trailer/edge-based lookup | Accuracy | |
| 1192 | | `coding_agents.rs` | **Keep**; feed provenance | Cheap signal | |
| 1193 | | PR overlaps, mergeability | **Keep**; extend to sessions and pairwise | Foundation of coordination | |
| 1194 | | `services/events`, webhooks | **Keep**; add cursor feed | Already right semantics | |
| 1195 | | `services/runner`, `crates/runner` | **Retain & refactor**: runs create sessions via `/v1`; add Codex harness; move `agent_runs` here | Already "external agent in a box" | |
| 1196 | | `work/lifecycle.rs` hosted automation, `pulls.managed/stage/...` | **Retain & refactor** into `PullAutomation` | Decouple PR from runtime | |
| 1197 | | `services/models` | **Keep** | Exact cost, BYO keys | |
| 1198 | | `work.memories` + capture pipeline | **Refactor**: output = suggestion PRs to `AGENTS.md`/ADRs | One source of truth | |
| 1199 | | `services/context` | **Defer** (keep running; off critical path) | Search value; not core | |
| 1200 | | `plans` (agent splits outcome into issues) | **Defer/freeze** | Orchestration, not forge | |
| 1201 | | `agent_messages` exchanges between agents | **Remove** (after migrating human→agent steering to session steer) | Agent chatter | |
| 1202 | | `services/agents` (workspace agents, Desk, `turn.ts` loop, `@g1t` orchestrator, hand_off, routines, personalities, org-chart roles) | **Freeze, then remove from the product surface** | A second agent runtime and a different product; can't open PRs anyway. Routines could return later as scheduled managed runs. | |
| 1203 | | Skills library, marketplaces, plugins, connectors (in agents) | **Retain the part that installs Claude Code plugins on managed runners and that publishes g1t's own plugin**; freeze the rest | Customize for runners is useful; a skills marketplace isn't core | |
| 1204 | | `services/chat`, `services/artifacts`, notify chat parts, Home digest of agent work | **Freeze** (no new investment); keep notifications | Not the forge. Notifications stay. | |
| 1205 | | Billing attribution per agent handle | **Keep** for managed runs only | — | |
| 1206 | | Desktop/mobile apps | **Freeze** | Distraction pre-PMF | |
| 1207 | | Deployments/pages, packages, security, actions | **Keep** | Forge-adjacent, already working, revenue | |
| 1208 | |
| 1209 | "Freeze" means: keep running for existing workspaces, remove from the dock |
| 1210 | and onboarding, accept no new features, and revisit in a quarter. Deletion |
| 1211 | is cheap later; reputation for broken features isn't. |
| 1212 | |
| 1213 | --- |
| 1214 | |
| 1215 | ## Part XVI — Phased plan |
| 1216 | |
| 1217 | ### Smallest vertical slice (build first, ~1–2 weeks) |
| 1218 | |
| 1219 | **"I can see all my Claude Code sessions on g1t/flagon-io/g1t, with what |
| 1220 | files each is touching, and each one is told about the others."** |
| 1221 | |
| 1222 | It needs: |
| 1223 | |
| 1224 | - `sessions` + `session_events` tables, |
| 1225 | - `POST /v1/sessions`, `/events` and `/end`, |
| 1226 | - `g1t login` + `g1t hook claude {SessionStart,PostToolUse,Stop,SessionEnd}` with a spool, |
| 1227 | - the project-scope Claude Code plugin, |
| 1228 | - an "Active work" panel on the repo page, |
| 1229 | - overlap computed in `work` by plain set intersection, no DO yet. |
| 1230 | |
| 1231 | Leases, trailers and PR linking come right after. |
| 1232 | |
| 1233 | Acceptance: |
| 1234 | |
| 1235 | - Open two Claude Code sessions, one local and one cloud with `G1T_TOKEN` and the network allowlisted. |
| 1236 | - Edit the same file in both. |
| 1237 | - Both appear on the panel within 5 s. |
| 1238 | - The second gets an `additionalContext` overlap note on its next edit. |
| 1239 | - Killing one with no `SessionEnd` makes it go idle within 15 min. |
| 1240 | |
| 1241 | ### Phase 1: make external Claude Code sessions genuinely useful |
| 1242 | |
| 1243 | - **Scope:** |
| 1244 | - the slice above, |
| 1245 | - `prepare-commit-msg` trailer + server-side trailer parsing on push and mirror sync, |
| 1246 | - recognition of existing `Claude-Session:` trailers, |
| 1247 | - auto-link sessions to PRs by trailer and branch, |
| 1248 | - PR "Work" tab, |
| 1249 | - why-blame on edges, |
| 1250 | - `AGENTS.md` in this repo, |
| 1251 | - client- and server-side redaction, |
| 1252 | - **agent grants set `acting`**: the approval-hole fix ships here, not later. |
| 1253 | - **Existing code:** `services/work` (`session_entries` → shim), `apps/api` (Op table, `session` tool), `crates/g1t`, `apps/web/app/routes/repo/{pull,why,sessions}.tsx`, `services/identity/src/oauth.rs`, `crates/contracts/src/{work,credentials,rules}.rs`, `services/repos/src/push_commits.rs` (trailers), `services/repos/src/mirror.rs`. |
| 1254 | - **DB:** `sessions`, `session_events`, `session_links`, `session_commits`, `pull_heads`. Add `acting`/`client` columns on OAuth grants. |
| 1255 | - **APIs:** `/v1/sessions*`, `/v1/repos/{o}/{r}/activity`, `/context`. MCP `session` tool and context resource. |
| 1256 | - **UI:** repo Active work panel; PR Work tab; session page (summary, touched files, commits, links, events with trust badges); why-blame. |
| 1257 | - **Acceptance:** |
| 1258 | - For a week, ≥ 90% of the founder's commits to g1t carry a session edge. |
| 1259 | - Every PR shows its sessions. |
| 1260 | - A Claude Code session holding an OAuth grant cannot satisfy a required approval. |
| 1261 | - Hooks add < 50 ms p95 to tool calls. |
| 1262 | - **Dependencies:** none beyond current infra. |
| 1263 | - **Risks:** |
| 1264 | - hook payload drift across Claude Code versions (mitigate with a pinned fixture suite), |
| 1265 | - cloud-session network policy, |
| 1266 | - spooled data leaking secrets (mitigate with redaction tests using `crates/scan` fixtures). |
| 1267 | |
| 1268 | ### Phase 2: structured collaboration between multiple external agents |
| 1269 | |
| 1270 | - **Scope:** |
| 1271 | - RepoCoordinator DO (presence, leases, overlap index), |
| 1272 | - base-drift advice, |
| 1273 | - issue leases, |
| 1274 | - `g1t claim` / MCP `session.lease`, |
| 1275 | - Codex adapter (`g1t hook codex …` from Codex hooks; `notify` fallback), |
| 1276 | - Cursor and Gemini adapters (command hooks; same normaliser), |
| 1277 | - cursor event feed, |
| 1278 | - pairwise trial merges for overlapping open PRs, |
| 1279 | - contract-path settings. |
| 1280 | - **Existing code:** `services/work/src/{reviews,mergeability}.rs`, `services/events`, the `notify` DO pattern for sockets if the board goes live. |
| 1281 | - **DB:** `leases` (DO SQLite + D1 snapshot), `repo_settings.coordination` JSON. |
| 1282 | - **APIs:** leases, `/events?after=`. |
| 1283 | - **UI:** board with leases and overlaps; issue "being worked on"; stacked-PR view. |
| 1284 | - **Acceptance:** the scenario of 3 humans and 2 remote agents, scripted with headless Claude Code and Codex in containers: |
| 1285 | - every pairwise overlap is surfaced to both within 10 s, |
| 1286 | - no lease survives an abandoned session past TTL, |
| 1287 | - a contract-file change notifies dependents. |
| 1288 | - **Risks:** |
| 1289 | - advice fatigue (mitigate with dedupe and only-on-touch delivery), |
| 1290 | - Cursor hook details unverified, |
| 1291 | - DO hot-spotting on giant monorepos (not a near-term concern). |
| 1292 | |
| 1293 | ### Phase 3: integration, provenance and human approvals |
| 1294 | |
| 1295 | - **Scope:** |
| 1296 | - squash and rebase merges with SHA maps, |
| 1297 | - `required_human_approvals`, sponsor-can't-approve, step-up for rule edits and bypass, |
| 1298 | - agent-credential scope ceilings, |
| 1299 | - independent queue lanes for disjoint path sets, |
| 1300 | - memory capture → suggestion PRs, |
| 1301 | - session retention policies per workspace, |
| 1302 | - optional advisory LLM review clearly labelled. |
| 1303 | - **Existing code:** `crates/rules`, `services/work/src/{rulesets,queue}.rs`, `services/repos/src/land.rs`, `0019_memory_capture`. |
| 1304 | - **DB:** `land_sha_map`, ruleset fields, retention settings. |
| 1305 | - **UI:** approval UI showing human vs agent; provenance view per PR. |
| 1306 | - **Acceptance:** |
| 1307 | - A squash-merged PR still answers "which sessions and checks produced line X". |
| 1308 | - The protected-branch audit shows no agent-credential approvals counted. |
| 1309 | - **Risks:** queue lane correctness, and migrating existing rulesets. |
| 1310 | |
| 1311 | ### Phase 4: reintroduce managed execution through the same interfaces |
| 1312 | |
| 1313 | - **Scope:** |
| 1314 | - the runner opens sessions via `/v1` with a minted session credential, |
| 1315 | - `g1t hook` runs inside the container, |
| 1316 | - `runs` move to the execution service, |
| 1317 | - a Codex harness, |
| 1318 | - `PullAutomation` replaces `made_by_g1t`, |
| 1319 | - "managed revise" available on any PR, |
| 1320 | - "hand off to managed": an external session ends with `successor = managed run` that resumes from the branch and session notes, |
| 1321 | - workspace agents retired or rebuilt as scheduled managed runs. |
| 1322 | - **Existing code:** `services/runner`, `crates/runner/src/{harness,steer,guard,report}.rs`, `services/work/src/lifecycle.rs`, `services/models`. |
| 1323 | - **DB:** `runs` (moved), `pull_automation`; drop the agent columns from `pulls` in a later two-step migration. |
| 1324 | - **Acceptance:** a managed run and a local session on the same repo are indistinguishable on the board except for `execution` and `capabilities`. Cancel works only for the managed one. |
| 1325 | - **Risks:** migration of live hosted-agent workspaces, and cost accounting regressions. |
| 1326 | |
| 1327 | --- |
| 1328 | |
| 1329 | ## Part XVII — Critical challenges |
| 1330 | |
| 1331 | ### Five assumptions most likely to be wrong |
| 1332 | |
| 1333 | 1. **That developers will install a CLI and plugin per machine and per cloud environment.** |
| 1334 | - If they won't, the fallback is trailer-only provenance plus MCP, which is weaker. |
| 1335 | - Test with yourself first, then with three outside teams. |
| 1336 | 2. **That file-level overlap plus base-drift advice is enough coordination.** |
| 1337 | - Teams may need symbol-level signals sooner, especially in large files like `operations.rs` (6k lines) where file overlap is constant noise. |
| 1338 | - Measure the false-positive rate on this repo. |
| 1339 | 3. **That agents act on injected context.** |
| 1340 | - Models may ignore `additionalContext` warnings mid-task. |
| 1341 | - Measure how often an overlap note changes behaviour. If rarely, the value shifts to the human-facing board and PR review. |
| 1342 | 4. **That the workforce/chat product can be frozen without losing the users or the story you have.** |
| 1343 | - If current alpha users are there for chat and agents, this verdict costs real traction. |
| 1344 | - Check usage before freezing. |
| 1345 | 5. **That GitHub mirroring is a sufficient bridge while cloud agents can't push to g1t.** |
| 1346 | - If Claude Code on the web and Codex cloud stay GitHub-only, g1t may be permanently the *second* remote for agent work. |
| 1347 | - That pushes g1t toward being a governance and coordination layer *over* GitHub, which is a different company. Decide deliberately (see "single most important decision"). |
| 1348 | |
| 1349 | ### Three biggest technical risks |
| 1350 | |
| 1351 | 1. Hook surfaces change fast and differ per vendor. The adapter is a permanent maintenance cost, and an outage there silently drops provenance. |
| 1352 | 2. Hooks that take the network on every tool call make agents slow or flaky, which gets the plugin uninstalled. The spool and async design is mandatory, not polish. |
| 1353 | 3. The authorisation model change (agent grants become agents) will break existing integrations that relied on agents approving or merging as their human. It needs a migration window and clear errors. |
| 1354 | |
| 1355 | ### Three biggest product risks |
| 1356 | |
| 1357 | 1. GitHub ships "local agent sessions on PRs" for Copilot *and* Claude/Codex via hooks before g1t has users, and the differentiation compresses to "not GitHub". |
| 1358 | 2. A board nobody looks at: if overlap is rare in small teams, the visible value is low until a team has 3+ concurrent agents. |
| 1359 | 3. Spreading across chat, workforce, deployments, packages, mobile and desktop leaves the core session experience half-done. The current codebase shows that pattern. |
| 1360 | |
| 1361 | ### What I would intentionally not build |
| 1362 | |
| 1363 | - Exclusive locks; symbol-level conflict detection (for now); an LLM semantic-conflict gate. |
| 1364 | - Agent-to-agent messaging; an orchestrator that plans for other agents. |
| 1365 | - A transcript store; a proprietary memory store; an integration frontier. |
| 1366 | - A g1t IDE or a g1t-specific agent; WebSockets for agents. |
| 1367 | - Any intent/task/changeset entity. |
| 1368 | |
| 1369 | ### The single most important architectural decision to make now |
| 1370 | |
| 1371 | **Make the session, not the pull request and not the hosted run, the unit |
| 1372 | of agent participation, with agent credentials distinct from human |
| 1373 | authority.** |
| 1374 | |
| 1375 | Concretely: |
| 1376 | |
| 1377 | - an external, provider-neutral `sessions` table linked to Git by commit trailers, |
| 1378 | - a session credential that marks the actor as an agent acting for a human. |
| 1379 | |
| 1380 | Everything else (coordination, provenance, managed runs, governance) |
| 1381 | hangs off that. Everything built on `pulls.agent`/`runtime`/`session_entries` |
| 1382 | deepens the wrong coupling. |
| 1383 | |
| 1384 | A close second, decided in the same breath: **is g1t the remote, or the |
| 1385 | layer over the remote?** Design Phase 1 so it works either way: trailers |
| 1386 | and mirrors make that possible. Then let your own usage over the next month |
| 1387 | tell you which. |
| 1388 | |
| 1389 | ### The one feature that would make you dogfood g1t tomorrow |
| 1390 | |
| 1391 | **An "Active work" panel for `flagon-io/g1t` that shows every Claude Code |
| 1392 | session you have open, local and cloud, with the files each is touching, |
| 1393 | and that tells each session, inside Claude Code, when another one is |
| 1394 | editing the same code or when `main` just changed what it's working on.** |
| 1395 | |
| 1396 | You are already running several concurrent Claude sessions on this repo |
| 1397 | (the last 127 commits carry at least four distinct `Claude-Session` ids), and |
| 1398 | nothing today tells them about each other. |