@@ -949,9 +949,10 @@ func slingFormulaUsesTargetBranch(formula string) bool {
949949}
950950
951951// resolveSlingEnv returns extra env vars for the sling command.
952- // For fixed (non-pool) agents, resolves the target's session name from
953- // the bead store and returns it as GC_SLING_TARGET. Pool agents don't
954- // need this — they use label-based dispatch.
952+ // For fixed single-session agents, resolves the target's session name from
953+ // the bead store and returns it as GC_SLING_TARGET. Default routing uses
954+ // gc.routed_to metadata for all agents, but custom sling_query templates may
955+ // still rely on the resolved concrete session target.
955956func resolveSlingEnv (a config.Agent , deps slingDeps ) map [string ]string {
956957 if isMultiSessionCfgAgent (& a ) {
957958 return nil
@@ -1121,7 +1122,7 @@ func decorateGraphWorkflowRecipe(recipe *formula.Recipe, routeVars map[string]st
11211122 if sessionName != "" {
11221123 defaultRoute .sessionName = sessionName
11231124 } else {
1124- defaultRoute .label = "pool:" + routedTo
1125+ defaultRoute .metadataOnly = true
11251126 }
11261127 routingRigContext := graphRouteRigContext (defaultRoute .qualifiedName )
11271128 controlRoute , err := controlDispatcherBinding (store , cityName , cfg , routingRigContext )
@@ -1213,7 +1214,7 @@ func workflowStoreRefForDir(storeDir, cityPath, cityName string, cfg *config.Cit
12131214type graphRouteBinding struct {
12141215 qualifiedName string
12151216 sessionName string
1216- label string
1217+ metadataOnly bool
12171218}
12181219
12191220func resolveGraphStepBinding (stepID string , stepByID map [string ]* formula.RecipeStep , stepAlias map [string ]string , depsByStep map [string ][]string , cache map [string ]graphRouteBinding , resolving map [string ]bool , fallback graphRouteBinding , rigContext string , store beads.Store , cityName string , cfg * config.City ) (graphRouteBinding , error ) {
@@ -1327,7 +1328,7 @@ func resolveGraphStepBindingWithVars(stepID string, stepByID map[string]*formula
13271328 }
13281329 binding := graphRouteBinding {qualifiedName : agentCfg .QualifiedName ()}
13291330 if isMultiSessionCfgAgent (& agentCfg ) {
1330- binding .label = "pool:" + agentCfg . QualifiedName ()
1331+ binding .metadataOnly = true
13311332 cache [stepID ] = binding
13321333 return binding , nil
13331334 }
@@ -1366,15 +1367,6 @@ func graphRouteRigContext(route string) string {
13661367 return route [:idx ]
13671368}
13681369
1369- func appendUniqueString (in []string , value string ) []string {
1370- for _ , existing := range in {
1371- if existing == value {
1372- return in
1373- }
1374- }
1375- return append (in , value )
1376- }
1377-
13781370func shouldPromoteWorkflowLaunchStatus (status string ) bool {
13791371 switch strings .ToLower (strings .TrimSpace (status )) {
13801372 case "" , "open" , "ready" , "todo" , "triage" , "backlog" :
@@ -1461,6 +1453,9 @@ func checkBeadState(q BeadQuerier, beadID string, a config.Agent) beadCheckResul
14611453 if b .Assignee != "" {
14621454 warnings = append (warnings , fmt .Sprintf ("warning: bead %s already assigned to %q" , beadID , b .Assignee ))
14631455 }
1456+ if routedTo := strings .TrimSpace (b .Metadata ["gc.routed_to" ]); routedTo != "" {
1457+ warnings = append (warnings , fmt .Sprintf ("warning: bead %s already routed to %q" , beadID , routedTo ))
1458+ }
14641459 for _ , l := range b .Labels {
14651460 if strings .HasPrefix (l , "pool:" ) {
14661461 warnings = append (warnings , fmt .Sprintf ("warning: bead %s already has pool label %q" , beadID , l ))
@@ -1470,8 +1465,20 @@ func checkBeadState(q BeadQuerier, beadID string, a config.Agent) beadCheckResul
14701465 }
14711466
14721467 target := a .QualifiedName ()
1468+ if strings .TrimSpace (b .Metadata ["gc.routed_to" ]) == target {
1469+ // Only idempotent if the bead is unassigned or already assigned
1470+ // consistently with the target. Otherwise the bead would be
1471+ // invisible to the target's work_query (which requires --unassigned
1472+ // for pool work in tier 3).
1473+ if b .Assignee == "" || b .Assignee == target {
1474+ return beadCheckResult {Idempotent : true }
1475+ }
1476+ return beadCheckResult {
1477+ Warnings : []string {fmt .Sprintf ("warning: bead %s routed to %q but assigned to %q" , beadID , target , b .Assignee )},
1478+ }
1479+ }
14731480
1474- // Fixed agent: check assignee match .
1481+ // Fixed agent: check legacy assignee routing as a compatibility fallback .
14751482 if ! isMultiSessionCfgAgent (& a ) {
14761483 if b .Assignee == target {
14771484 return beadCheckResult {Idempotent : true }
@@ -1480,6 +1487,9 @@ func checkBeadState(q BeadQuerier, beadID string, a config.Agent) beadCheckResul
14801487 if b .Assignee != "" {
14811488 warnings = append (warnings , fmt .Sprintf ("warning: bead %s already assigned to %q" , beadID , b .Assignee ))
14821489 }
1490+ if routedTo := strings .TrimSpace (b .Metadata ["gc.routed_to" ]); routedTo != "" {
1491+ warnings = append (warnings , fmt .Sprintf ("warning: bead %s already routed to %q" , beadID , routedTo ))
1492+ }
14831493 for _ , l := range b .Labels {
14841494 if strings .HasPrefix (l , "pool:" ) {
14851495 warnings = append (warnings , fmt .Sprintf ("warning: bead %s already has pool label %q" , beadID , l ))
@@ -1488,17 +1498,24 @@ func checkBeadState(q BeadQuerier, beadID string, a config.Agent) beadCheckResul
14881498 return beadCheckResult {Warnings : warnings }
14891499 }
14901500
1491- // Pool: check for matching pool label.
1492- poolLabel := "pool:" + target
1493- for _ , l := range b .Labels {
1494- if l == poolLabel {
1495- return beadCheckResult {Idempotent : true }
1501+ // Multi-session targets: pool labels are a legacy fallback only when
1502+ // gc.routed_to is absent. If gc.routed_to is set (even to a different
1503+ // target), it is authoritative — a stale pool label must not short-circuit.
1504+ if strings .TrimSpace (b .Metadata ["gc.routed_to" ]) == "" {
1505+ poolLabel := "pool:" + target
1506+ for _ , l := range b .Labels {
1507+ if l == poolLabel {
1508+ return beadCheckResult {Idempotent : true }
1509+ }
14961510 }
14971511 }
14981512 var warnings []string
14991513 if b .Assignee != "" {
15001514 warnings = append (warnings , fmt .Sprintf ("warning: bead %s already assigned to %q" , beadID , b .Assignee ))
15011515 }
1516+ if routedTo := strings .TrimSpace (b .Metadata ["gc.routed_to" ]); routedTo != "" {
1517+ warnings = append (warnings , fmt .Sprintf ("warning: bead %s already routed to %q" , beadID , routedTo ))
1518+ }
15021519 for _ , l := range b .Labels {
15031520 if strings .HasPrefix (l , "pool:" ) {
15041521 warnings = append (warnings , fmt .Sprintf ("warning: bead %s already has pool label %q" , beadID , l ))
0 commit comments