Skip to content

Where people and agents ship software together. The open-source git platform for the whole job: issues, agents, checks and deploys to the edge.

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