Inspiration
Long-form fiction breaks under its own weight. A novel spans hundreds of pages; a manga runs for a decade. Somewhere in there, even the author loses the thread — a hair colour that changes between chapters, a character who dies and then shows up alive, a timeline that quietly stops making sense. This isn't a hypothetical: creators of long-running series have publicly admitted to re-reading their own back catalog just to stay consistent. Franchises worth billions are built on exactly this fragile continuity.
We wanted to build the thing a human continuity editor does — read every page, remember every promise the story made, and flag it the moment something contradicts it — except it never gets tired, never forgets, and runs while you write instead of after you've shipped 300 pages you now have to fix.
What it does
Quill is a memory agent for long-form fiction. As a writer drafts a chapter, Quill reads each paragraph in the background and:
- Extracts entities and relationships — characters, places, objects, factions, events — into a persistent graph, one per fictional universe.
- Detects contradictions and plot holes live, using a tool-calling reasoning agent, not a single-shot prompt.
- Validates timeline consistency when an event's chronological position looks wrong.
- Forgets what stops mattering — entity relevance decays over time and archives quietly, then reactivates the moment something is mentioned again.
- Recalls only what fits — a hybrid retrieval system fuses six independently-ranked pipelines (vector, graph-walk, recency, keyword, consolidated summaries, writer preferences) with Reciprocal Rank Fusion, then fits the result into a token budget instead of dumping everything into the prompt.
- Learns the author, not just the story — accepted/rejected suggestions get promoted into durable writer preferences that shape future craft reviews.
A fourth agent, the Arbiter, sits on top of the three specialist agents (continuity, plot-hole, timeline) and synthesizes their raw findings into one prioritized note, instead of handing the writer three disconnected alerts for what's really one underlying issue.
How we built it
- Backend: Go 1.22 + Fiber v2, talking to PostgreSQL 16 with pgvector (embeddings) and Apache AGE (a property graph per universe). Repositories → services → handlers, wired by hand with no DI framework.
- Frontend: React 18 + TypeScript + Vite, TipTap for the editor, Cytoscape for the relationship graph, Zustand for state, a single WebSocket hub pushing live analysis results to the editor.
- AI: every model call goes to Qwen Cloud (DashScope). We wrote a native DashScope client from scratch — not just the OpenAI-compatible shim — to reach Qwen-specific features: explicit context caching, native reranking (
qwen3-rerank), and native token accounting. - Agents: contradiction detection, plot-hole evaluation, and timeline validation each run their own tool-calling agent loop (
RunAgentLoop), with their own system prompt and persona, callingsearch_vector_memoryandquery_entity_graphas tools when they decide they need evidence. The Arbiter reads their raw output and writes the final synthesis — same loop, empty tool set. - MCP: the same memory tools are exposed over a real MCP server (JSON-RPC
initialize/tools/list/tools/call), so external MCP clients can query a universe's memory directly. - Deployment: Docker Compose (Postgres + migrations + backend + Nginx-served frontend), deployed on an Alibaba Cloud Simple Application Server.
Challenges we ran into
- AGE + Cypher injection: Apache AGE forbids parameterized queries inside its Cypher blocks, and entity/relationship labels come from LLM output — so every interpolation point had to be defended explicitly (UUID-derived graph names, escaped string values, whitelist-validated identifiers for anything the model produces).
- The hybrid recall ablation told us something we didn't expect: on our eval corpus, vector+graph alone beat the full six-pipeline fusion. We kept the full fusion in production anyway — a six-query sample is exploratory evidence, not a statistically powered basis for hard-coding a narrower default — but we said so directly in the README instead of only reporting the number that looked best.
- A deployment bug we only found by testing the live public URL, not localhost:
crypto.randomUUID()andnavigator.clipboardare both restricted to secure browser contexts (HTTPS orlocalhost). Once we deployed over plain HTTP on a public IP — exactly how a hackathon judge reaches it — both the guided-demo entry point and the "copy MCP endpoint" button failed silently. We caught it during an end-to-end pass against the real deployed server and shipped acrypto.getRandomValues()-based fallback (which has no such restriction) the same session.
Accomplishments that we're proud of
- A genuinely multi-agent system — four independent agents with distinct personas and tool access, not one prompt reused four ways — with a consensus-forming Arbiter on top.
- A native DashScope client, not just an OpenAI-compatible wrapper, using context caching and native reranking.
- Measured, not asserted, results: a small but real eval harness reporting Recall@5 per pipeline, latency at scale, a forgetting timeline across decay ticks, and consolidation fidelity — published with an honest caveat about sample size rather than dressed up as a bigger study than it is.
- A memory system with two subjects, not one: the manuscript and the author, learning writer preferences from explicit and behavioural feedback.
What we learned
That hybrid recall isn't automatically better just because it's hybrid — the ablation matters more than the pitch. And that testing against localhost isn't testing your deployment; the most convincing bug we found all hackathon came from opening the actual public URL a judge would use.
What's next for Quill
- Grow the eval corpus past six gold queries into a properly powered retrieval benchmark.
- Let the Arbiter's synthesis feed back into writer-preference learning, not just the live alert.
- Support importing existing manuscripts from more formats (DOCX, Scrivener) beyond Markdown/plaintext.
Log in or sign up for Devpost to join the conversation.