Skip to content

[fix] Key the lifecycle capability table by the wire harness spellings - #6364

Open
mmabrouk wants to merge 1 commit into
mainfrom
fix/lifecycle-harness-kind
Open

[fix] Key the lifecycle capability table by the wire harness spellings#6364
mmabrouk wants to merge 1 commit into
mainfrom
fix/lifecycle-harness-kind

Conversation

@mmabrouk

Copy link
Copy Markdown
Member

Context

While tracing why a config change evicted a user's warm sandbox (the placeholder-401 investigation, #6362), the reconcile shadow log showed harness=unknown on every playground run. The lifecycle router's harnessKind matched only the bare literals pi, claude, codex, but the wire carries pi_core or pi_agenta, and an empty harness defaults to pi_core. Every playground Pi run therefore resolved to unknown and took the fail-closed capability row, where every facet maps to a full sandbox rebuild.

Two real costs:

  • The one live route that already ships (a model-only switch applies live through setModel) never fired for Pi. Every model switch in the playground tore down the warm sandbox, created fresh Daytona Secrets, and re-entered the substitution-propagation race that produces the placeholder 401s.
  • Every Pi shadow-log line planned an all-rebuild, so the DISAGREE audit the migration relies on was noise for the main harness.

Changes

harnessKind now normalizes exactly as buildRunPlan does before keying the capability table.

Before: harness: "pi_core" resolved to unknown (model switch plans rebuild-sandbox).

After: pi_core, pi_agenta, bare pi, and the empty default all resolve to pi (model switch plans apply-live; a harness-session change plans reopen-session).

claude and codex were already correct and are unchanged. A genuinely unknown harness still fails closed.

Tests

  • New assertions in lifecycle-reconcile-plan.test.ts pin the wire spellings, the empty default, and the recovered apply-live model route.
  • Full runner suite: 156 files pass; pnpm run typecheck clean.

What to QA

  • In the playground on a Daytona session, send a turn, switch only the model, send again. The second turn should reuse the same sandbox (fast start, no rebuild in the runner log).
  • Regression: switching the harness (Pi to Claude) must still rebuild.

https://claude.ai/code/session_014s6jqKCsVKsNMmnrJVJkZt

harnessKind matched only the bare literals 'pi', 'claude', 'codex', but
the wire carries 'pi_core' / 'pi_agenta' (empty defaults to pi_core), so
every playground Pi run resolved to 'unknown' and took the fail-closed
capability row where every facet rebuilds the sandbox. Two consequences:

- The one live route that already ships (a model-only switch applies live
  via setModel) never fired for Pi: every model switch in the playground
  tore down the warm sandbox, minted fresh Daytona Secrets, and re-entered
  the placeholder-propagation race (see #6362).
- Every reconcile shadow log line for Pi read 'harness=unknown' with an
  all-rebuild plan, so the step-4/5 DISAGREE audit was noise for the main
  harness.

Normalize exactly as buildRunPlan does before keying the capability table.

Claude-Session: https://claude.ai/code/session_014s6jqKCsVKsNMmnrJVJkZt
@dosubot dosubot Bot added the size:S This PR changes 10-29 lines, ignoring generated files. label Aug 29, 2026
@vercel

vercel Bot commented Aug 29, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
agenta-documentation Ready Ready Preview Aug 29, 2026 8:57am

Request Review

@coderabbitai

coderabbitai Bot commented Aug 29, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Summary by CodeRabbit

  • Bug Fixes

    • Corrected harness recognition for Pi configurations, including pi_core, pi_agenta, and unspecified harness values.
    • Pi model changes now use the live model-switch route instead of unnecessarily rebuilding the sandbox.
  • Tests

    • Added coverage for supported Pi harness variants and live model switching.

Walkthrough

The runner now maps absent, pi_core, and pi_agenta harness values to the pi capability row. Unit tests verify these mappings and confirm that pi_core uses the live model route.

Changes

Pi harness routing

Layer / File(s) Summary
Normalize harness values and validate live routing
services/runner/src/lifecycle/reconciliation-router.ts, services/runner/tests/unit/lifecycle-reconcile-plan.test.ts
The router defaults an absent harness to pi_core and maps pi_core, pi_agenta, and pi to the pi capability row. Tests verify the mappings and the apply-live route for model changes.

Estimated code review effort: 2 (Simple) | ~10 minutes

Merge Risk: 🔵 Low · up to 5c86f

The change lets supported Pi requests reuse warm sandboxes for model-only switches, reducing unnecessary rebuilds. Malformed harness values can still select the live Pi path instead of failing closed, and equivalent omitted and explicit pi_core values may trigger unnecessary rebuilds; the PR is mergeable with explicit owner awareness and follow-up on validation and canonicalization.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 60.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 1 functions across 2 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly identifies the main change: using wire harness spellings as keys for the lifecycle capability table.
Description check ✅ Passed The description directly explains the harness normalization change, its lifecycle impact, tests, and QA steps.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
  • Fix all pre-merge checks with AI
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/lifecycle-harness-kind

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.

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 1


ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository YAML (base), Organization UI (inherited)

Review profile: CHILL

Plan: Pro Plus

Run ID: a0b663a4-0e97-482b-97ed-b38f8538c2d6

📥 Commits

Reviewing files that changed from the base of the PR and between e626c8e and 5c86fef.

📒 Files selected for processing (2)
  • services/runner/src/lifecycle/reconciliation-router.ts
  • services/runner/tests/unit/lifecycle-reconcile-plan.test.ts

Included review availability: Your plan provides up to 8 included reviews per hour; 5 remain after this review.

Comment thread services/runner/src/lifecycle/reconciliation-router.ts
@mmabrouk
mmabrouk changed the base branch from release/v0.114.3 to main August 29, 2026 09:12
mmabrouk added a commit that referenced this pull request Aug 30, 2026
Found by a code audit of the cold/warm flows (follow-up to #6364). The
coordinator computed the eviction reason with an else-if chain and the
live model route then set mismatch = undefined wholesale, so a repair of
the CONFIG question also cleared every question the chain never reached:

- a model switch riding an EDITED transcript continued warm on a native
  conversation still holding the unedited turn;
- riding a ROTATED credential with no delivery port, it ran on the old
  baked key (silently, if the old key still worked);
- riding an EXPIRING mount lease, it let the turn die under the mount;
- riding a stale tail, it ran a turn the tail check should have evicted.

The claude live route has been exposed since step 6 shipped; #6364 makes
the route fire for pi_core too, widening the surface, so this belongs in
the same stack.

Each repair door now marks ITS reason repaired and re-asks the ordered
checks. A model switch with a deliverable rotation chains both repairs
and stays warm (pinned); the four bad pairings rebuild (pinned).

Claude-Session: https://claude.ai/code/session_014s6jqKCsVKsNMmnrJVJkZt
mmabrouk added a commit that referenced this pull request Aug 30, 2026
Found by a code audit of the cold/warm flows (follow-up to #6364). The
coordinator computed the eviction reason with an else-if chain and the
live model route then set mismatch = undefined wholesale, so a repair of
the CONFIG question also cleared every question the chain never reached:

- a model switch riding an EDITED transcript continued warm on a native
  conversation still holding the unedited turn;
- riding a ROTATED credential with no delivery port, it ran on the old
  baked key (silently, if the old key still worked);
- riding an EXPIRING mount lease, it let the turn die under the mount;
- riding a stale tail, it ran a turn the tail check should have evicted.

The claude live route has been exposed since step 6 shipped; #6364 makes
the route fire for pi_core too, widening the surface, so this belongs in
the same stack.

Each repair door now marks ITS reason repaired and re-asks the ordered
checks. A model switch with a deliverable rotation chains both repairs
and stays warm (pinned); the four bad pairings rebuild (pinned).

Claude-Session: https://claude.ai/code/session_014s6jqKCsVKsNMmnrJVJkZt
mmabrouk added a commit that referenced this pull request Aug 30, 2026
Cold/warm audit finding 2. modelCapabilities is the resolved model's
input modalities: per-turn data the attachment chain reads from each
incoming request, baked into nothing. But it rode the fingerprint and
the harnessSession facet, and it changes WITH the model — so switching
between a vision model and a text-only model moved two facets, the
mixed plan refused the live setModel route, and the switch rebuilt the
warm sandbox (fresh Daytona Secrets, the substitution race, ~15s) for
nothing. The Pi catalog splits 249 text+image vs 163 text-only models,
so this hit ordinary playground use even after #6364.

Same treatment as workflowRevision/isDraft in step 1: out of the hash,
documented in the volatile list, pinned in both directions (the
per-turn-volatile test gains the field; the old changes-the-fingerprint
test is reversed with the reason).

Claude-Session: https://claude.ai/code/session_014s6jqKCsVKsNMmnrJVJkZt
@mmabrouk mmabrouk added the lgtm This PR has been approved by a maintainer label Aug 31, 2026

@mmabrouk mmabrouk left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

lgtm

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

Labels

backend bug lgtm This PR has been approved by a maintainer size:S This PR changes 10-29 lines, ignoring generated files. tests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant