Skip to content

fix: keep two Search entities written alike apart - #84

Merged
jm-rivera merged 1 commit into
devfrom
fix/search-entity-identity
Oct 25, 2026
Merged

jm-rivera merged 1 commit into
devfrom
fix/search-entity-identity

Conversation

@jm-rivera

Copy link
Copy Markdown
Contributor

Search answers "aid from Congo to Congo" with two entity rows written alike, told apart by name or dcid, one Part.entity_refs entry per row, and options carrying entity_choice (DevelopmentFinanceObservatory/apis#119). dfo-ai could not send either field and keyed bound refs by text, so both Congos merged into one key and the evidence check lost one.

SearchEntity in services/data-mcp/src/data_mcp/search_tool.py takes an optional name or dcid, refusing both, and _wire_entity sends them. The tool description and README say how to post a text twice and how to settle entity_ambiguous: resend that row with the chosen candidate's dcid, keeping its text and role. digest.py lists each ambiguous entity group by its entity_choice index, with its words, role and candidate dcids, falling back to mention text for an option without the field.

In the harness, BoundEntity in planner/workflow.py holds a row's identity (dcid, else name, else text), text, role and refs, and SelectedSource.bound_refs is a tuple of them. compiler/sources.py bound_refs() pairs each entity_refs entry with its restatement row by position and binds nothing when the counts differ. compiler/evidence.py bound_refs_of matches a requirement entity to its row by identity, then text, preferring rows that fit its role, and resolve_name, resolve_group and _named_entity_issues read the bound row first, so a donor "Congo" no longer label-matches the recipient's node. capabilities.py re-pins the search input hash for the two new fields.

bound_refs is a hashed field, so a source carrying bound refs gets a new identity. Plans are stored as JSON in ADK's conversation store, and the repo replans a stored plan its model rejects (planner.plan.stored_plan), so a saved plan in the old shape is planned again once, and an old stored search block refuses as unusable. Harness discovery's _search_part still sends no name or dcid. docs/upstream-asks.md section 10 lists the current no_data fields and narrows the ask.

Testing: the data-mcp suite passed (1,629 passed, 20 skipped) and the harness suite passed (7,173 passed). The new tests fail on dev: SearchEntity with name and with dcid and the both-set refusal, the digest of the recorded dev response to "aid from Congo to Congo", and bound refs and evidence for two Congos pinned, named and named beside unnamed, in one role column and in two. ruff and ty check are clean.

Co-authored-by: Claude noreply@anthropic.com

data-mcp's SearchEntity takes an optional name or dcid, never both, and
sends them, so an agent can post "Congo" twice and settle an
entity_ambiguous refusal by resending the row with the candidate's
dcid. The digest lists each ambiguous entity group by its entity_choice
index, with its words, role and candidate dcids.

The harness keeps Search's entity_refs per restatement row: BoundEntity
carries the row's identity (dcid, else name, else text), text, role and
refs, and SelectedSource.bound_refs is a tuple of them. Evidence
resolution matches each requirement entity to its own row, by identity
then text, fitting the role first, so two Congos in one role column or
in two both resolve. A saved plan with the old dict form fails
validation and is planned again. The search capability's input hash is
re-pinned for the new fields.

Co-authored-by: Claude <noreply@anthropic.com>
@jm-rivera
jm-rivera marked this pull request as ready for review October 25, 2026 18:06
@jm-rivera
jm-rivera merged commit f3a9592 into dev Oct 25, 2026
6 checks passed
@jm-rivera
jm-rivera deleted the fix/search-entity-identity branch October 26, 2026 14:24
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