aws-samples / aws-samples/sample-agent-assisted-sdlc
[meta] Path B token consumption — data-grounded baseline + decomposed optimization plan
- Dominant language
- Python
- Stars
- 42
- Forks
- 9
- PR merge metrics
- No merged PRs in 30d
Description
> **This is a meta / planning issue, not a triggerable one.** It establishes the data-grounded baseline for Path B token consumption on the claude-code plugin, identifies where cost actually lives, and decomposes the optimization work into separate child issues. **Do NOT add `agent:start` to this issue.** The agent should not act on it directly.
>
> Each numbered deliverable below corresponds to a separate child issue (links added as they're filed).
## Audience
Maintainers of the SDLC pipeline who care about per-Path-B-run cost and latency. Path B (complex orchestration) currently routes ~76% of total cost on a representative complex issue; trimming it pays off on every future complex run.
## What this issue is NOT
- **Not a measurement-first plan.** The data has been gathered (see [Baseline data](#baseline-data) below). Child issues are concrete optimizations, not "go measure things first."
- **Not an output-quality regression.** Every child issue must produce equivalent or better PRs. Conservative correctness is non-negotiable.
- **Not a rewrite of the orchestrator.** Each child issue is a single targeted change.
## Baseline data
Captured 2026-06-04 from the deployed runtime's `otel-rt-logs` stream over the last 14 days. Source queries from `coding-assistants/claude-code/obs.md`. Each row in the tables below reflects **a complete Path B pipeline run** — orchestrator + every subagent + every re-invocation, summed.
### Per-session totals (top sessions, last 14d)
```
fields and filter as in obs.md § "Per-session cost rollup", grouped by attributes.session.id
```
| Session | cost_usd | input_tok | output_tok | **cache_read** | cache_create | api_calls |
|---|---|---|---|---|---|---|
| `sdlc-…-issue-00023-run` | **$23.96** | 2,123 | 154,383 | **23,057,459** | 1,369,198 | **351** |
| `sdlc-…-issue-00017-run` | $17.95 | 1,412 | 124,851 | 19,621,168 | 801,400 | 267 |
| `sdlc-…-issue-00027-run` | $10.65 | 12,707 | 76,166 | 8,862,663 | 680,347 | 193 |
| `sdlc-…-issue-00019-run` | $9.23 | 1,359 | 72,723 | 7,564,238 | 579,296 | 179 |
| `sdlc-…-issue-00021-run` | $6.29 | 6,779 | 66,872 | 5,295,752 | 310,570 | 58 |
| `sdlc-…-issue-00013-run` | $6.21 | 610 | 47,173 | 8,147,735 | 152,780 | 86 |
| `sdlc-…-issue-00010-run` | $5.20 | 1,194 | 38,602 | 4,690,454 | 301,887 | 82 |
| `sdlc-…-issue-00012-run` | $2.63 | 566 | 14,893 | 3,270,643 | 99,884 | 58 |
### The cost-driver finding
**`cache_read_tokens` dominate, not `input_tokens`.** On issue #23: 23M cache reads vs 2,123 fresh input tokens. Cached reads cost ~10% of fresh per token, but at 23M they still totaled $23.96.
This invalidates the original hypothesis (which was: "deduplicate ~90 KB of `issue.json` reads across subagents"). The issue.json content IS already being cached — that's why the fresh input column is tiny.
### Where the cache reads actually live (issue #23 breakdown)
```
parsed from claude_code.api_request events, grouped by attributes.query_source
```
| query_source | calls | cost_usd | cache_read | output_tok | avg cache_read/call |
|---|---|---|---|---|---|
| `agent:builtin:general-purpose` (Path B subagents) | **266** | **$17.72 (74%)** | **18,319,906** | 134,895 | **68,872** |
| `sdk` (orchestrator main loop) | 82 | $6.22 (26%) | 4,737,553 | 19,410 | 57,775 |
| `generate_session_title` | 3 | $0.01 | 0 | 78 | 0 |
**74% of cost lives inside `general-purpose` subagents.** Each subagent api_call replays an average of **68,872 tokens from cache**. Max observed: 144,864.
### What subagents do per-call (issue #23 tool breakdown)
```
parsed from claude_code.tool_result events, grouped by attributes.tool_name
```
| tool | calls | total_bytes | avg | max |
|---|---|---|---|---|
| **Read** | 127 | **1,031,378** | 8,251 | **96,545** |
| `mcp_tool` (gateway calls) | 23 | 153,020 | 6,653 | 57,988 |
| Bash | 114 | 108,723 | 988 | 23,164 |
| Agent (Task subagent return) | 7 | 17,816 | 2,545 | 3,198 |
| Grep | 27 | 12,424 | 460 | 3,113 |
| Glob | 25 | 4,086 | 163 | 705 |
| Write | 21 | 3,486 | 166 | 190 |
| Edit | 14 | 2,354 | 168 | 178 |
**Reads dump ~1 MB of content into the conversation across 127 calls.** A single Read returned 96 KB. That MB then participates in `cache_read` on every subsequent turn until context rolls. Reads, not Agents, are the dominant payload source.
### Per-subagent turn count (heuristic)
7 Task subagents spawned on issue #23 → 266 model calls inside subagent contexts → **~38 model calls per subagent on average**. Each call replays ~69K cache tokens.
### Caveat (acknowledged)
`obs.md` § Limitations claims Claude Code v2.1.156 doesn't stamp subagent identity. The data above shows that's outdated — service.version `2.1.159` (currently deployed) DOES stamp `attributes.agent.name` ("general-purpose") and `attributes.query_source` ("agent:builtin:general-purpose") on every `claude_code.api_request`. The `obs.md` Limitations note should be updated when one of the child issues lands. (See child issue D below.)
## What changed in our understanding
The `pathb-token-reduction-pickup` memory entry recommended deduplicating `issue.json` reads across subagents. The OTel data above shows that target was wrong:
- The 5×72 KB issue.json reads ≈ 360 KB are a **small fraction** of the 1 MB the Read tool produces in total, and an even smaller fraction of the 23M cache_read tokens that drive the bill.
- `input_tokens` (the metric the original target would move) is already ~2K out of a $24 session — it's not where the money is.
- The cost driver is **`cache_read_tokens × api_call count`** — i.e., subagent turn count and per-turn cache prefix size.
The optimizations below target the actual cost shape.
## Proposed decomposition
Four independent child issues. Each ships a single targeted change. Order them by expected impact / risk.
### A. Cap subagent turn count by sharpening skill prompts (highest leverage)
**Target:** the 266 `agent:builtin:general-purpose` calls on issue #23 — currently ~38 calls per subagent. Each call replays ~69K cache tokens. Reducing average subagent turns from 38 → 25 saves ~91 calls × 69K ≈ 6.3M cache_read tokens, ~$5–8 per Path B run at current rates.
**Mechanism:**
- Audit each subagent skill (`explore`, `clarification`, `implement`, `critique`, `pr`) for instructions that invite agentic exploration when a directive instruction would suffice.
- Add explicit stop conditions ("once you have written ``, stop. Do not continue exploring.").
- Where reasonable, replace `Use the Agent tool to...` with concrete steps that don't recursively spawn.
- Tighten the orchestrator skill's subagent prompts — pass exact paths and exact deliverables instead of open-ended task descriptions.
**Risk:** medium. Over-tightening can cause subagents to miss context they would have found via exploration. Mitigated by:
- Conservative: change one skill at a time and measure before tightening the next.
- Verification: a representative complex issue must still produce a working PR.
**Verification:** OTel deltas before/after via `obs.md` § "Per-session cost rollup" + per-source breakdown:
- Target: ≥25% reduction in `agent:builtin:general-purpose` `api_calls` count.
- No regression in `count(api_calls)` for `query_source = "sdk"` (orchestrator main loop).
### B. Trim large Read outputs (conservative, diffuse)
**Target:** the 96 KB max-Read and the long tail of >20 KB Reads on issue #23. Read produces 1 MB of conversation content; trimming the top decile is the highest-leverage cut.
**Mechanism:**
- Audit which file Reads return >20 KB. Likely culprits: `issue.json` (post-comments), generated `explore.md` from prior invocations, large source files read in full.
- Where `Read(file_path)` is used today on a >20 KB file, prefer `Read(file_path, limit=200)`, `Grep` for the relevant section, or pre-summarized files.
- For per-invocation artifacts (`explore.md`, `feedback.md`), enforce a size budget at write time rather than read time.
**Risk:** low. Each Read trim is a local change. Read is opt-in per-tool-call; we change the call sites, not Read itself.
**Verification:**
- Target: ≥30% reduction in `sum(attributes.tool_result_size_bytes)` for `tool_name = "Read"` on a representative session.
- No regression in `count(api_calls)` (we're trimming payload, not adding calls).
### C. Reduce subagent count (architectural)
**Target:** Path B currently spawns 5–7 subagents (explore, clarification, implement, critique, pr, plus orchestrator-spawned helpers). Each subagent has fixed startup overhead — system prompt, tools list, plugin context, skill prompt — that must enter cache before useful work begins.
**Mechanism (one of):**
- C1: merge `explore` + `clarification` into a single subagent. Both read the same context (`issue.json`, `project.json`); separating them costs one full subagent's startup overhead.
- C2: inline `pr-agent` into the orchestrator main loop. The pr-agent only writes a PR body; the orchestrator already has all the context.
**Risk:** higher. Architectural change touching the orchestrator's Path B routing. Mitigated by:
- Pick one merge target; do not rewrite the whole pipeline.
- Conservative: implement behind a config flag if needed; revert if PR quality drops.
**Verification:**
- `count(distinct attributes.query_source)` for the run.
- Target: number of `general-purpose` subagent invocations on a representative complex issue drops by 1.
- PR quality unchanged (subjective; reviewer judges).
### D. Update `obs.md` Limitations + add cost-shape query
**Target:** reference doc — the current `obs.md` § Limitations is stale (claims no `agent_id` attribution; service.version 2.1.159 has `agent.name` + `query_source`).
**Mechanism:**
- Update `coding-assistants/claude-code/obs.md` § Limitations to remove the "no `agent_id` / `parent_agent_id`" caveat (or narrow it to `parent_agent_id` only if that field is still missing).
- Add a new query "Cost breakdown by subagent" using `attributes.query_source` aggregation — exactly the query that produced the table in this issue's [Baseline data](#baseline-data).
- Add a query "Read tool payload distribution" parsed from `claude_code.tool_result` records — exactly the query that produced the tool breakdown in this issue.
**Risk:** trivial — docs only.
**Verification:** queries paste-runnable in CloudWatch console.
## Decomposition table
| # | Title | Path | Estimated effort | Expected savings | Dependencies |
|---|---|---|---|---|---|
| D | docs: update obs.md with subagent cost-shape queries and remove stale Limitations | Path A (single MD file) | < 1 hr | $0 directly; enables ongoing measurement | None. Should ship first — cheap, unblocks future measurement. |
| A | feat: cap subagent turn count via tightened skill prompts | Path B (touches all subagent skills) | ~3 hrs | ~25% cost reduction on Path B runs (~$5–8/run) | None. Highest leverage. **DO NOT TRIGGER until issue #34 ships** (orchestrator stale-base + hallucinated-merge bugs would compound the risk of skill-file edits). |
| B | feat: trim large Read tool outputs in subagent skills | Path B (touches subagent skills) | ~2 hrs | ~10–15% cost reduction (smaller cache prefix per turn) | A should ship first; B adjusts what A made smaller. **DO NOT TRIGGER until issue #34 ships.** |
| C | refactor: merge explore+clarification subagents into one (or inline pr-agent) | Path B (touches orchestrator skill + assistant base) | ~5 hrs | ~10% reduction (one fewer subagent startup) | A and B should ship first to establish baseline. **DO NOT TRIGGER until issue #34 ships.** |
## Constraints applying to ALL child issues
- **Conservative correctness.** A representative complex issue must still produce a working PR after each change. The user gates this by triggering one re-run before approving the next change.
- **Measurable.** Every child issue's PR body must quote OTel deltas: before vs after for a single representative session, using the per-session rollup query from `obs.md`.
- **Skill-file changes blocked on #34.** Issues A, B, C touch skill files. The orchestrator stale-base + hallucinated-merge bugs (#34) attack exactly that surface. Wait until #34 ships before triggering any of A/B/C.
- **No new dependencies.** No new pip packages, no new MCP servers, no new gateway tools.
- **No new OTel events.** Existing `claude_code.api_request` and `claude_code.tool_result` records carry everything needed. We measure with what's already there.
- **Apache-2.0 headers preserved on Python files.**
- **PR uses `.github/pull_request_template.md`.**
## Verification protocol applied to every child issue
For every child issue's PR:
1. **Baseline capture.** Before merge, trigger a representative complex issue (similar to #23 in shape) and record from `obs.md` § "Per-session cost rollup":
- `sum(cost_usd)`
- `sum(input_tokens)`, `sum(cache_read_tokens)`, `sum(cache_creation_tokens)`
- `count(api_calls)`
2. **Post-deploy capture.** Trigger an equivalent issue post-deploy. Same query, same fields.
3. **Quote both** in the PR body's `## Cost Impact` section, plus the % delta on the headline metric for that issue.
4. **No subagent-count regression.** The "Heuristic: was the issue SIMPLE or COMPLEX?" query from `obs.md` § "Heuristic" must return ≥1 (i.e., the run was still Path B). The exact subagent count is allowed to change for issue C; for A/B it must stay equal.
5. **No api_call count growth.** `count(api_calls)` must not increase post-change.
## Reference material
- `coding-assistants/claude-code/obs.md` — claude OTel architecture, attribute schema, Logs Insights queries. Updated by child issue D.
- This issue's [Baseline data](#baseline-data) — the data-of-record. Re-run the queries above any time the runtime is updated to spot drift.
- `pathb-token-reduction-pickup` memory entry — supersedes by this issue. The `issue.json` deduplication target it proposed is invalidated by the OTel data above; do NOT re-derive that optimization.
- `coding-assistants/claude-code/plugin/skills/orchestrator/SKILL.md` — Path A/B routing logic; child issues A and C will modify subagent prompts here.
- `coding-assistants/claude-code/plugin/skills/{explore,clarification,implement,critique,pr}/SKILL.md` — subagent skills; child issues A, B will tighten these.
- `project-management/shared/assistants/base.py` — `setup_workspace`, `refresh_for_reinvocation` (PR #18, PR #30) — write the inputs subagents read.
## Decision the user makes after reading this
1. Approve the four-component decomposition (A / B / C / D) or revise.
2. Pick which child issues to file, and in what order. **Recommended:** D first (cheap, unblocks measurement), then A (highest leverage), then B, then C — but **none of A/B/C ship until #34 lands**.
3. Tell me to file the chosen child issues — at which point I file them via the `create-sdlc-issue` skill, individually, **without `agent:start`** so the user controls trigger timing relative to #34.
This issue stays open as the parent / tracking issue. When the chosen child issues are merged, close this one. If only D ships and A/B/C are deferred, close this one anyway and re-open if Path B cost regresses.
Contributor guide
Research direction
This is a meta planning issue, not a direct implementation task. Start with coding-assistants/claude-code/obs.md, especially the per-session cost rollup and Limitations sections, then review the proposed child issues and their dependencies. It is complete when the separate optimization and documentation issues are filed with the stated verification criteria.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- aws
- Domain
- devtools, documentation, observability
- Issue type
- Refactor
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 25/100