docs: document search/neighbors/list wire shapes#954
Conversation
search() and neighbors() rows were undocumented and diverge from
list()'s full-record rows, so agents were discovering the shapes by
probing a live server. Document all three, field-by-field, verified
against the handler and runtime source:
- search(): narrow ranking projection {id, entity_kind|note_kind,
score, title, snippet}; score is an RRF rank score, not a
normalized similarity.
- neighbors(): flat per-edge rows {origin_id, id, edge_id, relation,
weight, name?, kind?, entity_type?}; name/kind/entity_type are
omitted (not null) when the neighbor can't be resolved, and kind
is overloaded between entity base-kind and note-kind.
- list(): the full stored record, shape depends on kind
(entity/note/edge/event); notes have no top-level tags field and
gain a lifecycle field when properties.status is set.
Full detail lives in docs/guide/api-reference.md (the verified
per-verb parameter/response contract); AGENTS.md's tool-schemas
table gets a compact pointer to each, matching the existing
merge/propose/context row style.
No behavior or response-shape change.
Refs #900
|
REJECT Findings: 0 Blocker, 2 High, 2 Medium, 1 Low [High] Default list and search wire shapes are documented as their unprojected formsEvidence: docs/guide/api-reference.md:227 says every list row is always the full stored record, and docs/guide/api-reference.md:352 says the search kind field is always present and can be null. The request server defaults an unspecified presentation mode to Agent at crates/khive-mcp/src/server.rs:1741. Agent presentation removes non-lifecycle null fields at crates/khive-runtime/src/presentation.rs:525, also dropping empty values and compacting IDs and timestamps at crates/khive-runtime/src/presentation.rs:535. Search is not one of the AlwaysVerbose verbs at crates/khive-types/src/pack.rs:145. The listed fields are useful canonical/verbose shapes, but they are not the default MCP wire shape. In particular, absent entity_kind/note_kind, title, and snippet keys are omitted under Agent rather than represented by null. The same unqualified full-record claim is repeated in AGENTS.md:439 and AGENTS.md:443. Fix: identify the listed object shapes as presentation="verbose" (or canonical handler) shapes, then document the default Agent projection and its conditional omissions. Alternatively, make every field statement explicitly conditional on presentation mode. [High] The documented search score semantics conflate entity and note resultsEvidence: docs/guide/api-reference.md:352 says note rows are identical except for note_kind, and docs/guide/api-reference.md:354 defines score as 1 / (10 + rank). Entity retrieval sums reciprocal-rank contributions and applies an exact-match boost at crates/khive-runtime/src/retrieval.rs:1000, crates/khive-runtime/src/retrieval.rs:1009, and crates/khive-runtime/src/retrieval.rs:1022. Notes use RRF_K = 60 at crates/khive-runtime/src/operations.rs:2971 and multiply the fused result by a salience-derived weight at crates/khive-runtime/src/operations.rs:3131. This publishes a false numeric contract for note scores and makes the entity formula incomplete. It also makes the two shapes appear more equivalent than their construction is. The repeated summary in AGENTS.md:443 has the same issue. Fix: describe score as an implementation-defined ranking value, or document entity and note scoring separately with their actual fusion, boost, and salience behavior. Do not state one K=10 formula for both kinds. [Medium] The supported proposal list shape is omitted and contradicts the full-record ruleEvidence: docs/guide/api-reference.md:199 lists proposal as a supported list kind, while docs/guide/api-reference.md:227 limits the documented rows to entity, note, edge, and event and calls every row a full stored record. Proposal handling is selected by the list handler at crates/khive-pack-kg/src/handlers/list.rs:61, but its response is a purpose-built projection at crates/khive-pack-kg/src/handlers/proposal.rs:538, not a full stored record. Fix: add the proposal projection to the row-shape documentation, or explicitly scope the full-record statement to entity, note, edge, and event while directing proposal consumers to its separate projection. [Medium] The neighbors omission condition incorrectly names soft-deleted entitiesEvidence: docs/guide/api-reference.md:415 says name/kind/entity_type may be omitted for a soft-deleted entity. The neighbors runtime removes hits for soft-deleted entity IDs before returning them at crates/khive-runtime/src/operations.rs:1919. Optional neighbor enrichment fields do omit when unresolved, as defined at crates/khive-storage/src/types/graph.rs:294, but a soft-deleted entity produces no row rather than a row with omitted enrichment. Fix: replace the soft-deleted-entity example with an unresolved/dangling/bogus ID example, and retain the precise rule that the optional fields are omitted when enrichment has no value. Mirror the correction in AGENTS.md:445. [Low] The added prose violates the no-em-dash requirementEvidence: the PR adds 17 lines containing an em dash, including docs/guide/api-reference.md:228, docs/guide/api-reference.md:355, and AGENTS.md:439. Fix: replace every added em dash with a colon, comma, parentheses, or a rewritten sentence. Confirmed
Checks run
Domain utility: SKIPPED - This field-level accuracy review used the implementation as the authoritative source. |
- list/search: label full-record shapes as presentation=verbose, document the default Agent-mode projection (conditional null/empty drops, id and timestamp compaction, score truncation) - search: describe entity vs note score construction separately instead of a single 1/(10+rank) formula (entities sum per-leg RRF + exact-match boost; notes use k=60 then a salience-derived weight) - list: document the proposal row as its own projection, not a full record - neighbors: fix the omission example (soft-deleted entities produce no row at all; omitted enrichment fields are for unresolved/dangling ids) - remove all em dashes from the added prose Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
APPROVE-WITH-FIXES Findings: 0 Blocker, 0 High, 1 Medium, 0 Low [Medium] Proposal list documentation incorrectly says its projection is presentation-invariantEvidence: docs/guide/api-reference.md:262 through docs/guide/api-reference.md:266 identify the proposal projection correctly, but state that it is always returned in that shape regardless of Why this matters: the new proposal projection is now documented, but its default MCP wire shape remains inaccurate. A default Agent-mode Suggested fix: label the listed proposal object as the Looks Right
Commands Run
What I Did Not Check
Re-Review Guidance
Domain utility: SKIPPED - This narrow wire-contract review used the cited implementation paths as the authoritative source. |
Agent mode applies the same generic null/empty omission, id shortening, and timestamp compaction to proposal rows; only the verbose projection has the unconditional field set. Addresses the round-2 review finding. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
APPROVE-WITH-FIXES Findings: 0 Blocker, 0 High, 0 Medium, 1 Low [Low] New API-reference sentence adds an em dashEvidence: Why this matters: the requested round-3 acceptance criteria explicitly require no added em dashes. The semantic correction is sound, but this punctuation violates that narrow documentation constraint. Suggested fix: replace the em dash at Looks Right
Commands Run
What I Did Not Check
Re-Review Guidance
Domain utility: SKIPPED - This narrow wire-contract check used the cited implementation paths as the authoritative source. |
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Round-3 finding applied: the single em dash at docs/guide/api-reference.md is now a colon. Per r3 re-review guidance no further review is needed. Marking ready (docs-only, fast lane). |
oceanwaves630
left a comment
There was a problem hiding this comment.
Approving: r3 clean at acaf292, docs wire-shape corrections verified through the codex rounds.
Summary
Closes #900.
search()andneighbors()returned rows whose wire shape was never documented and diverges fromlist()'s row shape, so integrators discovered it by probing a live server. This PR documents the current shapes precisely, field-by-field, verified against the handler and runtime source (crates/khive-pack-kg/src/handlers/search.rs,graph.rs,list.rs;crates/khive-storage/src/types/graph.rs,entity.rs,note.rs,event.rs). No behavior or response shape changes.docs/guide/api-reference.md— full "Response shape" sections added undersearch,neighbors, andlist, with a JSON example and explicit field list per verb/kind.AGENTS.md— the tool-schemas table rows forsearch,neighbors, andlistget a compact shape summary plus a pointer into the full reference, matching the existing style used formerge/propose/context.Divergences documented as current behavior (not changed):
search()rows are a narrow ranking projection{id, entity_kind|note_kind, score, title, snippet}— never the full record.scoreis an RRF-fused rank score (1/(k+rank),k=10), not a normalized 0-1 similarity.neighbors()rows are flat, one per edge:{origin_id, id, edge_id, relation, weight, name?, kind?, entity_type?}.name/kind/entity_typeare omitted from the JSON entirely (notnull) when the neighbor node can't be resolved — unlikesearch()'s kind field, which is always present and only evernull.kindis overloaded between an entity's base kind and a note's note_kind, with no separate substrate discriminant field.list()rows are the full stored record, and the shape differs again bykind: notes have no top-leveltagsfield (tags live inproperties.tags, unlike entities which have a first-classtagscolumn), and gain alifecyclefield (withstatusreplaced by the pack-levelproperties.statusvalue) when the note carries aproperties.status.Candidate follow-ups (not addressed here, flagging per the issue's ask)
search/neighbors/listif that's wanted — this PR only documents current behavior, it doesn't propose or implement convergence.neighbors()'skindfield relies on the entity-kind and note-kind vocabularies staying disjoint to be disambiguable by value alone (no substrate discriminant field). Works correctly today because the two closed vocabularies don't overlap, but it's a fragile implicit invariant worth a comment or an explicitsubstratefield if it's ever revisited.list()'s per-kind row caps are inconsistent (entity 500, note 200, edge 1000, event 1000) — not a bug, just worth a note if row-shape/behavior convergence work happens later.No
.rsfiles were touched, so no clippy/test changes are needed.make fmt(cargo fmt +deno fmt docs/) anddeno fmt --check AGENTS.mdboth pass with no reformatting required.Test plan
make fmt— no changes (docs already fmt-clean)make docs-check(deno fmt --check docs/) — passeddeno fmt --check AGENTS.md— passed🤖 Generated with Claude Code