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¶
- Tools · Knowledge & Research — the
search_knowledge/write_knowledgetools agents use. - Schema & Data Model — the
AgentMemory,ConversationMessage, andConversationSessionentities. - Configuration · ChromaDB — the endpoint, the relevance floor, the embedding templates and the generation pointer.