13 KiB
Conversation → API patterns
Treat these as recipes, not scripts. The agent decides when to combine them. Every example is plain HTTP — pick whichever client your environment already has (curl, fetch, requests, etc.).
Cross-platform note (Windows / macOS / Linux)
The bash + curl + jq snippets below are written for macOS / Linux. On Windows, translate them to whichever shell the user is in:
| What the bash example does | PowerShell equivalent | cmd.exe equivalent |
|---|---|---|
$TOKEN env var |
$env:LLM_WIKI_API_TOKEN |
%LLM_WIKI_API_TOKEN% |
URL-encode a stringprintf %s "$x" | jq -sRr @uri |
[System.Uri]::EscapeDataString($x) |
use a helper / curl --data-urlencode |
curl -s -H "Authorization: …" |
curl.exe -s -H "Authorization: …" (use curl.exe to avoid PowerShell's curl → Invoke-WebRequest alias) |
curl -s -H "Authorization: …" |
Backtick line-continuation \ at end of line |
backtick ` at end of line |
^ at end of line |
Paths on Windows:
- Always pass forward slashes in API request paths (
wiki/concepts/foo.md, neverwiki\concepts\foo.md). The server stores and accepts the forward-slash form. - When using a Windows filesystem path as
{id}(e.g.C:/Users/me/wiki), percent-encode the colon (C%3A/Users/me/wiki).EscapeDataString/encodeURIComponent/jq @uriall do this correctly. - If a path-as-id call returns 404 on Windows, fall back to
GET /api/v1/projectsand use the project's UUID — UUIDs are platform-agnostic and don't need encoding.
If you're calling from JavaScript / Python / Go / any other language with a real HTTP client (fetch, requests, httpx, net/http), platform doesn't matter — just encodeURIComponent or its equivalent and forget the shell quirks.
"What does my wiki say about X?"
The single most common ask. Workflow:
POST /api/v1/projects/current/searchwith{ query: X, topK: 5, includeContent: true }- Inspect
modein the response to know how to read scores (see below). - If the top results are clearly above the rest (big gap in
score), read those and synthesize. Otherwise read the top 3-5 and merge. - Cite each
pathyou used. Quote snippets directly. - If nothing is found (empty
resultsor a flat distribution with no clear winners), say so honestly. Do not fabricate.
curl -s -H "Authorization: Bearer $TOKEN" \
-H 'Content-Type: application/json' \
-d '{"query":"rope rotary position embedding","topK":5,"includeContent":true}' \
$BASE/api/v1/projects/current/search
How to read scores
The score's scale depends on mode:
mode |
Typical top score |
What "good" looks like |
|---|---|---|
keyword |
50–300+ (additive: filename-exact ≈ 200, phrase-in-title ≈ 50) | A clear gap (2×+) between top result and the rest. |
hybrid / vector |
0.015–0.035 (RRF: 1/(60+rank)-based) |
Top RRF score near 0.032 ≈ matched in both keyword and vector top-1. |
Don't apply a fixed threshold across modes. Sort by score descending and rely on the relative gap. Use vectorScore (when present) for "how strong was the semantic match" — it's a raw similarity in [0, 1], much easier to threshold than RRF.
Answer template:
Per
wiki/concepts/rope.md(matched via hybrid, vectorScore=0.94), rotary position embedding works by rotating Q and K vectors by an angle proportional to position. Your wiki specifically mentions …
"Read me the page about X"
User wants the full text, not a synthesis.
- If the user named a slug-like identifier (
rope,flash-attention), search first withtopK: 1to disambiguate. GET /api/v1/projects/current/files/content?path=wiki/concepts/rope.md- Render the content as markdown.
PATH_REL="wiki/concepts/rope.md"
# url-encode path component
ENCODED=$(printf %s "$PATH_REL" | jq -sRr @uri)
curl -s -H "Authorization: Bearer $TOKEN" \
"$BASE/api/v1/projects/current/files/content?path=$ENCODED"
In JS:
const encoded = encodeURIComponent("wiki/concepts/rope.md")
const r = await fetch(`${BASE}/api/v1/projects/current/files/content?path=${encoded}`, {
headers: { Authorization: `Bearer ${TOKEN}` },
})
const { content } = await r.json()
"What pages link to X?" / "Show me the neighborhood of X"
GET /api/v1/projects/current/graph?limit=1000— pull the whole graph once (cheap, < 1 MB for typical projects).- Find
nodes[i].id === X(or label substring match). - Filter
edgesforsource === X || target === X. The other endpoint is a neighbor.
curl -s -H "Authorization: Bearer $TOKEN" "$BASE/api/v1/projects/current/graph?limit=1000"
You can also let the API filter for you:
curl -s -H "Authorization: Bearer $TOKEN" "$BASE/api/v1/projects/current/graph?q=rope&limit=200"
This applies a substring filter on id or label (case-insensitive) and returns the matching subgraph including the edges between matched nodes.
Render a small mermaid graph when the user wants a visual:
graph LR
rope --- attention
rope --- transformer
attention --- flash-attention
"What's in my wiki?" / "Give me an overview"
Two angles:
Structural overview — file tree:
curl -s -H "Authorization: Bearer $TOKEN" \
"$BASE/api/v1/projects/current/files?root=wiki&recursive=true&maxFiles=500"
Summarize the directory structure (concepts/, entities/, sources/…) and rough page counts per category.
Topical overview — read the curated index:
for path in wiki/index.md wiki/overview.md purpose.md; do
encoded=$(printf %s "$path" | jq -sRr @uri)
curl -s -H "Authorization: Bearer $TOKEN" \
"$BASE/api/v1/projects/current/files/content?path=$encoded"
echo
done
The user's purpose.md describes intent; index.md enumerates pages; overview.md is the AI-generated topical summary. Quote the relevant chunks.
"I added new docs to the source folder — re-index"
POST /api/v1/projects/current/sources/rescan- Read back
changedTasks. Report:- "Detected N new / M modified / K deleted files."
- List the first ~5 file paths so the user can verify.
- Tell the user the actual ingest runs asynchronously via the desktop queue — encourage them to open the Activity panel if they want progress.
curl -s -X POST -H "Authorization: Bearer $TOKEN" \
"$BASE/api/v1/projects/current/sources/rescan"
If changedTasks is empty:
No file changes detected. If you added files but they're not appearing, check
Settings → Source Watch— your filters may be excluding them (e.g.,.jsonis excluded by default).
"Find every page that mentions Y" (broad sweep)
Search is ranked (hybrid when embeddings are configured, keyword otherwise) and capped at 50 hits per call. For exhaustive sweeps:
- Run
POST .../searchwithtopK: 50and your term. - If the 50th result still has a non-trivial score (relative to the top), run again with a more specific query — the API will not return more than 50 in one call.
- For exact-string sweeps where keyword tokenization mangles your phrase (e.g. CJK punctuation boundaries, code identifiers with underscores), walk every
wiki/*.mdviafiles+files/contentand grep client-side. Slow but reliable. - Pure-semantic sweeps: set
topK: 50and readvectorScoreon each hit — pages withoutvectorScorematched only via keyword.
"Search in my Reading project, not the current one"
When the user names a specific project rather than implying the active one.
-
List projects to resolve the name:
curl -s -H "Authorization: Bearer $TOKEN" "$BASE/api/v1/projects"Returns:
{ "projects": [ {"id":"abc-…","name":"Research Notes","path":"/Users/me/wiki/research","current":true}, {"id":"def-…","name":"Reading","path":"/Users/me/wiki/reading","current":false} ] } -
Match the user's spoken name. Case-insensitive substring on
name:const projects = (await (await fetch(`${BASE}/api/v1/projects`, { headers: { Authorization: `Bearer ${TOKEN}` }, })).json()).projects const match = projects.filter(p => p.name.toLowerCase().includes("reading")) -
Handle ambiguity:
- 0 matches → tell the user, list available names, ask which one. Don't silently fall back to
current— that would answer the wrong question. - 1 match → use its
idin all subsequent calls for this conversation. - 2+ matches → ask the user to disambiguate, showing both
name+path.
- 0 matches → tell the user, list available names, ask which one. Don't silently fall back to
-
Use the resolved id directly:
PROJECT_ID="def-…" # from step 2 curl -s -H "Authorization: Bearer $TOKEN" \ -H 'Content-Type: application/json' \ -d '{"query":"narrative voice","topK":5}' \ "$BASE/api/v1/projects/$PROJECT_ID/search" -
Cache the
idfor the rest of the conversation. Don't re-list projects on every call. Only re-resolve if the user switches contexts ("now search my Research project instead").
You can also pass the project's filesystem path directly (URL-encoded) when the user references it that way:
PROJECT_PATH=$(printf %s "/Users/me/wiki/reading" | jq -sRr @uri)
curl -s -H "Authorization: Bearer $TOKEN" \
"$BASE/api/v1/projects/$PROJECT_PATH/files?root=wiki"
"Compare what my Research and Reading projects say about X"
User wants cross-project synthesis.
-
GET /api/v1/projectsonce → grab both ids. -
Search each separately with the same query:
for ID in research-id reading-id; do curl -s -H "Authorization: Bearer $TOKEN" \ -H 'Content-Type: application/json' \ -d '{"query":"narrative voice","topK":3,"includeContent":true}' \ "$BASE/api/v1/projects/$ID/search" done -
Diff / contrast the result sets. Cite both project name and page path: "In Research Notes (
wiki/concepts/narrative.md)… vs. in Reading (wiki/concepts/voice.md)…".
current only refers to the active project; for multi-project queries always pass explicit IDs.
"Switch to project X" (mid-conversation)
User has been asking about the active project, then says "now check my Reading project for the same thing."
- Re-resolve via
GET /api/v1/projects(or use cached list if recent). - Replace your cached project id for the rest of the conversation.
- Confirm the switch in your reply once: "Switching to your Reading project…". Don't silently apply.
- Keep the user's query — apply it to the new project.
The desktop UI's active project does not change just because you used a different {id} — your API calls scoped to a non-current id are read-only and don't affect the user's UI state.
Anti-patterns
- Don't
POST /chat— it returns 501. Use the desktop chat UI, or call your own LLM with the wiki content as context. - Don't paginate the file tree with maxFiles=1, maxFiles=2, … — there's no offset/cursor. Use a higher cap or filter by
root=. - Don't bypass the API for writes. The API is read-mostly. Writing to
wiki/**orraw/sources/**via the filesystem directly bypasses the file-sync watcher. Use the desktop UI's Sources → Import flow or the ingest pipeline. - Don't include the token in URLs you echo / log / show. Use headers in any output visible to the user.
- Don't retry blindly on 429 / 503 — back off explicitly (1s / 2s respectively).
- Don't shell out to a wrapper script. The API is the contract. Direct
curl/fetchis the interface.
End-to-end transcript
User: "What does my wiki say about flash attention? Show me the related concepts too."
Agent (internally):
# 1. health + auth pre-flight
curl -s "$BASE/api/v1/health"
# → tokenSource=store, enabled=true, authConfigured=true ✓
# 2. search
curl -s -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
-d '{"query":"flash attention","topK":5,"includeContent":true}' \
"$BASE/api/v1/projects/current/search"
# 3. graph filter for "flash-attention" neighborhood
curl -s -H "Authorization: Bearer $TOKEN" \
"$BASE/api/v1/projects/current/graph?q=flash&limit=200"
Agent (to user):
Looking in your active project. Search ran in hybrid mode (tokenHits=12, vectorHits=8) — top hit
wiki/concepts/flash-attention.mdmatched both keyword and vector signals (vectorScore=0.91).Your wiki explains FlashAttention as a memory-efficient exact attention implementation that tiles the QKV matrices to avoid materializing the full N×N attention map. Key claim: 2-4× speedup at long context on A100s (per your wiki).
Related concepts in your graph:
wiki/concepts/attention.md(parent)wiki/concepts/long-context.md(sibling, both reachable fromflash-attention)wiki/entities/tri-dao.md(cited author)Want me to read any of these in full?