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