mirror of
https://github.com/langgenius/dify.git
synced 2026-07-22 04:25:29 -04:00
4ee43b8afc
Import the committed KnowledgeFS snapshot dc4072ee302317145612087ce7440851dc329fd0 under knowledge-fs/ without its Git history, local IDE settings, or build artifacts.
894 lines
67 KiB
Markdown
894 lines
67 KiB
Markdown
# Knowledge-FS Gateway — API Reference
|
||
|
||
Human-readable reference for the HTTP API served by the gateway (`apps/api`, Hono + zod-openapi).
|
||
It is maintained alongside the route definitions in `packages/api/src/*-routes.ts` and their zod
|
||
request/response schemas. `GET /openapi.json` is the authoritative machine-readable inventory when
|
||
the live spec and this companion document differ.
|
||
|
||
## Overview
|
||
|
||
- **Base URL (local dev)**: `http://localhost:8788` (the gateway). The Swagger UI proxy runs on
|
||
`http://localhost:8088` and fetches `/openapi.json` from the gateway.
|
||
- **Live spec**: `GET /openapi.json` — the machine-readable OpenAPI document (public, unauthenticated),
|
||
served via `app.doc(...)`. This reference is a human-readable companion.
|
||
- **Content type**: JSON (`application/json`) unless noted; document upload is `multipart/form-data`;
|
||
streaming answers/progress are `text/event-stream`; raw multimodal assets are
|
||
`application/octet-stream`.
|
||
|
||
## Authentication & scopes
|
||
|
||
- **Bearer token** (JWT) on every guarded route: `Authorization: Bearer <token>`. The tenant is taken
|
||
from the JWT; a knowledge space is always resolved as `spaces.get({ id, tenantId })`, so one tenant
|
||
can never read another tenant's space (returns `404`).
|
||
- **Scopes** (`getRequiredScope`): every `GET` needs `knowledge-spaces:read`; every `POST/PATCH/DELETE`
|
||
needs `knowledge-spaces:write` — **except** `POST /queries`, `POST /research-tasks/plan`, and
|
||
`POST /agent-workspace-snapshots/{id}/replay`, which need only `read`. The wildcard scope
|
||
`knowledge-spaces:*` satisfies both.
|
||
- **Auth is path-prefix middleware**, not per-route: it guards `/knowledge-spaces*`, `/queries*`,
|
||
`/jobs*`, `/research-tasks*`, `/agent-workspace-snapshots*`, `/bulk-jobs*`, `/retention-policy`.
|
||
**Public (no auth)**: `GET /health` and `GET /openapi.json`.
|
||
- **Auth failures**: `401` (missing/invalid token), `403` (token lacks the required scope), both
|
||
`{ error: string }`.
|
||
|
||
## Conventions
|
||
|
||
- **Errors**: non-2xx bodies are `{ error: string }` (unless a richer shape is documented, e.g. research
|
||
task `422`).
|
||
- **Pagination**: list endpoints take `cursor` (opaque string) + `limit`, and return
|
||
`{ items: [...], nextCursor?: string }`. Pass `nextCursor` back as `cursor` for the next page.
|
||
- **Strict bodies/queries**: most request bodies and query objects are `.strict()` — unknown keys are
|
||
rejected with `400`.
|
||
- **SSE**: streaming endpoints return `text/event-stream`; frames are `event: <name>\ndata: <json>\n\n`.
|
||
- **Datetimes** are ISO-8601 strings unless a field is documented as an epoch number.
|
||
|
||
---
|
||
|
||
## Knowledge spaces & documents
|
||
|
||
### `POST /knowledge-spaces`
|
||
**Description**: Create a new knowledge space for the caller's tenant.
|
||
**Auth**: Bearer; scope `knowledge-spaces:write`.
|
||
**Path params**: None.
|
||
**Query params**: None.
|
||
**Request body** (`application/json`, strict): `name` (string, 1–160, required); `slug` (string, required, `^[a-z0-9]+(?:-[a-z0-9]+)*$`) — tenant-unique; `description` (string, ≤2000, optional); `embeddingProfile` (optional) `{ pluginId, provider, model }`. When omitted, the configured deployment default is persisted for new spaces. Clients cannot set credentials, dimension, revision, or `vectorSpaceId`.
|
||
**Responses**:
|
||
- `201`: `KnowledgeSpace` `{ id: uuid, name, slug, description?, tenantId, createdAt, updatedAt }`.
|
||
- `409` slug conflict; `429` capacity exceeded; `401`/`403`.
|
||
|
||
### `GET /knowledge-spaces`
|
||
**Description**: List the tenant's knowledge spaces (cursor-paginated).
|
||
**Auth**: Bearer; scope `knowledge-spaces:read`.
|
||
**Query params** (strict): `limit` (int ≥1, optional, default 100); `cursor` (string, optional).
|
||
**Responses**: `200` `{ items: KnowledgeSpace[], nextCursor?: string }`; `400`; `401`/`403`.
|
||
|
||
### `GET /knowledge-spaces/{id}`
|
||
**Description**: Fetch a single knowledge space by id.
|
||
**Auth**: Bearer; scope `knowledge-spaces:read`.
|
||
**Path params**: `id` (uuid).
|
||
**Responses**: `200` `KnowledgeSpace`; `404`; `401`/`403`.
|
||
|
||
### `GET /knowledge-spaces/{id}/manifest`
|
||
**Description**: Return the space's control-plane manifest (storage/consistency/quota policies).
|
||
**Auth**: Bearer; scope `knowledge-spaces:read`.
|
||
**Path params**: `id` (uuid).
|
||
**Responses**:
|
||
- `200`: `KnowledgeSpaceManifest` — `{ id, knowledgeSpaceId, tenantId, manifestVersion: int, embeddingProfile?: { pluginId, provider, model, vectorSpaceId, revision, dimension? }, embeddingProfileFrozenAt?: datetime, minClientVersion, nodeSchemaVersion: int, parserPolicyVersion, projectionSetVersion, objectKeyPrefix, metadataDialect: enum(portable|postgres|tidb), storageProvider: enum(memory-dev|r2|s3-compatible), consistencyPolicy: { defaultClass: enum(path-consistent|snapshot-consistent|cache-consistent|eventual-preview), snapshotTtlSeconds, cacheTtlSeconds? }, encryptionPolicy: { strategy: enum(provider-managed|customer-managed|none), keyRef? }, retentionPolicy: { artifactVersionsToKeep, failedCommitRetentionDays, traceRetentionDays }, quotaPolicy: { maxActiveJobCount, maxActiveSessionCount, maxArtifactBytes, maxGraphEntityCount, maxGraphRelationCount, maxNodeCount, maxProjectionCount, maxRawDocumentBytes, maxSegmentCount, maxTraceBytes: int|null, providerBudgets: { maxEmbeddingTokensPerDay, maxLlmTokensPerDay, maxParserPagesPerDay, maxRerankRequestsPerDay: int|null } }, metadata, createdAt, updatedAt }`. `embeddingProfileFrozenAt` is set atomically when the first document ingestion is admitted.
|
||
- `404`; `401`/`403`.
|
||
|
||
### `PUT /knowledge-spaces/{id}/embedding-profile`
|
||
**Description**: Bind the space to a plugin-daemon embedding route and immutable vector-space identity.
|
||
**Auth**: Bearer; scope `knowledge-spaces:write`.
|
||
**Path params**: `id` (uuid).
|
||
**Request body** (`application/json`, strict): `{ pluginId, provider, model }` (all non-empty strings). Credentials remain tenant-scoped in plugin-daemon. The server owns `vectorSpaceId`, revision, and the dimension observed from the first real model response.
|
||
**Responses**:
|
||
- `200`: `{ pluginId, provider, model, vectorSpaceId, revision, dimension? }`.
|
||
- `409`: ingestion has already been admitted (or legacy content exists), so the candidate reindex/publish workflow is required before changing vector space. The admission latch is fail-closed and remains set even if the first upload later fails.
|
||
- `404`; `401`/`403`.
|
||
|
||
### `GET /knowledge-spaces/{id}/status`
|
||
**Description**: Bounded runtime status snapshot (leases, sessions, failed commits, index health, storage/parser).
|
||
**Auth**: Bearer; scope `knowledge-spaces:read`.
|
||
**Path params**: `id` (uuid).
|
||
**Responses**:
|
||
- `200`: `KnowledgeSpaceStatus` `{ knowledgeSpaceId, tenantId, generatedAt, activeLeases: { count, truncated, items: [{ id, leaseType: enum(read|publish|delete|reindex), targetType, virtualPath, expiresAt }] }, activeSessions: { count, truncated, items: [{ id, subjectId, clientKind: enum(api|mcp|worker|admin), consistencyClass, heartbeatAt, expiresAt }] }, failedCommits: { count, truncated, items: [{ id, status: enum(failed-retryable|failed-terminal), errorCode?, expiresAt?, updatedAt }] }, index: { nodeSchemaVersion, projectionVersion, projectionSetVersion, summaries: { denseVector, fts, graph, metadata: { total, ready, building, stale, failed } } }, manifest: { manifestVersion, metadataDialect, storageProvider, objectKeyPrefix, consistencyClass }, parser: { kind, policyVersion }, storage: { healthy, provider, objectStorageKind } }`.
|
||
- `404`; `401`/`403`.
|
||
|
||
### `GET /knowledge-spaces/{id}/stats`
|
||
**Description**: Low-cardinality aggregate statistics over a time window.
|
||
**Auth**: Bearer; scope `knowledge-spaces:read`.
|
||
**Path params**: `id` (uuid). **Query** (strict): `windowMinutes` (int 1–1440, optional, default 60).
|
||
**Responses**:
|
||
- `200`: `KnowledgeSpaceStats` `{ knowledgeSpaceId, tenantId, generatedAt, window: { start, end, minutes }, storage: { documentCount, rawDocumentBytes }, projections: { denseVector, fts, graph, metadata: { total, ready, building, stale, failed }, projectionVersion }, commits: { failedRetryable, failedTerminal, sampled, truncated }, cache: { available, entries, totalBytes }, metrics: { available, reason? }, runtime: { activeLeaseSampleCount, activeSessionSampleCount, truncated } }`.
|
||
- `400`; `404`; `401`/`403`.
|
||
|
||
### `GET /knowledge-spaces/{id}/fsck`
|
||
**Description**: Bounded filesystem-consistency diagnostics over one check class.
|
||
**Auth**: Bearer; scope `knowledge-spaces:read`.
|
||
**Path params**: `id` (uuid). **Query** (strict): `check` (enum `raw-objects|artifact-segments|references`, optional, default `raw-objects`); `cursor` (string 1–1024, optional).
|
||
**Responses**:
|
||
- `200`: `KnowledgeFsckReport` `{ knowledgeSpaceId, tenantId, scannedAt, cursor?, summary: { scanned, info, warning, error, critical, repairable }, issues: [{ code, message, type: enum(missing-raw-object|checksum-mismatch|size-mismatch|missing-artifact-object|segment-hash-mismatch|broken-path-target|missing-node-target|stale-projection|orphaned-staged-object|failed-commit-expired), severity: enum(info|warning|error|critical), repairability: enum(auto-repairable|manual|not-repairable), target: { type, id?, objectKey?, documentAssetId?, parseArtifactId?, virtualPath? } }] (≤1000) }`.
|
||
- `400`; `404`; `401`/`403`.
|
||
|
||
### `GET /knowledge-spaces/{id}/gc/staged-objects`
|
||
**Description**: Bounded dry-run listing of staged-object / failed-commit GC candidates.
|
||
**Auth**: Bearer; scope `knowledge-spaces:read`.
|
||
**Path params**: `id` (uuid). **Query** (strict): `cursor` (string 1–1024, optional); `stagedObjectPrefix` (string 1–1024, object-key-prefix regex, optional).
|
||
**Responses**:
|
||
- `200`: `KnowledgeFsGcDryRunReport` `{ knowledgeSpaceId, tenantId, dryRunId, generatedAt, cursor?, summary: { candidateCount, stagedObjectCount, failedCommitCount, estimatedBytes }, candidates: [{ candidateType: enum(staged-object|failed-commit|artifact-segment|parse-artifact|index-projection|answer-trace), count>0, estimatedBytes, idempotencyKey, reason, target: {...} }] (≤1000) }`.
|
||
- `400`; `404`; `401`/`403`.
|
||
|
||
### `POST /knowledge-spaces/{id}/gc/staged-objects/execute`
|
||
**Description**: Execute deletion for supplied GC candidates (skips objects with an active lease).
|
||
**Auth**: Bearer; scope `knowledge-spaces:write`.
|
||
**Path params**: `id` (uuid).
|
||
**Request body** (`application/json`, strict): `candidates` (array ≤100, required) — each `{ candidateType, count>0, estimatedBytes≥0, idempotencyKey (1–512), reason (1–1000), target: { type: enum(raw-object|artifact-object|artifact-segment|knowledge-path|knowledge-node|index-projection|staged-commit), id?, objectKey?, documentAssetId?, parseArtifactId?, virtualPath? } }`.
|
||
**Responses**:
|
||
- `200`: `{ tenantId, deleted: int, skipped: int, items: [{ objectKey, idempotencyKey, status: enum(deleted|skipped-active-lease) }] }`.
|
||
- `400`; `404`; `401`/`403`.
|
||
|
||
### `GET /knowledge-spaces/{id}/staged-commits`
|
||
**Description**: Read-only paginated staged-commit diagnostics, optionally filtered by status.
|
||
**Auth**: Bearer; scope `knowledge-spaces:read`.
|
||
**Path params**: `id` (uuid). **Query** (strict): `limit` (int ≥1, optional, default 100); `cursor` (uuid, optional); `status` (enum `received|object-staged|object-verified|metadata-prepared|artifacts-built|nodes-built|projections-built|published|failed-retryable|failed-terminal|canceled|gc-pending|gc-complete`, optional).
|
||
**Responses**:
|
||
- `200`: `{ items: KnowledgeSpaceStagedCommit[], nextCursor? }` — each `{ id, knowledgeSpaceId, tenantId, idempotencyKey, operationType: enum(document-upload|artifact-segment-write|bulk-reindex|projection-publish), status, documentAssetId?, parseArtifactId?, rawObjectKey?, publishedObjectKey?, projectionFingerprint?, checksum?, sizeBytes?, errorCode?, errorMessage?, expiresAt?, createdAt, updatedAt }`.
|
||
- `400`; `404`; `401`/`403`.
|
||
|
||
### `GET /knowledge-spaces/{id}/leases/active`
|
||
**Description**: Read-only paginated diagnostics of currently active KnowledgeFS leases.
|
||
**Auth**: Bearer; scope `knowledge-spaces:read`.
|
||
**Path params**: `id` (uuid). **Query** (strict): `limit` (int ≥1, optional, default 100); `cursor` (string 1–1024, optional).
|
||
**Responses**:
|
||
- `200`: `{ items: KnowledgeFsLease[], nextCursor? }` — each `{ id, knowledgeSpaceId, tenantId, sessionId, leaseType: enum(read|publish|delete|reindex), status: enum(active|released|expired|failed), targetId, targetType, targetVersion?, virtualPath, metadata, acquiredAt, heartbeatAt, expiresAt, updatedAt }`.
|
||
- `400`; `404`; `401`/`403`.
|
||
|
||
### `PATCH /knowledge-spaces/{id}`
|
||
**Description**: Update mutable fields (name/slug/description).
|
||
**Auth**: Bearer; scope `knowledge-spaces:write`.
|
||
**Path params**: `id` (uuid). **Body** (`application/json`, strict, all optional): `name` (1–160); `slug` (slug regex); `description` (≤2000).
|
||
**Responses**: `200` `KnowledgeSpace`; `404`; `409` slug conflict; `401`/`403`.
|
||
|
||
### `DELETE /knowledge-spaces/{id}`
|
||
**Description**: Delete a knowledge space.
|
||
**Auth**: Bearer; scope `knowledge-spaces:write`.
|
||
**Path params**: `id` (uuid).
|
||
**Responses**: `204`; `404`; `401`/`403`.
|
||
|
||
### `GET /knowledge-spaces/{id}/documents`
|
||
**Description**: List document assets (cursor-paginated).
|
||
**Auth**: Bearer; scope `knowledge-spaces:read`.
|
||
**Path params**: `id` (uuid). **Query** (strict): `limit` (int 1–100, optional, default 50); `cursor` (uuid, optional).
|
||
**Responses**:
|
||
- `200`: `{ items: DocumentAsset[], nextCursor?: uuid }` — each `DocumentAsset` `{ id, knowledgeSpaceId, sourceId?, filename, mimeType, objectKey, sha256, sizeBytes, parserStatus: enum(pending|parsed|failed), version: int>0, metadata, createdAt, updatedAt? }`.
|
||
- `404`; `401`/`403`.
|
||
|
||
### `GET /knowledge-spaces/{id}/documents/{documentId}`
|
||
**Description**: Fetch a single document asset.
|
||
**Auth**: Bearer; scope `knowledge-spaces:read`.
|
||
**Path params**: `id` (uuid); `documentId` (uuid).
|
||
**Responses**: `200` `DocumentAsset`; `404`; `401`/`403`.
|
||
|
||
### `GET /knowledge-spaces/{id}/documents/{documentId}/parse-artifacts/{version}`
|
||
**Description**: Fetch a specific version of a document's parse artifact (parsed elements).
|
||
**Auth**: Bearer; scope `knowledge-spaces:read`.
|
||
**Path params**: `id` (uuid); `documentId` (uuid); `version` (positive int).
|
||
**Responses**:
|
||
- `200`: `ParseArtifact` `{ id, documentAssetId, artifactHash, contentType: enum(text|structured|mixed), parser: enum(native-markdown|native-html|native-structured|unstructured), version: int>0, elements: [{ id, type: enum(title|heading|paragraph|table|list|image|code|page-break), text?, pageNumber?, sectionPath: string[], metadata }], metadata, createdAt, updatedAt? }`.
|
||
- `404`; `401`/`403`.
|
||
|
||
### `GET /knowledge-spaces/{id}/documents/{documentId}/outline`
|
||
**Description**: Fetch a document's hierarchical outline / table of contents.
|
||
**Auth**: Bearer; scope `knowledge-spaces:read`.
|
||
**Path params**: `id` (uuid); `documentId` (uuid).
|
||
**Responses**:
|
||
- `200`: `DocumentOutline` `{ id, knowledgeSpaceId, documentAssetId, parseArtifactId, artifactHash, outlineVersion, version: int>0, nodes: DocumentOutlineNode[], metadata, createdAt, updatedAt? }`; each node `{ id, title, level: int>0, tocSource, sectionPath: string[], childNodeIds: string[], children: node[] (recursive), sourceElementIds: string[], sourceNodeIds: string[], startOffset?, endOffset?, startPage?, endPage?, summary?, titleLocation?, metadata }`.
|
||
- `404`; `401`/`403`.
|
||
|
||
### `GET /knowledge-spaces/{id}/documents/{documentId}/multimodal`
|
||
**Description**: Fetch a document's multimodal manifest (images/tables/pages/code + enrichment status).
|
||
**Auth**: Bearer; scope `knowledge-spaces:read`.
|
||
**Path params**: `id` (uuid); `documentId` (uuid).
|
||
**Responses**:
|
||
- `200`: `DocumentMultimodalManifest` `{ id, knowledgeSpaceId, documentAssetId, parseArtifactId, artifactHash, manifestVersion, version: int>0, items: [{ id, parseElementId, modality: enum(code|image|page|table), title?, caption?, textPreview?, ocrText?, pageNumber?, boundingBox?: { x, y, width, height }, sectionPath: string[], startOffset?, endOffset?, assetRef?: { objectKey?, uri?, contentType?, sha256?, variants?: Record<string,{ objectKey?, uri?, contentType?, sha256?, width?, height? }> }, enrichment: { asset, caption, ocr, tableStructure, visualEmbedding: enum(missing|pending|provided|unsupported) }, sourceMetadata } ], metadata, createdAt, updatedAt? }`.
|
||
- `404`; `401`/`403`.
|
||
|
||
### `GET /knowledge-spaces/{id}/documents/{documentId}/multimodal/{itemId}/asset`
|
||
**Description**: Stream the raw binary asset (or a named variant) for a multimodal item.
|
||
**Auth**: Bearer; scope `knowledge-spaces:read`.
|
||
**Path params**: `id` (uuid); `documentId` (uuid); `itemId` (string 1–1024). **Query** (strict): `variant` (string 1–64, `^[A-Za-z0-9._=-]+$`, optional).
|
||
**Responses**:
|
||
- `200` (`application/octet-stream`): binary asset bytes.
|
||
- `404` not found; `409` external (non-inline) asset; `413` exceeds max readable size; `401`/`403`.
|
||
|
||
### `POST /knowledge-spaces/{id}/documents`
|
||
**Description**: Upload a single document — compiled synchronously, or accepted for durable async compilation.
|
||
**Auth**: Bearer; scope `knowledge-spaces:write`.
|
||
**Path params**: `id` (uuid).
|
||
**Request body** (`multipart/form-data`): `file` (binary, required); `sourceId` (uuid, optional).
|
||
**Responses**:
|
||
- `201`: `DocumentAsset` (compiled synchronously).
|
||
- `202`: `{ asset: DocumentAsset, compilationJob: { id, stage: "queued" }, statusUrl }` (async handoff).
|
||
- `400`; `404`; `413` too large; `429` capacity; `500` upload failed; `401`/`403`.
|
||
|
||
### `POST /knowledge-spaces/{id}/documents/bulk`
|
||
**Description**: Upload multiple documents for durable async compilation.
|
||
**Auth**: Bearer; scope `knowledge-spaces:write`.
|
||
**Path params**: `id` (uuid). **Body** (`multipart/form-data`): `files` (binary[], required).
|
||
**Responses**:
|
||
- `202`: `{ bulkJobId, total, items: [{ asset: DocumentAsset, compilationJob: { id, stage: "queued" }, statusUrl }] }`.
|
||
- `400`; `404`; `413`; `429`; `500`; `503` durable compilation not configured; `401`/`403`.
|
||
|
||
### `DELETE /knowledge-spaces/{id}/documents/bulk`
|
||
**Description**: Delete multiple documents by id; report per-document results.
|
||
**Auth**: Bearer; scope `knowledge-spaces:write`.
|
||
**Path params**: `id` (uuid). **Body** (`application/json`, strict): `documentIds` (uuid[], min 1).
|
||
**Responses**:
|
||
- `200`: `{ bulkJobId, total, items: [{ documentId, status: enum(deleted|not_found), objectDeleted, artifactsDeleted, nodesDeleted, projectionsDeleted }] }`.
|
||
- `400`; `404`; `401`/`403`.
|
||
|
||
### `POST /knowledge-spaces/{id}/documents/bulk/reindex`
|
||
**Description**: Queue reindex (recompilation) of all documents or a specified set.
|
||
**Auth**: Bearer; scope `knowledge-spaces:write`.
|
||
**Path params**: `id` (uuid). **Body** (`application/json`, strict; exactly one of): `all` (boolean) | `documentIds` (uuid[], min 1).
|
||
**Responses**:
|
||
- `202`: `{ bulkJobId, total, items: Array<{ asset, compilationJob: { id, stage: "queued" }, status: "queued", statusUrl } | { documentId, status: "not_found" }> }`.
|
||
- `400`; `404`; `413` quota; `503` durable compilation not configured; `401`/`403`.
|
||
|
||
### `GET /jobs/{id}`
|
||
**Description**: Fetch the status of a durable document compilation job.
|
||
**Auth**: Bearer; scope `knowledge-spaces:read`.
|
||
**Path params**: `id` (string, min 1).
|
||
**Responses**:
|
||
- `200`: `DocumentCompilationJob` `{ id, knowledgeSpaceId, tenantId, documentAssetId, queueJobId, stage: enum(queued|parsed|outline_built|nodes_generated|projection_built|smoke_eval_passed|published|failed|canceled), version: int>0, error?, createdAt: number, updatedAt: number, completedAt?: number }`.
|
||
- `404`; `503` compilation jobs unavailable; `401`/`403`.
|
||
|
||
### `DELETE /jobs/{id}`
|
||
**Description**: Cancel a durable document compilation job.
|
||
**Auth**: Bearer; scope `knowledge-spaces:write`.
|
||
**Path params**: `id` (string, min 1).
|
||
**Responses**: `200` `DocumentCompilationJob` (canceled); `404`; `409` terminal (cannot cancel); `503`; `401`/`403`.
|
||
|
||
---
|
||
|
||
## Query, answers, golden & failed queries
|
||
|
||
### `POST /queries`
|
||
**Description**: Run a knowledge-space query and stream the generated answer as SSE.
|
||
**Auth**: Bearer; scope `knowledge-spaces:read`.
|
||
**Request body** (`application/json`, strict): `knowledgeSpaceId` (uuid, required); `query` (string
|
||
1–16000, required); `mode` (enum `auto|deep|fast|research`, optional; defaults to the published
|
||
knowledge-space retrieval profile's `defaultMode`, or `fast` for a legacy space without a profile);
|
||
`activeDocumentIds` (uuid[] ≤100, optional, default `[]`); `activeEntityIds` (string[1–200][] ≤100,
|
||
optional, default `[]`); `sessionId` (uuid, optional).
|
||
|
||
`auto` is a public **routing selector**, not a fourth retrieval pipeline. Only an explicit
|
||
`mode: "auto"` invokes the knowledge space's published `reasoningModel` through plugin-daemon to
|
||
select exactly one concrete pipeline. Omitting `mode` does not invoke the router; it uses the
|
||
published `defaultMode`. Explicit `fast`, `research`, and `deep` requests also bypass routing.
|
||
|
||
The model is instructed to select the least expensive sufficient pipeline: **Fast** for ordinary
|
||
dense + FTS hybrid recall, fusion, and final rerank; **Research** for Summary/Outline/PageIndex tree
|
||
navigation without ordinary hybrid, Graph, or ordinary rerank; and **Deep** for ordinary hybrid
|
||
recall plus Graph expansion, followed by one unified final rerank. Selection is not implemented by
|
||
hard-coded CJK/language, query-length, word-count, or keyword rules. A timeout, provider failure,
|
||
invalid response, or model-identity mismatch safely degrades to the published `defaultMode` and is
|
||
recorded in the trace; it never falls back to the removed heuristic.
|
||
|
||
**Response headers**:
|
||
|
||
- `x-query-run-id`: server-generated UUID used as the durable `AnswerTrace.id`, retrieval lease ID,
|
||
session query ID, and query activity resource ID. Use this value with `GET /queries/{traceId}`.
|
||
- `x-trace-id`: HTTP request correlation ID. It can originate from the caller and is deliberately
|
||
separate from the durable query-run ID; do not use it as the AnswerTrace resource ID.
|
||
- `x-session-id`: generated or reused query session ID. A successful streamed query has a resolved
|
||
session even when the request omitted `sessionId`.
|
||
|
||
**Responses**:
|
||
|
||
- `200` (`text/event-stream`):
|
||
- `answer.delta` `{ delta, traceId }`: one generated answer fragment. `traceId` is the durable
|
||
query-run ID.
|
||
- `answer.done` `{ finishReason, metadata?, traceId }`: the single successful terminal frame.
|
||
Production finish reasons are `retrieval-evidence` and `no-retrieval-evidence`.
|
||
- `answer.error` `{ error, traceId }`: the terminal failure frame. No `answer.done` follows it.
|
||
- `400` invalid request or retrieval-profile/mode mismatch; `404` space not found; `409` query
|
||
admission rejected while deletion is active; `503` query generation, published runtime snapshot,
|
||
embedding profile, projection snapshot, or required retrieval capability unavailable; `401`/`403`.
|
||
|
||
`answer.done.metadata` is generator-dependent and can contain `generator`, `mode`, reasoning
|
||
`model`, `provider`, `providerFinishReason`, opaque `providerMetadata`, `topScore`, `citations`, the
|
||
complete `evidenceBundle`, multimodal evidence/answer metadata, and the same `projectionSnapshot`,
|
||
`retrievalProfile`, retrieval `plan`, and retrieval `metrics` persisted in the AnswerTrace.
|
||
|
||
The gateway buffers the terminal `answer.done` frame until the AnswerTrace transaction commits. If
|
||
the trace commit fails, the client receives `answer.error` instead of a false successful terminal
|
||
frame. A disconnect before terminal ownership is claimed normally cancels the run without creating
|
||
an AnswerTrace. After a successful trace commit, failed-query capture is best-effort and currently
|
||
recognizes `no-retrieval-evidence` and configured low-confidence results.
|
||
|
||
Internal `trace-step` events are collected for AnswerTrace persistence and are never emitted as SSE
|
||
frames. Answer deltas may arrive before the terminal persistence fence; after any generation,
|
||
lease, or trace-persistence failure the stream emits `answer.error` when the client is still
|
||
connected and then closes while retaining HTTP status `200` for the already-open SSE response.
|
||
|
||
### `GET /queries/{traceId}`
|
||
**Description**: Fetch the durable retrieval/generation execution trace for a query. This is an
|
||
execution trace, not a replayable copy of the generated answer.
|
||
**Auth**: Bearer; scope `knowledge-spaces:read`.
|
||
**Path params**: `traceId` (uuid).
|
||
**Responses**:
|
||
|
||
- `200`: `AnswerTrace` `{ id, knowledgeSpaceId, query, mode:
|
||
enum(fast|deep|research|auto), createdAt, evidenceBundleId?, steps: [{ name, status:
|
||
enum(ok|error|skipped), startedAt, endedAt?, metadata }] }`. New online-query traces store the
|
||
concrete resolved mode (`fast|research|deep`) at the top level; `auto` remains readable in the
|
||
schema for historical records.
|
||
- `404`; `401`/`403`.
|
||
|
||
`createdAt` is the terminal trace-recording time, not the query admission time. Public responses
|
||
omit the internal creator `subjectId`, permission-snapshot ID/revision/access channel, and database
|
||
`completed` flag. A trace is readable only by its original authenticated subject while the stored
|
||
permission snapshot, current knowledge-space read access, API-key binding (when applicable), and
|
||
all referenced node/document/asset visibility checks still pass. Hidden, deleted, legacy-unowned,
|
||
or unresolved-evidence traces fail closed with `403` or `404`.
|
||
|
||
Online queries normally persist these steps:
|
||
|
||
- `query.route`: routing provenance with `requestedMode`, concrete `resolvedMode`, `resolver`
|
||
(`explicit`, `llm`, or `fallback`), `selectionSource`, published reasoning-model/profile identity,
|
||
prompt version, bounded provider/usage metadata, duration, and a `degraded` flag plus safe error
|
||
class when Auto falls back. The step is persisted but is not emitted as an SSE frame. Explicit
|
||
modes and omitted-mode defaults record routing without making an LLM call.
|
||
- `query.embed` (Fast and Deep only): `durationMs`, observed embedding `model`, observed
|
||
`dimension`, and immutable `vectorSpaceId`. Research does not call the embedding capability and
|
||
omits this step.
|
||
- `query.retrieve`: `durationMs`, `itemCount`, optional immutable `projectionSnapshot`, published
|
||
`retrievalProfile`, resolved retrieval `plan`, and retrieval `metrics`.
|
||
- `query.answer`: `durationMs`, `answerChars`, `synthesis`, and, for LLM generation, `model`,
|
||
`provider`, and `providerFinishReason`. A failed multimodal attempt followed by a successful text
|
||
fallback can produce both an `error` and an `ok` `query.answer` step.
|
||
- `query.generate`: terminal summary with `eventCount`, optional `finishReason`, and the complete
|
||
terminal generator metadata. Its status is authoritative for the database `completed` state.
|
||
|
||
The retrieval `plan` contains `{ strategyVersion, requestedMode, resolvedMode, queryLanguage, topK,
|
||
denseTopK, ftsTopK, fusionLimit, rerankCandidateLimit }`. The `retrievalProfile` contains the
|
||
non-secret published configuration that governed the run: `{ revision, defaultMode, topK,
|
||
scoreThreshold, reasoningModel, rerank }`, including model `pluginId`, `provider`, and `model`
|
||
identifiers but no plugin credentials. The `projectionSnapshot` contains `{ publicationId,
|
||
fingerprint, headRevision, projectionVersion }`.
|
||
|
||
Retrieval metrics use a shared shape. Common fields include dense/FTS/fusion candidate counts and
|
||
latencies, final candidate count, permission/metadata/projection/score-threshold filtered counts,
|
||
rerank candidates and latency, multimodal/image/table/visual candidate counts, degradation flags,
|
||
and `totalMs`. Mode-specific fields are:
|
||
|
||
- **Fast**: ordinary dense + FTS hybrid recall and candidate fusion, followed by the single final
|
||
rerank pass when reranking is enabled by the published profile.
|
||
- **Research**: PageIndex/Outline/Summary fields such as `pageIndexMatchedNodes`,
|
||
`pageIndexOpenedRanges`, scanned outlines/nodes, candidate truncation, PageIndex score version,
|
||
Summary candidates/selected sections, and threshold-filtered candidates. Dense/FTS/rerank plan
|
||
limits are zero and no Graph expansion is used.
|
||
- **Deep**: ordinary hybrid fields plus `graphExpansionSeeds`, traversed entities, relations,
|
||
candidates, latency, and timeout state, followed by the single final rerank pass when reranking
|
||
is enabled by the published profile.
|
||
|
||
Graph expansion, PageIndex, and rerank currently appear inside `query.retrieve.plan/metrics`, not as
|
||
separate trace steps. A stage step is emitted only after that stage completes; an embedding or
|
||
retrieval exception before emission can therefore leave only the terminal `query.generate:error`
|
||
step. `completed=true` means execution reached a successful terminal summary: a
|
||
`no-retrieval-evidence` result is still completed, and a recovered intermediate error does not make
|
||
the terminal query fail.
|
||
|
||
AnswerTrace deliberately does **not** persist the generated answer text, SSE delta fragments,
|
||
prompt/messages, session ID, active document/entity context, raw query vector, normalized token
|
||
usage/cost, or exception stack. Token/usage data is present only when a provider places it in opaque
|
||
`providerMetadata`. Clients that require answer replay or audit must persist the SSE answer content
|
||
separately.
|
||
|
||
`steps[].metadata` is an open JSON object and the direct trace response returns it after the access
|
||
checks above. It can contain complete evidence text, provider and multimodal metadata, internal
|
||
projection identifiers, and aggregate filtered-candidate diagnostics; clients must treat it as
|
||
sensitive authorized content.
|
||
|
||
The terminal `EvidenceBundle` embedded in `query.generate.metadata.evidenceBundle` has this shape:
|
||
|
||
`{ id, query, traceId?, createdAt, state: enum(answerable|partial|not-enough-evidence|conflict|permission-limited), items: [{ nodeId, text, score, scores: { retrieval, rerank?, freshness?, final }, freshness, citations: [{ documentAssetId, documentVersion, artifactHash?, pageNumber?, sectionPath, startOffset?, endOffset? }], conflicts, metadata }], missingEvidence: [{ expectedEvidenceId?, reason, text, metadata }] }`.
|
||
|
||
For an online query the same complete bundle is stored in the terminal step metadata and in the
|
||
scoped `evidence_bundles` storage; `evidenceBundleId` identifies that persisted bundle.
|
||
|
||
### `GET /queries/{traceId}/evidence`
|
||
**Description**: List the evidence supporting an answer trace (paginated virtual-tree listing).
|
||
**Auth**: Bearer; scope `knowledge-spaces:read`.
|
||
**Path params**: `traceId` (uuid). **Query** (strict): `cursor` (string, optional); `limit` (int 1–100, optional, default 25).
|
||
**Responses**:
|
||
|
||
- `200`: KnowledgeFS list `{ path, truncated, items: [{ kind: "resource", name: nodeId, path,
|
||
resourceType: "node", targetId: nodeId, metadata: { citationCount, conflictCount, freshness,
|
||
score, scores } }], nextCursor? }`.
|
||
- `400`; `404`; `401`/`403`.
|
||
|
||
This is a compact virtual-tree projection. It does not repeat evidence text or citation arrays; the
|
||
authorized direct AnswerTrace currently contains the complete embedded EvidenceBundle.
|
||
|
||
### `GET /queries/{traceId}/conflicts`
|
||
**Description**: List conflicting evidence for an answer trace (paginated virtual-tree listing).
|
||
**Auth**: Bearer; scope `knowledge-spaces:read`.
|
||
**Path params**: `traceId` (uuid). **Query** (strict): `cursor`; `limit` (int 1–100, default 25).
|
||
**Responses**: `200` KnowledgeFS list with resource entries containing `{ nodeId, reason,
|
||
severity }` metadata and a target node ID; `400`; `404`; `401`/`403`.
|
||
|
||
### `GET /queries/{traceId}/missing`
|
||
**Description**: List evidence gaps (missing/not-found evidence) for an answer trace.
|
||
**Auth**: Bearer; scope `knowledge-spaces:read`.
|
||
**Path params**: `traceId` (uuid). **Query** (strict): `cursor`; `limit` (int 1–100, default 25).
|
||
**Responses**: `200` KnowledgeFS list with evidence entries containing the missing-evidence
|
||
`reason` plus its metadata and `expectedEvidenceId` when present; `400`; `404`; `401`/`403`.
|
||
|
||
### `GET /knowledge-spaces/{id}/quality/traces`
|
||
**Description**: List subject-owned, candidate-authorized AnswerTrace summaries for the Quality
|
||
history view.
|
||
**Auth**: Bearer; scope `knowledge-spaces:read`; current knowledge-space read authorization is also
|
||
required.
|
||
**Path params**: `id` (uuid).
|
||
**Query** (strict): `cursor` (opaque string, optional); `from`/`to` (datetime,
|
||
optional); `limit` (int 1–100, optional, default 50); `mode` (enum
|
||
`auto|deep|fast|research`, optional); `query` (trimmed string 1–500, optional); `status` (enum
|
||
`completed|failed`, optional).
|
||
**Responses**:
|
||
|
||
- `200`: `{ items: QualityAnswerTraceSummary[], nextCursor? }`, where each summary is
|
||
`{ id, query, mode, createdAt, completed, evidenceBundleId?, evidenceState?, finalScore?, scores:
|
||
{ retrieval?, rerank?, final? }, profile: { embeddingModel?, embeddingVectorSpaceId?,
|
||
reasoningModel?, rerankModel?, retrievalProfileRevision?, projectionPublicationId?,
|
||
projectionVersion? }, stages: [{ name, status, candidateCount? }] }`.
|
||
- `400`; `404`; `503` Quality runtime unavailable; `401`/`403`.
|
||
|
||
`completed` represents terminal execution success, not evidence answerability. This endpoint is the
|
||
paginated/searchable summary view; use `GET /queries/{traceId}` for the authorized full execution
|
||
trace. Quality replay can also create AnswerTrace-shaped stage records without a terminal
|
||
`query.generate` step or EvidenceBundle, so consumers must not assume every history row has the
|
||
online-query step sequence.
|
||
|
||
Compatibility note: the current `status` filter treats any trace containing an `error` step as
|
||
`failed`, even when a later `query.generate:ok` makes `completed` true. Consequently, a recovered
|
||
multimodal-to-text fallback can return `completed: true` while matching `status=failed`; the two
|
||
filters are not yet a strict partition of terminal execution state.
|
||
|
||
### `PATCH /knowledge-spaces/{id}/quality/traces/{traceId}/missing/{itemKey}`
|
||
**Description**: Create or update the review state of one missing-evidence item with optimistic
|
||
revision checking.
|
||
**Auth**: Bearer; scope `knowledge-spaces:write`; the referenced trace must remain subject-owned and
|
||
currently visible.
|
||
**Path params**: `id` (uuid); `traceId` (uuid); `itemKey` (`sha256:` followed by 64 lowercase hex
|
||
characters).
|
||
**Body** (`application/json`, strict): `{ expectedRevision: int>=0, status:
|
||
enum(active|dismissed), reason?: string 1–2000 }`.
|
||
**Responses**:
|
||
|
||
- `200`: `MissingEvidenceReview` `{ id, knowledgeSpaceId, itemKey, status, reason?, revision,
|
||
actorSubjectId, createdAt, updatedAt }`.
|
||
- `400`; `404`; `409` revision conflict; `503` Quality runtime unavailable; `401`/`403`.
|
||
|
||
### `GET /knowledge-spaces/{id}/quality/traces/{traceId}/missing/{itemKey}/history`
|
||
**Description**: Fetch the immutable review history for one missing-evidence item.
|
||
**Auth**: Bearer; scope `knowledge-spaces:read`; the referenced trace must remain subject-owned and
|
||
currently visible.
|
||
**Path params**: same as the missing-evidence review endpoint.
|
||
**Responses**:
|
||
|
||
- `200`: `{ items: [{ id, action, actorSubjectId, fromStatus?, toStatus, reason?, revision,
|
||
createdAt }] }`.
|
||
- `400`; `404`; `503` Quality runtime unavailable; `401`/`403`.
|
||
|
||
### `POST /knowledge-spaces/{id}/golden-questions`
|
||
**Description**: Create a golden (reference) question for evaluation.
|
||
**Auth**: Bearer; scope `knowledge-spaces:write`.
|
||
**Path params**: `id` (uuid). **Body** (`application/json`, strict): `question` (string 1–4000, required); `expectedEvidenceIds` (uuid[], optional, default `[]`); `tags` (string[1–80][], optional, default `[]`); `metadata` (object, optional, default `{}`).
|
||
**Responses**:
|
||
- `201`: `GoldenQuestion` `{ id, knowledgeSpaceId, question, expectedEvidenceIds: uuid[], tags: string[], metadata, createdAt, updatedAt }`.
|
||
- `404`; `429` capacity; `401`/`403`.
|
||
|
||
### `GET /knowledge-spaces/{id}/golden-questions`
|
||
**Description**: List golden questions (cursor-paginated).
|
||
**Auth**: Bearer; scope `knowledge-spaces:read`.
|
||
**Path params**: `id` (uuid). **Query** (strict): `cursor` (optional); `limit` (int ≥1, optional, default 100).
|
||
**Responses**: `200` `{ items: GoldenQuestion[], nextCursor? }`; `400`; `404`; `401`/`403`.
|
||
|
||
### `GET /knowledge-spaces/{id}/golden-questions/{questionId}`
|
||
**Description**: Fetch a single golden question.
|
||
**Auth**: Bearer; scope `knowledge-spaces:read`.
|
||
**Path params**: `id` (uuid); `questionId` (uuid).
|
||
**Responses**: `200` `GoldenQuestion`; `404`; `401`/`403`.
|
||
|
||
### `PATCH /knowledge-spaces/{id}/golden-questions/{questionId}`
|
||
**Description**: Partially update a golden question.
|
||
**Auth**: Bearer; scope `knowledge-spaces:write`.
|
||
**Path params**: `id` (uuid); `questionId` (uuid). **Body** (`application/json`, strict, all optional): `question` (1–4000); `expectedEvidenceIds` (uuid[]); `tags` (string[1–80][]); `metadata` (object).
|
||
**Responses**: `200` updated `GoldenQuestion`; `404`; `401`/`403`.
|
||
|
||
### `POST /knowledge-spaces/{id}/golden-questions/{questionId}/annotations`
|
||
**Description**: Record a human evaluation annotation (answer correctness + per-evidence relevance).
|
||
**Auth**: Bearer; scope `knowledge-spaces:write`.
|
||
**Path params**: `id` (uuid); `questionId` (uuid). **Body** (`application/json`, strict): `answerCorrectness` (enum `correct|incorrect|not-answerable|partially-correct`, required); `evidenceRelevance` (array ≤50, optional, default `[]`) each `{ evidenceId: uuid, relevant: boolean, note?: string 1–1000 }`; `note` (string 1–1000, optional).
|
||
**Responses**: `200` annotated `GoldenQuestion` (annotation in metadata); `400`; `404`; `401`/`403`.
|
||
|
||
### `DELETE /knowledge-spaces/{id}/golden-questions/{questionId}`
|
||
**Description**: Delete a golden question.
|
||
**Auth**: Bearer; scope `knowledge-spaces:write`.
|
||
**Path params**: `id` (uuid); `questionId` (uuid).
|
||
**Responses**: `204`; `404`; `401`/`403`.
|
||
|
||
### `POST /knowledge-spaces/{id}/production-bad-cases`
|
||
**Description**: Capture a production answer trace as a bad case, queued (as a golden question) for review.
|
||
**Auth**: Bearer; scope `knowledge-spaces:write`.
|
||
**Path params**: `id` (uuid). **Body** (`application/json`, strict): `traceId` (uuid, required); `reason` (string 1–1000, optional); `tags` (string[1–80][] ≤20, optional, default `[]`).
|
||
**Responses**: `201` `GoldenQuestion`; `404` space/trace not found; `429` capacity; `401`/`403`.
|
||
|
||
### `PATCH /knowledge-spaces/{id}/failed-queries/{failedQueryId}`
|
||
**Description**: Annotate a failed query — `retrieval-miss` promotes it to a golden question, `coverage-gap` marks it annotated, `irrelevant` dismisses it.
|
||
**Auth**: Bearer; scope `knowledge-spaces:write`.
|
||
**Path params**: `id` (uuid); `failedQueryId` (uuid). **Body** (`application/json`, strict): `verdict` (enum `retrieval-miss|coverage-gap|irrelevant`, required); `expectedEvidenceIds` (uuid[] ≤100, optional) — carried onto the promoted golden question; `note` (string ≤2000, optional).
|
||
**Responses**:
|
||
- `200`: `FailedQuery` `{ id, knowledgeSpaceId, query, mode: enum(fast|deep|research|auto), trigger: enum(no-retrieval-evidence|low-confidence|abstained), status: enum(pending-triage|triaged|pending-annotation|annotated|dismissed|promoted), metadata, answerTraceId?, createdAt, updatedAt }`.
|
||
- `404`; `401`/`403`.
|
||
|
||
### `GET /knowledge-spaces/{id}/failed-queries/metrics`
|
||
**Description**: Failed-query counts by status plus the golden-question promotion rate.
|
||
**Auth**: Bearer; scope `knowledge-spaces:read`.
|
||
**Path params**: `id` (uuid).
|
||
**Responses**:
|
||
- `200`: `{ total: number, promotionRate: number, byStatus: { "pending-triage", triaged, "pending-annotation", annotated, dismissed, promoted: number } }`.
|
||
- `404`; `401`/`403`.
|
||
|
||
### `GET /knowledge-spaces/{id}/failed-queries/clusters`
|
||
**Description**: Group failed queries into clusters (most frequent first) with a representative per cluster.
|
||
**Auth**: Bearer; scope `knowledge-spaces:read`.
|
||
**Path params**: `id` (uuid). **Query** (strict): `limit` (int 1–1000, optional, default 200); `status` (enum, optional).
|
||
**Responses**:
|
||
- `200`: `{ clusters: [{ clusterKey: string, count: number, failedQueryIds: string[], representative: FailedQuery }] }`.
|
||
- `404`; `401`/`403`.
|
||
|
||
### `POST /knowledge-spaces/{id}/failed-queries/triage`
|
||
**Description**: Run the relevance-triage runner over a batch of pending failed queries, auto-assigning verdicts.
|
||
**Auth**: Bearer; scope `knowledge-spaces:write`.
|
||
**Path params**: `id` (uuid). **Query** (strict): `limit` (int 1–200, optional).
|
||
**Responses**:
|
||
- `200`: `{ triaged: number, verdicts: { "retrieval-miss", "coverage-gap", irrelevant, uncertain: number } }`.
|
||
- `404`; `501` relevance triage not configured; `401`/`403`.
|
||
|
||
### `GET /knowledge-spaces/{id}/failed-queries`
|
||
**Description**: List failed queries (cursor-paginated), optionally filtered by status.
|
||
**Auth**: Bearer; scope `knowledge-spaces:read`.
|
||
**Path params**: `id` (uuid). **Query** (strict): `cursor` (optional); `limit` (int 1–200, optional, default 50); `status` (enum, optional).
|
||
**Responses**: `200` `{ items: FailedQuery[], nextCursor? }`; `400`; `404`; `401`/`403`.
|
||
|
||
---
|
||
|
||
## Data sources, graph, semantic & research
|
||
|
||
> **Source object & conventions**
|
||
> - `Source` = `{ id, knowledgeSpaceId, name, type: enum(upload|object-storage|connector|web), uri, status: enum(active|syncing|error|disabled), permissionScope: string[], metadata, version: int≥1, createdAt, updatedAt }`.
|
||
> - **Optimistic locking**: `version` is bumped on every write. Pass it back as `expectedVersion` on PATCH to fail with `409` instead of overwriting a concurrent modification.
|
||
> - **Scheduled sync**: set `metadata.syncPolicy` to `{"everyHours": N}` (1–720) or `{"dailyAt": ["HH:MM", …], "utcOffset": "±HH:MM"?}`. Invalid policies are rejected with `400` at create/update. A background scheduler (multi-replica safe; `KNOWLEDGE_SOURCE_SYNC*` env) then re-syncs due sources: web → re-crawl with content-hash dedup, connector pages → re-import previously imported pages whose `lastEditedTime` changed, drive → re-download previously imported files. The scheduler records progress under `metadata.syncState` `{ lastSyncAt, lastSyncStatus: ok|error, lastSyncError?, nextSyncAt, syncStartedAt? }`.
|
||
> - **Reserved metadata keys** (managed by the platform; user PATCHes should carry them through): `tenantId` (auto-stamped on create/update), `sync` (last sync summary), `crawled`, `imported`, `importedFiles` (per-source sync state), `syncState`.
|
||
|
||
### `POST /knowledge-spaces/{id}/sources`
|
||
**Description**: Register a new data source (upload/object-storage/connector/web).
|
||
**Auth**: Bearer; scope `knowledge-spaces:write`.
|
||
**Path params**: `id` (uuid). **Body** (`application/json`, strict): `name` (string 1–200, required); `type` (enum `upload|object-storage|connector|web`, required); `uri` (string, min 1, required); `status` (enum `active|syncing|error|disabled`, optional); `permissionScope` (string[], optional); `metadata` (object, optional) — connector/provider config (e.g. `pluginId`, `provider`, `datasource`, `credentials`, `parameters`) and optionally `syncPolicy` (see conventions above). The owning `tenantId` is stamped into metadata automatically.
|
||
**Responses**:
|
||
- `201`: `Source` (shape above, `version: 1`).
|
||
- `400` invalid `metadata.syncPolicy`; `404`; `429` capacity; `401`/`403`.
|
||
|
||
### `GET /knowledge-spaces/{id}/sources`
|
||
**Description**: List a space's sources (cursor-paginated).
|
||
**Auth**: Bearer; scope `knowledge-spaces:read`.
|
||
**Path params**: `id` (uuid). **Query** (strict): `limit` (int 1–200, optional, default 50); `cursor` (optional).
|
||
**Responses**: `200` `{ items: Source[], nextCursor? }`; `400`; `404`; `401`/`403`.
|
||
|
||
### `GET /knowledge-spaces/{id}/sources/{sourceId}`
|
||
**Description**: Fetch a single source.
|
||
**Auth**: Bearer; scope `knowledge-spaces:read`.
|
||
**Path params**: `id` (uuid); `sourceId` (uuid).
|
||
**Responses**: `200` `Source`; `404`; `401`/`403`.
|
||
|
||
### `PATCH /knowledge-spaces/{id}/sources/{sourceId}`
|
||
**Description**: Update a source's mutable fields (name/status/metadata), optionally guarded by optimistic locking.
|
||
**Auth**: Bearer; scope `knowledge-spaces:write`.
|
||
**Path params**: `id` (uuid); `sourceId` (uuid). **Body** (`application/json`, strict, all optional): `name` (1–200); `status` (enum); `metadata` (object, **replaces wholesale** — `tenantId` is re-stamped automatically); `expectedVersion` (int ≥1) — the `version` from your last read; when provided, a concurrent modification makes the update fail with `409` instead of overwriting it.
|
||
**Responses**:
|
||
- `200`: updated `Source` (`version` bumped).
|
||
- `400` invalid `metadata.syncPolicy`; `404`; `409` concurrent modification (`expectedVersion` mismatch); `401`/`403`.
|
||
|
||
### `DELETE /knowledge-spaces/{id}/sources/{sourceId}`
|
||
**Description**: Delete a source and, by default, cascade-delete its documents.
|
||
**Auth**: Bearer; scope `knowledge-spaces:write`.
|
||
**Path params**: `id` (uuid); `sourceId` (uuid). **Query** (strict): `documents` (enum `cascade|keep`, optional, default `cascade`).
|
||
**Responses**: `204`; `404`; `401`/`403`.
|
||
|
||
### `POST /knowledge-spaces/{id}/sources/{sourceId}/crawl`
|
||
**Description**: Trigger a website crawl for a `web` source and materialize crawled pages into documents, with content-hash dedup: pages whose content is byte-identical to the last crawl are **skipped**; changed pages are re-materialized and their superseded document is cascade-deleted (**replaced**); new pages are imported. Per-URL state lives in `metadata.crawled` `{ [url]: { documentAssetId, sha256 } }`.
|
||
**Auth**: Bearer; scope `knowledge-spaces:write`.
|
||
**Path params**: `id` (uuid); `sourceId` (uuid). **Body**: None (target from the source's `uri`/metadata).
|
||
**Responses**:
|
||
- `200`: `WebsiteCrawlResult` `{ pages: [{ sourceUrl, content, title?, description? }], status?, total?, completed?, failed?, imported?, replaced?, skipped? }` — `imported` = newly materialized documents, `skipped` = unchanged pages deduped by content hash, `replaced` = superseded documents deleted for changed pages.
|
||
- `400` not a web source; `404`; `501` connector not configured; `502` crawl failed; `401`/`403`.
|
||
|
||
### `GET /knowledge-spaces/{id}/sources/{sourceId}/pages`
|
||
**Description**: List authorized pages from an online-document connector (e.g. Notion), grouped by workspace.
|
||
**Auth**: Bearer; scope `knowledge-spaces:read`.
|
||
**Path params**: `id` (uuid); `sourceId` (uuid).
|
||
**Responses**:
|
||
- `200`: `OnlineDocumentPages` `{ workspaces: [{ workspaceId?, workspaceName?, total?, pages: [{ pageId, pageName, type, parentId?, lastEditedTime? }] }] }`.
|
||
- `400` not a connector; `404`; `501` connector not configured; `502` provider failed; `401`/`403`.
|
||
|
||
### `POST /knowledge-spaces/{id}/sources/{sourceId}/test`
|
||
**Description**: Validate the stored credentials for a source against its provider.
|
||
**Auth**: Bearer; scope `knowledge-spaces:write`.
|
||
**Path params**: `id` (uuid); `sourceId` (uuid).
|
||
**Responses**: `200` `{ valid: boolean, error? }`; `404`; `501` tester not configured; `401`/`403`.
|
||
|
||
### `POST /knowledge-spaces/{id}/sources/{sourceId}/import`
|
||
**Description**: Import selected online-document pages as documents; re-import skips pages whose `lastEditedTime` is unchanged. Imported pages are recorded in `metadata.imported` `{ [pageId]: { documentAssetId, lastEditedTime? } }`, which scheduled sync refreshes (never-imported pages are not auto-added — selection stays manual).
|
||
**Auth**: Bearer; scope `knowledge-spaces:write`.
|
||
**Path params**: `id` (uuid); `sourceId` (uuid). **Body** (`application/json`, strict): `pages` (array 1–200) each `{ pageId (min 1, required), workspaceId (min 1, required), type (min 1, required), name? (1–200), lastEditedTime? (min 1) }`.
|
||
**Responses**:
|
||
- `200`: `SourceImportResult` `{ documents: [{ documentAssetId, filename }], failed: [{ filename, error }], skipped: string[] }`.
|
||
- `400` not a connector; `404`; `501`; `502`; `401`/`403`.
|
||
|
||
### `GET /knowledge-spaces/{id}/sources/{sourceId}/files`
|
||
**Description**: Browse files from an online-drive connector (e.g. S3/Drive), grouped by bucket.
|
||
**Auth**: Bearer; scope `knowledge-spaces:read`.
|
||
**Path params**: `id` (uuid); `sourceId` (uuid). **Query** (strict): `bucket` (optional); `prefix` (optional); `maxKeys` (int 1–1000, optional).
|
||
**Responses**:
|
||
- `200`: `OnlineDriveFiles` `{ buckets: [{ bucket?, isTruncated?, files: [{ id, name, type, size? }] }] }`.
|
||
- `400` not a drive connector; `404`; `501`; `502`; `401`/`403`.
|
||
|
||
### `POST /knowledge-spaces/{id}/sources/{sourceId}/import-files`
|
||
**Description**: Import selected online-drive files as documents. Successfully imported files are recorded in `metadata.importedFiles` `{ [fileId]: { name, bucket?, mimeType? } }`, which scheduled sync re-downloads.
|
||
**Auth**: Bearer; scope `knowledge-spaces:write`.
|
||
**Path params**: `id` (uuid); `sourceId` (uuid). **Body** (`application/json`, strict): `files` (array 1–200) each `{ id (min 1, required), name (1–255, required), bucket?, mimeType? }`.
|
||
**Responses**: `200` `SourceImportResult` `{ documents, failed, skipped }`; `400`; `404`; `501`; `502`; `401`/`403`.
|
||
|
||
### `GET /knowledge-spaces/{id}/graph/traverse`
|
||
**Description**: Bounded breadth-limited traversal of the knowledge graph from a seed entity.
|
||
**Auth**: Bearer; scope `knowledge-spaces:read`.
|
||
**Path params**: `id` (uuid). **Query** (strict): `entityId` (uuid, required); `depth` (int 1–2, optional, default 2); `fanout` (int 1–50, optional, default 20); `maxNodes` (int 1–200, optional, default 50); `timeoutMs` (int 1–5000, optional, default 250).
|
||
**Responses**:
|
||
- `200`: `{ entities: GraphEntity[], relations: GraphRelation[], truncated, metrics: { depthReached, elapsedMs, exploredRelations, fanout, maxDepth, maxNodes, timedOut } }`. `GraphEntity` `{ id, knowledgeSpaceId, name, canonicalKey, aliases: string[], type: enum(date|metric|organization|person|policy|product|term), confidence, depth, extractionVersion, permissionScope: string[], sourceNodeIds: string[], metadata, createdAt, updatedAt }`. `GraphRelation` `{ id, knowledgeSpaceId, subjectEntityId, objectEntityId, type: enum(contradicts|defines|depends_on|mentions|references|supersedes), confidence, depth, extractionVersion, permissionScope, sourceNodeIds, metadata, createdAt, updatedAt }`.
|
||
- `404` space/seed entity not found; `401`/`403`.
|
||
|
||
### `POST /knowledge-spaces/{id}/semantic-views/topic/materialize`
|
||
**Description**: Materialize a KnowledgeFS topic semantic view (virtual topic path over documents).
|
||
**Auth**: Bearer; scope `knowledge-spaces:write`.
|
||
**Path params**: `id` (uuid). **Body** (`application/json`, strict, all optional): `topicName` (1–120); `topicSlug` (1–120, kebab-case); `generatedVersion` (1–120); `limit` (int 1–100, default 50).
|
||
**Responses**: `200` `{ knowledgeSpaceId, topicName, topicSlug, generatedVersion, documentCount, pathCount }`; `400`; `404`; `401`/`403`.
|
||
|
||
### `POST /knowledge-spaces/{id}/semantic-views/entities/extract`
|
||
**Description**: Extract & index semantic entities/relations from the space's nodes via the model provider.
|
||
**Auth**: Bearer; scope `knowledge-spaces:write`.
|
||
**Path params**: `id` (uuid). **Body** (`application/json`, strict): `limit` (int 1–100, optional, default 50).
|
||
**Responses**: `200` `{ knowledgeSpaceId, extractionMode: "provider", nodesScanned, nodesUpdated, entitiesExtracted, graphEntitiesIndexed, graphRelationsIndexed }`; `400`; `404`; `401`/`403`.
|
||
|
||
### `POST /knowledge-spaces/{id}/semantic-views/communities/materialize`
|
||
**Description**: Materialize a KnowledgeFS community semantic view (entity-cluster grouping).
|
||
**Auth**: Bearer; scope `knowledge-spaces:write`.
|
||
**Path params**: `id` (uuid). **Body** (`application/json`, strict): `generatedVersion` (1–120, optional).
|
||
**Responses**: `200` `{ knowledgeSpaceId, generatedVersion, communityCount, entityCount, documentCount, pathCount }`; `400`; `404`; `401`/`403`.
|
||
|
||
### `POST /research-tasks/plan`
|
||
**Description**: Produce a dry-run plan (cost/latency/retrieval estimates) for a research task without executing.
|
||
**Auth**: Bearer; scope `knowledge-spaces:read`.
|
||
**Body** (`application/json`, strict): `knowledgeSpaceId` (uuid, required); `query` (string 1–16000, required); `mode` (enum `auto|deep|fast|research`, optional); `topK` (int 1–50, optional); `budgetUsd` (number ≥0, optional).
|
||
|
||
An omitted `mode` uses the published `defaultMode`. An explicit `auto` is resolved through the
|
||
published reasoning model before the deterministic dry-run planner runs; `retrievalPlan` preserves
|
||
`requestedMode: "auto"` and reports the concrete `resolvedMode`.
|
||
|
||
**Responses**:
|
||
- `200`: `ResearchTaskDryRunPlan` `{ knowledgeSpaceId, query, strategyVersion, budget: { budgetUsd?, exceedsBudget, remainingBudgetUsd? }, estimates: { costUsd: { currency, estimated, min, max }, inputTokens, outputTokens, totalTokens, latencyMs: { p50, p95 }, retrievalSteps, scannedResources, toolCalls, cacheHitProbability }, retrievalPlan: { requestedMode, resolvedMode, queryLanguage, topK, denseTopK, ftsTopK, fusionLimit, rerankCandidateLimit, strategyVersion }, steps: [{ name: enum(analyze|generate|inspect|plan|retrieve), estimatedInputTokens, estimatedOutputTokens, estimatedToolCalls, estimatedLatencyMs, estimatedCostUsd }] }`.
|
||
- `400`; `404`; `401`/`403`.
|
||
|
||
### `POST /research-tasks`
|
||
**Description**: Create and enqueue an asynchronous research task job.
|
||
**Auth**: Bearer; scope `knowledge-spaces:write`.
|
||
**Body** (`application/json`): `knowledgeSpaceId` (uuid, required); `query` (string 1–16000, required); `mode` (enum `auto|deep|fast|research`, optional); `topK` (int 1–50, optional); `budgetUsd` (number ≥0, optional); `limits` (object, optional) `{ maxRetrievalSteps?, maxScannedResources?, maxToolCalls?, timeoutMs? }`; `metadata` (object, optional); `permissionScope` (object, optional).
|
||
|
||
For an explicit `auto`, creation authorizes the caller, freezes the published publication/profile
|
||
tuple, invokes that profile's reasoning model once, and persists the concrete mode plus bounded
|
||
routing provenance. Queue retries and worker restarts reuse the persisted decision and never
|
||
reclassify the query. Router failure uses the frozen profile's `defaultMode`; omitting `mode` uses
|
||
that default directly without an LLM call. Auto creation requires a query-ready published runtime
|
||
snapshot and returns `503` when that immutable tuple is unavailable; the legacy mutable-profile
|
||
compatibility path does not admit Auto.
|
||
|
||
**Responses**:
|
||
- `201`: `ResearchTaskJob` `{ id, tenantId, subjectId, knowledgeSpaceId, queueJobId, query, stage: enum(queued|planning|retrieving|analyzing|generating|completed|failed|canceled), limits?, budgetUsd?, cost: { totalUsd, budgetUsd?, budgetExceeded?, entries: [{ step, provider, costUsd, usage, recordedAt: number }] }, error?, createdAt: number, updatedAt: number, completedAt?: number }`.
|
||
- `400`; `404`; `422` limits exceeded `{ error, violations: [{ limit, limitValue, estimatedValue }] }`; `401`/`403`.
|
||
|
||
### `GET /research-tasks/{id}`
|
||
**Description**: Get the current status/result of a research task job.
|
||
**Auth**: Bearer; scope `knowledge-spaces:read`.
|
||
**Path params**: `id` (string, min 1).
|
||
**Responses**: `200` `ResearchTaskJob`; `404`; `401`/`403`.
|
||
|
||
### `GET /research-tasks/{id}/partials`
|
||
**Description**: List a research task's partial evidence bundles (cursor-paginated).
|
||
**Auth**: Bearer; scope `knowledge-spaces:read`.
|
||
**Path params**: `id` (string, min 1). **Query** (strict): `limit` (int 1–100, optional, default 25); `cursor` (optional).
|
||
**Responses**:
|
||
- `200`: `{ items: [{ tenantId, knowledgeSpaceId, researchTaskJobId, sequence: int>0, evidenceBundle }], nextCursor? }`. `EvidenceBundle` `{ id, query, state: enum(answerable|partial|not-enough-evidence|conflict|permission-limited), items: [{ nodeId, text, score: 0–1, scores, freshness, citations[], conflicts[], metadata }], missingEvidence: [{ text, reason: enum(not-retrieved|permission-filtered|stale|conflict|unknown), expectedEvidenceId?, metadata }], traceId?, createdAt }`.
|
||
- `404`; `401`/`403`.
|
||
|
||
### `GET /research-tasks/{id}/events`
|
||
**Description**: Stream research task progress as SSE.
|
||
**Auth**: Bearer; scope `knowledge-spaces:read`.
|
||
**Path params**: `id` (string, min 1). **Query** (strict): `limit` (int 1–100, optional, default 25); `cursor` (optional).
|
||
**Responses**: `200` (`text/event-stream`) progress event frames; `404`; `401`/`403`.
|
||
|
||
### `DELETE /research-tasks/{id}`
|
||
**Description**: Request cancellation of an in-flight research task job.
|
||
**Auth**: Bearer; scope `knowledge-spaces:write`.
|
||
**Path params**: `id` (string, min 1).
|
||
**Responses**: `200` `ResearchTaskJob` (stage `canceled`); `404`; `409` already terminal; `401`/`403`.
|
||
|
||
---
|
||
|
||
## System, policy, KnowledgeFS & agent workspace
|
||
|
||
### `GET /health`
|
||
**Description**: Platform runtime + per-component health.
|
||
**Auth**: **None (public)**.
|
||
**Responses**: `200` `{ ok: boolean, runtime: enum(cloudflare-workers|node-docker), components: Record<string, boolean> }`.
|
||
|
||
### `GET /openapi.json`
|
||
**Description**: The machine-readable OpenAPI 3 document for the whole API (served via `app.doc`).
|
||
**Auth**: **None (public)**.
|
||
**Responses**: `200` OpenAPI JSON document.
|
||
|
||
### `GET /bulk-jobs/{id}`
|
||
**Description**: Progress for a bulk document operation job.
|
||
**Auth**: Bearer; scope `knowledge-spaces:read`.
|
||
**Path params**: `id` (string, min 1).
|
||
**Responses**:
|
||
- `200`: `{ id, knowledgeSpaceId, type: enum(document_upload|document_delete|document_reindex), status: enum(running|completed|failed), totalItems, completedItems, failedItems, failedItemIds: string[], createdAt, updatedAt }`.
|
||
- `404`; `503` dependencies unavailable; `401`/`403`.
|
||
|
||
### `GET /retention-policy`
|
||
**Description**: The tenant-level retention policy.
|
||
**Auth**: Bearer; scope `knowledge-spaces:read`.
|
||
**Responses**:
|
||
- `200`: `RetentionPolicy` `{ id, tenantId, knowledgeSpaceId: uuid|null, scope: enum(tenant|knowledge_space), answerTraceRetentionDays: int>0, evidenceCacheRetentionDays: int>0, inactiveProjectionRetentionDays: int>0, parseArtifactVersions: int>0, rawDocumentRetentionDays: int>0|null, sessionInactivityMinutes: int>0, createdAt, updatedAt }`.
|
||
- `401`/`403`.
|
||
|
||
`answerTraceRetentionDays` is the operational cutoff consumed by bounded asynchronous retention
|
||
cleanup. Updating the policy does not synchronously delete traces, and a trace can become
|
||
unreadable earlier when its permission snapshot, current content visibility, or knowledge-space
|
||
lifecycle state no longer passes authorization.
|
||
|
||
The default operational policy is 90 days for AnswerTrace, 7 days for evidence cache, 30 days for
|
||
inactive projections, three parse-artifact versions, no raw-document expiry, and 30 minutes of
|
||
session inactivity. Tenant and knowledge-space policies are stored independently; an absent
|
||
knowledge-space policy receives its own defaults rather than inheriting the tenant row. The
|
||
manifest field `retentionPolicy.traceRetentionDays` is a separate manifest value; AnswerTrace
|
||
cleanup uses this API's `answerTraceRetentionDays`.
|
||
|
||
Retention policy routes are declarative and there is no public synchronous cleanup endpoint. A
|
||
deployment must schedule the bounded retention workers for a configured cutoff to take effect.
|
||
|
||
### `PATCH /retention-policy`
|
||
**Description**: Update the tenant-level retention policy.
|
||
**Auth**: Bearer; scope `knowledge-spaces:write`.
|
||
**Body** (`application/json`, strict, all optional): `answerTraceRetentionDays`, `evidenceCacheRetentionDays`, `inactiveProjectionRetentionDays`, `parseArtifactVersions`, `sessionInactivityMinutes` (each int>0); `rawDocumentRetentionDays` (int>0|null).
|
||
**Responses**: `200` updated `RetentionPolicy`; `400`; `401`/`403`.
|
||
|
||
### `GET /knowledge-spaces/{id}/retention-policy`
|
||
**Description**: The retention policy for a specific knowledge space.
|
||
**Auth**: Bearer; scope `knowledge-spaces:read`.
|
||
**Path params**: `id` (uuid).
|
||
**Responses**: `200` `RetentionPolicy` (`scope` may be `knowledge_space`); `404`; `401`/`403`.
|
||
|
||
### `PATCH /knowledge-spaces/{id}/retention-policy`
|
||
**Description**: Update the retention policy for a specific knowledge space.
|
||
**Auth**: Bearer; scope `knowledge-spaces:write`.
|
||
**Path params**: `id` (uuid). **Body** (`application/json`, strict): same fields as `PATCH /retention-policy`.
|
||
**Responses**: `200` updated `RetentionPolicy`; `400`; `404`; `401`/`403`.
|
||
|
||
> **KnowledgeFS virtual filesystem** — the `/fs/*` routes browse a virtual tree over the space's
|
||
> sources/knowledge/evidence/workspaces. `path` (and `oldPath`/`newPath`) must match
|
||
> `^/(sources|knowledge|evidence|workspaces)(/…)*$`. All query objects are strict; `consistencyClass`
|
||
> ∈ `path-consistent|snapshot-consistent|cache-consistent|eventual-preview`.
|
||
|
||
### `GET /knowledge-spaces/{id}/fs/ls`
|
||
**Description**: List direct children of a KnowledgeFS virtual directory.
|
||
**Auth**: Bearer; scope `knowledge-spaces:read`.
|
||
**Path params**: `id` (uuid). **Query** (strict): `path` (required, namespaced); `limit` (int ≥1, required); `cursor` (optional); `consistencyClass` (optional).
|
||
**Responses**:
|
||
- `200`: `{ path, items: [{ kind: enum(directory|resource), name, path, metadata, resourceType?: enum(source|document|node|artifact|evidence|workspace), targetId?, version? }], truncated, nextCursor?, consistencyClass?, preview? }`.
|
||
- `400`; `404`; `401`/`403`.
|
||
|
||
### `GET /knowledge-spaces/{id}/fs/tree`
|
||
**Description**: Recursive KnowledgeFS directory tree.
|
||
**Auth**: Bearer; scope `knowledge-spaces:read`.
|
||
**Path params**: `id` (uuid). **Query** (strict): `path` (required); `limit` (int ≥1, required); `depth` (int 1–8, optional); `cursor` (optional); `consistencyClass` (optional).
|
||
**Responses**: `200` `{ path, root: <nested tree>, truncated, nextCursor?, consistencyClass?, preview? }`; `400`; `404`; `401`/`403`.
|
||
|
||
### `GET /knowledge-spaces/{id}/fs/grep`
|
||
**Description**: Scoped full-text search across KnowledgeFS content.
|
||
**Auth**: Bearer; scope `knowledge-spaces:read`.
|
||
**Path params**: `id` (uuid). **Query** (strict): `q` (string 1–4000, required); `path` (required); `limit` (int ≥1, required); `cursor` (optional); `consistencyClass` (optional); `timeoutMs` (int 1–10000, optional).
|
||
**Responses**:
|
||
- `200`: `{ path, matches: [{ kind: enum(node|segment), path, snippet, startOffset, endOffset, metadata, nodeId?, segmentId? }], truncated, nextCursor? }`.
|
||
- `400`; `404`; `401`/`403`.
|
||
|
||
### `GET /knowledge-spaces/{id}/fs/find`
|
||
**Description**: Search KnowledgeFS entries by name/metadata/resource-type.
|
||
**Auth**: Bearer; scope `knowledge-spaces:read`.
|
||
**Path params**: `id` (uuid). **Query** (strict): `path` (required); `limit` (int ≥1, required); `cursor` (optional); `consistencyClass` (optional); `nameContains` (1–240, optional); `metadataKey` (1–120, optional); `metadataValue` (1–4000, optional); `resourceType` (enum, optional).
|
||
**Responses**: `200` (same shape as `fs/ls`); `400`; `404`; `401`/`403`.
|
||
|
||
### `GET /knowledge-spaces/{id}/fs/diff`
|
||
**Description**: Text (and optional semantic) diff between two KnowledgeFS paths.
|
||
**Auth**: Bearer; scope `knowledge-spaces:read`.
|
||
**Path params**: `id` (uuid). **Query** (strict): `oldPath` (required); `newPath` (required); `mode` (enum `line|word`, optional); `semantic` (enum `"true"|"false"`, optional); `consistencyClass` (optional).
|
||
**Responses**:
|
||
- `200`: `{ mode, oldPath, newPath, operations: [{ kind: enum(equal|insert|delete), text, oldStart?, oldEnd?, newStart?, newEnd? }], stats: { equal, insert, delete }, semantic?: { summary, changes: [{ category, summary, evidence: string[] }], metadata, model? } }`.
|
||
- `400`; `404`; `503` diff unavailable; `401`/`403`.
|
||
|
||
### `GET /knowledge-spaces/{id}/fs/open_node`
|
||
**Description**: A single citation-ready KnowledgeFS node with its source citation.
|
||
**Auth**: Bearer; scope `knowledge-spaces:read`.
|
||
**Path params**: `id` (uuid). **Query** (strict): `nodeId` (uuid, required); `consistencyClass` (optional).
|
||
**Responses**:
|
||
- `200`: `{ citation: { documentAssetId, parseArtifactId, artifactHash, sectionPath: string[], startOffset, endOffset, pageNumber? }, node: KnowledgeNode }`; `KnowledgeNode` `{ id, knowledgeSpaceId, documentAssetId, parseArtifactId, artifactHash, kind: enum(chunk|section|table|image|summary), text, startOffset, endOffset, sourceLocation, permissionScope, metadata, updatedAt? }`.
|
||
- `404`; `401`/`403`.
|
||
|
||
### `GET /knowledge-spaces/{id}/fs/cat`
|
||
**Description**: Read file content at a KnowledgeFS path.
|
||
**Auth**: Bearer; scope `knowledge-spaces:read`.
|
||
**Path params**: `id` (uuid). **Query** (strict): `path` (required); `limit` (int ≥1, optional); `cursor` (optional); `consistencyClass` (optional).
|
||
**Responses**: `200` `{ path, contentType, text, truncated, nextCursor? }`; `404`; `401`/`403`.
|
||
|
||
### `GET /knowledge-spaces/{id}/fs/stat`
|
||
**Description**: Metadata for a KnowledgeFS path without reading content.
|
||
**Auth**: Bearer; scope `knowledge-spaces:read`.
|
||
**Path params**: `id` (uuid). **Query** (strict): `path` (required); `consistencyClass` (optional).
|
||
**Responses**:
|
||
- `200`: `{ path, targetId, resourceType: enum(source|document|node|artifact|evidence|workspace), metadata, contentType?, sha256?, sizeBytes?, version?, parserStatus?: enum(pending|parsed|failed), consistencyClass?, preview? }`.
|
||
- `404`; `401`/`403`.
|
||
|
||
### `POST /knowledge-spaces/{id}/fs/write`
|
||
**Description**: Overwrite a KnowledgeFS file with new text content.
|
||
**Auth**: Bearer; scope `knowledge-spaces:write`.
|
||
**Path params**: `id` (uuid). **Body** (`application/json`, strict): `path` (required, namespaced); `text` (string, ≤262144 bytes / 256 KiB, required).
|
||
**Responses**: `200` `{ path, targetId, objectKey, version, bytesWritten, mode: "write" }`; `400`; `404`; `401`/`403`.
|
||
|
||
### `POST /knowledge-spaces/{id}/fs/append`
|
||
**Description**: Append text to an existing KnowledgeFS file.
|
||
**Auth**: Bearer; scope `knowledge-spaces:write`.
|
||
**Path params**: `id` (uuid). **Body** (`application/json`, strict): `path` (required, namespaced); `text` (string, ≤256 KiB, required).
|
||
**Responses**: `200` `{ path, targetId, objectKey, version, bytesWritten, mode: "append" }`; `400`; `404`; `401`/`403`.
|
||
|
||
### `POST /agent-workspace-snapshots`
|
||
**Description**: Create an immutable agent-workspace snapshot (mounts, evidence, command log, source versions).
|
||
**Auth**: Bearer; scope `knowledge-spaces:write`.
|
||
**Body** (`application/json`, strict): `knowledgeSpaceId` (uuid, required); `indexProjection` `{ fingerprint (1–512), projectionIds: string[] }` (required); `manifestVersion` (int>0, default 1); `commandLog` (array, default `[]`) each `{ command (1–4000), startedAt, completedAt?, input (default {}), outputSummary? (≤4000), cost? }`; `evidenceBundles` (EvidenceBundle[], default `[]`); `mounts` (ResourceMount[], default `[]`); `pathVersions` (array, default `[]`) `{ virtualPath (1–1000), version (1–512) }`; `sourceVersions` (array, default `[]`) `{ provider (1–120), providerResourceKey (1–1000), version (1–512) }`; `traceIds` (string[], default `[]`); `researchTaskJobId` (1–240, optional); `metadata` (object, default `{}`).
|
||
**Responses**:
|
||
- `201`: `AgentWorkspaceSnapshot` `{ id, tenantId, knowledgeSpaceId, fingerprint (/^snapshot-sha256:[a-f0-9]{64}$/), manifestVersion, commandLog, evidenceBundles, mounts, indexProjection, pathVersions, sourceVersions, permissionSnapshot: { subjectId, tenantId, scopes: string[] }, traceIds, researchTaskJobId?, metadata, createdAt }`.
|
||
- `400`; `404`; `401`/`403`.
|
||
|
||
### `GET /agent-workspace-snapshots/{id}`
|
||
**Description**: Fetch a previously created agent-workspace snapshot.
|
||
**Auth**: Bearer; scope `knowledge-spaces:read`.
|
||
**Path params**: `id` (string, 1–240).
|
||
**Responses**: `200` `AgentWorkspaceSnapshot`; `404`; `401`/`403`.
|
||
|
||
### `POST /agent-workspace-snapshots/{id}/replay`
|
||
**Description**: Re-execute a snapshot's recorded command log and report per-command drift.
|
||
**Auth**: Bearer; scope `knowledge-spaces:read` (this POST maps to `read`, unlike other POSTs).
|
||
**Path params**: `id` (string, 1–240). **Body**: None.
|
||
**Responses**:
|
||
- `200`: `AgentWorkspaceReplay` `{ id, snapshotId, tenantId, knowledgeSpaceId, startedAt, completedAt, traceId?, summary: { total, matched, changed, failed }, commands: [{ commandIndex, command, status: enum(matched|changed|failed), input, startedAt, completedAt, originalOutputSummary?, replayedOutputSummary?, error? }] }`.
|
||
- `404`; `409` replay failed; `401`/`403`.
|