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
README repositioned and restructured. sage-wiki's README now leads
with what it has become — a graph memory and knowledge base for AI
agents and humans, scaling personal → team → company — and shrinks from
1058 to ~335 lines by linking into the guides instead of inlining them.
Two new guides absorb the extracted depth: docs/guides/configuration.md (the full annotated config, multi-provider
recipes, the serve-mode compile worker, price-table override) and docs/guides/customizing-prompts.md (prompt scaffolding — all EIGHT
template files, not the five previously listed — and custom frontmatter
fields). Guide merges preserve README-only content: the block-level
co-occurrence rule for relation extraction (configurable-relations), the
in-memory vector-cache restart caveat and opt-in ANN section
(search-quality), external-parser hardening details (CONTRIBUTING +
team-setup), and webui dist regeneration (CONTRIBUTING).
User-visible corrections riding along: summary_max_tokens default is
4000 (was misdocumented as 2000) and init writes max_parallel: 4;
batch API availability includes Gemini; the deprecated ontology.relations key reference now reads relation_types; benchmark
numbers refreshed to eval/REPORT.md's current results (85.9–86.7%
overall, 97.5–99.7% recall@1) with fixed eval/ paths; the command table
gains ontology resolve, hub init|compile, compile --fresh, source, coverage, and version; .bmp added to supported image
formats; the stale "durable workers — do not assume" note in
storage-backends.md now points at the shipped serve.worker. Translated
READMEs carry a parity banner and the corrected 18-tool count (7 read);
full translation parity follows in dedicated commits.
Added
Claude-driven entity resolution (P3-3), opt-in. With ontology.resolve.enabled: true, a new compile pass proposes links between
surface-form variants of one entity — "NASA" and "National Aeronautics and
Space Administration" — so that once applied, the canonical carries the union
of the cluster's edges. Proposals are queued, not applied: see the default
below.
It links; it does not collapse: both entity rows survive and the canonical
gains the alias's edges, so nothing is ever deleted. It is also reversible
— sage-wiki ontology resolve --unlink <alias> removes exactly the edges that
link caused and rejects the pair so a later compile cannot re-apply it.
Defaults to off; and when
enabled, auto_apply_threshold defaults to 0.85 — a proposal at or above
it that passes every guard is applied automatically, and --unlink makes a
mistaken link cost one command, which is what justifies the default. Set an
explicit 1.0 for review-only and it is a hard guarantee: 1.0 means never
auto-apply, exactly, so even a model reporting confidence 1.0 cannot clear
it. The pass warns at the default log level whenever proposals are waiting,
and when an out-of-range threshold falls back to the 0.85 default.
Auto-apply additionally requires a description on at least one side,
and triple extraction is the only compile-path writer of those — though sage-wiki scribe writes them too, so resolve on with triples off is not
by itself a guarantee. A proposal goes to review when the threshold forbids
auto-apply (an explicit 1.0), when confidence is below it, when the model flags a
member as strictly "broader", or when neither side has a description; decide
them with sage-wiki ontology resolve --review|--apply|--reject|--unlink. Rejection is symmetric,
so re-rolling the direction cannot bypass it. Candidate blocking is seeded only
by entities the compile touched (a new unmatched entity costs zero LLM calls)
and discards name tokens shared by more than 5% of a type, with an absolute
floor so a rare name in a small vault survives. use_embeddings optionally
widens candidates to names sharing no tokens, in memory and globally capped. sage-wiki ontology resolve --sweep re-applies approved links with no LLM
calls — the remedy for edges added outside a compile. SQLite migration V11 and
Postgres migration v4 add the entity_aliases table on both backends. See docs/guides/graph-memory.md, including the cost section and the notes on
derived-edge provenance and the partial prune contract.
Multi-hop graph query with per-fact provenance (P3-4). New MCP tool wiki_graph_query ({question, hops?, max_edges?}) answers relational
questions by traversal: seed entities are resolved from the question via
hybrid search (an alias seed lands on its canonical entity), a bounded
subgraph is serialized as numbered triples, and the model must answer ONLY
from those edges — every citation returns with source_doc and confidence (plus the evidence span when present). Zero matched entities
and zero edges each short-circuit with a distinct answer and no LLM call.
Bounds come from ontology.graph_query (max_hops default 2, max_edges
default 60; out-of-range values fall back rather than clamping),
overridable per call, and truncation is reported in the response. The
serialized subgraph is framed as untrusted content with delimiter-spoof
neutralization (the P1-6 frame). Strictly additive — all 18 tool schemas
are now pinned by a per-name golden. The regular Q&A context also gains
edge provenance: each related-article fallback block names its connecting
edge (via: (a) --[rel]--> (b) {source, confidence}); graph-EXPANDED
articles are deliberately not annotated, because expansion aggregates many
signals and naming one edge would be false provenance.
Alias-aware retrieval surfaces. Once entity resolution links an alias,
every user-facing graph surface starts from its canonical entity:
graph-expansion seeds in query/search, the query context's fallback
traversal, the web graph (?center= resolves, and the response gains an
additive center field naming the node actually centered on), and sage-wiki ontology query --entity (which prints a note on stderr; stdout
stays one valid JSON document). MCP behaviour change:wiki_ontology_query
now resolves an alias entity argument to its canonical before traversing —
previously it traversed the alias's own stored view. The payload shape is
unchanged (a bare entity array, no extra fields). Store-level reads are
deliberately untouched — Traverse/GetRelations on an alias still return
only its own stored edges, pinned by a conformance test on both backends —
as are wiki_list (browsing, not seeding) and --unlink/--apply/--reject, which take the alias by definition.
LLM structured-output triple extraction (P3-2), opt-in. With ontology.triples.enabled: true, each Tier-3 document gets one additional
Pass-2 LLM call that extracts typed entities (each with a one-sentence
grounded description) and evidenced (subject, predicate, object) triples,
persisted as P3-1 evidenced relations. Defaults to off: the pass costs one
call per document, and an upgrade should not change anyone's bill. Model
resolution follows ontology.triples.model → models.extract → models.summarize. Keyword extraction (Pass 3) is unchanged and still runs.
See docs/guides/graph-memory.md, including the cost section — --re-extract
is O(all summaries), and the --batch compile path does not run the pass.
Evidence spans are quoted from a document's compiled summary, not its raw
source; source_doc names the origin document.
Evidenced, provenance-bearing relations (P3-1). Ontology edges now carry evidence (the source span supporting the edge), confidence (0–1), and source_doc (the originating document), plus valid_from, valid_to and invalidated_by reserved for temporal validity. Schema addition only —
backward compatible: existing rows read back with zero values, and every
caller that does not set the new fields behaves exactly as before. SQLite
migration V10 and Postgres migration v3 are both plain ADD COLUMN, with
upgrade tests from the prior schema on each backend. Re-asserting an edge now
updates its evidence only when the incoming confidence is strictly higher; created_at always keeps the earliest assertion's value. Evidence spans are
quoted from a document's compiled summary, not its raw source — Pass 2 sees
summaries — so a citation names source_doc as the origin while the summary
is what the span is verifiable against.
Changed
AddEntity no longer erases a stored value with an empty one. An empty
(or, on Postgres, NULL) incoming name, definition or article_path now
leaves the stored value alone; non-empty values still overwrite. Previously a
re-assert that omitted a field wiped it — most visibly, re-indexing an article
erased its entity definition.
An entity's type is now correctable on SQLite. The SQLite upsert
previously ignored type entirely, so a wrong type was permanent; it is now
written on every upsert, matching Postgres. Consequently sage-wiki scribe
can retype an existing entity where it previously could not.
sage-wiki pack apply no longer resets ontology.triples. The ontology
merge rebuilt the ontology: node from a literal carrying only relation and
entity types, so any other key under it was erased. A pack cannot set triples itself; the user's value is now preserved.
Keyword-extracted edges can appear where they previously did not (only
with ontology.triples.enabled: true). Keyword extraction gates each pattern
on the stored entity types and skips a pattern whose target entity does not
exist yet. Once triple extraction populates typed entities in Pass 2, some
previously-skipped keyword edges start being created. No code in the keyword
pass changed.
Article re-indexing reads the article's declared type and display name. reconcile, sage-wiki write and the MCP write_article tool previously
hard-coded type: concept and the raw slug when indexing an already-written
article. With type now writable, that would have demoted a technique on
every run. All three read entity_type: from the article's frontmatter
(falling back to concept, including when the declared type is no longer
configured) and write the formatted display name. Frontmatter is parsed on CRLF
checkouts as well as LF.
Fixed
Postgres could write a NULL updated_at over a stored timestamp. AddEntity defaulted UpdatedAt only when CreatedAt was empty, so a caller
supplying one but not the other bound NULL — and the upsert wrote it. The two
now default independently, matching SQLite.
GetRelations with Both and a relation filter returned the wrong edges on
SQLite. The query built WHERE source_id=? OR target_id=? and appended AND relation=?, which parses as source_id=? OR (target_id=? AND relation=?) — so outbound edges of every type were returned regardless of the
filter. Reachable through wiki_ontology_query and sage-wiki ontology.
Postgres was already correct; both backends are now covered by the shared
conformance suite.