playmaker-cli 0.4.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
playmaker/__init__.py ADDED
@@ -0,0 +1,10 @@
1
+ """playmaker — multi-agent orchestration CLI."""
2
+
3
+ from importlib.metadata import PackageNotFoundError, version
4
+
5
+ try:
6
+ __version__ = version("playmaker-cli")
7
+ except PackageNotFoundError: # source checkout without an install
8
+ __version__ = "0.0.0+unknown"
9
+
10
+ __all__ = ["__version__"]
playmaker/__main__.py ADDED
@@ -0,0 +1,4 @@
1
+ from playmaker.cli import app
2
+
3
+ if __name__ == "__main__":
4
+ app()
@@ -0,0 +1,282 @@
1
+ ---
2
+ name: playmaker-coach
3
+ description: Playing-coach orchestration of Claude/Codex/Antigravity (agy) sub-agents via the `playmaker` CLI and in-session Task sub-agents. Triggered when the user gives a complex multi-component task where decomposition gives >2x parallel speedup (e.g. "build admin dashboard with schema, backend, FE, tests, docs"). NOT triggered for single-file refactor, single bug fix, single component, or simple Q&A — those stay in this thread without delegation.
4
+ ---
5
+
6
+ # playmaker-coach — playing-coach orchestration
7
+
8
+ Use the `playmaker` CLI as a facade to dispatch sub-tasks to Codex / Antigravity (`agy`) / a sibling Claude, monitor them, read their threads, review their diffs, and feed back. The coach (this thread) does its own portion of the work in parallel.
9
+
10
+ > **`agy` (Antigravity CLI)** does not only serve Google models: `agy models` exposes **Gemini 3.5 Flash (Low/Medium/High)**, **Gemini 3.1 Pro (Low/High)**, **Claude Sonnet 4.6 (Thinking)**, **Claude Opus 4.6 (Thinking)**, and **GPT-OSS 120B (Medium)**. Opus via agy runs on *Google's* quota pool — top-tier Claude work that does not touch the Anthropic subscription's scarce Opus weekly bucket. Model names are the exact display strings (quote them in bash): `--model "Claude Opus 4.6 (Thinking)"`.
11
+
12
+ **The point of the coach pattern:** all available models do useful work in parallel under your direction. The coach's job is *orchestration*, not *production*. A coach that does its own implementation, runs its own codebase recon, and reviews everything line by line burns the same budget delegation was meant to save. Treat your own context window as the most expensive resource on the table — every byte you read or generate yourself is a byte that could have come from a cheaper model. Push out as much as you can: implementation, recon, summarization, even drafting the per-subtask prompts when the task is large enough.
13
+
14
+ ## Execution lanes — where work runs
15
+
16
+ All Claude work — coach, internal sub-agents, external `claude -p` — draws from the **same Claude subscription**. So routing is about **which weekly bucket** you spend and **where results land**, not who pays. Three lanes:
17
+
18
+ 1. **Coach (this thread).** Interactive Claude Code on the **Opus** weekly bucket — the scarcest, hardest-to-replenish one. Serial and the most expensive in *context*. Reserve for orchestration, architecture judgment, final integration.
19
+
20
+ 2. **Internal sub-agents — the `Task`/`Agent` tool in THIS session.** Run inside this session, **write files** (they inherit the coach's permission mode), return their result straight into the coach's context, and run in parallel. They draw on the same subscription, so the gate is "is weekly quota healthy?" — glance at `playmaker quotas`. **Use them freely** for Claude-side chunks the coach will fold back in directly; spawn several at once, don't be shy.
21
+
22
+ 3. **External dispatch — `playmaker dispatch <agent>`.** Separate OS processes, tracked in playmaker (`list`/`watch`/`thread`/`continue`):
23
+ - **`claude -p` (sibling Claude):** same subscription. Its key lever is the model bucket — **Sonnet is a separate weekly bucket from Opus**, usually idle while Opus depletes, so default **`--model sonnet`** to spare the scarce Opus bucket (**`--model haiku`** for trivial mechanical work). It **can write files** (playmaker forwards `--dangerously-skip-permissions`; see §10). Use it over an internal sub-agent when you want a **tracked, detached work-stream** you can monitor/continue independently of the coach's turn.
24
+ - **`codex` / `agy`:** each on its own subscription/quota — the right home for **write-heavy** parallel implementation that can leave the Anthropic subscription. `agy` is special: besides Gemini tiers it carries **Claude Sonnet/Opus 4.6 (Thinking)** on Google's pool, so even "must be Claude-quality" work can leave the Anthropic quota.
25
+
26
+ **Routing cheat-sheet for Claude-side work:**
27
+
28
+ | The subtask… | Lane | Why |
29
+ |---|---|---|
30
+ | writes files and the coach integrates the result directly | **internal sub-agent** (Task tool) | in-session, write-capable, returns into context |
31
+ | is an independent stream you'll monitor / continue separately | **external `dispatch claude --model sonnet`** | tracked & detached; Sonnet's own weekly bucket spares Opus |
32
+ | is heavy reasoning only the coach can do | **coach** | top-tier Opus, serial |
33
+ | is write-heavy and can leave Claude | **codex / agy** | their own quotas |
34
+ | needs top-tier Claude but the Anthropic Opus weekly is precious | **`dispatch agy --model "Claude Opus 4.6 (Thinking)"`** | Opus quality on Google's pool |
35
+
36
+ **Sonnet is your cheap parallel Claude worker** — a separate weekly bucket that usually sits idle while Opus depletes. Reach for it (via `dispatch claude --model sonnet`, or by pointing an internal sub-agent at Sonnet) instead of burning Opus on mid-tier work.
37
+
38
+ ## Activation
39
+
40
+ **Activate when** the task has 3+ independent work-streams and parallel execution would meaningfully shorten the wall-clock. Typical: backend + frontend + tests + docs; or schema + handlers + tests; or several disjoint modules.
41
+
42
+ **Do NOT activate** for: single bug fix, single-file refactor, naming change, simple question, single component. The coach overhead is not worth it; just do the work in this thread.
43
+
44
+ If unsure, ask the user briefly: "This looks like a multi-component task — want me to delegate parts to Codex/agy in parallel, or handle it myself?"
45
+
46
+ ## Protocol
47
+
48
+ ### 1. Reconnaissance
49
+
50
+ ```bash
51
+ playmaker agents # who is installed and reachable?
52
+ playmaker quotas --refresh # current capacity, broken out per model
53
+ ```
54
+
55
+ **Why we pull quotas:** to deliberately offload work *away from* the most-depleted models and *toward* the freshest. The coach is Claude (top-tier), and the top-tier weekly bucket is the scarcest, hardest-to-replenish resource — every chunk the coach handles itself burns it. So the quota table is not a passive health check; it is the load-balancing input to step 3.
56
+
57
+ **Read the table at model granularity, not provider granularity.** Each provider exposes multiple tiers and they have separate quotas:
58
+
59
+ - Claude: two non-coach ways to run it — an **internal sub-agent** (Task tool; in-session, write-capable, result returns to the coach) and an **external `claude -p` dispatch** (tracked, detached stream). Both draw on the subscription; the difference is where results land, not cost. See "Execution lanes". Default external Claude to `--model sonnet` — its weekly bucket is separate from Opus and usually idle, so it spares the scarce coach (Opus) bucket.
60
+ - Antigravity (`agy`): one Google pool split across model families — Gemini Flash tiers for cheap bulk work, Gemini 3.1 Pro for hard Gemini work, **Claude Sonnet/Opus 4.6 (Thinking)** as Anthropic-quality capacity that spends *Google's* quota, GPT-OSS 120B as a spare mid-tier. `playmaker quotas` shows the **full categorized breakdown** — `Gemini 5h` / `Gemini weekly` and `Claude/GPT 5h` / `Claude/GPT weekly`. Two things share a bucket: all Gemini models draw the Gemini bucket, and Claude *and* GPT-OSS share the Claude/GPT bucket. So dispatching Opus 4.6 via agy spends the same `Claude/GPT` bucket as Sonnet or GPT-OSS — watch the `Claude/GPT 5h` window when fanning out several agy-Claude jobs. (This needs agy's local daemon up — normally true when any agy process is running; if `playmaker quotas` tags agy "daemon offline" it fell back to a coarse Gemini-only view.)
61
+ - Codex: top-tier vs lighter modes (where applicable).
62
+
63
+ If `quotas.json` is more than 1h old (or shows errors), say so before relying on the numbers.
64
+
65
+ ### 1a. Delegate the recon, not just the implementation
66
+
67
+ Codebase exploration ("find where the User entity lives, list its existing fields, locate the auth middleware") is itself delegable — and one of the highest-leverage things to delegate, because raw reading is exactly what burns coach context cheapest models can do. Before doing your own `grep`/`Read` sweep, ask: *can a Flash / Sonnet model produce the answer I need in a short structured report?*
68
+
69
+ Pattern: dispatch a recon subtask with an explicit deliverable.
70
+
71
+ ```bash
72
+ playmaker dispatch agy --model "Gemini 3.5 Flash (Low)" --cwd $(pwd) --sync \
73
+ --prompt "Recon only — do not edit any files. In apps/backend/, locate: (a) the User entity/schema and the migration tooling used (Prisma vs TypeORM vs other), (b) the auth middleware that resolves the current user, (c) where DTOs are defined for user PATCH endpoints if any. Report under 200 words as a numbered list with file paths and line ranges."
74
+ ```
75
+
76
+ Use `--sync` here so the report comes back inline; the coach reads ~200 words instead of skimming dozens of files. Cost: a fraction of doing it yourself. The coach uses that report to write the *implementation* prompts.
77
+
78
+ Don't over-use this for trivial recon (one file, one symbol — just `Grep` it). The win is when recon would otherwise span many files.
79
+
80
+ ### 2. Load profiles
81
+
82
+ Read `./.playmaker/agents/*.md` first (project-local override), fall back to `~/.playmaker/agents/*.md` (global). The bodies describe each agent's strengths, weaknesses, and when to delegate to them. Match these against the task's subtasks.
83
+
84
+ ### 3. Decompose and propose a plan
85
+
86
+ Break the task into 2-5 subtasks (don't go finer-grained than that on first run — too much coordination overhead). For each subtask, name the agent and state why.
87
+
88
+ **Load-distribution heuristic** (apply on top of profile fit):
89
+
90
+ 1. Sort *models* (not agents) by remaining capacity — freshest at the top. A provider with one fresh model and one depleted model is two separate buckets.
91
+ 2. **Tier-match each subtask to the cheapest model that can finish it cleanly:**
92
+ - **Architectural / spec judgment / cross-module integration:** top tier (Opus, agy "Claude Opus 4.6 (Thinking)" / "Gemini 3.1 Pro (High)", Codex top-tier). The coach lives here; agy-Opus is the overflow lane when the Anthropic Opus weekly is precious.
93
+ - **Pattern-following implementation, well-scoped CRUD, mechanical refactor, test scaffolding, writing inside an existing convention:** mid tier (Claude Sonnet, agy "Claude Sonnet 4.6 (Thinking)" / "Gemini 3.5 Flash (High)" / "Gemini 3.1 Pro (Low)", mid-tier Codex). This is where the bulk of delegated implementation goes.
94
+ - **Recon, summarization, mechanical loops over many files, name normalization:** cheap tier (agy "Gemini 3.5 Flash (Low)"/"(Medium)", **Claude Haiku**, Sonnet for recon-with-judgment).
95
+
96
+ For Claude specifically, tier is orthogonal to **lane** (see "Execution lanes"): pick the model tier here, then decide *internal sub-agent* (coach integrates the result) vs *external `-p`* (tracked detached stream).
97
+ 3. Pass the model explicitly: `playmaker dispatch <agent> --model <name> ...`. Without `--model`, the agent CLI uses its default — which is usually fine but means the coach gives up control over tier-matching. **For sibling Claude, default to `--model sonnet`** (Haiku for trivial mechanical work); never omit it and let the CLI pick Opus — that burns the scarce shared Opus weekly bucket.
98
+ 4. Reserve the most-depleted top-tier model for the lightest role — usually the coach's own coordination, glue, and final integration. Mid- and cheap-tier work goes to mid- and cheap-tier models, even if the depleted top-tier model could technically do it.
99
+ 5. If the coach's own weekly is below ~50%, aggressively shrink its slice: design decisions and final integration only. Push everything else — implementation, recon, prompt-drafting for sub-subtasks — to lower tiers.
100
+ 6. State per-model capacity in the plan proposal (not per-agent), so the user sees the rationale and can correct the routing.
101
+
102
+ **Sizing rule (critical — otherwise the coach saves nothing):**
103
+
104
+ Delegation only pays off when the subtask is **finishable without coach supervision**. If the agent needs multiple rounds of "no, that's wrong, try again," the coach burns Claude tokens reviewing — exactly the budget delegation was meant to save. Two mistakes here defeat the whole pattern:
105
+
106
+ - **Subtask too big or underspecified** → agent rambles or solves the wrong problem; coach reviews heavily, re-prompts repeatedly. Net: coach spent more than just doing it.
107
+ - **Subtask above the agent's ceiling** → agent flails on something it can't actually do (architectural judgment it lacks context for, integration across modules it can't see, novel API design). Net: same as above plus a discarded diff.
108
+
109
+ To make a subtask finishable, every dispatch must carry:
110
+
111
+ 1. **Hard scope boundary.** Files allowed to touch (or files explicitly off-limits). "Edit only `apps/backend/src/users/user.entity.ts` and the matching migration." Never "do the backend changes" — that's a research task disguised as a work task.
112
+ 2. **Acceptance criteria as a check the agent runs itself.** A green command — `npx prisma validate`, `pnpm test users.spec.ts`, `tsc --noEmit`, an `eslint` pass on a specific file. The agent is told to keep iterating until that command exits 0 and to surface its output in the final answer. This replaces coach-side review for ~80% of the work.
113
+ 3. **Done definition in one sentence**, written so the coach can confirm it in seconds without reading the diff line by line. "Column added, migration generated, prisma validate green." If the coach can't write such a sentence, the subtask isn't sized right yet — split or specify further before dispatching.
114
+ 4. **Context the agent needs but doesn't have.** Spec section excerpts, naming conventions, the one related file it should mirror. Paste these into the prompt; don't make the agent find them by reading half the repo. If your team keeps durable notes (a docs folder, a wiki, a notes vault), pass the *exact* paths worth reading rather than asking the agent to go looking.
115
+ 5. **Match to capability.** Codex / agy-Gemini do well on pattern-following, well-scoped CRUD, test scaffolding, mechanical refactors, and writing within an existing convention. They do poorly on architectural decisions across files they haven't been pointed at, novel API design, and judging whether a spec rule applies. Keep those for the coach — or for agy's "Claude Opus 4.6 (Thinking)" when the subtask genuinely needs top-tier judgment but should not burn the Anthropic Opus weekly. If a profile in `.playmaker/agents/<name>.md` exists, trust its guidance over these defaults.
116
+
117
+ A useful smell test before dispatching: *"If this came back done, would I review by running one command and reading one paragraph — or would I need to read the whole diff and think hard about whether it's right?"* If the latter, re-scope before sending.
118
+
119
+ Output the plan as a short proposal:
120
+ ```
121
+ Plan:
122
+ - Coach (me): schema design + integration glue
123
+ - Codex: FastAPI handlers in apps/api/
124
+ - agy (Gemini 3.5 Flash High): pytest tests + README
125
+
126
+ Quotas: Claude session 93% / weekly 80%, Codex 100%,
127
+ agy Gemini 5h 100% / weekly 96%, Claude/GPT 5h 88% / weekly 71%.
128
+ OK to proceed?
129
+ ```
130
+
131
+ ### 4. Wait for explicit approval
132
+
133
+ Do not dispatch anything until the user confirms. Approval can be selective:
134
+
135
+ - "go" / "ok" → execute as proposed
136
+ - "go but skip agy" → drop agy's tasks, redistribute
137
+ - "go but reroute tests to claude" → swap agent for that subtask
138
+ - "no, change X" → revise and re-propose
139
+
140
+ If the user modifies the plan, re-state the modified plan in one line, then dispatch.
141
+
142
+ ### 5. Dispatch in parallel
143
+
144
+ For each delegated subtask:
145
+
146
+ ```bash
147
+ playmaker dispatch <agent> --model <name> --prompt "<scoped prompt>" --cwd $(pwd)
148
+ ```
149
+
150
+ `playmaker dispatch` is **detached by default** — it returns immediately with a session id and the agent runs in the background. That's the whole point of the coach pattern; never wait on a single agent unless you specifically need its output before doing anything else.
151
+
152
+ Always pass `--cwd $(pwd)` — `playmaker`'s default is the *coach process's* current dir, which is not always what you want.
153
+
154
+ Always pass `--model` when you've made a tier-matching decision in step 3. Without it, the agent CLI uses its own default (which may be its top-tier model, defeating the load-distribution effort). Model name is what the agent's native CLI accepts: `claude --model sonnet`, `codex exec -m gpt-5-codex`, `agy --model "Gemini 3.5 Flash (High)"` — agy takes the exact display names from `agy models` and they contain spaces, so **always quote them**. Note: agy's own default is a top-tier model, another reason never to omit `--model` on agy dispatches meant to be cheap.
155
+
156
+ **agy prompt discipline:** the agy agent's shell lives in a private scratch directory, not the workspace. playmaker automatically prepends a workspace preamble to every agy dispatch, but reinforce it: word file instructions with paths relative to the workspace root or absolute paths, never "in the current directory".
157
+
158
+ **Bad-model handling (playmaker ≥0.4 does this for you):** a wrong `--model` is the classic silent failure. Codex returns a `turn.failed` while exiting 0 with empty output; agy silently runs its *default* model instead of erroring. playmaker catches both — a codex model/auth failure raises `codex turn failed: …`, and an unknown agy model raises with the valid roster before dispatch. So a dispatch that comes back **failed** with a model message means: fix the `--model` string (for agy, copy it exactly from `agy models`) and re-dispatch. Don't retry the same string.
159
+
160
+ **For sibling Claude, `--model sonnet` is the default, `--model haiku` for trivial mechanical work.** External `claude -p` draws on the subscription like everything else; defaulting to Sonnet keeps the work on Sonnet's separate weekly bucket and spares the scarce Opus one, so Opus externally must be a deliberate choice. Claude-side writes work on either lane — internal sub-agent when the coach folds the result in directly, external dispatch when you want a tracked, detached stream.
161
+
162
+ **Tag every fan-out with `--batch <label>`.** Pass the *same* short label to all dispatches you launch as one parallel batch. playmaker then suppresses per-agent success pings and fires a **single "N/N done" summary** when the whole batch drains — the signal the user actually waits on (its notification is clickable and opens a combined view of all outputs). Failures still ping immediately. Omit `--batch` only for a true one-off dispatch.
163
+
164
+ ```bash
165
+ B=admin-dashboard # any short label shared across this fan-out
166
+ playmaker dispatch codex --batch "$B" --model gpt-5-codex --prompt "..." --cwd $(pwd)
167
+ playmaker dispatch agy --batch "$B" --model "Gemini 3.5 Flash (High)" --prompt "..." --cwd $(pwd)
168
+ ```
169
+
170
+ `playmaker continue <id> --model <name>` overrides the model for one follow-up turn while keeping the live session; without `--model` it inherits the parent session's model.
171
+
172
+ For sequential delegation (e.g. "I'll do schema first, then Codex builds on top"), commit a checkpoint to git and use `--sync` to block:
173
+
174
+ ```bash
175
+ git commit -am "checkpoint: schema before backend dispatch"
176
+ playmaker dispatch codex --model gpt-5-codex --prompt "..." --cwd $(pwd) --sync # blocks, prints output
177
+ ```
178
+
179
+ ### 6. Coach's own work
180
+
181
+ Between polls, do the part of the task you reserved for yourself in this thread.
182
+
183
+ ### 7. Monitor
184
+
185
+ Periodically (or after finishing your own chunks):
186
+
187
+ ```bash
188
+ playmaker list # who's done, who's running
189
+ playmaker get <id> --wait # block until specific agent finishes
190
+ ```
191
+
192
+ ### 8. Review
193
+
194
+ For each finished subtask:
195
+
196
+ ```bash
197
+ playmaker summary <id> # the agent's own narrative of what they did
198
+ git diff <since> # what they actually changed in code
199
+ ```
200
+
201
+ Read `summary` first. If you need more context to judge: `playmaker thread <id>` (last 5 turns by default). If actively debugging *why* an agent went sideways: `playmaker thread <id> --all --include-tools`. The default `--max-bytes 50000` is a safety cap, not an excuse to skip selectivity.
202
+
203
+ ### 9. Decide per subtask
204
+
205
+ - **Approve**: nothing more to do; move on
206
+ - **Resume for follow-ups (default)**: small fix or refinement →
207
+ `playmaker continue <id> --prompt "Y is still broken because Z — please fix"`.
208
+ This sends the prompt into the agent's live session, so its prior reasoning,
209
+ tool history, and file context are still in scope. Don't re-paste context
210
+ the agent already has.
211
+ - **Fresh dispatch only when the old context is a liability**: requirements
212
+ shifted materially, the agent is looping/confused, or you hit tool errors
213
+ that won't resolve mid-thread. Then start clean and link the lineage:
214
+ `playmaker dispatch <agent> --prompt "..." --parent <id>`.
215
+ - **Fix it yourself**: small enough that re-dispatching is overkill
216
+ - **Hand to user**: bigger redesign needed; surface the issue
217
+
218
+ ### 10. Failure handling
219
+
220
+ If `playmaker dispatch` returns an error (binary missing, auth bad, agent unavailable), don't silently retry. Surface it, propose a re-routed plan ("Codex unavailable; want me to give the backend to Claude instead?"), and wait for user confirmation.
221
+
222
+ **Sibling-Claude writes are enabled** — playmaker forwards `--dangerously-skip-permissions`, so external `claude -p` dispatch/resume runs headless with permissions skipped and *can* edit files in a detached session. (This is configurable; a user who set `skip_permissions = false` will see detached runs stall on the first tool prompt.)
223
+
224
+ Both Claude lanes are on the subscription, so pick by **where the work lives**, not cost:
225
+ - **Coach folds the result in directly → internal sub-agent (Task tool).** In-session, write-capable, returns into context. Default choice for "more Claude."
226
+ - **Independent stream you'll monitor / continue separately → `playmaker dispatch claude --model sonnet`.** Tracked, detached; Sonnet's separate weekly bucket spares Opus.
227
+ - **Work that can leave the Claude family → Codex / agy** (their own quotas).
228
+
229
+ If a dispatch comes back with zero file changes, check `playmaker summary <id>`: a leftover "click Allow" pivot means the local `claude` is older than the skip-permissions wiring or overrides it — fall back to an internal sub-agent or Codex/agy for that subtask. For **agy** specifically, a "done" with no file changes usually means the files landed in agy's private scratch dir (`~/.gemini/antigravity-cli/scratch/`) — the prompt referred to "the current directory" instead of workspace paths; re-dispatch with explicit paths.
230
+
231
+ ## Reading discipline
232
+
233
+ - `playmaker summary` first — usually answers "is it done and what did it claim to do"
234
+ - `playmaker thread <id>` (default last 5) — when summary is insufficient
235
+ - `--include-tools` and `--all` — only when actively debugging agent logic
236
+ - Watch out for context bloat: a long agent thread can be tens of thousands of tokens. The `--max-bytes` cap protects you, but you should pre-decide what you need before reading.
237
+
238
+ ## Quota awareness
239
+
240
+ - **Read Claude quota at model granularity:** Sonnet weekly and Opus weekly are **independent buckets**. Coach and internal sub-agents on Opus spend the scarce one; Sonnet usually sits idle. Push mid-tier work to Sonnet (an internal sub-agent set to Sonnet, or `dispatch claude --model sonnet`) to spare Opus.
241
+ - Read quotas at *model* granularity, not *agent* granularity. For agy, the probe reports four windows — `Gemini 5h/weekly` and `Claude/GPT 5h/weekly`; for Codex, top-tier vs lighter modes.
242
+ - Skip a *model* if its `*_left` is below ~10%; reroute the subtask to the same agent with a different model, or to a different agent entirely.
243
+ - If a top-tier `weekly_*_left` is degrading toward the deadline, push as much work as possible to mid- and cheap-tier models on the same provider (which are often nowhere near depleted), instead of switching providers blindly.
244
+ - agy's **5-hour** window is the one that bites during a fan-out: it "smooths aggregate demand", so a burst of agy dispatches drains `Gemini 5h` or `Claude/GPT 5h` well before the weekly. If a 5h window is low, spread the burst or move some slices to Codex/coach.
245
+ - If `quotas.json` is stale (>1h), refresh before drafting the plan; if you can't refresh, mention it in the plan.
246
+
247
+ ## Tooling reference
248
+
249
+ ```
250
+ playmaker agents # who's installed
251
+ playmaker quotas [--refresh] # capacity, broken out per model
252
+ playmaker dispatch <agent> [--model NAME] [--batch LABEL] --prompt "..." [--cwd <dir>] [--files ...] [--sync]
253
+ playmaker continue <id> [--model NAME] --prompt "..." # resume sub-agent in its existing session
254
+ playmaker list [--status running|done|failed] [--agent NAME] [--limit N]
255
+ playmaker get <id> [--wait] # metadata + final output
256
+ playmaker summary <id> # last 2 assistant messages
257
+ playmaker thread <id> [--last N] [--all] [--role assistant|user|tool]
258
+ [--include-tools] [--max-bytes N] [--follow]
259
+ playmaker logs <id> [--follow] # subprocess stdout for detached runs
260
+ playmaker kill <id> # SIGTERM
261
+ playmaker watch # Rich live TUI of sessions
262
+ ```
263
+
264
+ Every command takes `--json` for machine-readable output.
265
+
266
+ `--model NAME` is forwarded to the agent's native CLI: `claude --model sonnet`, `codex exec -m gpt-5-codex`, `agy --model "Claude Opus 4.6 (Thinking)"` (display names from `agy models`, always quoted). Without it the agent CLI uses its own default. Model is stored on the session row, so detached re-runs and `continue` inherit it; `continue --model X` overrides for that one turn.
267
+
268
+ agy model roster (from `agy models`): `Gemini 3.5 Flash (Low)` / `(Medium)` / `(High)`, `Gemini 3.1 Pro (Low)` / `(High)`, `Claude Sonnet 4.6 (Thinking)`, `Claude Opus 4.6 (Thinking)`, `GPT-OSS 120B (Medium)`. Re-check the roster with `agy models` when in doubt — it changes with Antigravity releases.
269
+
270
+ ## Anti-patterns
271
+
272
+ - **Coach doing everything itself "to be safe".** Defeats the point. If the task fits the activation criteria, *delegate*.
273
+ - **Coach doing its own codebase recon when a Flash/Sonnet recon dispatch would do it cheaper.** Reading is the cheapest model's job; coach reads the *summary*, not the codebase. See step 1a.
274
+ - **Coach dispatching for trivial tasks.** Activation overhead. Just do them.
275
+ - **Coach dispatching subtasks that are too big or too vague to verify cheaply.** If reviewing the result requires reading the whole diff and judging architectural decisions, the coach pays in top-tier tokens what it tried to save. Re-scope: tighten file boundaries, attach a self-running check, write a one-sentence done-condition. Re-prompting an agent that's gone sideways is the most expensive way to use this tool.
276
+ - **Coach delegating tasks above the agent's ceiling.** Architectural judgment, cross-module integration, spec interpretation belong to the coach (or agy-Opus as the top-tier overflow lane). Codex/agy-Gemini are best on pattern-following inside a tight scope.
277
+ - **Coach ignoring per-model quotas.** Burning top-tier weekly to dispatch ten things in series when Sonnet/Flash were sitting idle is the original problem this skill was built to solve. Top-tier work goes to top-tier models *only*. Pattern-following work goes to mid-tier. Recon goes to cheap-tier.
278
+ - **Coach defaulting to top-tier for every dispatch by omitting `--model`.** Without an explicit `--model`, the agent CLI tends to use its top-tier default and depletes the wrong bucket.
279
+ - **Coach silently retrying or hiding agent failures.** User stays informed even when things go wrong.
280
+ - **Coach treating `playmaker dispatch` as the only delegation lane.** In-session `Task` sub-agents are a first-class lane — in-session, write-capable, parallel. When you want "more Claude," reach for an internal sub-agent before an external `claude -p`. Don't be shy about spawning several.
281
+ - **Coach burning the Opus weekly bucket when Sonnet was idle.** Dispatching Opus via `claude -p`, or doing mid-tier work on the coach itself, depletes the scarcest bucket for something Sonnet — a separate, usually-idle weekly bucket, via an internal sub-agent or external dispatch — would have done. Default sibling Claude to Sonnet; reserve Opus for genuine top-tier judgment.
282
+ - **Coach reading whole agent threads into context unprompted.** Use `summary` and selective `--last N`.
File without changes
@@ -0,0 +1,342 @@
1
+ """Antigravity CLI (`agy`) handler.
2
+
3
+ Empirically (agy 1.1.1):
4
+ - oneshot: `agy -p "<prompt>" [--model "<display name>"] [--dangerously-skip-permissions]
5
+ [--print-timeout 60m] [--log-file <path>]`; stdout is the final response as
6
+ plain text (no JSON envelope).
7
+ - resume: `agy --conversation <uuid> -p "..."` — keeps the same conversation id.
8
+ - models are addressed by *display name* from `agy models`, e.g.
9
+ "Claude Opus 4.6 (Thinking)", "Gemini 3.5 Flash (Low)".
10
+ - the conversation id is NOT printed to stdout; it is recovered from the CLI
11
+ debug log (`--log-file`) via the `Created conversation <uuid>` line, which
12
+ appears a few seconds in — that is our early on_session_started signal.
13
+ - session transcript is plain JSONL, one step per line:
14
+ ~/.gemini/antigravity-cli/brain/<conversation-id>/.system_generated/logs/transcript_full.jsonl
15
+ with fields: step_index, source (USER_EXPLICIT|SYSTEM|MODEL), type
16
+ (USER_INPUT|PLANNER_RESPONSE|RUN_COMMAND|CODE_ACTION|VIEW_FILE|...),
17
+ status, created_at, content.
18
+ - the agent's shell cwd is agy's private scratch dir
19
+ (~/.gemini/antigravity-cli/scratch), NOT the workspace — relative file writes
20
+ land there. dispatch() therefore prepends a workspace preamble instructing
21
+ the agent to use absolute paths under the target cwd.
22
+ """
23
+
24
+ from __future__ import annotations
25
+
26
+ import functools
27
+ import json
28
+ import os
29
+ import re
30
+ import shutil
31
+ import subprocess
32
+ import tempfile
33
+ import time
34
+ from pathlib import Path
35
+
36
+ from playmaker.agents.base import DispatchResult, SessionStartedCallback, Turn
37
+ from playmaker.config import agent_setting
38
+
39
+ AGY_BRAIN_ROOT = Path("~/.gemini/antigravity-cli/brain").expanduser()
40
+
41
+ # `Created conversation <uuid>` for fresh runs; resumes keep the known id.
42
+ _CONVERSATION_RE = re.compile(r"Created conversation ([0-9a-f-]{36})")
43
+
44
+ _USER_REQUEST_RE = re.compile(r"<USER_REQUEST>\n?(.*?)\n?</USER_REQUEST>", re.DOTALL)
45
+
46
+
47
+ class AgyHandler:
48
+ name = "agy"
49
+
50
+ def is_available(self) -> bool:
51
+ return shutil.which("agy") is not None
52
+
53
+ @staticmethod
54
+ @functools.lru_cache(maxsize=1)
55
+ def available_models() -> tuple[str, ...]:
56
+ """Exact model display names agy accepts, from `agy models`.
57
+
58
+ agy resolves an unknown `--model` to its default *silently* (no error,
59
+ wrong model runs), so we validate against this list before dispatch.
60
+ Cached per-process; returns () if the roster can't be read (then we skip
61
+ validation rather than block a dispatch on a probe failure).
62
+ """
63
+ if shutil.which("agy") is None:
64
+ return ()
65
+ try:
66
+ proc = subprocess.run(
67
+ ["agy", "models"], capture_output=True, text=True, timeout=15
68
+ )
69
+ except (OSError, subprocess.SubprocessError):
70
+ return ()
71
+ if proc.returncode != 0:
72
+ return ()
73
+ return tuple(
74
+ line.strip() for line in proc.stdout.splitlines() if line.strip()
75
+ )
76
+
77
+ def _validate_model(self, model: str | None) -> None:
78
+ if not model:
79
+ return
80
+ roster = self.available_models()
81
+ if roster and model not in roster:
82
+ raise RuntimeError(
83
+ f"agy has no model {model!r}; agy would silently fall back to its "
84
+ f"default. Available: {', '.join(roster)}"
85
+ )
86
+
87
+ def dispatch(
88
+ self,
89
+ prompt: str,
90
+ cwd: Path,
91
+ files: list[Path] | None = None,
92
+ on_session_started: SessionStartedCallback | None = None,
93
+ model: str | None = None,
94
+ ) -> DispatchResult:
95
+ return self._run(
96
+ prompt,
97
+ cwd,
98
+ files or [],
99
+ on_session_started=on_session_started,
100
+ model=model,
101
+ conversation_id=None,
102
+ )
103
+
104
+ def resume(
105
+ self,
106
+ prompt: str,
107
+ cwd: Path,
108
+ agent_session_id: str,
109
+ files: list[Path] | None = None,
110
+ on_session_started: SessionStartedCallback | None = None,
111
+ model: str | None = None,
112
+ ) -> DispatchResult:
113
+ return self._run(
114
+ prompt,
115
+ cwd,
116
+ files or [],
117
+ on_session_started=on_session_started,
118
+ model=model,
119
+ conversation_id=agent_session_id,
120
+ )
121
+
122
+ def _run(
123
+ self,
124
+ prompt: str,
125
+ cwd: Path,
126
+ files: list[Path],
127
+ *,
128
+ on_session_started: SessionStartedCallback | None,
129
+ model: str | None,
130
+ conversation_id: str | None,
131
+ ) -> DispatchResult:
132
+ """Shared oneshot/resume runner.
133
+
134
+ stdout/stderr go to temp files (agy prints the whole response at the
135
+ end, and a PIPE nobody drains can deadlock on long answers); while the
136
+ process runs we poll the debug log for the conversation id so
137
+ on_session_started fires within seconds, not at completion.
138
+ """
139
+ self._validate_model(model)
140
+ full_prompt = self._build_prompt(prompt, files, cwd)
141
+
142
+ fd_log, log_name = tempfile.mkstemp(prefix="playmaker-agy-", suffix=".log")
143
+ os.close(fd_log)
144
+ log_path = Path(log_name)
145
+
146
+ cmd = ["agy", "-p", full_prompt, "--log-file", str(log_path)]
147
+ if conversation_id:
148
+ cmd += ["--conversation", conversation_id]
149
+ # Detached runs have no human to approve tool prompts (same rationale
150
+ # as the claude handler). Opt out via [agents.agy] skip_permissions.
151
+ if agent_setting("agy", "skip_permissions", True):
152
+ cmd.append("--dangerously-skip-permissions")
153
+ # agy's built-in print timeout is 5m — too short for real subtasks.
154
+ cmd += ["--print-timeout", str(agent_setting("agy", "print_timeout", "60m"))]
155
+ if model:
156
+ cmd += ["--model", model]
157
+
158
+ t0 = time.monotonic()
159
+ agent_session_id: str | None = conversation_id
160
+ if agent_session_id and on_session_started is not None:
161
+ try:
162
+ on_session_started(agent_session_id)
163
+ except Exception:
164
+ pass
165
+
166
+ with tempfile.TemporaryFile("w+", encoding="utf-8", errors="replace") as out_fh, \
167
+ tempfile.TemporaryFile("w+", encoding="utf-8", errors="replace") as err_fh:
168
+ proc = subprocess.Popen(
169
+ cmd,
170
+ cwd=str(cwd),
171
+ stdout=out_fh,
172
+ stderr=err_fh,
173
+ stdin=subprocess.DEVNULL,
174
+ text=True,
175
+ )
176
+ try:
177
+ while proc.poll() is None:
178
+ if agent_session_id is None:
179
+ agent_session_id = self._conversation_from_log(log_path)
180
+ if agent_session_id and on_session_started is not None:
181
+ try:
182
+ on_session_started(agent_session_id)
183
+ except Exception:
184
+ pass
185
+ time.sleep(0.3)
186
+ except BaseException:
187
+ proc.kill()
188
+ raise
189
+ duration = time.monotonic() - t0
190
+
191
+ out_fh.seek(0)
192
+ stdout_text = out_fh.read()
193
+ err_fh.seek(0)
194
+ stderr_text = err_fh.read()
195
+
196
+ if agent_session_id is None:
197
+ agent_session_id = self._conversation_from_log(log_path)
198
+ if agent_session_id and on_session_started is not None:
199
+ try:
200
+ on_session_started(agent_session_id)
201
+ except Exception:
202
+ pass
203
+
204
+ log_tail = self._log_tail(log_path)
205
+ log_path.unlink(missing_ok=True)
206
+
207
+ if proc.returncode != 0:
208
+ raise RuntimeError(
209
+ f"agy failed (exit {proc.returncode}): "
210
+ f"{stderr_text.strip() or stdout_text.strip()[:500] or log_tail}"
211
+ )
212
+ if not agent_session_id:
213
+ raise RuntimeError(
214
+ f"agy finished but no conversation id found in its log; tail:\n{log_tail}"
215
+ )
216
+
217
+ session_file = self.find_session_file(agent_session_id, cwd)
218
+ initial_output = stdout_text.strip() or self._last_assistant_text(session_file)
219
+ return DispatchResult(
220
+ agent_session_id=agent_session_id,
221
+ cwd=str(cwd),
222
+ session_file=session_file,
223
+ initial_output=initial_output,
224
+ cost_usd=None, # agy reports neither tokens nor USD in print mode
225
+ duration_seconds=duration,
226
+ exit_code=proc.returncode,
227
+ )
228
+
229
+ @staticmethod
230
+ def _conversation_from_log(log_path: Path) -> str | None:
231
+ try:
232
+ m = _CONVERSATION_RE.search(log_path.read_text(encoding="utf-8", errors="replace"))
233
+ except OSError:
234
+ return None
235
+ return m.group(1) if m else None
236
+
237
+ @staticmethod
238
+ def _log_tail(log_path: Path, lines: int = 15) -> str:
239
+ try:
240
+ return "\n".join(
241
+ log_path.read_text(encoding="utf-8", errors="replace").splitlines()[-lines:]
242
+ )
243
+ except OSError:
244
+ return ""
245
+
246
+ def _last_assistant_text(self, session_file: Path | None) -> str:
247
+ """Fallback when stdout is empty: last non-empty assistant turn.
248
+
249
+ The transcript can lag process exit slightly, so retry briefly.
250
+ """
251
+ if session_file is None:
252
+ return ""
253
+ for _ in range(5):
254
+ for turn in reversed(self.parse_session_file(session_file)):
255
+ if turn.role == "assistant" and turn.content.strip():
256
+ return turn.content
257
+ time.sleep(0.2)
258
+ return ""
259
+
260
+ def find_session_file(self, agent_session_id: str, cwd: Path) -> Path | None:
261
+ # Transcripts are keyed by conversation id only — cwd is irrelevant.
262
+ logs_dir = AGY_BRAIN_ROOT / agent_session_id / ".system_generated" / "logs"
263
+ for name in ("transcript_full.jsonl", "transcript.jsonl"):
264
+ path = logs_dir / name
265
+ if path.exists():
266
+ return path
267
+ return None
268
+
269
+ def parse_session_file(self, path: Path) -> list[Turn]:
270
+ """Read the brain transcript JSONL and yield normalized turns.
271
+
272
+ - USER_INPUT -> user (unwrapped from <USER_REQUEST> tags)
273
+ - PLANNER_RESPONSE -> assistant (empty ones are streaming placeholders)
274
+ - other MODEL steps (RUN_COMMAND, CODE_ACTION, VIEW_FILE, ...) -> tool
275
+ - SYSTEM steps (CONVERSATION_HISTORY, CHECKPOINT) -> skipped
276
+ """
277
+ from datetime import datetime
278
+
279
+ turns: list[Turn] = []
280
+ if not path.exists():
281
+ return turns
282
+ with path.open("r", encoding="utf-8") as fh:
283
+ for raw in fh:
284
+ raw = raw.strip()
285
+ if not raw:
286
+ continue
287
+ try:
288
+ obj = json.loads(raw)
289
+ except json.JSONDecodeError:
290
+ continue
291
+
292
+ source = obj.get("source")
293
+ step_type = obj.get("type")
294
+ content = obj.get("content") or ""
295
+
296
+ ts = None
297
+ ts_raw = obj.get("created_at")
298
+ if isinstance(ts_raw, str):
299
+ try:
300
+ ts = datetime.fromisoformat(ts_raw.replace("Z", "+00:00"))
301
+ except ValueError:
302
+ pass
303
+
304
+ if step_type == "USER_INPUT":
305
+ m = _USER_REQUEST_RE.search(content)
306
+ turns.append(
307
+ Turn(
308
+ role="user",
309
+ content=(m.group(1) if m else content).strip(),
310
+ timestamp=ts,
311
+ )
312
+ )
313
+ elif step_type == "PLANNER_RESPONSE":
314
+ if content.strip():
315
+ turns.append(Turn(role="assistant", content=content, timestamp=ts))
316
+ elif source == "MODEL":
317
+ turns.append(
318
+ Turn(
319
+ role="tool",
320
+ content="",
321
+ tool_calls=[{"id": None, "name": step_type, "input": None}],
322
+ tool_results=[{"tool_use_id": None, "content": content}],
323
+ timestamp=ts,
324
+ )
325
+ )
326
+ return turns
327
+
328
+ @staticmethod
329
+ def _build_prompt(prompt: str, files: list[Path], cwd: Path) -> str:
330
+ # The agy agent's shell starts in a private scratch dir; without this
331
+ # preamble, "create foo.txt" lands in ~/.gemini/antigravity-cli/scratch
332
+ # instead of the workspace (verified empirically on agy 1.1.1).
333
+ parts = [
334
+ f"Workspace root: {cwd}\n"
335
+ "Do all file reads and writes inside the workspace root, always using "
336
+ "absolute paths — your shell's default cwd is a private scratch "
337
+ "directory outside the workspace, so relative paths would land there.",
338
+ prompt,
339
+ ]
340
+ if files:
341
+ parts.append("Relevant files:\n" + "\n".join(str(p) for p in files))
342
+ return "\n\n".join(parts)