Skip to content

Apply, clamp or refuse launch controls on every route - #2064

Open
ppXD wants to merge 1 commit into
mainfrom
fix/dispose-every-launch-control
Open

ppXD wants to merge 1 commit into
mainfrom
fix/dispose-every-launch-control

Conversation

@ppXD

@ppXD ppXD commented Sep 30, 2026

Copy link
Copy Markdown
Owner

Summary

  • An Auto launch routed to plan-map or single-agent silently dropped the operator's allowed model pool, persona pool, decision critic and delivery spec (only SupervisorDefinitionBuilder read them), and plan-map dropped the acceptance floor. Every route-dependent control — allowedModelIds, allowedAgentDefinitionIds, acceptanceChecks, decisionReviewMode, deliverySpec, requirePlanConfirmation, plannerReviewMode — now gets a LaunchControlDisposition (Applied / Clamped / NotApplicable / Refused, with the reason) from one ILaunchControlResolver that both TaskLaunchService and TaskRoutePreviewService call. It is returned on LaunchTaskResult.ControlDispositions and TaskRoutePreviewResult.ControlDispositions; a refused control stops the launch before any session or run with TaskLaunchControlRefusedException (task_launch_control_refused, details list each refused control).
  • Model pool, by row id: plan-map bakes it into plan.author / plan.confirm (WorkflowPlanRequest.AllowedModelIds, so the planner's catalog lists only the pool) and into every branch agent.run; single-agent into its agent. AgentCodeNode carries it onto AgentTask.AllowedModelIds, HarnessModelReconciler resolves the task's pooled row (its named model's, else the pool default), and the executor runs and persists that row, names a moved model on the run's timeline, and fails the run if the pool resolves nothing. Quick also clamps a pinned model outside the pool at launch, so the frozen config shows the model that runs. On single-agent / plan-map a persona the pool excludes is refused.
  • Operator floor: an explicit Standard launch with acceptanceChecks is refused with the preview's own acceptance verdict. On Auto the floor is a routing signal — EffortRouter sets aside tiers whose builder does not advertise OperatorAcceptance.AcceptsCommand and takes the policy's next matching row (Standard → Quick), naming the move in RoutePlan.DegradedReason, which removes the composer's Auto + Delivery dead-end with no FE change. The Delivery/Unattended mandate now covers every route that grades a floor (single-agent included). Grounding is resolved before routing in both the launch and the route snapshot, so a chat follow-up is classified as one, and NeedsConfirmCard also fires on Signals.Ambiguous.
  • Where the brief and the code differ: builders stay pure (no DB), so the Quick clamp is decided by the resolver at launch and the builder bakes the result; the dispatch clamp lives in the reconciler but is applied by the executor, which owns the task write; a pinned model outside the pool on plan-map is reported Clamped without rewriting the profile (the synthesis keeps its model, each branch is clamped at dispatch). Consequences worth knowing: a Continue preview now brings the session summary up to date, as the launch always did; the TaskLaunchAuto benchmark arm now needs confirmation for an ambiguous classification, as it already did for a low-confidence one. Follow-ups, not here: grading plan-map's integrated tree against the operator floor (Q-3), and the composer rendering the dispositions.

Test plan

  • Unit: LaunchControlResolverTests (every lane × control; clamp / refuse / not-applicable reasons), LlmEffortClassifierTests (ambiguous confirms; an operator floor moves an auto Standard route to Quick, never an explicit or pinned lane, leaves Deep), TaskLaunchServiceQualityTierTests, TaskRoutePreviewServiceTests (preview dispositions equal the resolver's; wire shape), builder / AgentCodeNode / PlanAuthorNode pool tests, FailureTaxonomyTests pin. Full unit suite: 11,653 passed, 1 skipped, 1 load-timing failure in AgentRunLogCaptureBridgeTests (path untouched here; 20/20 three times in isolation)
  • Integration (real Postgres): TaskLaunchFlowTests 55/55, TaskRoutePreviewFlowTests 6/6, TaskLaunchContractFlowTests 11/11, HarnessModelReconcilerFlowTests 16/16, AgentRunExecutorTests 146/146, PlannerModelPoolFlowTests 1/1, PlannerCassetteDriftTests 2/2 (ExpectedPlannerKey unchanged), plus the router, snapshot, session, supervisor and benchmark classes on these paths
  • E2E: TaskRouteSnapshotEndpointTests 20/20, TaskLaunchEndpointE2ETests 6/6, plan-map fan-out flows
  • Mutations — each reverts one decision's key line; the named test went red and the file was restored by copy:
    • preview drops its dispositions → TaskRoutePreviewServiceTests.Preview_reports_the_same_control_dispositions_the_launch_resolves_for_the_same_input
    • launch result drops them → TaskRoutePreviewFlowTests.The_preview_reports_the_same_control_dispositions_the_launch_then_applies
    • refusal not thrown → TaskLaunchFlowTests.An_explicit_standard_launch_with_an_operator_floor_is_refused_with_the_reason_its_preview_gives
    • Quick clamp skipped → TaskLaunchFlowTests.An_auto_route_to_quick_clamps_a_pinned_model_outside_the_allowed_pool_and_says_so
    • planner lists the whole pool → PlannerModelPoolFlowTests.The_planners_catalog_lists_only_the_allowed_pool_and_the_whole_pool_when_unbounded
    • pool not baked → SingleAgentDefinitionBuilderTests.The_allowed_model_pool_rides_the_agent_node_and_an_unbounded_launch_is_byte_identical, PlanMapSynthDefinitionBuilderTests.The_allowed_model_pool_bounds_the_planners_catalog_and_rides_every_branch
    • node drops the pool → AgentCodeNodeTests.The_allowed_model_pool_a_projection_baked_is_carried_onto_the_task
    • reconciler skips the pool default → HarnessModelReconcilerFlowTests.A_bounded_task_naming_a_model_outside_its_pool_runs_the_pools_default_row_and_says_so
    • executor ignores the pooled row → AgentRunExecutorTests.A_bounded_run_naming_a_model_outside_its_pool_runs_the_pooled_row_persists_it_and_names_the_move
    • excluded persona applied → TaskLaunchFlowTests.An_auto_route_to_quick_refuses_a_persona_the_allowed_pool_excludes_before_any_run
    • supervisor-only controls dropped instead of named → LaunchControlResolverTests.Every_control_the_launch_carries_gets_exactly_one_disposition_per_lane
    • plan-map floor applied → TaskLaunchContractFlowTests.A_standard_launch_carrying_an_operator_floor_is_refused_and_records_nothing
    • auto floor not rerouted → TaskLaunchFlowTests.An_auto_route_with_an_operator_floor_never_lands_on_plan_map_and_grades_the_floor_instead
    • mandate supervisor-only → TaskLaunchFlowTests.A_quick_launch_at_delivery_quality_without_an_acceptance_check_is_rejected_before_any_run_is_created
    • launch or snapshot routes before grounding → TaskLaunchFlowTests.A_continue_is_routed_on_the_threads_grounding_by_both_the_preview_and_the_launch
    • ambiguity ignored → LlmEffortClassifierTests.The_router_STILL_confirms_an_AMBIGUOUS_task_even_when_the_model_is_confident
  • Pins flipped deliberately: TaskLaunchServiceQualityTierTests.A_non_supervisor_launch_at_delivery_or_unattended_quality_is_never_rejected (single-agent is now rejected; plan-map is not asked for a floor it cannot grade); TaskLaunchFlowTests.A_quick_launch_at_delivery_quality_is_never_rejected_for_a_missing_acceptance_check → …_without_an_acceptance_check_is_rejected_before_any_run_is_created; the Standard row of TaskLaunchContractFlowTests.Every_launch_lane_records_original_controls_in_the_frozen_run_detail sends no floor, with the refusal pinned in its own test

An Auto launch that routed to plan-map or single-agent silently dropped
the operator's allowed model pool, persona pool, decision critic and
delivery spec (only the supervisor builder read them), and plan-map
dropped the acceptance floor while the composer demanded one. The
planner was shown the whole team pool, and dispatch never bounded the
model it ran.

Every route-dependent control now gets a disposition - Applied, Clamped,
NotApplicable or Refused, with the reason - from one resolver the launch
and its route preview share, so a preview can no longer promise a
launch it will not get. A refusal stops the launch before any session
or run exists, with task_launch_control_refused.

- The model pool bounds the plan-map planner's catalog and rides every
  agent.run; dispatch runs a model outside it on the pool's default row
  and names the move on the run. Quick clamps a pinned model outside the
  pool at launch, so the frozen config shows the model that runs.
- A persona the pool excludes refuses a plan-map or quick launch.
- An acceptance floor on an explicit plan-map launch is refused with the
  preview's own verdict; on Auto it keeps the route off plan-map.
- The Delivery/Unattended mandate covers every route that grades a
  floor, single-agent included.
- Grounding is resolved before routing, so a chat follow-up is
  classified as one, and an ambiguous classification asks to confirm.
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.

1 participant