Skip to content

refactor(runner): move session decisions out of server.ts - #5749

Merged
mmabrouk merged 2 commits into
release/v0.110.0from
agent-config-editing-s6
Aug 7, 2026
Merged

refactor(runner): move session decisions out of server.ts#5749
mmabrouk merged 2 commits into
release/v0.110.0from
agent-config-editing-s6

Conversation

@mmabrouk

@mmabrouk mmabrouk commented Aug 5, 2026

Copy link
Copy Markdown
Member

Context

Part of the agent-config-editing stack. Targets agent-config-editing-s1a. Read the stack bottom up.

Expect no behavior change from this PR. The warm gate, the approval-resume path, the miss path, the eviction choice, and the re-parking policy move out of server.ts byte for byte. The whole keep-alive suite passes unedited, and the characterization tests added in s5 pin the behavior across the move. An extraction that also changes behavior cannot be reviewed, so this one does not.

server.ts had grown to hold both transport concerns and every session decision. The later lanes need to change those decisions, and they cannot do that safely while the logic lives inside the HTTP handler with no seam to test it through.

Changes

src/lifecycle/session-coordinator.ts now holds runWithKeepalive and the seams around it: KeepaliveEngine for tests to inject a fake, KeepaliveContext for the engine, pool, config, and per-turn accessors, and the two provider and dispatch resolvers. server.ts keeps HTTP, authentication, request decoding, concurrency, and the watchdog. It drops about 740 lines and re-exports the seams the existing tests import, so no import path breaks.

realKeepaliveEngine stays in server.ts on purpose. If the coordinator imported the live engine, the seam it exists to provide would be pointless.

Beside the extraction, three new pure modules describe reconciliation without performing it:

  • desired-state.ts normalizes a request into semantic facets and gives each one a digest. One whole-request hash cannot answer "which part changed", and the later lanes need that answer per facet.
  • reconcile-plan.ts is the plan value: an ordered action list, the most expensive action in it, and the outcome it implies. Building a plan runs nothing and needs no environment, so a plan can be logged and asserted on in a unit test.
  • reconciliation-router.ts builds a plan from the request and the environment's applied facets.

This is Terraform's plan-then-apply. Shadow mode falls out of it rather than being a special mode: the router builds a plan on every dispatch and logs it beside the decision the coordinator actually took, with an agreement or disagreement marker. The plan decides nothing in this PR.

Tests / notes

  • 47 new tests across the desired-state, reconcile-plan, and coordinator suites. The keep-alive suites are unchanged apart from import paths.
  • The shadow log line is [reconcile] shadow key=... decision=... plan=... agree|DISAGREE facets=[...]. It carries facet names and action kinds only, never configuration and never credentials.
  • Expect some DISAGREE lines from the start. The router answers "does this environment need rebuilding" while the coordinator answers a broader question, and continuity-scoped decisions are logged but not counted for that reason.

@vercel

vercel Bot commented Aug 5, 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 6, 2026 5:14pm

Request Review

@coderabbitai

coderabbitai Bot commented Aug 5, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Summary by CodeRabbit

  • New Features
    • Added facet-level environment tracking for more precise lifecycle decisions.
    • Added reconciliation planning to determine when environments can be reused, refreshed, restarted, or rebuilt.
    • Added centralized session keep-alive coordination, including warm-session reuse, approval resumption, and cold-execution fallback.
    • Added conservative handling for configuration mismatches, session races, and unknown runtime types.
  • Bug Fixes
    • Improved teardown and retry behavior after failures, mismatches, and expired sessions.
    • Added safe, non-sensitive logging for reconciliation decisions.
  • Tests
    • Expanded coverage across state tracking, reconciliation, session coordination, approvals, pooling, and fallback behavior.

Walkthrough

The change adds normalized facet digests to applied environment state, introduces facet-based reconciliation plans, and centralizes keep-alive session coordination in a lifecycle module.

Changes

Keep-alive lifecycle

Layer / File(s) Summary
Facet state and lifecycle contracts
services/runner/src/lifecycle/desired-state.ts, services/runner/src/engines/sandbox_agent/*, services/runner/tests/unit/lifecycle-desired-state.test.ts, services/runner/tests/unit/session-keepalive-*.test.ts, services/runner/tests/unit/session-lifecycle-characterization.test.ts, services/runner/tests/unit/session-pool.test.ts
Requests produce ordered facet digests. Applied environments store and commit the digests through updated lifecycle contracts.
Reconciliation plans and routing
services/runner/src/lifecycle/reconcile-plan.ts, services/runner/src/lifecycle/reconciliation-router.ts, services/runner/tests/unit/lifecycle-reconcile-plan.test.ts
Facet changes map to ranked rebuild, restart, refresh, or reopen actions. Shadow logging records sanitized router decisions.
Centralized session coordination
services/runner/src/lifecycle/session-coordinator.ts, services/runner/src/server.ts, services/runner/tests/unit/lifecycle-session-coordinator.test.ts
Keep-alive dispatch, approval resumption, parking, teardown, retries, and cold fallback move into the session coordinator. server.ts preserves the existing exports and transport wiring.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant AgentRunRequest
  participant runWithKeepalive
  participant SessionPool
  participant KeepaliveEngine
  AgentRunRequest->>runWithKeepalive: dispatch request
  runWithKeepalive->>SessionPool: validate and checkout session
  SessionPool-->>runWithKeepalive: warm session or pool miss
  runWithKeepalive->>KeepaliveEngine: execute continuation or cold run
  KeepaliveEngine-->>runWithKeepalive: return turn result
  runWithKeepalive->>SessionPool: park, evict, or destroy environment
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 36.36% which is insufficient. The required threshold is 60.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
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.
Title check ✅ Passed The title clearly describes the primary change: moving session decision logic from server.ts into a lifecycle coordinator.
Description check ✅ Passed The description directly explains the session-coordinator extraction, reconciliation modules, shadow mode, behavior preservation, and tests.
✨ 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 agent-config-editing-s6

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.

* else (miss, mismatch, busy, no mount, remote) evicts as needed and runs today's cold path.
* A validation failure never fails the turn: it degrades to cold.
*/
export async function runWithKeepalive(

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.

Read this function as a code move, not as a rewrite. The warm gate, the approval resume path, the miss path, the eviction choice, and the re-park policy all moved here unchanged.

The proof that behavior is preserved is external to this file. The keep-alive suites pass unedited, and the characterization suite added in the lane below pins the same decisions from outside. An extraction that also changes behavior cannot be reviewed, so this one changes none.

If you find a decision here that you think is wrong, it was wrong before this PR as well. Say so, and it gets fixed in a lane where the diff shows only that change.

* The engine seam the keep-alive dispatch drives. The default wires to the real engine; tests
* inject a fake to exercise the pool/dispatch policy without a live harness.
*/
export interface KeepaliveEngine {

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.

This interface is the seam the tests need. A fake engine exercises every session decision with no live harness, no sandbox, and no model.

realKeepaliveEngine stays in server.ts on purpose. If the coordinator imported the live engine, the seam would be pointless, because a test importing the coordinator would pull the engine in with it. server.ts is the composition root and does the wiring.

A unit test in this PR asserts that separation directly, so a later import cannot quietly undo it.

* changed and the plan is a rebuild. That is the honest answer, and it matches what the
* coordinator does today.
*/
export function planReconcile(

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.

This function is pure. It reads two values and returns a third. It touches no environment, starts nothing, and cannot fail a turn.

That purity is what makes shadow mode possible without a shadow mode. The router builds a plan on every dispatch and the plan is logged beside the decision the coordinator actually took. Nothing acts on it in this PR. This is Terraform's plan and apply, with apply arriving in a later lane.

Because a plan is a value, a test can assert the whole plan without an environment. The suite in this PR does exactly that, which is why the later lane that acts on plans is reviewable at all.

// lines is the signal that the router's naming or ownership is still wrong.
const marker = agree ? "agree" : "DISAGREE";
log(
`shadow key=${input.key} harness=${harnessKind(input.request)} ` +

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.

This line carries facet names and action kinds only. It never carries configuration, and it never carries credentials.

That is a constraint on every reason string in this file, not a property of this line alone. The reasons are written to be content free for this purpose.

Expect disagreement markers from the first day. The router answers one question, which is whether the environment needs rebuilding. The coordinator answers a broader one, and some of its reasons are about the conversation rather than the environment. Those are logged with a continuity scope and are not counted, because counting them would produce a permanent false signal that no router work could drive to zero.

@mmabrouk

mmabrouk commented Aug 5, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 5, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

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

Actionable comments posted: 2

🧹 Nitpick comments (4)
services/runner/tests/unit/lifecycle-session-coordinator.test.ts (1)

135-214: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚖️ Poor tradeoff

Consider moving the fake environment fixture to tests/utils/.

FakeEnv, makeEngine, and makeCtx here repeat the fixtures in session-keepalive-dispatch.test.ts, session-keepalive-approval.test.ts, and session-lifecycle-characterization.test.ts. All four now seed applied state the same way through appliedStateForRequest(request). A shared factory in tests/utils/ would keep the next lifecycle-contract change to one edit instead of four.

Treat this as optional for this PR. The duplication is pre-existing, and this file only adds one more copy.

As per coding guidelines, "Place runner unit tests under tests/unit/**/*.test.ts; use Vitest, and keep shared helpers and fixtures under tests/utils/."

Source: Coding guidelines

services/runner/src/lifecycle/reconciliation-router.ts (2)

245-265: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

The doc block is stale, and appliedDigestsFrom has no consumer.

The comment states that AppliedEnvironmentState carries one whole-request fingerprint and no per-facet digests. Layer 1 of this stack changed that: applied-state.ts stores facet digests, and session-coordinator.ts line 279 passes existing.environment.appliedState.facets into logReconcileShadow. So the router already receives real applied digests, and the recompute-from-the-acquiring-request fallback described here is not used.

Remove appliedDigestsFrom and the stale paragraph, or state why the fallback is retained.


46-51: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

mcpServers is an unreachable capability field.

actionForFacet reads capabilities.workspace, capabilities.model, and capabilities.toolCatalog only. MCP servers live in the harnessSession facet, which routes through capabilities.model, so mcpServers can never affect a plan. Remove the field, or route a facet to it.

services/runner/src/lifecycle/session-coordinator.ts (1)

630-631: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Three dispatch paths emit no shadow line.

The module header states that the router runs on every dispatch. The supersede branch at lines 793-800 and both lost-checkout fall-throughs at lines 630-631 and 792 reach coldAndPark without calling shadowRoute. The logged sample then omits exactly the contended paths, so a DISAGREE burst on them stays invisible.

Add a shadowRoute call on each path, or narrow the header claim to the paths that log.

♻️ Proposed fix
   } else if (existing) {
     klog(`evict (supersede-${existing.state}) key=${key}; cold`);
+    shadowRoute(existing, "rebuild", `supersede:${existing.state}`);
     await pool.evict(key, `supersede-${existing.state}`, "failed-turn");
   } else {

Also applies to: 792-804


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: f5301565-838f-4465-8d2e-4847102ba282

📥 Commits

Reviewing files that changed from the base of the PR and between 212e428 and 6793e05.

📒 Files selected for processing (15)
  • services/runner/src/engines/sandbox_agent/applied-state.ts
  • services/runner/src/engines/sandbox_agent/environment-setup.ts
  • services/runner/src/engines/sandbox_agent/runtime-contracts.ts
  • services/runner/src/lifecycle/desired-state.ts
  • services/runner/src/lifecycle/reconcile-plan.ts
  • services/runner/src/lifecycle/reconciliation-router.ts
  • services/runner/src/lifecycle/session-coordinator.ts
  • services/runner/src/server.ts
  • services/runner/tests/unit/lifecycle-desired-state.test.ts
  • services/runner/tests/unit/lifecycle-reconcile-plan.test.ts
  • services/runner/tests/unit/lifecycle-session-coordinator.test.ts
  • services/runner/tests/unit/session-keepalive-approval.test.ts
  • services/runner/tests/unit/session-keepalive-dispatch.test.ts
  • services/runner/tests/unit/session-lifecycle-characterization.test.ts
  • services/runner/tests/unit/session-pool.test.ts

Comment thread services/runner/tests/unit/lifecycle-reconcile-plan.test.ts Outdated
Comment thread services/runner/tests/unit/lifecycle-reconcile-plan.test.ts Outdated
@mmabrouk

mmabrouk commented Aug 5, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 5, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai

coderabbitai Bot commented Aug 5, 2026

Copy link
Copy Markdown

Note

GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer.

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

Actionable comments posted: 2


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 9a64ace6-eb1c-44b5-9864-6839c803ebc6

📥 Commits

Reviewing files that changed from the base of the PR and between df8b834 and de02f74.

📒 Files selected for processing (15)
  • services/runner/src/engines/sandbox_agent/applied-state.ts
  • services/runner/src/engines/sandbox_agent/environment-setup.ts
  • services/runner/src/engines/sandbox_agent/runtime-contracts.ts
  • services/runner/src/lifecycle/desired-state.ts
  • services/runner/src/lifecycle/reconcile-plan.ts
  • services/runner/src/lifecycle/reconciliation-router.ts
  • services/runner/src/lifecycle/session-coordinator.ts
  • services/runner/src/server.ts
  • services/runner/tests/unit/lifecycle-desired-state.test.ts
  • services/runner/tests/unit/lifecycle-reconcile-plan.test.ts
  • services/runner/tests/unit/lifecycle-session-coordinator.test.ts
  • services/runner/tests/unit/session-keepalive-approval.test.ts
  • services/runner/tests/unit/session-keepalive-dispatch.test.ts
  • services/runner/tests/unit/session-lifecycle-characterization.test.ts
  • services/runner/tests/unit/session-pool.test.ts
🚧 Files skipped from review as they are similar to previous changes (14)
  • services/runner/tests/unit/session-keepalive-approval.test.ts
  • services/runner/src/engines/sandbox_agent/runtime-contracts.ts
  • services/runner/tests/unit/session-pool.test.ts
  • services/runner/src/engines/sandbox_agent/environment-setup.ts
  • services/runner/tests/unit/lifecycle-session-coordinator.test.ts
  • services/runner/tests/unit/lifecycle-reconcile-plan.test.ts
  • services/runner/tests/unit/lifecycle-desired-state.test.ts
  • services/runner/src/lifecycle/reconcile-plan.ts
  • services/runner/tests/unit/session-lifecycle-characterization.test.ts
  • services/runner/src/engines/sandbox_agent/applied-state.ts
  • services/runner/src/server.ts
  • services/runner/src/lifecycle/session-coordinator.ts
  • services/runner/tests/unit/session-keepalive-dispatch.test.ts
  • services/runner/src/lifecycle/desired-state.ts

Comment thread services/runner/src/lifecycle/reconciliation-router.ts
Comment thread services/runner/src/lifecycle/reconciliation-router.ts Outdated
@mmabrouk
mmabrouk force-pushed the agent-config-editing-s1a branch from df8b834 to d047462 Compare August 6, 2026 11:10
@mmabrouk
mmabrouk force-pushed the agent-config-editing-s6 branch from de02f74 to 1ab8e68 Compare August 6, 2026 11:11
@mmabrouk
mmabrouk marked this pull request as ready for review August 6, 2026 12:30
@dosubot dosubot Bot added size:XXL This PR changes 1000+ lines, ignoring generated files. backend refactor A code change that neither fixes a bug nor adds a feature tests labels Aug 6, 2026
@github-actions

github-actions Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Railway Preview Environment

Status Destroyed (PR closed)

Updated at 2026-08-07T09:43:33.743Z

@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

@mmabrouk mmabrouk added the lgtm This PR has been approved by a maintainer label Aug 6, 2026
@mmabrouk
mmabrouk force-pushed the agent-config-editing-s1a branch from d047462 to e02e5e3 Compare August 6, 2026 12:39
@mmabrouk
mmabrouk force-pushed the agent-config-editing-s6 branch from c6b6b4a to 5a9d60b Compare August 6, 2026 14:14
mmabrouk added a commit that referenced this pull request Aug 6, 2026
@mmabrouk
mmabrouk force-pushed the agent-config-editing-s6 branch from 5a9d60b to 4536b2a Compare August 6, 2026 14:22
mmabrouk added a commit that referenced this pull request Aug 6, 2026
@mmabrouk
mmabrouk force-pushed the agent-config-editing-s6 branch from 4536b2a to 54a7d8d Compare August 6, 2026 14:43
@mmabrouk
mmabrouk force-pushed the agent-config-editing-s1a branch from cbc8fab to 84a9bb8 Compare August 6, 2026 14:58
mmabrouk added a commit that referenced this pull request Aug 6, 2026
@mmabrouk
mmabrouk force-pushed the agent-config-editing-s6 branch from 54a7d8d to 29807f8 Compare August 6, 2026 14:58
@mmabrouk
mmabrouk force-pushed the agent-config-editing-s1a branch from 84a9bb8 to 24018cb Compare August 6, 2026 15:13
mmabrouk added a commit that referenced this pull request Aug 6, 2026
@mmabrouk
mmabrouk force-pushed the agent-config-editing-s6 branch from 29807f8 to 39d989c Compare August 6, 2026 15:13
@mmabrouk
mmabrouk force-pushed the agent-config-editing-s1a branch from 24018cb to 8099b56 Compare August 6, 2026 15:59
mmabrouk added a commit that referenced this pull request Aug 6, 2026
@mmabrouk
mmabrouk force-pushed the agent-config-editing-s6 branch from 39d989c to 540f3de Compare August 6, 2026 16:00
…ion router (slice S6)

Pure code motion: the warm gate, approval resume, miss path, eviction,
and re-parking move from server.ts into lifecycle/session-coordinator.ts
with every keepalive behavior assertion unedited. Beside it, the shadow
router: desired-state facets, an explicit ReconcilePlan with apply-live
as a first-class action kind (unused in v1), uniform reopen-session
capability rows, and side-by-side logging with a disagreement marker.
The old decision still rules. Two disagreements documented: transcript
mismatches over-rebuild today (router right, fix waits for step 6);
credential rotation is invisible to facet digests (router wrong by
design until it reads the credential epoch; pinned as a known gap).
@mmabrouk
mmabrouk force-pushed the agent-config-editing-s1a branch from 8099b56 to cf6316c Compare August 6, 2026 17:13
@mmabrouk
mmabrouk force-pushed the agent-config-editing-s6 branch from 540f3de to 4768cf9 Compare August 6, 2026 17:13
@mmabrouk
mmabrouk changed the base branch from agent-config-editing-s1a to release/v0.110.0 August 7, 2026 09:42
@mmabrouk
mmabrouk merged commit 4344644 into release/v0.110.0 Aug 7, 2026
59 checks passed
@mmabrouk
mmabrouk deleted the agent-config-editing-s6 branch August 7, 2026 10:20
mmabrouk added a commit that referenced this pull request Aug 7, 2026
mmabrouk added a commit that referenced this pull request Aug 7, 2026
refactor(runner): move session decisions out of server.ts
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

backend lgtm This PR has been approved by a maintainer refactor A code change that neither fixes a bug nor adds a feature size:XXL This PR changes 1000+ lines, ignoring generated files. tests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant