GES Extension: Code linkage and agent session capture
Part of the Git Event Store specification family. Extends the core specification
(../git-event-store-spec.md); RFC-2119 keywords per core §0.1. An implementation claims this extension per core §1 (conformance clauses C18/C19). Shadow refs live under the core-reserved namespace refs/ges/<store>/shadow/* (core §3.3.1) and are never pushed.
Section and requirement numbering is retained from the pre-family document for E-ID and
cross-reference stability.
3.4 Linkage coupling and shadow capture
Section titled “3.4 Linkage coupling and shadow capture”3.4. Linkage coupling. An implementation MUST support and declare one of:
- Decoupled (default): events live only on
refs/ges/<store>/streams/*; a linkage trailer (§7.3) MAY connect a code commit to the append that justified it. - Coupled: an append’s events and the corresponding source change are committed together. When coupled, the implementation MUST still expose the events under the stream ref namespace (via an additional ref update in the same operation) so that stream reads remain uniform.
3.4.1 Shadow capture (working tree → shadow ref, no side effects)
Section titled “3.4.1 Shadow capture (working tree → shadow ref, no side effects)”An implementation that captures uncommitted working-tree state (e.g. to associate source with the events a session produced) MUST do so without touching the user’s HEAD, index, or working tree, using a temporary index and plumbing only:
tmp=$(mktemp -u)GIT_INDEX_FILE=$tmp git read-tree HEAD # temp index := HEAD treeGIT_INDEX_FILE=$tmp git add -A # + working-tree changes (honors .gitignore)tree=$(GIT_INDEX_FILE=$tmp git write-tree) # tree object from the temp indexparent=$(git rev-parse -q --verify refs/ges/<store>/shadow/<name> || git rev-parse HEAD)commit=$(git commit-tree "$tree" -p "$parent" -m "shadow capture")git update-ref refs/ges/<store>/shadow/<name> "$commit"rm -f "$tmp"Because every index command targets GIT_INDEX_FILE rather than .git/index, the real index and working tree are untouched; commit-tree creates the object without moving HEAD; update-ref moves only the shadow ref. The first capture is parented on HEAD; each subsequent one on the prior shadow tip, producing a capture history on the shadow ref such that git diff HEAD refs/ges/<store>/shadow/<name> is always exactly the uncommitted delta. Implementations MAY use libgit2/go-git equivalents, but the durable-write ordering of §11 still applies.
3.4.1.1 Scoped capture (capture the turn, not the working tree)
Section titled “3.4.1.1 Scoped capture (capture the turn, not the working tree)”When the capture trigger (§3.4.5) provides the exact set of paths an agent changed in a turn (e.g. from the agent transcript’s tool-use file_paths), the implementation SHOULD stage only those paths so the capture equals the turn’s diff rather than the whole — possibly noisy — working tree:
tmp=$(mktemp -u)GIT_INDEX_FILE=$tmp git read-tree HEADGIT_INDEX_FILE=$tmp git add -A -- "${paths[@]}" # -A so deletions of these paths are captured tootree=$(GIT_INDEX_FILE=$tmp git write-tree)# ... commit-tree + update-ref as in 3.4.1Requirements and caveats:
- MUST use
-A -- <paths>(not baregit add <paths>) so that deletions and renames-away of the listed paths are recorded, not only modifications. - Agent-reported paths are frequently absolute; the implementation MUST normalize them to repository-relative pathspecs.
- A rename is a deletion plus an addition of two distinct paths; the implementation MUST include both, or the old path remains stale in the capture. Scoped capture is only as accurate as the reported path set, and the implementation MUST document that unreported changes are excluded by design.
- The result is a tree equal to
HEADoverlaid with exactly the turn’s changes — the correct basis for per-turn (“per-wave”) event attribution (§8.3).
3.4.2 Anchoring (how the shadow ref maps to the working tree)
Section titled “3.4.2 Anchoring (how the shadow ref maps to the working tree)”3.4.2.1. The shadow ref name MUST encode two things: <HEAD7> — the abbreviated HEAD at capture start, which records which code state the captures branch from (this is the association to the working tree); and <worktree-id> — a discriminator so multiple worktrees of one repository, sharing an object store but each with its own HEAD/index, do not collide on one shadow ref.
3.4.2.2. Parenting the shadow chain on HEAD (§3.4.1) makes the anchor structural, not merely nominal: the shadow tip’s ancestry contains the base code commit.
3.4.2.3. Agents in separate worktrees (normative for this deployment). Where agents work in their own git worktree checkouts on their own branches rather than in the main repository, the implementation:
- MUST resolve
HEAD, the temp-index seed, and the staged working-tree changes in the context of the agent’s worktree — i.e. run capture with that worktree’sGIT_DIR($(git rev-parse --git-dir)inside the worktree, typically.git/worktrees/<id>), not the main repository’sHEAD. - MUST derive
<worktree-id>from a stable per-worktree identity: the git-assigned worktree id (the.git/worktrees/<id>basename) or a hash of the worktree’s absolute path. The branch name alone is acceptable only because Git forbids checking out one branch in two worktrees simultaneously, but the worktree id is more robust. - Note — concurrency:
refs/ges/<store>/shadow/*andrefs/ges/<store>/streams/*both live in the common object store. Concurrent condensation from multiple agent worktrees into the same stream is already serialized by the ref compare-and-swap (§9.2); the CAS now guards across worktrees, not only across clones. No additional mechanism is required, but implementations MUST NOT assume single-writer just because each worktree has a private index. - Note — branch resilience: because condensation (§3.4.4) writes events to the shared
refs/ges/<store>/streams/*at commit time, the events persist independently of the agent branch’s fate. If the agent branch is later squash-merged, theGES-Linkpath from the merged code commit back to the append may be lost (the per-commit trailers are collapsed), but the events themselves are not. Implementations SHOULD either preserveGES-Linktrailers across merges (e.g. aggregate them onto the merge/squash commit) or accept the broken back-link and rely on the durable events. This is the known squash-merge linkage limitation, scoped to the code→events direction only.
3.4.3 Bidirectional linkage (at the code commit)
Section titled “3.4.3 Bidirectional linkage (at the code commit)”3.4.3.1. To make the association durable and bidirectional, the code commit MUST (coupled) or MAY (decoupled) carry a GES-Link: <id> trailer (§7.3.2), stamped by a prepare-commit-msg hook, where <id> resolves the condensed append/events:
id=$(cat "$(git rev-parse --git-dir)/ges/current-append-id" 2>/dev/null) || exit 0git interpret-trailers --in-place --trailer "GES-Link: $id" "$1"3.4.3.2. This yields both directions: code commit → events (read the trailer, resolve <id> via the sharded path or ref), and events → code commit (git log --all --grep="GES-Link: $id").
3.4.4 Condensation and cleanup
Section titled “3.4.4 Condensation and cleanup”3.4.4.1. On code commit (post-commit), the implementation SHOULD condense the shadow state into the permanent refs/ges/<store>/streams/* (as one-event commits per §5.2) and then delete or reset the shadow ref. Shadow refs are scratch (§3.3.1); only the condensate is durable and pushable.
3.4.5 The capture trigger is external to Git
Section titled “3.4.5 The capture trigger is external to Git”3.4.5.1. Git has no “working tree changed” hook. The capture trigger therefore MUST come from outside Git — an agent lifecycle hook (e.g. an AI agent’s turn-complete event), a filesystem watcher, an editor save hook, or an explicit ges capture command. Git hooks carry only the other lifecycle moments (§3.4.6). This is precisely why session-capture tools bind to the agent, not to Git.
3.4.5.2. The trigger SHOULD supply, alongside the fire signal, (a) the set of changed paths for scoped capture (§3.4.1.1) and (b) the identity of the worktree in which the change occurred (§3.4.2.3), so capture runs in the correct worktree context and captures exactly the turn.
3.4.6 HEAD-movement migration
Section titled “3.4.6 HEAD-movement migration”3.4.6.1. When HEAD moves, the <HEAD7> anchor is stale. Implementations MUST handle this via post-rewrite (rebase/amend), post-checkout, and post-merge (pull) hooks by recomputing the shadow ref name from the new HEAD and renaming/re-anchoring. Re-anchoring (updating the name and base) is REQUIRED; rebasing the accumulated captures onto the new base is OPTIONAL, and an implementation MAY instead accept a shifted diff base and document it. pre-push MUST push only the condensate, never shadow refs.
Requirements (EARS)
Section titled “Requirements (EARS)”Moved verbatim from the companion catalog (../ges-review-and-ears.md); IDs unchanged.
- E-3.4-1 Where shadow capture is offered, the capture subsystem shall capture working-tree state via a temporary index without modifying HEAD, index, or working tree. (test 19)
- E-3.4-2 When a changed-path set is supplied, the capture subsystem shall stage only those paths, including their deletions and renames. (test 21)
- E-3.4-3 The capture subsystem shall encode the base HEAD and a stable worktree discriminator in the shadow ref name.
- E-3.4-4 While agents run in separate worktrees, the capture subsystem shall resolve HEAD and staged changes in the agent worktree’s Git directory. (test 22)
- E-3.4-5 When HEAD moves, the capture subsystem shall recompute the shadow ref name and re-anchor.
- E-3.4-6 The capture subsystem shall keep shadow refs local and shall not push them. (test 20)
Git Event Store is an independent open specification for an event store that uses Git as its storage component. It is not affiliated with or endorsed by the Git Project or Software Freedom Conservancy. Git and the Git logo are either registered trademarks or trademarks of Software Freedom Conservancy, Inc., corporate home of the Git Project, in the United States and/or other countries.
Git Event Store is not affiliated with or endorsed by Kurrent, the maker of KurrentDB (formerly EventStoreDB). "Event store" is used here as the generic name of the event-sourcing storage pattern.