You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Part of #5039 — the reverse-lookup / route-preview slice. The display-projection half landed separately (#5065, #5066).
Problem / 问题
A coordinator that is asked to route work to a named peer can currently do two things and neither is the right one: call resolve-agent-thread, which answers "which Agent owns this exact link" and therefore requires the link the coordinator does not have; or read agent-directory, which projects identity, current work and observation/delivery references but says nothing about how many bindings address that peer. With no published answer, the cheapest move is to substitute whatever worker is convenient — the behaviour #5039 reports, where the user had to supply the task link by hand.
loopx/thread_agent_binding.py already refuses to guess in the forward direction: resolve_thread_agent_binding is documented as resolving "without guessing from registry order". Nothing offered the same discipline backwards.
Proposed slice
Add the reverse lookup to the module that owns bindings, and publish its answer on the directory row:
summarize_agent_binding_routes(goals, *, agent_id) returns {schema_version, agent_id, outcome, address_shared, candidate_count, candidates, scope, provenance} where outcome is single_candidate (exactly one accepted binding addresses the Agent within the goals supplied), multiple_candidates (more than one) or no_candidate (none). Candidates are deduplicated in first-seen order across every Goal supplied; candidate_count is the full distinct total. address_shared records that the same host thread also names a different registered Agent — the condition the forward resolver answers conflict for — and scope is goals_supplied, so a lone candidate here is not a project-level uniqueness claim.
agent-directory attaches that object to each row as peer_route, and docs/reference/protocols/peer-agent-directory-and-observation-v0.md states the vocabulary.
Candidates are exactly the bindings the owner's own normalisation accepts. Measured consequence: a binding whose host_surface is absent is rejected by _normalized_host_surface inside _bindings_for_goal, so it is dropped there rather than counted here — the summary never advertises a route the forward resolver could not later match.
Explicitly not in this slice
It selects nothing and verifies nothing. When several candidates exist the answer is multiple_candidates, and a lone candidate sharing its address with another Agent carries address_shared: true, and the caller must still resolve an exact link before addressing the peer. No host call is made, so the unavailable and not_authorized outcomes Resolve existing peer Agent delivery routes before substituting host sub-agents #5039 lists belong to the host-verification slice, not here.
No second registry, scheduler or session store, and no session copying or rebinding across host/profile boundaries. thread_agent_binding.py stays the only reader of coordination.thread_agent_bindings for routing purposes; directory.py calls into it rather than parsing the payload itself.
One binding reports single_candidate with that candidate; two distinct bindings report multiple_candidates and carry both; no binding reports no_candidate with an empty list.
A thread bound to two Agents gives each of them single_candidateandaddress_shared: true, agreeing with the forward resolver's conflict on the same data.
A candidate that fails the existing public-output boundary is withheld and declared in limitations, yet still counted in candidate_count; it is never truncated.
A binding republished across two Goals counts once, and five bindings yield three candidates with candidate_count: 5.
Another Agent's bindings never enter these candidates, and an unknown, empty, non-string or over-long agent_id reports no_candidate instead of matching loosely.
A malformed binding is excluded rather than counted.
The directory row carries peer_route while the packet's existing non-authorization guarantees (caller_membership, lease_state_not_projected) are unchanged, and all 30 pre-existing tests in the two owning files still pass unmodified.
Part of #5039 — the reverse-lookup / route-preview slice. The display-projection half landed separately (#5065, #5066).
Problem / 问题
A coordinator that is asked to route work to a named peer can currently do two things and neither is the right one: call
resolve-agent-thread, which answers "which Agent owns this exact link" and therefore requires the link the coordinator does not have; or readagent-directory, which projects identity, current work and observation/delivery references but says nothing about how many bindings address that peer. With no published answer, the cheapest move is to substitute whatever worker is convenient — the behaviour #5039 reports, where the user had to supply the task link by hand.loopx/thread_agent_binding.pyalready refuses to guess in the forward direction:resolve_thread_agent_bindingis documented as resolving "without guessing from registry order". Nothing offered the same discipline backwards.Proposed slice
Add the reverse lookup to the module that owns bindings, and publish its answer on the directory row:
summarize_agent_binding_routes(goals, *, agent_id)returns{schema_version, agent_id, outcome, address_shared, candidate_count, candidates, scope, provenance}whereoutcomeissingle_candidate(exactly one accepted binding addresses the Agent within the goals supplied),multiple_candidates(more than one) orno_candidate(none). Candidates are deduplicated in first-seen order across every Goal supplied;candidate_countis the full distinct total.address_sharedrecords that the same host thread also names a different registered Agent — the condition the forward resolver answersconflictfor — andscopeisgoals_supplied, so a lone candidate here is not a project-level uniqueness claim.agent-directoryattaches that object to each row aspeer_route, anddocs/reference/protocols/peer-agent-directory-and-observation-v0.mdstates the vocabulary.Candidates are exactly the bindings the owner's own normalisation accepts. Measured consequence: a binding whose
host_surfaceis absent is rejected by_normalized_host_surfaceinside_bindings_for_goal, so it is dropped there rather than counted here — the summary never advertises a route the forward resolver could not later match.Explicitly not in this slice
multiple_candidates, and a lone candidate sharing its address with another Agent carriesaddress_shared: true, and the caller must still resolve an exact link before addressing the peer. No host call is made, so theunavailableandnot_authorizedoutcomes Resolve existing peer Agent delivery routes before substituting host sub-agents #5039 lists belong to the host-verification slice, not here.thread_agent_binding.pystays the only reader ofcoordination.thread_agent_bindingsfor routing purposes;directory.pycalls into it rather than parsing the payload itself.Acceptance
single_candidatewith that candidate; two distinct bindings reportmultiple_candidatesand carry both; no binding reportsno_candidatewith an empty list.single_candidateandaddress_shared: true, agreeing with the forward resolver'sconflicton the same data.limitations, yet still counted incandidate_count; it is never truncated.candidate_count: 5.agent_idreportsno_candidateinstead of matching loosely.peer_routewhile the packet's existing non-authorization guarantees (caller_membership,lease_state_not_projected) are unchanged, and all 30 pre-existing tests in the two owning files still pass unmodified.