Quickstart
Paste a document, see findings, forge clarity. By the end of this page you’ll have run a real analysis and applied an AI rewrite.
Pellucid runs in two halves: a fast rule layer and a slower multi-agent layer. The rule layer returns within ~200 ms and gives you the obvious wins (vague adjectives, passive voice, “etc”). The agent layer streams in over the next 5–15 seconds and catches the subtle stuff (untestable claims, hidden assumptions, risk-weighted gaps).
Prerequisites
- Python 3.12 or newer
- Node 20 or newer, with
pnpm9 - An API key for any LiteLLM-compatible provider (xAI, Anthropic, OpenAI, OpenRouter)
Boot the stack
From the repo root:
# 1. Install JS deps
pnpm install
# 2. Install Python deps + the spaCy model
cd apps/api
uv sync
uv run python -m spacy download en_core_web_sm
# 3. Set an LLM key (or do it later in /settings)
cp .env.example .env
# edit .env and set XAI_API_KEY (or any provider — see /docs/byok)
# 4. Boot both servers (from repo root)
cd ../..
pnpm dev:api # http://localhost:8000
pnpm dev:web # http://localhost:3000Hit http://localhost:8000/health to confirm the API is up; you should see {status: ok}.
Paste a document
Open http://localhost:3000, click Try sample document, and paste in your own text — anything from a one-paragraph requirement to a full SRS works. The editor on the left holds the source; the right rail fills with findings as they stream in.
Within roughly 200 ms you’ll see rule-layer findings highlighted inline. Over the next ~10 seconds, the four agents (Lexical, Syntactic, Domain, Risk) report independently, then critique each other. The aggregator merges overlapping spans and weights each finding’s confidence by the level of agreement.
Under the hood — what just happened
The web app called four endpoints, in order:
POST /api/v1/documents— persists the pasted text and returns adocument_id.POST /api/v1/documents/{id}/analyze— opens a Server-Sent Events stream. The first event isrule_findings; subsequent events are per-agent findings, critique votes, and a final aggregated event.GET /api/v1/debate/{id}— fetches the full debate transcript when the user opens the Debate panel.POST /api/v1/findings/{id}/rewrite— when the user clicksForge Clarity, returns 2–3 candidate rewrites.
You can do all of this from the command line. Here’s the minimal end-to-end flow:
# 1. Create a document
DOC_ID=$(curl -sX POST http://localhost:8000/api/v1/documents \
-H "Content-Type: application/json" \
-d '{"content": "The system shall be user-friendly and respond quickly."}' \
| jq -r .id)
# 2. Open the SSE stream — JSON events stream until "done"
curl -N -X POST http://localhost:8000/api/v1/documents/$DOC_ID/analyze
# 3. Pick a finding id from the stream and forge a rewrite
curl -sX POST http://localhost:8000/api/v1/findings/$FINDING_ID/rewrite | jq .The SSE event stream
Pellucid streams progress so the UI can light up incrementally instead of making the user wait for the slowest agent. Each event is a JSON object on a single data: line.
| Event | When it fires | Payload |
|---|---|---|
rule_findings | ~200 ms after analyze starts | { findings: Finding[] } |
agent_findings | Once per agent, as each finishes | { agent, findings, reasoning, self_confidence } |
critique | After all agents have reported | { votes: CritiqueVote[] } |
final | Once, after aggregation + persistence | { findings, debate } |
done | Stream complete — close the connection | {} |
error | Pipeline error — terminal | { message: string } |
A minimal browser consumer using the standard EventSource API:
// EventSource is GET-only, so use fetch + a streaming reader for POST.
const res = await fetch(`/api/v1/documents/${docId}/analyze`, {
method: "POST",
});
const reader = res.body.getReader();
const decoder = new TextDecoder();
let buf = "";
for (;;) {
const { value, done } = await reader.read();
if (done) break;
buf += decoder.decode(value, { stream: true });
// SSE frames are separated by a blank line.
let i;
while ((i = buf.indexOf("\n\n")) >= 0) {
const frame = buf.slice(0, i);
buf = buf.slice(i + 2);
const event = /^event:\s*(.+)$/m.exec(frame)?.[1] ?? "message";
const data = /^data:\s*(.+)$/m.exec(frame)?.[1] ?? "";
handle(event, JSON.parse(data));
}
}
function handle(event, payload) {
if (event === "rule_findings") render(payload.findings);
if (event === "final") replaceWith(payload.findings);
}Forge Clarity — your first rewrite
Pick any finding (the right rail in the web app, or any item from thefinal event payload) and POST its id to the rewrite endpoint. The response holds 2–3 candidates, each with a one-sentence rationale.
{
"finding_id": "find_abc123",
"candidates": [
{
"text": "The system shall present a labelled control for every primary action visible at all times.",
"rationale": "Replaces 'user-friendly' with a measurable affordance: labelled, primary, visible."
},
{
"text": "The system shall complete every user-initiated action within 200 ms at the 95th percentile.",
"rationale": "Promotes 'respond quickly' to a numeric latency target with a percentile."
}
]
}Next steps
- Bring your own key — swap in Anthropic, OpenAI, or OpenRouter without touching code.
- API reference — every endpoint with request/response examples.
- Webhooks — receive an
analysis.completedPOST in Slack, Discord, or your own service when the pipeline finishes. - Browser & VS Code extensions — run Pellucid against any page or any markdown file in your editor.