Knowledge
Package knowledge is your team's wiki and your agents' memory, searchable by meaning.
Package knowledge is your team's wiki and your agents' memory, searchable by meaning.
| Base URL | https://api.hanzo.ai |
| Operations | 9 |
| Auth | Authorization: Bearer $HANZO_API_KEY |
Specification
HIP-1260 · Knowledge — Wiki and Agent Memory — Draft · read the specification →
/v1/knowledge is one organization's knowledge, searchable by meaning: wiki
pages a person writes, memories an agent files, and documents a connector
ingests are ONE document store, indexed into that organization's own vector
namespace on every save and read back as semantic search, a link graph, or an
imported vault. It is implemented in hanzoai/cloud at apps/knowledge. This
HIP states the property everything rests on — human wiki and agent memory are one
store indexed once, so an agent retrieves exactly what the team can read — and
the surface that exposes it.
Motivation
A team wiki and an agent's memory are usually two systems: two stores, two
permission models, two search indexes, and an integration that copies one into
the other and drifts. Here a wiki page IS a document, a memory IS a
document, an ingested Slack thread IS a document (apps/knowledge/kb.go:15-24),
and one indexing path serves them all — so "what does this org know about X"
has one answer whether a person or an agent asks.
Specification
The key words MUST, MUST NOT, SHOULD, SHOULD NOT and MAY are to be interpreted as in RFC 2119.
It owns no store
The documents are the framework engine's, as module kb
(apps/knowledge/kb.go:40): CRUD, permissions and tenant isolation are the
framework's generic surface, and this capability adds behaviour, not storage.
The vectors are the shared vector store's, one collection per organization,
written only through the one index path (apps/knowledge/index.go:22-27).
Both stores are owned elsewhere; this capability MUST NOT open a store of its
own, and any new document kind MUST enter as a framework DocType so the same
hooks index it.
The address
The capability answers under /v1/knowledge: semantic search (the RAG entry
point), the link graph, vault import, and the connector set — list, catalog,
connect, callback, sync, disconnect (apps/knowledge/subsystem.go:84-99).
Today's router still serves this surface at /v1/kb; that pair is carried by
hanzoai/cloud openapi/misfiled.txt and closes by fold, the route group
being one literal (apps/knowledge/subsystem.go:82).
Every operation is typed except the import, which cannot be: its body is the
upload itself — a vault zip, an .enex XML document or a JSON export chosen by
?format= — not JSON a typed input could decode, so it is declared with prose
beside the route (apps/knowledge/subsystem.go:92-99,
apps/knowledge/import.go:129-150). An importer normalizes to one pure shape
(apps/knowledge/vault/vault.go) and files through the same ingest path a
connector uses; no importer MAY write the store or the index directly.
Tenant
Every handler resolves its organization from the validated principal —
principal.Acting (apps/knowledge/subsystem.go:141,
apps/knowledge/connectors.go:223) — and a request without one is refused.
Isolation at the index is physical AND filtered: each organization has its own
collection and every point carries the organization in its payload, so a
search must pass both (apps/knowledge/index.go:31-35, :297).
Meter, events, observability, stage
Metered, and the unit is one connector piece run (plugin/knowledge/main.go,
Price: cloud.Metered): a long-tail connector's sync executes a JavaScript
piece on the auto engine's sandbox pods, and reaching that capacity by
in-cluster URL does not make the pod cheaper. The fee is
CLOUD_KB_FEE_CENTS_PIECE over CLOUD_KB_FEE_CENTS, defaulting to one cent —
sized like the compute it buys, one bounded execution on a pod already held —
gated before the engine is asked and debited only after it answers
(apps/knowledge/meter.go). The native-Go connectors start no pod and stay
free, as does everything else on the surface. The other debit lands through
ai: embeddings for index and query go the metered gateway path with the same
model on both sides (apps/knowledge/index.go:50-51).
It publishes no events on the bus. Beyond the request span it registers
nothing; its degradations are on the wire instead — indexing is fail-open (a
save never blocks on the index) and query is fail-honest, answering an empty
result with degraded: true rather than a 5xx or a fabricated hit
(apps/knowledge/index.go:37-42, apps/knowledge/subsystem.go:124-127).
Its stage is ga.
Upstreams
It derives from none: no OSS project is forked, embedded or mirrored. The
importers implement third-party export formats from their public shapes, in
pure Go with no upstream code taken — the Lexical EditorState JSON the editor
renders (apps/knowledge/lexical/lexical.go), Obsidian vault markdown, Roam's
JSON export, Evernote ENEX/ENML, and the Notion API's result shapes
(apps/knowledge/{obsidian,roam,evernote,notion}).
Rationale
The alternative is a dedicated knowledge service with its own database and its own permission model. It buys nothing the framework does not already have and costs a second tenancy implementation — the exact place a wiki leaks. Attaching behaviour to the framework's store means the fourth app lane after cms, erp and help reuses isolation that is already tested, and the only new code is the one index path and the pure normalizers.
Fail-open indexing was chosen because the store is the record and the index is derived: losing a search hit until reindex is recoverable; losing a save is not.
Security Considerations
The surface is an organization's institutional memory — the most valuable
single corpus a tenant hands us — so the wrong implementation leaks a whole
company at once. The cross-tenant argument is doubled on purpose: a
collection-name bug cannot leak because the payload filter still excludes
foreign points, and vice versa (apps/knowledge/index.go:31-35).
Connector tokens are third-party credentials (Slack, GitHub, Google). They
live in KMS at a per-org path; the connector document holds only the path and
non-secret metadata, and the token is never logged
(apps/knowledge/connectors.go:13-15). The OAuth state is HMAC-bound to the
organization (apps/knowledge/connectors.go:16), so a callback cannot be
replayed into another tenant's connection.
The import route parses attacker-supplied archives; every read is bounded
(apps/knowledge/import.go:142, :174), so a crafted zip exhausts a limit, not
the process. And because everything indexed is retrieved into agent context,
ingested text is data, never instructions — the retrieval surface returns
documents and MUST NOT execute them.
Four surfaces
| Surface | Reaches this capability as | Coverage |
|---|---|---|
| REST | knowledge at its own prefix | 9 operations |
| CLI | — | no command reaches it yet — use HTTP or an SDK |
| SDK | — | no published client declares one yet — regenerating the clients is what adds them |
| MCP | tool knowledge on https://api.hanzo.ai/v1/mcp | 9 operations, 0 under the document's own id — ask describe for the rest |
Quickstart
export HANZO_API_KEY=sk-... # console.hanzo.ai → API keysThen the first call — a read that needs nothing but the key. GET /v1/knowledge/graph, operation get_knowledge_graph:
hanzo has no subcommand for this operation — the CLI serves only what cloud's live route table confirms. Use HTTP or an SDK.
import { Configuration, KnowledgeApi } from 'hanzoai';
const api = new KnowledgeApi(new Configuration({ accessToken: process.env.HANZO_API_KEY }));
const { data } = await api.getKnowledgeGraph();from hanzoai.cloud import ApiClient, Configuration
from hanzoai.cloud.api import KnowledgeApi
client = ApiClient(Configuration(access_token=os.environ["HANZO_API_KEY"]))
result = KnowledgeApi(client).get_knowledge_graph()cfg := cloud.NewConfiguration()
cfg.AddDefaultHeader("Authorization", "Bearer "+os.Getenv("HANZO_API_KEY"))
client := cloud.NewAPIClient(cfg)
resp, _, err := client.KnowledgeAPI.GetKnowledgeGraph(context.Background()).Execute()
if err != nil {
return err
}use hanzo_cloud::apis::{configuration::Configuration, knowledge_api};
let mut cfg = Configuration::new();
cfg.bearer_access_token = std::env::var("HANZO_API_KEY").ok();
let result = knowledge_api::get_knowledge_graph(&cfg, Default::default()).await?;import ai.hanzo.cloud.ApiClient;
import ai.hanzo.cloud.api.KnowledgeApi;
ApiClient client = new ApiClient();
client.setRequestInterceptor(b -> b.header("Authorization", "Bearer " + System.getenv("HANZO_API_KEY")));
var result = new KnowledgeApi(client).getKnowledgeGraph();The method above is the one at the current release of the document. [email protected] (npm) and [email protected] (PyPI) were generated from an earlier release, where this operation carried a different id, so it spells the method differently — regenerating the clients is what makes the two agree. SDKs →
curl https://api.hanzo.ai/v1/knowledge/graph \
-H "Authorization: Bearer $HANZO_API_KEY"The door reaches knowledge through the knowledge tool, which names its 9 operations with its own verbs — this one among them, under a name only the door declares. describe explains any of them:
curl -X POST https://api.hanzo.ai/v1/mcp \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "describe",
"arguments": {
"op": "list_kb_connectors"
}
}
}'Answers 200 with object — ok.
Endpoints
| Endpoint | What it does |
|---|---|
GET /v1/knowledge/connectors/{provider}/callback | CompleteConnectorOAuth finishes an OAuth connection: it exchanges the provider's code for a token, seals that token in KMS, and records the… |
GET /v1/knowledge/connectors/{provider}/connect | StartConnectorOAuth returns the provider authorize URL the console opens to connect this org's account. |
POST /v1/knowledge/connectors/{provider}/sync | Pulls the provider's documents for the caller's org and files them as knowledge sources, which the store's own hook then indexes — so a synced… |
DELETE /v1/knowledge/connectors/{provider} | Revokes a connection: it tombstones the stored credential so a later sync cannot reuse it, purges this provider's points from the org's vector… |
GET /v1/knowledge/connectors/catalog | Returns the ONE catalog of everything a caller can connect: every first-party connector and every long-tail one, in a single list sorted by provider. |
GET /v1/knowledge/connectors | Returns every supported knowledge connector with THIS org's connection state and the REAL number of documents each has ingested into the org's store. |
GET /v1/knowledge/graph | Returns the caller org's knowledge as a node/edge graph shaped for a force-directed renderer: pages, memories and synced sources as nodes; the page… |
POST /v1/knowledge/import | Import an Obsidian, Notion, Roam or Evernote export into the org's knowledge base |
POST /v1/knowledge/search | Runs a semantic search over the caller org's own knowledge — its wiki pages, its agent memories and everything its connectors have synced — and… |
How is this guide?