Skip to content

Antigravity: track subagents as tasks on the parent, and count tokens from the transcript - #2827

Open
peyton-alt wants to merge 6 commits into
mainfrom
peyton/antigravity-subagents
Open

peyton-alt wants to merge 6 commits into
mainfrom
peyton/antigravity-subagents

Conversation

@peyton-alt

@peyton-alt peyton-alt commented Oct 20, 2026 •

Copy link
Copy Markdown
Contributor

https://entire.io/gh/entireio/cli/trails/1619

Problem

Entire tracks Antigravity (agy) subagents as unrelated top-level sessions and counts agy's tokens from the wrong source. A live run on main (agy 1.4.2, one subagent):

Entire on main agy's own per-call records
Parent session 16,478 in / 134 out, 2 calls 81,107 / 926, 10 calls
Subagent 0 18,519 / 254, 3 calls
Plain session, 4 turns 95 / 27 48,383 / 98

The causes:

  • Wrong token source. Entire reads agy's tokens only from the title/statusline feed, which has three problems:
    • the snapshot carrying a turn's last call usually lands just after Stop, so single-call turns record nothing;
    • its total_input_tokens is how full the context is, not a running total;
    • it never fires for subagent conversations or agy -p.
  • Subagents aren't linked to their parent. agy runs each invoke_subagent as a separate conversation with its own hooks, and Entire records it as a separate top-level session.
  • Parent turns restart mid-turn. A parent waiting on a subagent is resumed by a new execution whose first PreInvocation has invocationNum 0. That fired an unconditional TurnStart, so everything before the last resume dropped out of the turn.
  • Parent turns can stay open forever. If the parent's last Stop (fullyIdle: false) lands before the subagent finishes, agy sends the parent nothing further. Its turn never ends, and in -p mode the parent stays ACTIVE as a ghost.

Change

  1. Tokens from the transcript. Since 1.4, agy writes each model call's input_tokens, output_tokens and cache_read_tokens on its PLANNER_RESPONSE step in transcript_full.jsonl.
    • Antigravity now implements TokenCalculator over those steps, so resolveTurnTokenUsage reads them before the statusline.
    • output_tokens already includes reasoning: in print mode agy reports thinking_tokens 2310 inside output_tokens 2313.
    • Transcripts without these fields (older agy) return nil, so the statusline stays the fallback for them.
  2. Subagent support. Antigravity implements SubagentSessionResolver, the interface Factory Droid Workers use.
    • The link needs two text formats agy writes, and both must be present:
      • the child transcript's first step is a SYSTEM_MESSAGE with sender=<parent>;
      • the parent's transcript has a "Created the following subagent" step naming the child.
    • The task's tool-use ID is the child's conversation ID, since agy tool calls have no IDs. Its type and description come from the call's TypeName and Role.
    • If agy changes either format, the child falls back to being a top-level session, as on main.
  3. Subagent sessions never act on their own (shared code, so Droid Workers get this too).
    • Once its parent is known, a subagent's state carries ParentSessionID. Such a session never links a commit, never condenses on its own, and is never picked as "the calling session". Identity matching would otherwise tie it with the parent, because they share an agy process.
    • Its file changes go to the parent.
    • A live task record on the parent carries its transcript so far, so a commit made while the subagent runs goes to the parent's checkpoint as the parent's task.
    • Condensing a subagent session from doctor or the exited-owner sweep hands its files to the parent and marks it done.
    • Later hooks rebuild the link from the parent's task record instead of re-reading transcripts.
  4. A wake-up doesn't restart the parent's turn. At PreInvocation, a new prompt's USER_INPUT step is already in the transcript, while a subagent's reply isn't yet. So invocationNum 0 starts a turn unconditionally only when the transcript ends with USER_INPUT, or doesn't exist yet. An Esc-interrupted turn fires no Stop at all, and the next prompt still starts its own turn.
  5. A paused parent is ended when its subagents finish.
    • A fullyIdle: false Stop records the pause (AwaitingSubagentsAt), and a resume clears it.
    • Whichever hook sees both the pause and the last subagent finished claims it and ends the parent's turn exactly as a reported one. That can be the subagent's turn end or the parent's Stop, which land milliseconds apart in either order.
    • It only acts within a minute of a subagent finishing, so a pause for a background command isn't mistaken for this.
    • A task record left open by a subagent that crashed or was killed doesn't hold the parent's turn open. Only a record whose subagent session is still active, alive and not stuck counts as running.

Tests

  • Unit tests:

    • the token calculator, on a trimmed real transcript;
    • the resolver, including its fail-closed cases: a top-level conversation, a sender that didn't create the child, no child transcript yet, a missing parent transcript, an invalid sender ID, a path outside the brain directory, and several calls in one step;
    • the PreInvocation wake-up rule;
    • the shared filters: commit linking, caller resolution, and condense handing work to the parent.
  • Integration tests (simulated hooks, in the hook order captured from live agy):

    • tokens from transcript steps, with late and context-fill statusline snapshots present;
    • a subagent writing a file, with a commit while it runs: one checkpoint session plus tasks/<child>/, and the parent's turn kept whole across the wake-up;
    • the parent's turn ended in both race orders.

    Each fails without its change.

  • Live runs with this branch (agy 1.4.2):

    Run Parent (Entire = agy) Subagent's task (Entire = agy)
    Subagent writes notes.txt 22,120 / 292, 3 calls 22,294 / 272, 3 calls
    Subagent writes notes2.txt 22,054 / 293, 3 calls 22,248 / 190, 3 calls
    Read-only subagent 22,257 / 399, 3 calls 18,704 / 343, 3 calls
    Read-only subagent 22,005 / 334, 3 calls 18,338 / 253, 3 calls
    • The checkpoints after commits hold one session plus tasks/<child>/, with the child's transcript, files, type and description.
    • The race that point 5 handles happened in one of the four runs. That build predated point 5, so its parent stayed open with its session total missing the turn, while the checkpoint was still exact. Point 5 is covered by the integration test above.

Known limits and follow-ups

  • Format coupling. The link depends on two agy text formats, as described above; a change to either fails closed to today's behaviour.
  • Cache semantics are unverified. No local agy session has reported a cache read, so whether input_tokens includes cache_read_tokens is unknown. Both are mapped across as they are.
  • Task-record tokens aren't in totals for any agent. A subagent's tokens reach task.json but not the checkpoint total or entire status. Separate PR; an audit found this is right for OpenCode, Droid and agy, and would double count for Claude Code, Codex and Copilot.
  • Overlap with Add per-agent cumulative token sources #2782. Add per-agent cumulative token sources #2782 adds a cumulative agy token source over the same transcript fields. The two should share one parser once both land.
  • A repeated Stop double counts on main. For every agent, a repeated Stop on a turn that changed files counts the turn twice in the session total. Reproduced on main with Claude Code; separate PR.

🤖 Generated with Claude Code

peyton-alt and others added 4 commits October 19, 2026 16:47
agy writes each model call's usage on its PLANNER_RESPONSE transcript
step. Entire read only the statusline snapshots, which miss the turn's
last call when Stop fires before the snapshot lands, track context fill
rather than a running input total, and never fire for subagent
conversations or print mode. In a live session that recorded 95 input
tokens for four turns that used 48,383.

Implement TokenCalculator over those steps. It returns nil when no step
in range has the fields, so older agy keeps the statusline fallback.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Entire-Checkpoint: 01M5B8ZKKG267HXYPGTACPA40Y
agy runs invoke_subagent as a conversation with its own hooks, which
Entire tracked as an unrelated top-level session. Implement
SubagentSessionResolver from the two steps agy writes to link them, and
mark the subagent's session once its parent is known: it never links a
commit, condenses on its own, or answers caller resolution, and its
file changes go to the parent, where a live task record carries its
transcript so far.

A parent waiting on a subagent is resumed by a new execution whose
first PreInvocation has invocationNum 0. That restarted the parent's
turn, so everything before the last resume dropped out of the turn's
tokens. Treat invocationNum 0 as a new turn only when the transcript
ends with the prompt's USER_INPUT step, which a resume does not.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Entire-Checkpoint: 01M5BF7Q8MB15D3SCP09NACCD4
A marked subagent session now rebuilds its link from the parent's task
record instead of re-reading both transcripts on every hook, and
refreshes the parent's interaction time at most once a minute. agy's
resolver reads only the head of the child's transcript and decodes only
the parent lines that can name the child. A read-only subagent, which
reaches no ToolUse, is marked on its suppressed follow-up calls.

Condensing a subagent's own session (doctor, the exited-owner sweep)
used to return success having done nothing, so it was offered again
forever. It now moves any files it holds to the parent and marks itself
fully condensed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Entire-Checkpoint: 01M5BFZYV1XFPD68WPTNB4APB0
agy reports a parent's Stop with fullyIdle=false while its subagents run.
When that Stop lands before the last subagent's turn ends, agy sends the
parent nothing further, so its turn never ended and its tokens never
reached the session total; in -p mode every such parent stayed ACTIVE.

A fullyIdle=false Stop now records the pause, a resume clears it, and
whichever hook sees both the pause and the last subagent finished (the
subagent's turn end or the parent's Stop, which land milliseconds apart
in either order) claims it and ends the parent's turn as a reported one.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Entire-Checkpoint: 01M5BKRYD73JMN0W6J7WN79B7R
@peyton-alt
peyton-alt requested a review from a team as a code owner October 20, 2026 03:04
Copilot AI balanced review requested due to automatic review settings October 20, 2026 03:04

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit b96ae0f. Configure here.

Comment thread cmd/entire/cli/lifecycle.go

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Unresolved lifecycle and attribution errors can prematurely finalize checkpoints, miscount tokens or lose pending file handoffs.

5 open issues
What changed in this PR

Improves Antigravity tracking in Entire CLI by counting transcript tokens and associating detached subagents with their parent sessions.

Changes:

  • Reads per-call transcript usage, retaining the older statusline fallback.
  • Records subagents as parent tasks and excludes them from standalone attribution.
  • Adds pause/resume handling, regression tests and documentation.
File Description
docs/​architecture/​agent-guide.md Documents token sources and detached subagents.
cmd/​entire/​cli/​strategy/​subagent_session_test.go Tests child exclusion and file handoff.
cmd/​entire/​cli/​strategy/​session_identity.go Excludes children from commit linking.
cmd/​entire/​cli/​strategy/​manual_commit_condensation.go Transfers child work to parents.
cmd/​entire/​cli/​strategy/​manual_commit_condensation_test.go Clarifies fallback test comments.
cmd/​entire/​cli/​strategy/​caller_session.go Excludes children from caller resolution.
cmd/​entire/​cli/​state.go Clarifies token-baseline comments.
cmd/​entire/​cli/​session/​state.go Adds parent linkage and pause state.
cmd/​entire/​cli/​lifecycle.go Coordinates child attribution and parent completion.
cmd/​entire/​cli/​integration_test/​antigravity_transcript_tokens_test.go Tests transcript-based token accounting.
cmd/​entire/​cli/​integration_test/​antigravity_subagent_test.go Tests subagent attribution and hook ordering.
cmd/​entire/​cli/​agent/​event.go Adds the awaiting-subagents event flag.
cmd/​entire/​cli/​agent/​antigravity/​transcript.go Reuses step constants; updates comments.
cmd/​entire/​cli/​agent/​antigravity/​tokens.go Implements transcript token calculation.
cmd/​entire/​cli/​agent/​antigravity/​tokens_test.go Tests offsets, fallback and deduplication.
cmd/​entire/​cli/​agent/​antigravity/​testdata/​transcript_tokens.jsonl Adds captured token fixtures.
cmd/​entire/​cli/​agent/​antigravity/​subagent_session.go Resolves parent links and detects prompts.
cmd/​entire/​cli/​agent/​antigravity/​subagent_session_test.go Tests linking and wake-up detection.
cmd/​entire/​cli/​agent/​antigravity/​statusline.go Documents fallback behavior.
cmd/​entire/​cli/​agent/​antigravity/​lifecycle.go Distinguishes prompts, wake-ups and pauses.
cmd/​entire/​cli/​agent/​antigravity/​lifecycle_test.go Tests paused Stop events.
cmd/​entire/​cli/​agent/​antigravity/​AGENT.md Updates integration behavior and limitations.
cmd/​entire/​cli/​agent/​agent.go Clarifies token-source interface comments.

Review effort: Balanced

Comment thread cmd/entire/cli/lifecycle.go Outdated
Comment thread cmd/entire/cli/lifecycle.go
Comment thread cmd/entire/cli/lifecycle.go
Comment thread cmd/entire/cli/lifecycle.go Outdated
Comment thread cmd/entire/cli/strategy/manual_commit_condensation.go Outdated
peyton-alt and others added 2 commits October 19, 2026 20:24
… turn

The parent's turn waited for every uncompleted task record, so one left
open by a subagent that crashed or was killed kept it ACTIVE for good.
Only a record whose subagent session is still ACTIVE, with a live owner
and not stuck, counts as running.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Entire-Checkpoint: 01M5BNCTWVDK4352DK9CGTB81N
…ask records

Review found the parent's turn could end too early or never: task records
track pending checkpoint work, not which subagents run. A subagent woken
again kept its completed record, a commit could prune the record that
signalled completion, an unmarked subagent had no record at all, and a
parent about to wake on a reply was ended first.

The parent's turn now ends only once every subagent the turn involved
(launched, messaged, or heard from, per the parent's transcript) has
stopped and every reply it sent is in the parent's transcript; a reply
still in flight will wake the parent. A subagent whose agent died counts
as stopped. The claim lasts for the turn, so a later pause cannot re-arm
it. New agent capability: SubagentReplyCounter.

Also: a subagent is marked at any tool call, not only file edits, since a
shell command can edit and commit; and condensing a subagent's session
hands its files to the parent before removing them, leaving them
retryable when the parent cannot take them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Entire-Checkpoint: 01M5BRJSZB9JHY9PCRBHZVJ7C6
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants