feat(provider): gemini-cli adapter — third AgentProvider, pinned on 0.42.0 (#126) #129

Open
dominik.polakovics wants to merge 8 commits from afk/126 into main

Third AgentProvider: Gemini CLI, at the codex bar (ADR-0036 DoD), every fragile coupling pinned against gemini-cli 0.42.0 on the lab host. Spike evidence is in #126's Amendment 1 (posted before adapter code, live + bundle provenance); ADR-0041 pins the decisions; internal/compat/gemini/compat.md pins the couplings.

Commits

  • feat(provider): LiveSignals.Setup gains an env return (the issue's open implementation point) — gemini arms per-run telemetry wholly through spawn env; claude-code unchanged (nil env), settings-file arming byte-identical.
  • feat(provider): internal/provider/gemini mirrors codex file-for-file — yolo/skip-trust/session-id spawn, UUIDv5 lab-minted session identity (collision = exit 42, live-pinned), eager-birth session-JSONL transcript with last-writer-wins-by-id fold + $rewindTo, telemetry-outfile liveness (user_prompt/conversation_finished — the latter fires exactly under lab's yolo-interactive posture, #62 hard evidence), oauth-code login (URL scrape + code paste, completion = creds-file birth), pin-guarded user-settings merge (context.fileName list, selectedType-if-absent, Clearcut kill; never touches sessionRetention), native .gemini/skills discovery, single-Escape interrupt with the double-Esc//rewind and Ctrl-C/quit bans. Registered in the conformance table, neutrality exemptions, and cmd/lab/main.go.
  • feat(deploy): agentPackages.gemini = pkgs.gemini-cli (ADR-0033), flake module-check assertions extended.
  • docs(adr): ADR-0041.
  • docs(compat): the compat record — 11 provenance-tagged sections + live re-verification; hermetic probe tests plus four LAB_COMPAT_LIVE=1 no-auth recipes, all green against the real 0.42.0 binary (version, help surface, eager birth + exit-42 collision, NO_BROWSER URL print).

Verification

  • Tier-1 providertest.Conformance green for claude-code, codex, gemini (incl. seeding-exclude-coverage, seeding-incogni, dual-engine scrub-markers).
  • go build ./..., full go test ./... (except internal/tmuxx integration tests, which fail identically at pristine main in this sandbox — environmental; CI has the real tmux/prlimit rig), golangci-lint 2.12.2 0 issues, web vitest 593/593, nix build .#checks.x86_64-linux.nixos-module green, labctl secret scan clean.

Creds-gated residuals (documented, not blocking)

~/.gemini/oauth_creds.json was not seeded during this run (decision 3 expected a maintainer copy; push notification sent). Everything needing an authenticated turn is bundle-pinned and enumerated in Amendment 1's residual list + compat.md §Live re-verification: live turn-fold fixtures + conversation_finished-in-outfile confirmation, /memory show probe, a completed login round-trip, the /model free-tier sweep (catalog is bundle-pinned until then), and incogni attribution ground truth from a real run. Once creds land, the upgrade path is: run the live sweep, post Amendment 2, refresh compat provenance.

Also recorded: pinned nixpkgs deploys gemini-cli 0.47.0 while these couplings pin 0.42.0 (the host binary) — compat.md's re-verification section owns that gap.

Closes #126

🤖 Generated with Claude Code

https://claude.ai/code/session_019EwjhpDCaYvxkwRC2f5HGz

Third `AgentProvider`: **Gemini CLI**, at the codex bar (ADR-0036 DoD), every fragile coupling pinned against **gemini-cli 0.42.0** on the lab host. Spike evidence is in #126's Amendment 1 (posted before adapter code, live + bundle provenance); ADR-0041 pins the decisions; `internal/compat/gemini/compat.md` pins the couplings. ## Commits - `feat(provider)`: LiveSignals.Setup gains an env return (the issue's open implementation point) — gemini arms per-run telemetry wholly through spawn env; claude-code unchanged (nil env), settings-file arming byte-identical. - `feat(provider)`: `internal/provider/gemini` mirrors codex file-for-file — yolo/skip-trust/session-id spawn, UUIDv5 lab-minted session identity (collision = exit 42, live-pinned), eager-birth session-JSONL transcript with last-writer-wins-by-id fold + `$rewindTo`, telemetry-outfile liveness (`user_prompt`/`conversation_finished` — the latter fires exactly under lab's yolo-interactive posture, #62 hard evidence), oauth-code login (URL scrape + code paste, completion = creds-file birth), pin-guarded user-settings merge (`context.fileName` list, `selectedType`-if-absent, Clearcut kill; never touches `sessionRetention`), native `.gemini/skills` discovery, single-Escape interrupt with the double-Esc//rewind and Ctrl-C/quit bans. Registered in the conformance table, neutrality exemptions, and `cmd/lab/main.go`. - `feat(deploy)`: `agentPackages.gemini = pkgs.gemini-cli` (ADR-0033), flake module-check assertions extended. - `docs(adr)`: ADR-0041. - `docs(compat)`: the compat record — 11 provenance-tagged sections + live re-verification; hermetic probe tests plus four `LAB_COMPAT_LIVE=1` no-auth recipes, **all green against the real 0.42.0 binary** (version, help surface, eager birth + exit-42 collision, NO_BROWSER URL print). ## Verification - Tier-1 `providertest.Conformance` green for claude-code, codex, **gemini** (incl. seeding-exclude-coverage, seeding-incogni, dual-engine scrub-markers). - `go build ./...`, full `go test ./...` (except `internal/tmuxx` integration tests, which fail identically at pristine `main` in this sandbox — environmental; CI has the real tmux/prlimit rig), `golangci-lint 2.12.2` 0 issues, web vitest 593/593, `nix build .#checks.x86_64-linux.nixos-module` green, `labctl secret scan` clean. ## Creds-gated residuals (documented, not blocking) `~/.gemini/oauth_creds.json` was **not seeded** during this run (decision 3 expected a maintainer copy; push notification sent). Everything needing an authenticated turn is bundle-pinned and enumerated in Amendment 1's residual list + compat.md §Live re-verification: live turn-fold fixtures + `conversation_finished`-in-outfile confirmation, `/memory show` probe, a completed login round-trip, the `/model` free-tier sweep (catalog is bundle-pinned until then), and incogni attribution ground truth from a real run. Once creds land, the upgrade path is: run the live sweep, post Amendment 2, refresh compat provenance. Also recorded: pinned nixpkgs deploys gemini-cli **0.47.0** while these couplings pin **0.42.0** (the host binary) — compat.md's re-verification section owns that gap. Closes #126 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_019EwjhpDCaYvxkwRC2f5HGz
gemini-cli 0.42.0 has no --telemetry* CLI flags — telemetry (the gemini
adapter's live-signal channel) is configured via env vars alone. Generalize
the LiveSignals lifecycle capability so Setup can arm through the spawn
environment as well as (or instead of) a settings file:

- provider seam: Setup gains a fourth return, env []string ("K=V" entries
  lab adds to the runner's extraEnv, riding tmux -e); settingsPath "" now
  means "no file to write", not an error
- claudecode: returns nil env (arms wholly through the --settings file;
  payload byte-identical)
- instance: armDialogHooks renamed armLiveSignals; env-only arming skips
  the settings write but still threads args+env; env appended to the
  launch-local extraEnv before runner.Start
- providertest.Fake: SetHookEnvOnly scripts the env-only shape; two new
  launch tests pin env-only arming (env reaches extraEnv, no settings file,
  no --settings flag) and the unchanged claude-style shape

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019EwjhpDCaYvxkwRC2f5HGz
internal/provider/gemini mirrors codex file-for-file, every fragile
coupling pinned against gemini-cli 0.42.0 (issue #126 Amendment 1,
live + bundle provenance):

- spawn: --approval-mode yolo --skip-trust --session-id <uuid> [-m M]
  [prompt]; per-invocation trust, zero global mutation; the session id
  is UUIDv5-derived from the tmux session name (collision = hard CLI
  exit 42, live-pinned)
- transcript: eagerly-born session JSONL under tmp/<slug>/chats/,
  located via projects.json + sid8-exact-else-newest (/clear rotates
  under a CLI-minted id); fold is last-writer-wins by record id with
  $rewindTo suffix-drop — the CLI's own loader semantics
- liveness: LiveSignals armed wholly through spawn env (per-run local
  OTEL outfile; 0.42.0 has no telemetry flags) — working/idle edges
  from gemini_cli.user_prompt / conversation_finished, which fires
  exactly under lab's yolo-interactive posture (#62 hard evidence)
- auth: oauth-code — LoginStart scrapes the NO_BROWSER Google URL
  (always the LAST one; failed codes re-prompt with fresh PKCE),
  LoginSubmitCode pastes the code, completion = oauth_creds.json birth;
  status is creds-file-based (no auth subcommand exists); logout =
  creds removal
- seeding: GEMINI.local.md + native .gemini/skills discovery; one
  idempotent pin-guarded merge into user settings.json (context
  filename list, selectedType-if-absent, Clearcut kill); never touches
  sessionRetention
- interrupt: single Escape; double-Esc (/rewind) and Ctrl-C (second
  press quits) are hazard-banned; replies queue natively mid-turn
- scrub patterns defensive-only (0.42.0 has zero attribution
  machinery), name-scoped per the codex precedent

Registered in the Tier-1 conformance table, the neutrality exemption
list, and cmd/lab/main.go; google/uuid promoted to a direct dep.
Conformance green for all three adapters.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019EwjhpDCaYvxkwRC2f5HGz
nix/module.nix gains the gemini = pkgs.gemini-cli default (Apache-2.0,
no unfree caveat) so the binary lands on the unit PATH like the other
providers; the flake's nixos-module check asserts it on the defaults
PATH, its survival under the claude-code null opt-out, and nulls it out
of the text-level dummy's closure. Pinned nixpkgs ships gemini-cli
0.47.0 — the compat record pins couplings against 0.42.0 (the lab
host's binary) and its re-verification section owns that gap.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019EwjhpDCaYvxkwRC2f5HGz
Pins the decisions (the compat record pins the couplings, issue #126
Amendment 1 holds the spike evidence): lab-minted session identity,
telemetry-outfile liveness over the env-extended LiveSignals seam,
the pin-guarded user-settings merge for GEMINI.local.md discovery,
oauth-code login, single-Escape interrupt with the double-Esc/Ctrl-C
bans, free-tier quota honesty, and the honest-degradation list.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019EwjhpDCaYvxkwRC2f5HGz
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019EwjhpDCaYvxkwRC2f5HGz
docs(compat): gemini compat record — couplings pinned on 0.42.0 (#126)
All checks were successful
ci / native (pull_request) Successful in 6m11s
ci-nix / flake-check (pull_request) Successful in 16m43s
957cc0aa0e
internal/compat/gemini mirrors the codex record: eleven numbered
sections + a live re-verification recipe, every claim provenance-tagged
(live / bundle / cli extraction / fixture), fixtures version-tagged
-0.42.0. Hermetic probe tests pin the session-id derivation (UUIDv5
literal), spawn argv snapshots, the model catalog, the login-pane
last-URL-wins scrape, creds-file status parsing, the three session-
grammar folds, the settings-merge pins, the 34-row command catalog, and
the scrub markers. LAB_COMPAT_LIVE=1 recipes probe the real binary with
no auth required: version, help surface, eager birth + the exit-42
session-id collision, and the NO_BROWSER URL print — all four green
against the host's nix-store 0.42.0. The record carries the nixpkgs
0.47.0 deploy-default gap and the five creds-gated residuals from issue
#126 Amendment 1.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019EwjhpDCaYvxkwRC2f5HGz
fix(gemini): port Models() to the enriched ModelOption catalog (#156)
All checks were successful
ci / native (pull_request) Successful in 6m13s
ci-nix / flake-check (pull_request) Successful in 17m55s
45e3e48317
Merge-resolution fixup: main's #158 changed AgentProvider.Models() to
return []provider.ModelOption (per-model efforts + reported default),
which git merged textually but left the gemini adapter implementing the
pre-#156 []provider.Option signature.

gemini has no reasoning-effort knob at all — SpawnArgv emits no effort
flag and the catalog is a single "default" sentinel — so it takes
claude-code's shape: every model carries the same shared efforts list
and no DefaultEffort (the first-entry rule resolves the fallback).
Values, order and the spawn default are unchanged; the compat §10 pin is
restated in the enriched shape.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014u58bFzx6t6PG7rhVL3g9w
All checks were successful
ci / native (pull_request) Successful in 6m13s
Required
Details
ci-nix / flake-check (pull_request) Successful in 17m55s
This pull request has changes conflicting with the target branch.
  • cmd/lab/main.go
  • internal/instance/launch.go
  • internal/provider/provider.go
  • internal/provider/providertest/fake.go
View command line instructions

Manual merge helper

Use this merge commit message when completing the merge manually.

Checkout

From your project repository, check out a new branch and test the changes.
git fetch -u origin afk/126:afk/126
git switch afk/126
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
Cloonar/coding-lab!129
No description provided.