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