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.

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