ochakai 0.13.0
The first release since 0.12.1, and the largest so far: 71 commits, four
migrations, ten new design docs. Two changes are breaking — the compile
surface is gone, and exported bundles now speak OKF v0.2 — and the rest
is the write-back loop finally closing: an agent reports a bad answer, the
entry surfaces in a re-verification feed, a human verifies it, and the
provenance says who did what.
Breaking: compile_sql and the semantic-model surface are gone (design doc 0028)
ochakai never executed SQL (0001), but it did assemble it: compile_sql
turned a semantic-model entry into a query string. That put a
type-specific behavior registry in a product whose own position is that
types are a vocabulary, not a registry — and nobody was using it as of
0.12.1. It is removed rather than deprecated.
- MCP tool
compile_sql— removed. POST /api/v1/compile— removed.- CLI
ochakai compile— removed. - The
Semantic Modeltype keeps working as an ordinary type: it is
stored, searched, exported and rendered like everything else. Only the
behavior attached to it is gone.
Callers that used compile must build their own SQL from the entry body,
which is where that knowledge belonged.
Breaking: exports speak OKF v0.2 (design doc 0034)
OKF moved to v0.2 and gave provenance, trust and lifecycle standard
spellings, so ochakai stops inventing its own. Bundles now declare
okf_version: "0.2" and carry the spec's keys:
generated: { by: agent:analyst@example.iam.gserviceaccount.com, at: 2026-07-26T04:12:00Z }
verified:
- { by: human:tanaka@example.co.jp, at: 2026-07-26T09:30:00Z }timestamp→generated.at; the body's# Citationsheading →
sources. Both are v0.2's own breaking renames (SPEC §13.1) and the
v0.1 spellings are no longer written.verified_by/verified_at→ a one-elementverifiedlist. An entry
with statusverifiedalways emits the key, since its presence is
what carries the trust tier.statusmaps to the OKF vocabulary:draft→draft,verified→
stable+verified:,deprecatedandrejected→deprecatedwith
astatus_note(rejected also keepsrejected_by/rejected_atas
extension keys).- Delegation (0027) rides along as a sibling key:
generated: { by: …, via: … }, sobystays a single well-formed actor.
Import still reads v0.1 spellings, so old bundles load. As before, it does
not adopt provenance values from a payload — the one thing it reads is
whether a verified key exists, which decides stable → verified vs
draft.
Two envelope fields come with it, on every surface: updated_by (who
produced the content as it now stands — created_by was a lie after
anyone else edited) and stale_after, v0.2's lifecycle date.
Closing the write-back loop (design docs 0025, 0033)
An agent could already report that an entry led it wrong; nothing consumed
the report. Now failed reports have an exit:
sort=failedlists entries by failed outcome reports — a
re-verification feed, alongside the existingsort=verified_at(oldest
verification first) andsort=usage.POST /api/v1/verify/{id}andochakai verify <id>record a human's
verification against the entry as it stands — promoting a draft,
re-affirming an already-verified entry (which is what takes it out of
both feeds), or clearing a rejection. Previously only the Web UI could
do this.- MCP cannot overwrite or delete an entry a human has decided on —
not justverifiedbut the whole human-decided set. Agents write
freely up to the moment a person signs off.
get_context also changed shape: hits is now purely the ranking behind
the pack, not a second copy of the knowledge, which travels once under
entries (0033). The byte budget covers the whole response, and entries
that do not fit come back as outlines inside that same budget. Search
results are unaffected — there the hits are the answer.
purge: a second stage of delete (design doc 0031)
Delete leaves a tombstone so an id can never be silently reused. Purge is
the deliberate second step that frees it: DELETE /api/v1/knowledge/{id} ?purge=true and ochakai purge <id>. Two calls, deliberately — the
first is reversible, the second is not, and purging a live entry is a
409. It is not exposed over MCP or in the Web UI: destroying history
is a human decision, and an irreversible button next to the reversible
one is a trap. The purge itself is recorded in knowledge_purge — which
id, by whom, how much history went with it — the one table a purge does
not touch. It loses to a concurrent revival, and it does not reclaim
attachment bytes; as with detach, nothing collects them afterwards, which
the docs now say plainly instead of implying otherwise.
Optimistic locking with If-Match (design doc 0030)
If-Match on update, opt-in: send the version you read and a concurrent
edit fails with 412 instead of overwriting. Documented in the OpenAPI
document and exposed in the CLI, so the contract is the same on every
surface.
Identity: delegation and IAP (design docs 0027, 0032)
- A caller acting for an end user can now pass that user through
(A via B), so provenance records the human, not just the integration.
The Web UI shows the "via". serve-uitakes the browser user from the IAP assertion and never from
the browser — client-supplied identity headers are dropped
unconditionally.- MCP resolves the actor from each call's own HTTP headers, on every
tool, not only on writes.
Search, embeddings, and the read path
- Lexical search is rewritten to be answerable and indexable: a stored
search_textcolumn with a working trigram index (migration 0016),
query-fragment matching with IDF weighting, and scoring done over an id
projection. Empty queries are a 400 instead of a Postgres 500, and
ILIKEwildcards are escaped. - HNSW index on the pgvector column when the dimension is within the
index limit;ef_searchis sized to the search's own limit. - Embeddings take their input budget from the model, truncate on rune
boundaries, refuse to start when the configured dimension changes under
an existing corpus, andreembednow runs to completion — covering
attachment vectors, paging past entries that always fail, and reporting
what is actually left. - Usage recording is off the read path (0029): buffered and flushed
periodically instead of writing on every search, with flush failures
logged rather than swallowed.
Export, move, and correctness
Export streams entries from a single snapshot, can leave attachments out,
and no longer claims a determinism it did not have; failures after the
header are always logged. Move rewrites the moved entry's own relative
links to absolute form, folds that rewrite into the move revision, and
locks the referrers it rewrites. create can no longer resurrect a
tombstone. Verification timestamps are stamped at stored precision, so
what you read back is what was written. Rejections answer in the
documented error envelope; boolean query parameters are rejected rather
than read as unset; serve waits for Shutdown to finish before
returning, and the SIGTERM drain leaves room for the final usage flush.
Upgrade
Four migrations run on start (0016–0019): the search_text column and its
index, delegated-provenance columns, the purge audit table, and
updated_by / stale_after with a backfill from revision history. They
are additive; a rebuild of search_text and the backfill both run over
the full table, so expect the first start on a large corpus to take
longer than usual.
Before upgrading, check for callers of compile_sql, POST /api/v1/compile, or ochakai compile — those break. Consumers that parse
exported bundles need to read v0.2 keys (generated, verified,
sources); ochakai still imports v0.1. Clients reading get_context
should take the knowledge from entries and treat hits as a ranking.
Existing entries keep their embeddings; run reembed only if you change
the model or dimension.