diff --git a/CHANGELOG.md b/CHANGELOG.md index 58699af..5c3e6ce 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +- **Antigravity CLI (`agy`) as the gemini replacement orchestrator (#61)** — the packaged + example roster documents an `antigravity` sub entry (`agy -p {prompt}`, `parse: plain`; + verified live on agy 1.0.10: bare response on stdout, ~5s, rides the old CLI's `~/.gemini` + OAuth), restoring the third orchestrator slot the 2026-06-18 gemini sunset emptied. No + delegate injection yet — agy exposes no per-invocation MCP flags; #81 tracks wiring the + local-delegate tool when it does. The live suite exercises the entry when `agy` is installed. + - **Serve-origin marker + parent-task attribution (#74)** — usage records now carry an `origin` field (`cli` | `gui` | `serve`) so serve-mode traffic is distinguishable from CLI and panel runs, and `tanglebrain --stats` shows the per-origin split (records predating the field diff --git a/README.md b/README.md index ce015d8..c54cd6a 100644 --- a/README.md +++ b/README.md @@ -80,7 +80,7 @@ posture. | Tier | Example | Default | |---|---|---| | **Free local** | a local model via Ollama / any OpenAI-compatible server you run | **active** | -| **Subscription / authenticated CLI** | command-line tools you've installed and logged in (e.g. `claude -p`, `codex exec`, `gemini -p`) | opt-in (commented) | +| **Subscription / authenticated CLI** | command-line tools you've installed and logged in (e.g. `claude -p`, `codex exec`, `agy -p`) | opt-in (commented) | | **Paid API** | bring-your-own-key overflow (any OpenAI-compatible endpoint you hold a key for) | opt-in, off by default | > **Opt-in adapters & your responsibility.** The subscription / authenticated-CLI tier and the @@ -322,9 +322,9 @@ Register it with an orchestrator CLI (exact flags vary by CLI version — check ```sh # Claude Code: claude mcp add tanglebrain-delegate -- tanglebrain-delegate -# Gemini CLI: -gemini mcp add tanglebrain-delegate tanglebrain-delegate # Codex: add a stdio MCP server entry pointing at `tanglebrain-delegate` in its MCP config. +# Antigravity CLI (`agy`, the gemini CLI's successor): no per-invocation MCP flags as of +# 1.0.10 — antigravity orchestrates without the delegate for now (TangleBrain #81). ``` Claude Code users can instead install it as a **plugin** — this repo is its own plugin marketplace diff --git a/tanglebrain/cli.py b/tanglebrain/cli.py index b3dd0f1..23e2a1f 100644 --- a/tanglebrain/cli.py +++ b/tanglebrain/cli.py @@ -10,7 +10,7 @@ tanglebrain "Refactor this module and add tests." # default: frontier-first router tanglebrain --task code "..." # task-fit hint for the router tanglebrain --local "Write a haiku about local inference." # force the free local tier - tanglebrain --model gemini "Summarize this long document." # pin a specific roster entry + tanglebrain --model antigravity "Summarize this long document." # pin a specific roster entry """ from __future__ import annotations diff --git a/tanglebrain/config/roster.yaml b/tanglebrain/config/roster.yaml index 2a02c41..74f4141 100644 --- a/tanglebrain/config/roster.yaml +++ b/tanglebrain/config/roster.yaml @@ -60,7 +60,7 @@ # --- OPT-IN subscription / authenticated-CLI tier — COMMENTED OUT so it stays inert. --- # These entries drive authenticated CLIs you already have installed and logged in (Claude Code / -# Codex / Gemini). They are OPT-IN: uncomment only the ones you use. Driving these CLIs is YOUR +# Codex / Antigravity). They are OPT-IN: uncomment only the ones you use. Driving these CLIs is YOUR # responsibility under each provider's terms of service — see DISCLAIMER.md. Once uncommented, an # entry flagged `can_orchestrate: true` joins the orchestrator rotation (rotation + failover for # resilience). Adapt the `cmd`/`parse`/`delegate_args` to your installed CLI's flags. @@ -94,17 +94,20 @@ # good_at: [code, agentic-code] # can_orchestrate: true # -# - id: gemini +# - id: antigravity # tier: sub # invoke: # kind: cli -# # -p requires the prompt as its value, so {prompt} marks the injection point. -# cmd: ["gemini", "-p", "{prompt}", "--output-format", "json"] -# parse: gemini-json -# # gemini has no per-invocation MCP-server config — it needs a one-time -# # gemini mcp add tanglebrain-delegate -- -m tanglebrain.mcp_server -# # (see README); these per-invocation flags then allow + auto-approve the tool. -# delegate_args: ["--allowed-mcp-server-names", "tanglebrain-delegate", "--approval-mode", "yolo"] +# # Antigravity CLI (`agy`) — Google's successor to the sunset `gemini` CLI (individuals, +# # 2026-06-18). `-p/--print` runs one prompt non-interactively and prints the bare response +# # to stdout, so parse is `plain`. Rides the same ~/.gemini OAuth as the old CLI. +# cmd: ["agy", "-p", "{prompt}"] +# parse: plain +# # No delegate_args: agy (verified 1.0.10 and 1.0.16) exposes no per-invocation MCP-server +# # flags, so this orchestrator answers directly instead of offloading to the local delegate +# # (#81 tracks wiring delegation when agy grows per-call MCP config). No scrub_env either — +# # unlike the old gemini CLI, agy reads no GEMINI_API_KEY/GOOGLE_API_KEY (verified against +# # the binary), so there is no API-key path to divert billing through; OAuth is the only auth. # cost: subscription # good_at: [long-context, structured-json] # can_orchestrate: true diff --git a/tanglebrain/delegate.py b/tanglebrain/delegate.py index df78d8f..7581a19 100644 --- a/tanglebrain/delegate.py +++ b/tanglebrain/delegate.py @@ -1,6 +1,6 @@ """Delegation logic — the sub-task offload behind the MCP tools. -A frontier orchestrator (e.g. claude / codex / gemini) decomposes a task and hands sub-tasks to a +A frontier orchestrator (e.g. claude / codex / antigravity) decomposes a task and hands sub-tasks to a **configured** backend, then reviews the result. The default target is TangleBrain's **free local tier** at $0 marginal cost (:func:`run_local_delegate` / ``target=None``); the orchestrator may also target any roster entry flagged ``can_delegate`` (:func:`run_delegate`) — a cheaper sub or a diff --git a/tanglebrain/mcp_server.py b/tanglebrain/mcp_server.py index 47f8c55..8c5d74a 100644 --- a/tanglebrain/mcp_server.py +++ b/tanglebrain/mcp_server.py @@ -1,6 +1,6 @@ """MCP server exposing TangleBrain's delegate tools to an orchestrator. -A stdio MCP server an orchestrator (e.g. claude / codex / gemini) registers so it can offload +A stdio MCP server an orchestrator (e.g. claude / codex / antigravity) registers so it can offload sub-tasks to a configured backend mid-task — the mechanism that makes frontier-first decompose actually offload work rather than running everything on the orchestrator itself. It exposes four tools: ``delegate_local`` (free local default), ``delegate`` (route to a configured ``can_delegate`` diff --git a/tests/test_live.py b/tests/test_live.py index 4c3ea25..9969659 100644 --- a/tests/test_live.py +++ b/tests/test_live.py @@ -208,11 +208,15 @@ def test_claude_returns_text(self): def test_codex_returns_text(self): self._route("codex") - def test_gemini_returns_text(self): - # The `gemini` CLI sunset for individuals on 2026-06-18 (migrated to Antigravity); it now exits - # non-zero with an IneligibleTierError, so this can no longer pass. The roster entry is disabled - # (can_orchestrate: false) and a replacement is tracked in #61 — skip until then. - self.skipTest("gemini CLI sunset 2026-06-18 (migrated to Antigravity); see issue #61") + def test_antigravity_returns_text(self): + # Antigravity (`agy`) replaced the sunset `gemini` CLI as the third orchestrator (#61). + # The roster id ("antigravity") differs from the binary ("agy"), so this can't use + # _route()'s which(model) guard directly. + if shutil.which("agy") is None: + self.skipTest("agy (Antigravity CLI) not installed/logged in") + text = run_once("Reply with exactly: PONG", model="antigravity") + self.assertIsInstance(text, str) + self.assertTrue(text.strip(), "expected non-empty text from antigravity") @unittest.skipUnless(LIVE, "set TANGLEBRAIN_LIVE=1 to run the live router test") diff --git a/tests/test_roster.py b/tests/test_roster.py index 723f96b..d0e3a78 100644 --- a/tests/test_roster.py +++ b/tests/test_roster.py @@ -30,7 +30,7 @@ class PackagedRosterTest(unittest.TestCase): """The GENERIC example roster shipped with the package parses correctly. R2a (public-OSS rollout): the bundled default ships exactly ONE active entry — the free local - tier — and no orchestrators. The opt-in subscription/authenticated-CLI tier (claude/codex/gemini) + tier — and no orchestrators. The opt-in subscription/authenticated-CLI tier (claude/codex/antigravity) and the paid-API tier ship as COMMENTED examples, so a fresh clone routes to local out of the box and an operator opts in by uncommenting. Pinned to ``packaged_roster_path()`` so it tests the bundled example regardless of any operator roster the dev machine may have at @@ -70,7 +70,8 @@ def test_opt_in_tiers_present_as_commented_examples(self): # The subscription-CLI and paid-API tiers ship as commented opt-in examples, not active # entries — uncommenting one (and its tier-specific gate) is how an operator enables it. raw = packaged_roster_path().read_text() - for marker in ("# - id: claude", "# - id: codex", "# - id: gemini", "# - id: paid-overflow"): + # antigravity replaced gemini as the third CLI example when the gemini CLI sunset (#61). + for marker in ("# - id: claude", "# - id: codex", "# - id: antigravity", "# - id: paid-overflow"): self.assertIn(marker, raw, f"{marker!r} should ship as a commented opt-in example")