Skip to content

API · Knowledge & Memory

The knowledge base (files, RAG index, domain collections, knowledge packs), per-project knowledge seeding, agent memory, and conversation history. All endpoints are unauthenticated (see API conventions).

KnowledgeController

api/knowledge — the largest controller. Global and project knowledge files, substring and RAG search, domain collections, promotion candidates, and the knowledge-pack catalog.

Files

Verb Route Request Response Async
GET /api/knowledge/global/path — string? yes
GET /api/knowledge/global/folders — List<KnowledgeFolderInfo> (the library's folders, plus packs kept under Aetherium:KnowledgeIndex:KbRoot; dot folders are hidden) yes
GET /api/knowledge/global/folders/{folderName}/files route folderName:string List<KnowledgeFileInfo> yes
GET /api/knowledge/global/files/content query path:string (inside the library or the KB root) FileContentResponse / 400 outside both yes
GET /api/knowledge/project/{projectId}/files route projectId:int List<KnowledgeFileInfo> yes
GET /api/knowledge/project/{projectId}/files/content route projectId:int; query path:string FileContentResponse yes
POST /api/knowledge/global/folders body CreateFolderRequest (Name, Description?) KnowledgeFolderInfo yes
POST /api/knowledge/global/folders/{folderName}/files route folderName; [FromForm] IFormFile file KnowledgeFileInfo (multipart upload; a picture or recording is queued for the index job as its caption or transcript); 400 bad name, 415 not text, PDF, picture or audio, 413 over 25 MB yes
PUT /api/knowledge/global/files/content body UpdateFileContentRequest (Path, Content) 200 (writes + re-indexes under the file's pack, its top-level folder) / 503 not indexed yes
PUT /api/knowledge/project/{projectId}/files/content route projectId:int; body UpdateFileContentRequest 200 (writes + re-indexes into the project's collection) / 503 not indexed yes
DELETE /api/knowledge/global/folders/{folderName} route folderName 204 / 404 · loopback only yes

Search & RAG index

Verb Route Request Response Async
GET /api/knowledge/search query query:string, projectId:int? List<SearchResult> (substring) yes
GET /api/knowledge/rag/search query query:string, maxResults:int=5, source:string?; loopback only: template:string?, scope:all\|team?, node:string?, projectId:int? List<SearchMatch> / 503 yes
GET /api/knowledge/rag/context query query:string, maxTokens:int=2000 string yes
POST /api/knowledge/rag/rebuild query salvage:string="domains,projects", flip:bool=true 202 KnowledgeBuildSnapshot / 409 while a build runs / 503 · loopback only no
GET /api/knowledge/rag/rebuild — KnowledgeBuildSnapshot (state, phase, progress, verification, whether it flipped) / 503 no
GET /api/knowledge/rag/stats — IndexStats yes
DELETE /api/knowledge/rag/index — 204 · loopback only yes
GET /api/knowledge/activity query projectId:int?, limit:int=20 List<KnowledgeActivityEntry> yes
GET /api/knowledge/semantic-search query q:string, collection:string?, projectId:int?, maxResults:int=5 List<KnowledgeSearchResult> yes

Without template or scope, rag/search, rag/context and semantic-search search every pack plus the project when projectId is given (semantic-search with collection searches that domain, that pack, or with all every pack and every domain; a name that is none of them is a 404 listing the valid domains and packs). template=<id> searches what that template's team would retrieve — one node's scope with node=, the team's otherwise — and scope=all every pack; both are for retrieval evaluation and answer only on the Engine's own machine. A search that cannot run (ChromaDB down, no embedding model, a collection in another embedding space) is a 503 naming the reason, never an empty list. rag/rebuild builds the next index generation beside the live one, verifies it and switches to it only when every check passes; the live index keeps serving throughout, and GET rag/rebuild reports progress.

Loopback-only routes

The routes marked loopback only carry [LocalOnly]: they answer 403 unless the caller is on the Engine's own machine and the request did not come through a proxy: one that carries X-Forwarded-For is refused even from loopback, because a reverse proxy on the same machine forwards every caller from there. They drop collections or delete the library, which is one request away for anything that can reach the Engine's port. Library uploads take one plain file name (^[A-Za-z0-9._ -]{1,128}$, not . or ..) — a name with a path is refused, not trimmed — and text, PDF, pictures (PNG, JPEG, WebP) or recordings (WAV, MP3, OGG, Opus, FLAC, M4A) only.

XML-comment auth claim, not enforced

GET /api/knowledge/semantic-search carries a comment claiming x-openclaw-token support; the method never reads or validates any header. It is unauthenticated like everything else.

Domain collections

Verb Route Request Response Async
GET /api/knowledge/collections — List<DomainCollectionInfo> yes
GET /api/knowledge/collections/{collection}/documents route collection; query limit:int=100 List<DomainDocument> yes
GET /api/knowledge/collections/{collection}/search route collection; query q:string, maxResults:int=10 List<SearchResult> yes
POST /api/knowledge/collections/{collection}/documents route collection; body DomainDocumentRequest (Filename, Content, Metadata?) 200 / 503 yes
DELETE /api/knowledge/collections/{collection} route collection 204 / 404 / 503 · loopback only yes
GET /api/knowledge/domains — List<KnowledgeDomainDto> / 503 yes
GET /api/knowledge/domains/{domain}/documents route domain List<KnowledgeDomainDocumentDto> / 400 / 404 / 503 yes
GET /api/knowledge/domains/{domain}/documents/{documentId} route domain, documentId KnowledgeDomainDocumentDetailDto (the document and its whole text) / 400 / 404 / 503 yes
GET /api/knowledge/domains/{domain}/documents/{documentId}/media route domain, documentId the picture or recording an approved media document was made from / 400 / 404 / 503 yes

The domains routes read each domain collection of the live index generation: its documents, how many the owner approved and when, and whether a shipped template writes there (Shared). They read the collection, not the folder, so a document a rebuild carried over with no copy on disk is listed too. Documents come approved first, newest first, each with its source, kind, project and run; a document's text is its kept copy under the KB root, or its chunks joined. The web app's Knowledge page lists them under "domains".

Promotion candidates

Verb Route Request Response Async
GET /api/knowledge/candidates — List<KnowledgeCandidateDto> / 503 yes
GET /api/knowledge/candidates/{projectId}/{documentId} route projectId:int, documentId (16 lowercase hex) KnowledgeCandidateDetailDto (the candidate and its whole text) / 400 / 404 / 503 yes
GET /api/knowledge/candidates/{projectId}/{documentId}/media route ids a media candidate's picture or recording, as offered / 400 / 404 / 503 yes
POST /api/knowledge/candidates/{projectId}/{documentId}/approve route ids; body KnowledgeApproveRequest? (Domain?) KnowledgeCurationResultDto / 400 / 404 / 503 yes
POST /api/knowledge/candidates/{projectId}/{documentId}/reject route ids KnowledgeCurationResultDto / 400 / 404 / 503 yes

A shared domain is one that any loaded template names in target_collections. It takes a project's document only once the owner approves it; until then the document is a candidate. Candidates come from an agent's write_knowledge into a shared domain (Tools · Knowledge) and from completed and failed runs, offered to the team's first target_collections domain. A completed or failed run always offers its run notes (Project/runs/run-<id>-notes.md, kind run_notes), written from the run's own records with no model and naming the team by its template id: the brief, each plan step with its check verdict and reason, the files the run made, and the run judge's score and summary, which the notes wait up to three minutes for. They are offered even when no file passed its check. A completed run's files are offered too only when the domain already holds something, approved or salvaged: each step that completed and whose check did not fail offers the text files it wrote (at most 50, 200 KB each, nothing under knowledge/ or a dot directory), and its pictures and recordings as their captions and transcripts. For a domain that holds nothing yet, those files are indexed into the run's own project instead. A failed run offers no file, and a cancelled run or one in benchmark mode offers nothing. A candidate sits in its own project's collection, so that project searches it at once, and no other team retrieves it.

Approval writes the text to domains/<domain>/ under Aetherium:KnowledgeIndex:KbRoot and indexes it into the domain it was offered to, or into Domain from the body, as owner-approved under the source Domain/<domain>/<path>; every team bound to that domain retrieves it from then on. A candidate that names no domain, approved without one, is a 400. Rejection leaves the document as its project's own knowledge and takes it off the list. The web app's Knowledge page lists the same queue, with read, approve and reject in place.

The index embeds text only, so a picture or a recording enters as text: a picture as a caption from the vision model Aetherium:Media:CaptionModel (default qwen/qwen3.5-9b), loaded on CaptionSlot under one batch lease for each batch of pictures, and a recording as a speech-to-text transcript made on the CPU by SttCommand, cut into SegmentSeconds pieces. A recording over MaxAudioSeconds or a picture over MaxImageBytes is skipped. Captions and transcripts are cached by the file's content under the KB root's media/ folder, so an unchanged file is described once. A media document's MediaType is image or audio; its file is kept beside its text, moves into the domain's folder on approval and is deleted on rejection, and the media routes serve it. Library uploads take the same path, and a pack kept by hand (one with no GitHub source) has up to 200 of its pictures and recordings indexed when it is built; downloaded packs index text only.

Knowledge packs

Verb Route Request Response Async
GET /api/knowledge/packs query includeManualOnly:bool=false List<KnowledgePack> yes
GET /api/knowledge/packs/by-category query includeManualOnly:bool=false Dictionary<string,List<KnowledgePack>> yes
GET /api/knowledge/packs/{name} route name:string KnowledgePack / 404 yes
POST /api/knowledge/packs/{name}/index route name:string 202 { pack, queued } (the index job re-indexes it into the live generation) / 404 yes
POST /api/knowledge/packs/{name}/download route name:string; query andIndex:bool=false KnowledgePackDownloadResult (499 on cancel) yes
POST /api/knowledge/packs/{name}/refresh route name:string 202 { pack, queued } / 400 no GitHub source / 404 / 503 no index job yes
POST /api/knowledge/packs/detect body TeamConfig List<KnowledgePack> yes
POST /api/knowledge/packs body CreatePackRequest (Name, Description, Category, Tags?) KnowledgePack yes
DELETE /api/knowledge/packs/{name} route name:string 204 / 404 · loopback only yes

A pack is fetched once, the first time a team needs it, and nothing fetches it again by itself; refresh does it on request. The index job downloads the pack's GitHub source beside the pack, swaps the new folder in whole (so a file the source deleted goes too), then replaces the pack's chunks in the live generation. A failed download leaves the pack's files and chunks as they were. Either way the registry records the download date and the new counts; the pack keeps its generation stamp. download replaces the folder the same way but runs inside the request. The Knowledge page shows each pack's fetch date and offers "download again" for a GitHub-sourced one.

KnowledgeFolderInfo, KnowledgeFileInfo, FileContentResponse, CreateFolderRequest, SearchResult, UpdateFileContentRequest, KnowledgeActivityEntry, CreatePackRequest, KnowledgeSearchResult, and DomainDocumentRequest are declared inline; the candidate and domain DTOs live in Aetherium.Shared/DTOs/KnowledgeCurationDtos.cs. RAG tuning lives in the ChromaDb and Aetherium:Embeddings config sections (Configuration), the relevance floor included.

KnowledgeSeedingController

api/projects/{id}/seed-knowledge — trigger and poll the one-per-project knowledge-seeding job.

Verb Route Request Response Async
POST /api/projects/{id}/seed-knowledge route id:int KnowledgeSeedStatusDto / 404 yes
GET /api/projects/{id}/seed-knowledge route id:int KnowledgeSeedStatusDto yes

Seeding behavior is governed by the Aetherium:KnowledgeSeeding config (Configuration).

ConversationController

api/conversation — per-session chat history, fork/archive, and the soft-interrupt "steer" path.

Verb Route Request Response Async
GET /api/conversation/{projectId}/sessions route projectId:int session list/details yes
GET /api/conversation/{projectId}/latest route projectId:int { sessionId, messages } yes
GET /api/conversation/{projectId}/{sessionId} route projectId:int, sessionId:string { sessionId, messages } yes
POST /api/conversation/{projectId} route projectId:int; body SaveMessageRequest { Id, SequenceNumber } / 400 yes
POST /api/conversation/{projectId}/{sessionId}/archive route ids 200 yes
POST /api/conversation/{projectId}/{sessionId}/fork route ids; body ForkSessionRequest? (Title?) 200 / 404 yes
POST /api/conversation/{projectId}/{sessionId}/steer route ids; body SteerRequest (Message) 200 / 400 (soft-interrupt) yes
DELETE /api/conversation/{projectId}/{sessionId} route ids 200 yes
DELETE /api/conversation/{projectId} route projectId:int 200 yes

SaveMessageRequest, ForkSessionRequest, and SteerRequest are declared inline.

Where to go next