Skip to content

EdgeQuake Cookbook

Product: v0.19.0 · Contract: openapi.snapshot.json · Spec ops: Ingestion cancel & fairness

Practical Recipes for Common Tasks

This cookbook provides copy-paste solutions for common EdgeQuake operations. Each recipe includes complete code examples and expected outputs.

Headers: make dev sets EDGEQUAKE_DEV_MODE=true (no auth). Production requires Authorization: Bearer $TOKEN or X-API-Key. Scope requests with X-Workspace-ID.

Status fields: Prefer DocumentSummary.display_status / ui_phase / current_stage / track_id over legacy status alone.


Recipe: Upload a PDF and Wait for Processing

Section titled “Recipe: Upload a PDF and Wait for Processing”
#!/bin/bash
# Upload and poll for completion
# Upload the document
RESPONSE=$(curl -s -X POST http://localhost:8080/api/v1/documents/upload \
-H "X-Workspace-ID: default" \
-F "file=@document.pdf")
DOC_ID=$(echo $RESPONSE | jq -r '.id')
echo "Uploaded document: $DOC_ID"
# Poll for completion (DocumentSummary SSOT)
while true; do
ROW=$(curl -s "http://localhost:8080/api/v1/documents/$DOC_ID" \
-H "X-Workspace-ID: default")
DISPLAY=$(echo "$ROW" | jq -r '.display_status')
PHASE=$(echo "$ROW" | jq -r '.ui_phase')
echo "display_status: $DISPLAY (ui_phase: $PHASE)"
if [ "$DISPLAY" = "completed" ]; then
echo "Document processed successfully!"
break
elif [ "$DISPLAY" = "failed" ] || [ "$DISPLAY" = "cancelled" ]; then
echo "Document processing ended: $DISPLAY"
exit 1
fi
sleep 2
done

#!/bin/bash
# Upload all PDFs in a directory
WORKSPACE="default"
DIR="./documents"
for file in "$DIR"/*.pdf; do
if [ -f "$file" ]; then
echo "Uploading: $file"
curl -s -X POST http://localhost:8080/api/v1/documents/upload \
-H "X-Workspace-ID: $WORKSPACE" \
-F "file=@$file" | jq '.id, .name'
echo "---"
fi
done
echo "All files uploaded!"

Terminal window
# Upload raw text without a file
curl -X POST http://localhost:8080/api/v1/documents \
-H "Content-Type: application/json" \
-H "X-Workspace-ID: default" \
-d '{
"content": "EdgeQuake is a Graph-RAG framework built in Rust...",
"name": "about-edgequake.txt",
"metadata": {
"source": "manual",
"category": "documentation"
}
}'

Recipe: Delete All Documents in a Workspace

Section titled “Recipe: Delete All Documents in a Workspace”
#!/bin/bash
# Clean up a workspace (destructive!)
WORKSPACE="test-workspace"
# Get all document IDs
DOC_IDS=$(curl -s "http://localhost:8080/api/v1/documents?workspace_id=$WORKSPACE" \
-H "X-Workspace-ID: $WORKSPACE" | jq -r '.documents[].id')
# Delete each document
for id in $DOC_IDS; do
echo "Deleting: $id"
curl -s -X DELETE "http://localhost:8080/api/v1/documents/$id" \
-H "X-Workspace-ID: $WORKSPACE"
done
echo "Workspace cleaned!"

Terminal window
# Query with source citations
curl -X POST http://localhost:8080/api/v1/query \
-H "Content-Type: application/json" \
-H "X-Workspace-ID: default" \
-d '{
"query": "What are the main topics discussed?",
"mode": "hybrid",
"top_k": 5,
"include_sources": true
}' | jq '{
answer: .answer,
sources: [.sources[] | {doc: .document_id, score: .score, snippet: .snippet[:80]}]
}'

#!/bin/bash
# Compare results across query modes
QUERY="Who is mentioned in the documents?"
for MODE in naive local global hybrid; do
echo "=== Mode: $MODE ==="
RESPONSE=$(curl -s -X POST http://localhost:8080/api/v1/query \
-H "Content-Type: application/json" \
-H "X-Workspace-ID: default" \
-d "{\"query\": \"$QUERY\", \"mode\": \"$MODE\"}")
echo "$RESPONSE" | jq '{
mode: "'$MODE'",
source_count: (.sources | length),
answer_preview: .answer[:100]
}'
echo ""
done

# Python: Stream chat response
import requests
import json
def stream_chat(message, workspace_id="default"):
response = requests.post(
"http://localhost:8080/api/v1/chat/completions/stream",
json={
"message": message,
"workspace_id": workspace_id,
"mode": "hybrid"
},
stream=True
)
for line in response.iter_lines():
if line:
if line.startswith(b"data: "):
data = json.loads(line[6:])
if "content" in data:
print(data["content"], end="", flush=True)
if data.get("done"):
print() # New line at end
break
# Usage
stream_chat("What are the key findings?")

Terminal window
# Find information about a specific entity
curl -X POST http://localhost:8080/api/v1/query \
-H "Content-Type: application/json" \
-H "X-Workspace-ID: default" \
-d '{
"query": "What projects is John Smith working on?",
"mode": "local",
"entity_filter": ["JOHN_SMITH"],
"top_k": 10
}'

Terminal window
# task_id from PDF upload (PdfUploadResponse.task_id) or document track_id
TASK_ID="pdf-550e8400-e29b-41d4-a716-446655440000"
curl -X POST "http://localhost:8080/api/v1/tasks/${TASK_ID}/cancel" \
-H "X-Workspace-ID: default"
# Poll until display_status=cancelled, ui_phase=terminal
curl -s "http://localhost:8080/api/v1/documents/$DOC_ID" \
-H "X-Workspace-ID: default" | jq '{display_status, ui_phase}'

See Ingestion cancel & fairness.


Recipe: PDF Convert → Ingest (Two Phases)

Section titled “Recipe: PDF Convert → Ingest (Two Phases)”

PDF admission runs convert (TaskType::PdfProcessing) first; after markdown is stored, a separate ingest task (TaskType::Insert) builds the knowledge graph. Ingest failure keeps PDF Completed + markdown checkpoint.

Terminal window
# Upload PDF (returns task_id for convert phase)
UPLOAD=$(curl -s -X POST http://localhost:8080/api/v1/documents/pdf \
-H "X-Workspace-ID: default" \
-F "file=@report.pdf" \
-F "title=Q3 Report")
TASK_ID=$(echo "$UPLOAD" | jq -r .task_id)
PDF_ID=$(echo "$UPLOAD" | jq -r .pdf_id)
# Poll convert progress
curl -s "http://localhost:8080/api/v1/documents/pdf/progress/${TASK_ID}" \
-H "X-Workspace-ID: default" | jq '{stage, percent, message}'
# After convert completes, watch document row for ingest (display_status → completed)

Details: PDF Ingestion Tutorial.


Terminal window
# Server-side model search for picker / integrations
curl -s "http://localhost:8080/api/v1/models/search?q=gpt&capability=chat" \
-H "Authorization: Bearer $TOKEN" | jq '.models[:5] | .[] | {id, provider, capabilities}'

Terminal window
# Worker queue depth, claim/lease health (SPEC-057 observability)
curl -s http://localhost:8080/api/v1/pipeline/queue-metrics \
-H "Authorization: Bearer $TOKEN" | jq '{
pending: .pending_count,
processing: .processing_count,
blocked: .readiness_blockers
}'

Also check GET /ready for traffic-blocking readiness blockers.


Terminal window
# Export graph for visualization
curl -s "http://localhost:8080/api/v1/graph?limit=1000" \
-H "X-Workspace-ID: default" | jq '{
nodes: [.nodes[] | {id: .id, label: .label, type: .type}],
edges: [.edges[] | {source: .source, target: .target, label: .label}]
}' > graph_export.json
echo "Exported $(jq '.nodes | length' graph_export.json) nodes"
echo "Exported $(jq '.edges | length' graph_export.json) edges"

Terminal window
# Get all relationships for an entity
ENTITY="JOHN_SMITH"
curl -s "http://localhost:8080/api/v1/graph/entities/$ENTITY" \
-H "X-Workspace-ID: default" | jq '{
entity: .name,
type: .entity_type,
outgoing: [.relationships[] | select(.source == .name) | {to: .target, type: .relation_type}],
incoming: [.relationships[] | select(.target == .name) | {from: .source, type: .relation_type}]
}'

Terminal window
# Find connection path between two entities
curl -s "http://localhost:8080/api/v1/graph/path?from=ENTITY_A&to=ENTITY_B" \
-H "X-Workspace-ID: default" | jq '.path'

Terminal window
# Create workspace with custom configuration
curl -X POST http://localhost:8080/api/v1/workspaces \
-H "Content-Type: application/json" \
-d '{
"name": "Research Project",
"description": "Documents for Q3 research",
"settings": {
"default_query_mode": "hybrid",
"enable_gleaning": true,
"gleaning_iterations": 2,
"chunk_size": 1000
}
}'

Terminal window
# Get workspaces with document counts
curl -s http://localhost:8080/api/v1/workspaces | jq '.workspaces[] | {
id: .id,
name: .name,
documents: .document_count,
entities: .entity_count,
created: .created_at[:10]
}'

"""EdgeQuake Python Client with common operations."""
import requests
from typing import Generator, Optional, List
class EdgeQuakeClient:
def __init__(self, base_url: str = "http://localhost:8080", workspace: str = "default"):
self.base_url = base_url
self.workspace = workspace
self.headers = {"X-Workspace-ID": workspace}
def upload_text(self, content: str, name: str) -> dict:
"""Upload text content."""
return requests.post(
f"{self.base_url}/api/v1/documents",
json={"content": content, "name": name},
headers=self.headers
).json()
def upload_file(self, file_path: str) -> dict:
"""Upload a file."""
with open(file_path, "rb") as f:
return requests.post(
f"{self.base_url}/api/v1/documents/upload",
files={"file": f},
headers=self.headers
).json()
def query(self, question: str, mode: str = "hybrid") -> dict:
"""Execute a query."""
return requests.post(
f"{self.base_url}/api/v1/query",
json={"query": question, "mode": mode},
headers=self.headers
).json()
def chat_stream(self, message: str) -> Generator[str, None, None]:
"""Stream chat response."""
import json
response = requests.post(
f"{self.base_url}/api/v1/chat/completions/stream",
json={"message": message},
headers=self.headers,
stream=True
)
for line in response.iter_lines():
if line and line.startswith(b"data: "):
data = json.loads(line[6:])
if "content" in data:
yield data["content"]
def list_documents(self) -> List[dict]:
"""List all documents."""
return requests.get(
f"{self.base_url}/api/v1/documents",
headers=self.headers
).json().get("documents", [])
def get_graph(self, limit: int = 100) -> dict:
"""Get knowledge graph."""
return requests.get(
f"{self.base_url}/api/v1/graph?limit={limit}",
headers=self.headers
).json()
# Usage example
if __name__ == "__main__":
client = EdgeQuakeClient()
# Upload a document
doc = client.upload_text("EdgeQuake is great!", "test.txt")
print(f"Uploaded: {doc['id']}")
# Query
result = client.query("What is EdgeQuake?")
print(f"Answer: {result['answer']}")
# Stream chat
print("Streaming: ", end="")
for chunk in client.chat_stream("Tell me more"):
print(chunk, end="", flush=True)
print()

#!/bin/bash
# Health check script
echo "=== EdgeQuake Health Check ==="
# API health
API_STATUS=$(curl -s http://localhost:8080/health | jq -r '.status')
echo "API: $API_STATUS"
# Database connection
DB_STATUS=$(curl -s http://localhost:8080/health | jq -r '.storage_mode')
echo "Database: $DB_STATUS"
# Document count
DOC_COUNT=$(curl -s http://localhost:8080/api/v1/documents \
-H "X-Workspace-ID: default" | jq '.documents | length')
echo "Documents: $DOC_COUNT"
# Entity count
ENTITY_COUNT=$(curl -s "http://localhost:8080/api/v1/graph?limit=1" \
-H "X-Workspace-ID: default" | jq '.total_nodes')
echo "Entities: $ENTITY_COUNT"
echo "=== Done ==="

Terminal window
# Get cost tracking data
curl -s http://localhost:8080/api/v1/costs/summary \
-H "X-Workspace-ID: default" | jq '{
total_input_tokens: .total_input_tokens,
total_output_tokens: .total_output_tokens,
estimated_cost_usd: .estimated_cost,
by_operation: .breakdown
}'

docker-compose.dev.yml
version: "3.8"
services:
edgequake:
build: .
ports:
- "8080:8080"
environment:
- DATABASE_URL=postgresql://edgequake:edgequake@postgres:5432/edgequake
- OPENAI_API_KEY=${OPENAI_API_KEY}
- RUST_LOG=info,edgequake=debug
depends_on:
postgres:
condition: service_healthy
volumes:
- ./data:/app/data
postgres:
image: ghcr.io/raphaelmansuy/edgequake-postgres:0.19.0
environment:
POSTGRES_USER: edgequake
POSTGRES_PASSWORD: edgequake
POSTGRES_DB: edgequake
volumes:
- postgres_data:/var/lib/postgresql
healthcheck:
test: ["CMD-SHELL", "pg_isready -U edgequake"]
interval: 5s
timeout: 5s
retries: 5
volumes:
postgres_data:

Start with:

Terminal window
docker compose -f docker-compose.dev.yml up -d

#!/bin/bash
# Backup EdgeQuake data
BACKUP_DIR="./backups/$(date +%Y%m%d)"
mkdir -p "$BACKUP_DIR"
# Backup PostgreSQL
docker compose exec postgres pg_dump -U edgequake edgequake > "$BACKUP_DIR/db.sql"
# Backup uploaded files (if any)
cp -r ./data/uploads "$BACKUP_DIR/uploads" 2>/dev/null || true
echo "Backup complete: $BACKUP_DIR"

Restore:

#!/bin/bash
# Restore from backup
BACKUP_DIR="$1"
if [ -z "$BACKUP_DIR" ]; then
echo "Usage: ./restore.sh <backup_dir>"
exit 1
fi
# Restore database
docker compose exec -T postgres psql -U edgequake edgequake < "$BACKUP_DIR/db.sql"
echo "Restore complete!"

#!/bin/bash
# Simple query benchmark
QUERY="What are the main topics?"
ITERATIONS=10
echo "Benchmarking $ITERATIONS queries..."
total_time=0
for i in $(seq 1 $ITERATIONS); do
start=$(date +%s%N)
curl -s -X POST http://localhost:8080/api/v1/query \
-H "Content-Type: application/json" \
-H "X-Workspace-ID: default" \
-d "{\"query\": \"$QUERY\", \"mode\": \"hybrid\"}" > /dev/null
end=$(date +%s%N)
duration=$(( (end - start) / 1000000 ))
total_time=$((total_time + duration))
echo "Query $i: ${duration}ms"
done
avg=$((total_time / ITERATIONS))
echo "---"
echo "Average: ${avg}ms"

Terminal window
# Check document processing details
DOC_ID="your-doc-id"
curl -s "http://localhost:8080/api/v1/documents/$DOC_ID" \
-H "X-Workspace-ID: default" | jq '{
id: .id,
name: .name,
display_status: .display_status,
ui_phase: .ui_phase,
current_stage: .current_stage,
track_id: .track_id,
chunks: .chunk_count,
entities: .entity_count,
error: .error_message
}'

#!/bin/bash
# Diagnose empty query responses
QUERY="$1"
if [ -z "$QUERY" ]; then
QUERY="test query"
fi
echo "Testing query: $QUERY"
echo ""
# Check document count
DOC_COUNT=$(curl -s http://localhost:8080/api/v1/documents \
-H "X-Workspace-ID: default" | jq '.documents | length')
echo "Documents in workspace: $DOC_COUNT"
# Check entity count
ENTITY_COUNT=$(curl -s "http://localhost:8080/api/v1/graph?limit=1" \
-H "X-Workspace-ID: default" | jq '.total_nodes')
echo "Entities in graph: $ENTITY_COUNT"
# Try query
RESPONSE=$(curl -s -X POST http://localhost:8080/api/v1/query \
-H "Content-Type: application/json" \
-H "X-Workspace-ID: default" \
-d "{\"query\": \"$QUERY\", \"mode\": \"hybrid\"}")
echo ""
echo "Query result:"
echo "$RESPONSE" | jq '{
has_answer: (.answer | length > 0),
sources_found: (.sources | length)
}'