Skip to content

feat(tui): make the spawned binary part of adapter identity - #399

Draft
albi-bon wants to merge 2 commits into
bmad-code-org:mainfrom
albi-bon:feat/adapter-binary-identity
Draft

feat(tui): make the spawned binary part of adapter identity#399
albi-bon wants to merge 2 commits into
bmad-code-org:mainfrom
albi-bon:feat/adapter-binary-identity

Conversation

@albi-bon

@albi-bon albi-bon commented Jul 30, 2026

Copy link
Copy Markdown

Stacked on #397, which must merge first; this branch needs a rebase once it does. #398 is a sibling — it needs nothing from this PR, and vice versa. Review the feat(tui): make the spawned binary… commit for this change alone. Base reads main only because a cross-repo PR cannot target a fork branch.

What

Makes the spawned executable part of a session's adapter identity, so an overridden binary is visible wherever the profile name already is: the TUI run header, the tasks-table agent cell, and status --json.

agent claude(cc-work) · opus · dev              # header, session open
agents dev claude(cc-work)·opus review codex    # header, idle (from the policy snapshot)
claude(cc-work)·opus                            # tasks-table agent cell
claude·opus                                     # no override — byte-identical to before

Why

Fixes #395 (part 3 of 3).

With #397 alone, a run on an overridden binary is invisible everywhere except policy.toml. The header, the tasks table and --json all report the profile, so a session billed to cc-work displays as claude — the same blind spot validate had before #397 fixed it, and the reason to fix it here is the same: the whole point of the feature is running one profile under two accounts, so the profile name alone stops being an identity.

How

  • Carries binary through the identity path Active Agent Indicator #153 already built for name+model: SessionRecord, the journal's session-start entry, ActiveAgent, and both --json projections (adapters, adapters_used). The engine stamps it from the same cfg it already reads name/model from.
  • Label format is profile(binary)·model. Both facts are load-bearing and neither substitutes for the other — the profile explains the session's behavior (hook dialect, prompt template, bypass flags), the binary explains which install it billed. The parens appear only when an override is set.
  • binary stays client-specific, so a stage that switches client shows that profile's own executable — the gemini review stage in the engine test reports "", not the base's cc-work.

The settings editor needed no code. That screen is schema-driven from data/settings/core.toml, which #397 already populated, so [adapter] binary and all three [adapter.<stage>] binary fields render today. test_every_policy_field_is_covered_by_exactly_one_spec is what makes that automatic rather than lucky.

image

Compatibility

"" means "the profile's own executable" — exactly what a SessionRecord, journal entry or policy snapshot predating the field reports. Old runs render and serialize unchanged, and the --json addition is additive: a consumer ignoring the key reads what it read before, so STATUS_SCHEMA_VERSION stays at 1 per machine.py's additive-only rule. This is the only place the stack touches a published contract — say the word if you'd rather bump the version anyway.

Testing

uv run pytest -q — 3701 passed, with the same two pre-existing macOS-only failures noted in #397, which reproduce on a clean checkout. pyright unchanged, ruff clean, black/prettier at the versions pinned in .trunk/trunk.yaml.

Coverage: label rendering including the un-overridden no-change case; ActiveAgent from a stamped entry, from a pre-#395 stamp (reads "", not None), and rebuilt from a snapshot; SessionRecord round-trip and legacy-state default; engine stamping of both the journal entry and the record, including the client-switch carve-out; status --json carrying the override on both projections. The existing test_status_json_exposes_adapter_identity contract assertions are widened to the new shape. Per the AGENTS.md ablation rule I deleted the engine stamping and confirmed the engine test FAILS first.

Rendering also verified by driving the real dashboard headlessly and by hand against a scratch project, not only through assertions.

albi-bon added 2 commits July 30, 2026 18:12
…g#395)

A profile's binary was fixed, so running the same CLI under a second name
— a work account launched as `cc` beside a personal `claude`, or two
aliases splitting usage limits — meant copying the whole packaged profile
into .bmad-loop/profiles/ to change one field. That copy then freezes
bypass_args, [env], seed_files and env_fault_patterns against upstream
forever: the cost of one field is permanent drift on eight others.

Add `binary` to [adapter] and [adapter.<stage>]. Empty (the default) keeps
the profile's own binary, so existing projects are untouched. Both adapter
families already accepted a `binary` argument and resolved it as
`binary or profile.binary`; policy is what was never plumbed into it, so
make_adapters passes the resolved value and the seam does the rest.

Inheritance follows the model/extra_args rule: client-specific, inherited
from the base only when the stage runs the same client, so a `cc` alias of
claude can never become argv[0] for a stage that switched to codex.

Two read paths had to follow the effective binary rather than the
profile's: validate's PATH probe (it would report "claude found" for a run
that spawns a typo'd `cc`) and the --dry-run invocation render.

The override is for an alias or wrapper of the same CLI — the profile
still supplies the hook dialect, config path and transcript layout.
…e-org#395)

A run on an overridden executable was invisible everywhere except the
policy file: the TUI header, the tasks table and status --json all
reported the profile name, so a session billed to `cc-work` displayed as
`claude` — the same blind spot validate had before the override landed.

Carry the binary through the identity path already built for name+model
in bmad-code-org#153: SessionRecord, the journal's session-start entry, ActiveAgent,
and both --json projections (adapters, adapters_used). The TUI renders
`profile(binary)·model`. Both facts are load-bearing and neither
substitutes for the other — the profile explains the session's behavior
(hook dialect, prompt template, bypass flags), the binary explains which
install it billed.

Backward compatible by construction: "" means the profile's own
executable, which is exactly what a record, journal entry or snapshot
predating the field reports, so old runs render and serialize as before
and a --json consumer ignoring the key sees no change. The settings
editor needed no work — it is schema-driven, so core.toml carried the
field the moment the policy field existed.
@coderabbitai

coderabbitai Bot commented Jul 30, 2026

Copy link
Copy Markdown

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 22392385-a688-4ba1-be56-0b7990b7ca00

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: override a CLI profile's binary per project and per run

1 participant