Key Features

Manual Document Libraries

How teams create, manage, search, and read manually maintained Markdown libraries.

Summary

Manual document libraries let an organization store curated Markdown documents that can be retrieved by API callers and local MCP agents. A library is named within an organization, and each document is identified by its normalized path inside that library.

The first implementation supports manual writes only. External source ingestion, secondary embedding passes, and arbitrary front-matter search are modeled for later phases but are not active write paths yet.

Library Management

Library names are part of API routes and MCP tool input, so they must be stable and route-safe. Names may contain only ASCII letters, numbers, hyphens, and underscores.

OperationEndpointNotes
List librariesGET /api/v1/librariesReturns libraries in the caller's active organization.
Create libraryPOST /api/v1/librariesRequires name; optional description.
Read libraryGET /api/v1/libraries/{name}Resolves by organization and library name.
Update libraryPUT /api/v1/libraries/{name}Renames the library; omitted descriptions preserve the existing description.
Delete libraryDELETE /api/v1/libraries/{name}Deletes the library and its documents.

Library and document APIs use the caller's active organization claim. Team ownership is recorded when present, but document identity remains organization plus library plus path.

Document Writes

Documents are written through PUT /api/v1/libraries/{name}/documents.

The request body contains the document path and full Markdown source:

{
  "path": "guides/getting-started.md",
  "content": "---\ntitle: Getting Started\nkeywords:\n  - docs\n---\n\n# Getting Started\n\nBody text."
}

The path is intentionally body data instead of a route segment. This keeps nested paths opaque and avoids generated clients interpolating raw slashes into URLs.

On write, Zeeq normalizes the path, parses Markdown front matter, extracts title, keywords, headings, body content, content hash, and estimated token count, then stores the document with ProcessingStatus = Pending. If the parsed content hash has not changed, the write service returns the existing row without updating timestamps or processing state.

Relative . and .. path segments are rejected before persistence.

Retrieval Modes

Manual libraries support two retrieval modes.

ModeStore methodBehavior
Path lookupGetByPathAsyncResolves exact normalized path, suffix path, or file name.
Combined searchSearchAsyncRuns ranked Postgres websearch over title, keywords, headings, and content and trigram title matching in one pass, so partial names and typos still surface results.

Combined search ranking

A single query unions both signals. The search_vector GIN index and the trigram GIN index on title_normalized are combined with an OR predicate so Postgres can use both via a bitmap OR. The full-text rank (ts_rank_cd) is normalized with DivideByMeanHarmonicDistanceBetweenExtents | DivideByItselfPlusOne so it is bounded to [0, 1) and directly comparable to the [0, 1] trigram similarity.

Results are ordered by a tiered combiner so a content (full-text) hit always outranks a fuzzy-only hit, and a document matching both signals ranks highest:

score = (matchesFullText ? 1.0 : 0.0)   // content match is the dominant tier
      +  fullTextScore                   // orders within the full-text tier
      + (matchesFuzzy ? fuzzyScore : 0)  // tie-break; lifts "Both" matches

Each result reports its matchType (FullText, Fuzzy, or Both) and both raw component scores (fullTextScore, fuzzyScore) so callers can see why a document surfaced.

Document deletes use DELETE /api/v1/libraries/{name}/documents?path=.... The query parameter is normalized with the same path rules used by writes.

Caching

Path resolution is cached through HybridCache with keys shaped as:

doc:path:{organizationId}:{libraryId}:{normalizedInput}

Both absolute and local cache expiration are 60 seconds. Cache keys include the library id so the same document name in different libraries cannot collide. Missing documents are not retained in cache; the miss is removed immediately so a later write can be read without waiting for a negative-cache entry to expire.

There is no write-driven invalidation in this phase. The 60-second TTL keeps the optimization bounded while avoiding extra invalidation contracts before the ingestion and indexing flows are complete.

MCP Tools

Authenticated MCP clients can read manual document libraries through the Zeeq MCP server. Tools use ILibraryDocumentStore directly and always require an organization id. Document tools also require the library name.

ToolRequired parametersReturns
list_librariesorganizationIdJSON array of libraries.
list_documentsorganizationId, libraryJSON array of document summaries.
read_document_by_pathorganizationId, library, pathMarkdown document content or a not-found message.
search_documentsorganizationId, library, query, optional limitJSON array of combined full-text + fuzzy results, each with its matchType and component scores.

Search limits default to 10 and are capped at 50. Blank organization, library, path, or query inputs return descriptive validation messages rather than throwing transport-level errors.