feat(api): delete, external ids, and a JSON HTTP API - #22
Merged
Conversation
dimittal
force-pushed
the
feat/retrieval-audit
branch
from
August 11, 2026 17:54
d11ec32 to
d856d33
Compare
Completes CRUD and puts it on a second surface. The explorer stays read-only —
writes keep going through a validated path.
**delete_entity**, end to end through both backends, Brain, CLI, MCP and the
API. Three things it has to get right:
the file for a `storage: file` doc_type the JSON on disk is the source of
truth, so removing only the index row is a pause, not a delete —
the entity returns on the next reindex.
the edges in both directions. An edge outliving its target still renders
on the map and in neighbour lists, and reads as corruption
rather than as a deletion.
the order the file goes first. If the index delete then fails, a reconcile
cleans up; the other order leaves a file that silently restores
a deleted entity.
A delete that cannot remove the file deletes nothing and says so, rather than
unindexing an entity whose file will bring it back.
**external_id** — the id the source system already knows a thing by: a ticket
key, a CRM record, a UUID. `id` keeps its `<doc_type>:<slug>` shape, because
that is what lets an agent read a document's type off its id; the free-form
identifier goes beside it, with `lookup_by_external_id` and a CLI `lookup`.
Also `get_entities([ids])`, which agents were emulating with N round trips.
OpenSearch maps fields explicitly, so external_id had to be added to the
mapping and both conversion directions — omitted, it would have been dropped on
write and the lookup would have silently never matched.
**A JSON API** at `/<index>/api/v1`, mounted beside the explorer so one process
and one port serve the UI, the API and MCP for every index. It mirrors the MCP
tools rather than inventing a second vocabulary, and calls the same Brain
methods, so the two cannot disagree about what a search means.
- search with mode and `filter.<field>`, returning the same `match` block
- entities: get, batch, by-external-id, PUT, DELETE
- schema, and trace lookup
- the URL is authoritative on PUT: a body id that disagrees is a 400, so
PUT /entities/a can never write entity b
- a caller's mistake is a 4xx with a message that says how to fix it — an
unknown mode and an unfilterable field are 400, not 500
Auth is off unless a token is configured, and gates writes only, matching the
MCP endpoint. A deployment with real data sets OPEN_INDEX_TOKEN; one serving
public demo data does not, and nothing changes for it.
Found while doing this: the assertion tying the help tab's tool list to the
tools the server actually registers was lost when the Streamlit test suite was
replaced. It is restored, and it immediately did its job — the page had gone
stale against three new tools. A tool list that lies about what an agent can do
is worse than no list.
629 tests pass; api.py at 100%.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
dimittal
force-pushed
the
feat/crud-and-api
branch
from
August 11, 2026 17:54
1678a4a to
a2fbb39
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Completes CRUD and puts it on a second surface. The explorer stays read-only — writes keep going through a validated path, as decided.
delete_entity
End to end through both backends,
Brain, the CLI, MCP and the API. Three things it has to get right:The file. For a
storage: filedoc_type the JSON on disk is the source of truth, so removing only the index row is a pause, not a delete — the entity returns on the next reindex. There is a test that deletes, reindexes, and asserts it stays gone.The edges, in both directions. An edge outliving its target still renders on the map and in neighbour lists; it reads as corruption rather than as a deletion. On OpenSearch that needs an
update_by_queryas well as the delete, because edges live on the source document.The order. The file goes first. If the index delete then fails, a reconcile cleans up; the other order leaves a file that silently restores a deleted entity. A delete that cannot remove the file deletes nothing and says so.
external_id
The id the source system already knows a thing by — a ticket key, a CRM record, a UUID.
idkeeps its<doc_type>:<slug>shape, because that is what lets an agent read a document's type straight off its id. The free-form identifier goes beside it, withlookup_by_external_idon MCP, the API and the CLI. Alsoget_entities([ids]), which agents were emulating with N round trips.OpenSearch maps fields explicitly — the code already warns that anything unnamed is dropped on write — so
external_idneeded adding to the mapping and both conversion directions. Omitted, the lookup would have silently never matched.A JSON HTTP API
At
/<index>/api/v1, mounted beside the explorer, so one process and one port serve the UI, the API and the MCP endpoint for every index on the host.It mirrors the MCP tools rather than inventing a second vocabulary, and calls the same
Brainmethods, so the two cannot disagree about what a search means or what a write validates.400, soPUT /entities/acan never write entityb.400, not500— the search contract's fail-closed behaviour surfaces as a fixable error rather than an opaque one./api/...is registered before the/{name}page routes, so it is never read as an index named "api".Auth
Off unless a token is configured, and it gates writes only — reads stay open, matching how the MCP endpoint already behaves. A deployment holding real data sets
OPEN_INDEX_TOKEN(or a per-indexOPEN_INDEX_TOKEN_<NAME>); one serving public demo data does not, and nothing changes for it.401 with a
WWW-Authenticatechallenge rather than 403: the caller can fix it by presenting a token, and should be told how.A lost test, restored
The assertion tying the help tab's documented tool list to the tools the server actually registers went missing when the Streamlit test suite was replaced. It is back — and it immediately did its job, catching that the page had gone stale against three new tools. A tool list that lies about what an agent can do is worse than no list at all.
629 tests pass,
api.pyat 100%. New docs page for the API, and the navigation guide now covers identifiers and when not to delete.🤖 Generated with Claude Code