@alextis59/athena 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +15 -0
- package/README.md +147 -0
- package/dist/agent/ask.d.ts +7 -0
- package/dist/agent/ask.d.ts.map +1 -0
- package/dist/agent/ask.js +35 -0
- package/dist/agent/ask.js.map +1 -0
- package/dist/agent/citations.d.ts +27 -0
- package/dist/agent/citations.d.ts.map +1 -0
- package/dist/agent/citations.js +190 -0
- package/dist/agent/citations.js.map +1 -0
- package/dist/agent/fake-provider.d.ts +9 -0
- package/dist/agent/fake-provider.d.ts.map +1 -0
- package/dist/agent/fake-provider.js +51 -0
- package/dist/agent/fake-provider.js.map +1 -0
- package/dist/agent/index.d.ts +10 -0
- package/dist/agent/index.d.ts.map +1 -0
- package/dist/agent/index.js +6 -0
- package/dist/agent/index.js.map +1 -0
- package/dist/agent/loop.d.ts +11 -0
- package/dist/agent/loop.d.ts.map +1 -0
- package/dist/agent/loop.js +156 -0
- package/dist/agent/loop.js.map +1 -0
- package/dist/agent/providers.d.ts +17 -0
- package/dist/agent/providers.d.ts.map +1 -0
- package/dist/agent/providers.js +171 -0
- package/dist/agent/providers.js.map +1 -0
- package/dist/agent/tools.d.ts +8 -0
- package/dist/agent/tools.d.ts.map +1 -0
- package/dist/agent/tools.js +233 -0
- package/dist/agent/tools.js.map +1 -0
- package/dist/agent/types.d.ts +84 -0
- package/dist/agent/types.d.ts.map +1 -0
- package/dist/agent/types.js +2 -0
- package/dist/agent/types.js.map +1 -0
- package/dist/cli/help.d.ts +2 -0
- package/dist/cli/help.d.ts.map +1 -0
- package/dist/cli/help.js +28 -0
- package/dist/cli/help.js.map +1 -0
- package/dist/cli/main.d.ts +3 -0
- package/dist/cli/main.d.ts.map +1 -0
- package/dist/cli/main.js +5 -0
- package/dist/cli/main.js.map +1 -0
- package/dist/cli/options.d.ts +21 -0
- package/dist/cli/options.d.ts.map +1 -0
- package/dist/cli/options.js +97 -0
- package/dist/cli/options.js.map +1 -0
- package/dist/cli/run.d.ts +21 -0
- package/dist/cli/run.d.ts.map +1 -0
- package/dist/cli/run.js +144 -0
- package/dist/cli/run.js.map +1 -0
- package/dist/config/index.d.ts +4 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/index.js +3 -0
- package/dist/config/index.js.map +1 -0
- package/dist/config/loader.d.ts +14 -0
- package/dist/config/loader.d.ts.map +1 -0
- package/dist/config/loader.js +352 -0
- package/dist/config/loader.js.map +1 -0
- package/dist/config/schema.d.ts +6 -0
- package/dist/config/schema.d.ts.map +1 -0
- package/dist/config/schema.js +72 -0
- package/dist/config/schema.js.map +1 -0
- package/dist/config/types.d.ts +53 -0
- package/dist/config/types.d.ts.map +1 -0
- package/dist/config/types.js +2 -0
- package/dist/config/types.js.map +1 -0
- package/dist/doctor/doctor.d.ts +47 -0
- package/dist/doctor/doctor.d.ts.map +1 -0
- package/dist/doctor/doctor.js +159 -0
- package/dist/doctor/doctor.js.map +1 -0
- package/dist/embeddings/index.d.ts +117 -0
- package/dist/embeddings/index.d.ts.map +1 -0
- package/dist/embeddings/index.js +515 -0
- package/dist/embeddings/index.js.map +1 -0
- package/dist/errors.d.ts +19 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +37 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +9 -0
- package/dist/index.js.map +1 -0
- package/dist/indexer/discovery.d.ts +10 -0
- package/dist/indexer/discovery.d.ts.map +1 -0
- package/dist/indexer/discovery.js +38 -0
- package/dist/indexer/discovery.js.map +1 -0
- package/dist/indexer/hash.d.ts +3 -0
- package/dist/indexer/hash.d.ts.map +1 -0
- package/dist/indexer/hash.js +11 -0
- package/dist/indexer/hash.js.map +1 -0
- package/dist/indexer/index.d.ts +7 -0
- package/dist/indexer/index.d.ts.map +1 -0
- package/dist/indexer/index.js +5 -0
- package/dist/indexer/index.js.map +1 -0
- package/dist/indexer/kinds.d.ts +4 -0
- package/dist/indexer/kinds.d.ts.map +1 -0
- package/dist/indexer/kinds.js +38 -0
- package/dist/indexer/kinds.js.map +1 -0
- package/dist/indexer/run.d.ts +42 -0
- package/dist/indexer/run.d.ts.map +1 -0
- package/dist/indexer/run.js +563 -0
- package/dist/indexer/run.js.map +1 -0
- package/dist/logging/logger.d.ts +11 -0
- package/dist/logging/logger.d.ts.map +1 -0
- package/dist/logging/logger.js +18 -0
- package/dist/logging/logger.js.map +1 -0
- package/dist/mcp/index.d.ts +35 -0
- package/dist/mcp/index.d.ts.map +1 -0
- package/dist/mcp/index.js +392 -0
- package/dist/mcp/index.js.map +1 -0
- package/dist/parsers/chunking.d.ts +4 -0
- package/dist/parsers/chunking.d.ts.map +1 -0
- package/dist/parsers/chunking.js +112 -0
- package/dist/parsers/chunking.js.map +1 -0
- package/dist/parsers/index.d.ts +8 -0
- package/dist/parsers/index.d.ts.map +1 -0
- package/dist/parsers/index.js +5 -0
- package/dist/parsers/index.js.map +1 -0
- package/dist/parsers/line-parser.d.ts +13 -0
- package/dist/parsers/line-parser.d.ts.map +1 -0
- package/dist/parsers/line-parser.js +242 -0
- package/dist/parsers/line-parser.js.map +1 -0
- package/dist/parsers/normalize.d.ts +4 -0
- package/dist/parsers/normalize.d.ts.map +1 -0
- package/dist/parsers/normalize.js +17 -0
- package/dist/parsers/normalize.js.map +1 -0
- package/dist/parsers/parser.d.ts +14 -0
- package/dist/parsers/parser.d.ts.map +1 -0
- package/dist/parsers/parser.js +77 -0
- package/dist/parsers/parser.js.map +1 -0
- package/dist/parsers/pdf2llm.d.ts +14 -0
- package/dist/parsers/pdf2llm.d.ts.map +1 -0
- package/dist/parsers/pdf2llm.js +215 -0
- package/dist/parsers/pdf2llm.js.map +1 -0
- package/dist/parsers/types.d.ts +48 -0
- package/dist/parsers/types.d.ts.map +1 -0
- package/dist/parsers/types.js +2 -0
- package/dist/parsers/types.js.map +1 -0
- package/dist/retrieval/index.d.ts +4 -0
- package/dist/retrieval/index.d.ts.map +1 -0
- package/dist/retrieval/index.js +3 -0
- package/dist/retrieval/index.js.map +1 -0
- package/dist/retrieval/schemas.d.ts +127 -0
- package/dist/retrieval/schemas.d.ts.map +1 -0
- package/dist/retrieval/schemas.js +65 -0
- package/dist/retrieval/schemas.js.map +1 -0
- package/dist/retrieval/tools.d.ts +31 -0
- package/dist/retrieval/tools.d.ts.map +1 -0
- package/dist/retrieval/tools.js +803 -0
- package/dist/retrieval/tools.js.map +1 -0
- package/dist/retrieval/types.d.ts +104 -0
- package/dist/retrieval/types.d.ts.map +1 -0
- package/dist/retrieval/types.js +2 -0
- package/dist/retrieval/types.js.map +1 -0
- package/dist/server/index.d.ts +3 -0
- package/dist/server/index.d.ts.map +1 -0
- package/dist/server/index.js +2 -0
- package/dist/server/index.js.map +1 -0
- package/dist/server/server.d.ts +49 -0
- package/dist/server/server.d.ts.map +1 -0
- package/dist/server/server.js +517 -0
- package/dist/server/server.js.map +1 -0
- package/dist/shared/types.d.ts +98 -0
- package/dist/shared/types.d.ts.map +1 -0
- package/dist/shared/types.js +2 -0
- package/dist/shared/types.js.map +1 -0
- package/dist/storage/database.d.ts +18 -0
- package/dist/storage/database.d.ts.map +1 -0
- package/dist/storage/database.js +66 -0
- package/dist/storage/database.js.map +1 -0
- package/dist/storage/index.d.ts +8 -0
- package/dist/storage/index.d.ts.map +1 -0
- package/dist/storage/index.js +5 -0
- package/dist/storage/index.js.map +1 -0
- package/dist/storage/migrations.d.ts +15 -0
- package/dist/storage/migrations.d.ts.map +1 -0
- package/dist/storage/migrations.js +170 -0
- package/dist/storage/migrations.js.map +1 -0
- package/dist/storage/paths.d.ts +15 -0
- package/dist/storage/paths.d.ts.map +1 -0
- package/dist/storage/paths.js +27 -0
- package/dist/storage/paths.js.map +1 -0
- package/dist/storage/schema.d.ts +11 -0
- package/dist/storage/schema.d.ts.map +1 -0
- package/dist/storage/schema.js +40 -0
- package/dist/storage/schema.js.map +1 -0
- package/dist/ui/index.d.ts +2 -0
- package/dist/ui/index.d.ts.map +1 -0
- package/dist/ui/index.js +2 -0
- package/dist/ui/index.js.map +1 -0
- package/dist/ui/static-app.d.ts +2 -0
- package/dist/ui/static-app.d.ts.map +1 -0
- package/dist/ui/static-app.js +735 -0
- package/dist/ui/static-app.js.map +1 -0
- package/docs/adr/0001-sqlite-driver.md +23 -0
- package/docs/agentic-rag-specifications.md +659 -0
- package/docs/agentic-rag-study.md +1056 -0
- package/docs/implementation-plan.md +1425 -0
- package/docs/mcp-integration.md +154 -0
- package/docs/validation-corpus.md +71 -0
- package/package.json +79 -0
|
@@ -0,0 +1,1056 @@
|
|
|
1
|
+
# Agentic RAG Tool Design Study
|
|
2
|
+
|
|
3
|
+
Date: 2026-07-03
|
|
4
|
+
|
|
5
|
+
Source chat: https://chatgpt.com/share/6a47e6fe-1124-83ed-b2b6-8d3321dbcfcc
|
|
6
|
+
|
|
7
|
+
This study turns the chat session into an implementation-oriented plan for a
|
|
8
|
+
lightweight Node.js documentation RAG tool. It treats `athena` as the project
|
|
9
|
+
that will own the CLI, index, retrieval engine, local chat UI, and optional
|
|
10
|
+
agent integrations.
|
|
11
|
+
|
|
12
|
+
Naming convention:
|
|
13
|
+
|
|
14
|
+
- Project name: `athena`.
|
|
15
|
+
- npm package name: `@alextis59/athena`.
|
|
16
|
+
- CLI binary/command name: `athena`.
|
|
17
|
+
- Package-run form: `npx @alextis59/athena`.
|
|
18
|
+
- Local cache directory: `.athena/`.
|
|
19
|
+
|
|
20
|
+
## Executive Summary
|
|
21
|
+
|
|
22
|
+
The target product is a local-first documentation assistant that can be started
|
|
23
|
+
from any documentation folder with a single Node command. It should discover
|
|
24
|
+
docs, parse them, build a reusable cache, launch a local web chat UI, and answer
|
|
25
|
+
questions with exact source references.
|
|
26
|
+
|
|
27
|
+
The settled direction from the chat is:
|
|
28
|
+
|
|
29
|
+
```txt
|
|
30
|
+
Node.js + TypeScript CLI
|
|
31
|
+
+ custom agent loop with no agent SDK dependency
|
|
32
|
+
+ local web chat UI
|
|
33
|
+
+ SQLite cache/index
|
|
34
|
+
+ SQLite FTS5 lexical search
|
|
35
|
+
+ embedded vector search
|
|
36
|
+
+ local default embeddings
|
|
37
|
+
+ optional cloud embeddings
|
|
38
|
+
+ pdf-2-llm for PDF ingestion
|
|
39
|
+
+ exact citations into source files and PDF page regions
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
The key architectural decision is that no SDK should handle Athena's agentic
|
|
43
|
+
part. Athena should implement the agent loop directly: model-provider adapters,
|
|
44
|
+
JSON-schema tools, tool execution, step limits, streaming, memory compaction, and
|
|
45
|
+
citation validation. Existing agent hosts such as OpenCode can still be
|
|
46
|
+
supported later through MCP or custom tools, but they should call Athena's
|
|
47
|
+
retrieval API rather than replace it.
|
|
48
|
+
|
|
49
|
+
The second major decision is that PDF handling must be treated as a serious
|
|
50
|
+
ingestion subsystem. The chat originally explored building a custom PDF parser,
|
|
51
|
+
then updated the plan once the user pointed to `pdf-2-llm`. The final design
|
|
52
|
+
should use the published `pdf-2-llm` npm package as the canonical PDF parser and
|
|
53
|
+
consume its Markdown, source maps, document IR, assets, warnings, diagnostics,
|
|
54
|
+
confidence scores, and table sidecars.
|
|
55
|
+
|
|
56
|
+
The third major decision is local-first embeddings. Athena should default to a
|
|
57
|
+
small, fast, open-source embedding model executed locally, with cloud embeddings
|
|
58
|
+
available as optional provider adapters. A good first default is
|
|
59
|
+
`mixedbread-ai/mxbai-embed-xsmall-v1` via Transformers.js or ONNX. It is an
|
|
60
|
+
open-source English retrieval model with 384-dimensional embeddings and 4096
|
|
61
|
+
context support, which keeps a local SQLite vector index practical.
|
|
62
|
+
|
|
63
|
+
## Product Goals
|
|
64
|
+
|
|
65
|
+
Athena should optimize for documentation lookup in large local repositories,
|
|
66
|
+
SDK docs, generated reference docs, PDFs, Markdown handbooks, and mixed-format
|
|
67
|
+
technical archives.
|
|
68
|
+
|
|
69
|
+
Primary goals:
|
|
70
|
+
|
|
71
|
+
- Run from a folder with one command.
|
|
72
|
+
- Build and reuse a local index.
|
|
73
|
+
- Avoid reprocessing unchanged files.
|
|
74
|
+
- Answer questions through a local chat UI.
|
|
75
|
+
- Use retrieval tools rather than stuffing entire docs into the model context.
|
|
76
|
+
- Return exact evidence references for every substantive answer.
|
|
77
|
+
- Let users click citations to open the source file, line range, Markdown
|
|
78
|
+
anchor, or PDF page region.
|
|
79
|
+
- Preserve useful follow-up context without replaying every intermediate tool
|
|
80
|
+
call.
|
|
81
|
+
- Keep the default stack lightweight and Node-native.
|
|
82
|
+
- Keep private documentation local by default.
|
|
83
|
+
|
|
84
|
+
Non-goals for the first version:
|
|
85
|
+
|
|
86
|
+
- Hosted multi-tenant service.
|
|
87
|
+
- Distributed vector database.
|
|
88
|
+
- Full replacement for a coding agent.
|
|
89
|
+
- Fully autonomous repository editing.
|
|
90
|
+
- Perfect PDF reconstruction for every malformed or scanned PDF.
|
|
91
|
+
- Large framework dependency as the agent runtime.
|
|
92
|
+
|
|
93
|
+
## Default Stack
|
|
94
|
+
|
|
95
|
+
| Layer | Recommended Default | Notes |
|
|
96
|
+
|---|---|---|
|
|
97
|
+
| Runtime | Node.js + TypeScript | Matches the single-command requirement. |
|
|
98
|
+
| CLI | `athena` | The project and command are named `athena`; `npx @alextis59/athena` is only the package-run form. |
|
|
99
|
+
| Agent loop | Custom implementation | No SDK owns tool calling, step control, memory, or citation validation. |
|
|
100
|
+
| Chat UI | Local web app served by the CLI | Gives control over citations and source previews. |
|
|
101
|
+
| Cache/index | SQLite | One portable local database file. |
|
|
102
|
+
| Lexical search | SQLite FTS5 | Good for exact API names, symbols, flags, and errors. |
|
|
103
|
+
| Vector search | `sqlite-vec` or equivalent embedded SQLite vector extension | Keeps semantic search local and serverless. |
|
|
104
|
+
| Default embeddings | `mixedbread-ai/mxbai-embed-xsmall-v1` | Small local open-source retrieval model. |
|
|
105
|
+
| Embedding runtime | Transformers.js/ONNX adapter first | In-process local embedding path. |
|
|
106
|
+
| Optional local embeddings | Ollama adapter | Useful for users already running local models. |
|
|
107
|
+
| Optional cloud embeddings | OpenAI, Voyage, Cohere, Gemini adapters | Opt-in quality/cost tradeoff. |
|
|
108
|
+
| PDF parser | `pdf-2-llm` | Use the published npm package and its structured result, not plain extracted text. |
|
|
109
|
+
| Optional agent integration | MCP/OpenCode | Expose Athena's retrieval tools externally. |
|
|
110
|
+
|
|
111
|
+
SQLite FTS5 is a native SQLite virtual table module for full-text search, so it
|
|
112
|
+
is a good lexical-search base for a local index. `sqlite-vec` is a lightweight
|
|
113
|
+
SQLite vector-search extension with Node bindings and no separate vector server.
|
|
114
|
+
Those properties match the "single local command" requirement.
|
|
115
|
+
|
|
116
|
+
## Alternatives and Tradeoffs from the Chat
|
|
117
|
+
|
|
118
|
+
The chat evaluated several existing tools before settling on the custom core.
|
|
119
|
+
Those tools are still useful context:
|
|
120
|
+
|
|
121
|
+
- Vercel AI SDK was the initial recommendation for streaming, tool calling, and
|
|
122
|
+
multi-step agent behavior, but the final decision is to avoid SDK-managed
|
|
123
|
+
agentic behavior entirely. Athena should implement its own agent loop and use
|
|
124
|
+
third-party SDKs only as optional model-provider adapters when needed.
|
|
125
|
+
- `remark`/`unified` remain good candidates for Markdown and MDX parsing because
|
|
126
|
+
their syntax-tree model can preserve positions for line-level citations.
|
|
127
|
+
- LlamaIndex.TS and LangChain/LangGraph are useful references for agentic RAG
|
|
128
|
+
patterns, but they should not own Athena's runtime.
|
|
129
|
+
- OpenAI File Search is a viable hosted alternative for teams that accept remote
|
|
130
|
+
storage and model-provider coupling, but it is not the default because Athena
|
|
131
|
+
is intended to be local-first and citation-controlled.
|
|
132
|
+
- OpenCode is an integration target, not the product core. It can call Athena's
|
|
133
|
+
tools through MCP after the index and citation model are reliable.
|
|
134
|
+
|
|
135
|
+
The final architecture keeps these options as reference points or adapters
|
|
136
|
+
rather than foundations.
|
|
137
|
+
|
|
138
|
+
## User Experience
|
|
139
|
+
|
|
140
|
+
The intended workflow:
|
|
141
|
+
|
|
142
|
+
```bash
|
|
143
|
+
cd path/to/docs-or-project
|
|
144
|
+
athena
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
For ad hoc use without installing the binary first:
|
|
148
|
+
|
|
149
|
+
```bash
|
|
150
|
+
npx @alextis59/athena
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
The command should:
|
|
154
|
+
|
|
155
|
+
1. Discover supported files.
|
|
156
|
+
2. Create or update `.athena/index.sqlite`.
|
|
157
|
+
3. Hash files and skip unchanged content.
|
|
158
|
+
4. Convert PDFs through `pdf-2-llm`.
|
|
159
|
+
5. Parse Markdown, HTML, text, and code-adjacent docs.
|
|
160
|
+
6. Chunk content with stable source locations.
|
|
161
|
+
7. Build lexical and vector indexes.
|
|
162
|
+
8. Launch a local web UI, for example `http://localhost:4317`.
|
|
163
|
+
9. Let the user ask questions.
|
|
164
|
+
10. Answer with citations that can be opened in context.
|
|
165
|
+
|
|
166
|
+
Common CLI modes:
|
|
167
|
+
|
|
168
|
+
```bash
|
|
169
|
+
athena # index if needed, then launch UI
|
|
170
|
+
athena index # build/update index only
|
|
171
|
+
athena serve # serve existing index and UI
|
|
172
|
+
athena ask "..." # one-shot terminal question
|
|
173
|
+
athena doctor # inspect parser/index/runtime health
|
|
174
|
+
athena mcp # optional MCP server for external agents
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
## Repository and Cache Layout
|
|
178
|
+
|
|
179
|
+
The local cache should live inside the indexed folder and be easy to remove:
|
|
180
|
+
|
|
181
|
+
```txt
|
|
182
|
+
.athena/
|
|
183
|
+
index.sqlite
|
|
184
|
+
blobs/
|
|
185
|
+
extracted-assets/
|
|
186
|
+
pdf-sidecars/
|
|
187
|
+
models/
|
|
188
|
+
embeddings/
|
|
189
|
+
logs/
|
|
190
|
+
runs/
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
The cache should include enough version metadata to invalidate only what is
|
|
194
|
+
necessary:
|
|
195
|
+
|
|
196
|
+
- Athena index schema version.
|
|
197
|
+
- parser version by file type.
|
|
198
|
+
- `pdf-2-llm` package version and option hash.
|
|
199
|
+
- embedding provider, model, dimensions, pooling, normalization, and quantization.
|
|
200
|
+
- chunking strategy version.
|
|
201
|
+
- source file path, size, mtime, and content hash.
|
|
202
|
+
- source-map hash for PDFs and generated Markdown.
|
|
203
|
+
|
|
204
|
+
## Ingestion Pipeline
|
|
205
|
+
|
|
206
|
+
The ingestion pipeline should be deterministic and resumable:
|
|
207
|
+
|
|
208
|
+
```txt
|
|
209
|
+
discover files
|
|
210
|
+
-> classify by type
|
|
211
|
+
-> hash source bytes
|
|
212
|
+
-> skip unchanged sources
|
|
213
|
+
-> parse to normalized document records
|
|
214
|
+
-> chunk with stable source anchors
|
|
215
|
+
-> embed chunks
|
|
216
|
+
-> write FTS rows
|
|
217
|
+
-> write vector rows
|
|
218
|
+
-> write source-map and asset metadata
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
Supported first-wave formats:
|
|
222
|
+
|
|
223
|
+
- Markdown: `.md`, `.mdx`.
|
|
224
|
+
- Plain text: `.txt`, `.rst`, `.adoc`.
|
|
225
|
+
- HTML: `.html`, `.htm`.
|
|
226
|
+
- PDF: `.pdf` through `pdf-2-llm`.
|
|
227
|
+
- Code documentation files: `README`, `CHANGELOG`, `CONTRIBUTING`, package docs.
|
|
228
|
+
|
|
229
|
+
File discovery should default to documentation-heavy paths and avoid expensive
|
|
230
|
+
or generated directories:
|
|
231
|
+
|
|
232
|
+
```txt
|
|
233
|
+
include:
|
|
234
|
+
**/*.md
|
|
235
|
+
**/*.mdx
|
|
236
|
+
**/*.txt
|
|
237
|
+
**/*.rst
|
|
238
|
+
**/*.adoc
|
|
239
|
+
**/*.html
|
|
240
|
+
**/*.pdf
|
|
241
|
+
|
|
242
|
+
exclude:
|
|
243
|
+
node_modules/**
|
|
244
|
+
.git/**
|
|
245
|
+
dist/**
|
|
246
|
+
build/**
|
|
247
|
+
coverage/**
|
|
248
|
+
.athena/**
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
The implementation should allow users to override both include and exclude
|
|
252
|
+
rules in a config file.
|
|
253
|
+
|
|
254
|
+
## PDF Ingestion with `pdf-2-llm`
|
|
255
|
+
|
|
256
|
+
The chat's final PDF decision is clear: Athena should not implement its own PDF
|
|
257
|
+
layout system as a parallel project. It should consume the existing
|
|
258
|
+
`pdf-2-llm` output contract.
|
|
259
|
+
|
|
260
|
+
`pdf-2-llm` is now a public npm package. As of July 4, 2026, the npm registry
|
|
261
|
+
shows `pdf-2-llm@1.1.0` as `latest`, licensed `0BSD`, with CLI binaries named
|
|
262
|
+
`pdf-2-llm` and `pdf2md`. The package exports Node, browser, worker, WASM, and
|
|
263
|
+
schema entrypoints.
|
|
264
|
+
|
|
265
|
+
Install it normally:
|
|
266
|
+
|
|
267
|
+
```bash
|
|
268
|
+
npm install pdf-2-llm
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
The package exposes:
|
|
272
|
+
|
|
273
|
+
- Markdown output.
|
|
274
|
+
- source-map entries back to PDF page regions.
|
|
275
|
+
- document IR.
|
|
276
|
+
- sidecar assets, including tables and extraction artifacts.
|
|
277
|
+
- warning codes.
|
|
278
|
+
- diagnostics.
|
|
279
|
+
- confidence scores.
|
|
280
|
+
- Node, browser, worker, CLI, schema, and WASM-related entrypoints.
|
|
281
|
+
|
|
282
|
+
Athena should treat this as the default PDF dependency, not as a local workspace
|
|
283
|
+
placeholder.
|
|
284
|
+
|
|
285
|
+
Recommended conversion path:
|
|
286
|
+
|
|
287
|
+
```ts
|
|
288
|
+
import { readFile } from "node:fs/promises";
|
|
289
|
+
import { convertPdfToMarkdown } from "pdf-2-llm/node";
|
|
290
|
+
|
|
291
|
+
export async function parsePdf(path: string) {
|
|
292
|
+
const bytes = await readFile(path);
|
|
293
|
+
return convertPdfToMarkdown(bytes, {
|
|
294
|
+
markdown: { pageAnchors: true },
|
|
295
|
+
tables: { csvSidecars: true },
|
|
296
|
+
security: { timeoutMs: 30_000 }
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
Athena should store the full structured result, not just the Markdown string.
|
|
302
|
+
The source map and IR are essential to exact citations, PDF highlights, table
|
|
303
|
+
references, extraction-confidence ranking, and future debugging.
|
|
304
|
+
|
|
305
|
+
## PDF Alternatives and Fallback Strategy
|
|
306
|
+
|
|
307
|
+
Before the `pdf-2-llm` update, the chat explored a layered PDF strategy:
|
|
308
|
+
|
|
309
|
+
- `pdfjs` as a JavaScript-friendly baseline engine;
|
|
310
|
+
- MuPDF as a stronger native extraction engine when packaging complexity is
|
|
311
|
+
acceptable;
|
|
312
|
+
- a robust mode with OCR and heavier layout recovery for difficult PDFs;
|
|
313
|
+
- Docling and PyMuPDF4LLM as references or optional plugins, not default
|
|
314
|
+
dependencies.
|
|
315
|
+
|
|
316
|
+
After the user identified `pdf-2-llm`, the right move is to use that library as
|
|
317
|
+
the primary parser. The fallback concepts still matter operationally:
|
|
318
|
+
|
|
319
|
+
- low-confidence `pdf-2-llm` output should trigger warnings in the UI;
|
|
320
|
+
- scanned PDFs should route to OCR-capable workflows when available;
|
|
321
|
+
- parser diagnostics should be stored so users can understand extraction limits;
|
|
322
|
+
- Athena should keep the parser adapter boundary clean enough to add a MuPDF,
|
|
323
|
+
Docling, or PyMuPDF4LLM plugin later if `pdf-2-llm` cannot handle a corpus.
|
|
324
|
+
|
|
325
|
+
## PDF Citation Model
|
|
326
|
+
|
|
327
|
+
For Markdown and text files, a citation can be:
|
|
328
|
+
|
|
329
|
+
```json
|
|
330
|
+
{
|
|
331
|
+
"kind": "text",
|
|
332
|
+
"path": "docs/example.md",
|
|
333
|
+
"startLine": 42,
|
|
334
|
+
"endLine": 55,
|
|
335
|
+
"snippetHash": "..."
|
|
336
|
+
}
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
For PDFs, a citation should use both the generated Markdown location and the PDF
|
|
340
|
+
source-map location:
|
|
341
|
+
|
|
342
|
+
```json
|
|
343
|
+
{
|
|
344
|
+
"kind": "pdf",
|
|
345
|
+
"path": "manual.pdf",
|
|
346
|
+
"markdownOffsetStart": 1204,
|
|
347
|
+
"markdownOffsetEnd": 1422,
|
|
348
|
+
"page": 17,
|
|
349
|
+
"regions": [
|
|
350
|
+
{ "x": 72, "y": 144, "width": 440, "height": 88 }
|
|
351
|
+
],
|
|
352
|
+
"confidence": 0.91,
|
|
353
|
+
"warningCodes": []
|
|
354
|
+
}
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
The UI should show both:
|
|
358
|
+
|
|
359
|
+
- an answer-side citation label, such as `[manual.pdf p.17]`;
|
|
360
|
+
- a source panel with the exact converted Markdown snippet and a page-region
|
|
361
|
+
preview when possible.
|
|
362
|
+
|
|
363
|
+
The citation validator should reject answer claims that cannot be tied to a
|
|
364
|
+
retrieved chunk or source range.
|
|
365
|
+
|
|
366
|
+
## Chunking Strategy
|
|
367
|
+
|
|
368
|
+
Chunking should be source-aware rather than purely token-count based.
|
|
369
|
+
|
|
370
|
+
Markdown and HTML chunking:
|
|
371
|
+
|
|
372
|
+
- Prefer heading-bounded chunks.
|
|
373
|
+
- Keep stable heading paths.
|
|
374
|
+
- Preserve line ranges.
|
|
375
|
+
- Keep code blocks intact.
|
|
376
|
+
- Avoid splitting tables unless they are too large.
|
|
377
|
+
- Store neighboring chunk IDs for context expansion.
|
|
378
|
+
|
|
379
|
+
PDF chunking:
|
|
380
|
+
|
|
381
|
+
- Chunk from `pdf-2-llm` Markdown and IR.
|
|
382
|
+
- Preserve PDF page anchors.
|
|
383
|
+
- Keep tables, captions, figures, and paragraphs coherent.
|
|
384
|
+
- Use source-map entries to map chunk spans back to page regions.
|
|
385
|
+
- Mark chunks derived from low-confidence extraction.
|
|
386
|
+
- Keep sidecar references for CSV tables and asset outputs.
|
|
387
|
+
|
|
388
|
+
Chunk size guidelines:
|
|
389
|
+
|
|
390
|
+
- Target 300 to 900 tokens for normal prose.
|
|
391
|
+
- Allow larger chunks for API reference sections with dense examples.
|
|
392
|
+
- Keep a small overlap only when it preserves sentence or list continuity.
|
|
393
|
+
- Store both raw text and normalized searchable text.
|
|
394
|
+
|
|
395
|
+
## Index Schema
|
|
396
|
+
|
|
397
|
+
A practical first schema can stay small:
|
|
398
|
+
|
|
399
|
+
```sql
|
|
400
|
+
create table sources (
|
|
401
|
+
id integer primary key,
|
|
402
|
+
path text not null unique,
|
|
403
|
+
kind text not null,
|
|
404
|
+
sha256 text not null,
|
|
405
|
+
size_bytes integer not null,
|
|
406
|
+
mtime_ms integer not null,
|
|
407
|
+
parser_name text not null,
|
|
408
|
+
parser_version text not null,
|
|
409
|
+
parser_options_hash text not null,
|
|
410
|
+
indexed_at text not null
|
|
411
|
+
);
|
|
412
|
+
|
|
413
|
+
create table documents (
|
|
414
|
+
id integer primary key,
|
|
415
|
+
source_id integer not null references sources(id),
|
|
416
|
+
title text,
|
|
417
|
+
metadata_json text not null
|
|
418
|
+
);
|
|
419
|
+
|
|
420
|
+
create table chunks (
|
|
421
|
+
id integer primary key,
|
|
422
|
+
document_id integer not null references documents(id),
|
|
423
|
+
source_id integer not null references sources(id),
|
|
424
|
+
ordinal integer not null,
|
|
425
|
+
heading_path text,
|
|
426
|
+
text text not null,
|
|
427
|
+
normalized_text text not null,
|
|
428
|
+
token_count integer not null,
|
|
429
|
+
locator_json text not null,
|
|
430
|
+
extraction_confidence real,
|
|
431
|
+
warning_codes_json text not null
|
|
432
|
+
);
|
|
433
|
+
|
|
434
|
+
create virtual table chunks_fts using fts5(
|
|
435
|
+
normalized_text,
|
|
436
|
+
heading_path,
|
|
437
|
+
content='chunks',
|
|
438
|
+
content_rowid='id'
|
|
439
|
+
);
|
|
440
|
+
|
|
441
|
+
create table embeddings (
|
|
442
|
+
chunk_id integer primary key references chunks(id),
|
|
443
|
+
provider text not null,
|
|
444
|
+
model text not null,
|
|
445
|
+
dimensions integer not null,
|
|
446
|
+
config_hash text not null,
|
|
447
|
+
vector_blob blob not null
|
|
448
|
+
);
|
|
449
|
+
```
|
|
450
|
+
|
|
451
|
+
If `sqlite-vec` is used, vector storage can be implemented with a `vec0` virtual
|
|
452
|
+
table or a compatible extension-specific table. Keep the relational metadata in
|
|
453
|
+
normal SQLite tables so vector-extension migrations do not force a full metadata
|
|
454
|
+
rewrite.
|
|
455
|
+
|
|
456
|
+
## Embedding Model Recommendation
|
|
457
|
+
|
|
458
|
+
The default should be local, open-source, and fast. The chat converged on
|
|
459
|
+
`mixedbread-ai/mxbai-embed-xsmall-v1` as a strong default because it is small,
|
|
460
|
+
retrieval-oriented, 384-dimensional, and supports 4096 context. Those properties
|
|
461
|
+
are attractive for a local SQLite-backed index:
|
|
462
|
+
|
|
463
|
+
- 384 dimensions reduces index size and speeds similarity scoring.
|
|
464
|
+
- 4096 context gives chunking more headroom for long documentation sections.
|
|
465
|
+
- Local inference avoids sending private docs to a cloud service.
|
|
466
|
+
- Apache 2.0 licensing is easier for broad reuse than unclear model terms.
|
|
467
|
+
- Matryoshka and binary-quantization support leave room for future size/speed
|
|
468
|
+
experiments.
|
|
469
|
+
|
|
470
|
+
Recommended default embedding config:
|
|
471
|
+
|
|
472
|
+
```yaml
|
|
473
|
+
embeddings:
|
|
474
|
+
provider: transformers-js
|
|
475
|
+
model: mixedbread-ai/mxbai-embed-xsmall-v1
|
|
476
|
+
dimensions: 384
|
|
477
|
+
pooling: mean
|
|
478
|
+
normalize: true
|
|
479
|
+
batchSize: 32
|
|
480
|
+
```
|
|
481
|
+
|
|
482
|
+
Provider alternatives:
|
|
483
|
+
|
|
484
|
+
| Provider | Role | Notes |
|
|
485
|
+
|---|---|---|
|
|
486
|
+
| `transformers-js` | Default in-process local runtime | Best default for single-command Node usage. |
|
|
487
|
+
| `ollama` | Optional local service runtime | Useful when users already manage local models with Ollama. |
|
|
488
|
+
| `openai` | Optional cloud adapter | Simple external quality baseline. |
|
|
489
|
+
| `voyage` | Optional cloud adapter | Consider for code-heavy docs and retrieval quality experiments. |
|
|
490
|
+
| `cohere` | Optional cloud adapter | Consider for multilingual or enterprise setups. |
|
|
491
|
+
| `gemini` | Optional cloud adapter | Consider when already using Google infrastructure. |
|
|
492
|
+
|
|
493
|
+
Ollama should be treated as an optional local provider, not the default, because
|
|
494
|
+
it requires a separate local service. It is still valuable: Ollama documents an
|
|
495
|
+
embedding API and currently recommends models such as `embeddinggemma`,
|
|
496
|
+
`qwen3-embedding`, and `all-minilm`.
|
|
497
|
+
|
|
498
|
+
Cloud embeddings should always be opt-in. The CLI should require explicit
|
|
499
|
+
configuration before sending document text to a remote service.
|
|
500
|
+
|
|
501
|
+
## Embedding Survey Notes
|
|
502
|
+
|
|
503
|
+
The chat's final recommendation favored `mxbai-embed-xsmall-v1`, but the model
|
|
504
|
+
survey also identified alternatives worth keeping in the design:
|
|
505
|
+
|
|
506
|
+
- `all-MiniLM` is a small, established local baseline and is especially useful
|
|
507
|
+
through Ollama or ONNX distributions.
|
|
508
|
+
- BGE-M3 is a stronger multilingual option with dense, sparse, and multi-vector
|
|
509
|
+
retrieval behavior, but it is heavier than the default.
|
|
510
|
+
- Qwen3 embedding models are promising quality-oriented local options,
|
|
511
|
+
especially for multilingual and code-heavy retrieval, but should be optional.
|
|
512
|
+
- EmbeddingGemma is a useful local Ollama option when users already run Ollama.
|
|
513
|
+
- `nomic-embed-text` is another local-friendly option with long-context
|
|
514
|
+
retrieval variants.
|
|
515
|
+
- FastEmbed-style runtimes are relevant for speed, but Athena should keep a
|
|
516
|
+
provider interface so runtime choice can change without rewriting indexing.
|
|
517
|
+
- Matryoshka representation support and binary quantization are future
|
|
518
|
+
optimization paths for smaller indexes and faster local retrieval.
|
|
519
|
+
|
|
520
|
+
The rule should be empirical: ship one fast local default, then compare optional
|
|
521
|
+
providers on Athena's own documentation-evaluation harness.
|
|
522
|
+
|
|
523
|
+
## Embedding Provider Interface
|
|
524
|
+
|
|
525
|
+
Athena should isolate embeddings behind a small interface:
|
|
526
|
+
|
|
527
|
+
```ts
|
|
528
|
+
export type EmbeddingInput = {
|
|
529
|
+
id: string;
|
|
530
|
+
text: string;
|
|
531
|
+
metadata?: Record<string, unknown>;
|
|
532
|
+
};
|
|
533
|
+
|
|
534
|
+
export type EmbeddingVector = {
|
|
535
|
+
id: string;
|
|
536
|
+
vector: Float32Array;
|
|
537
|
+
dimensions: number;
|
|
538
|
+
};
|
|
539
|
+
|
|
540
|
+
export interface EmbeddingProvider {
|
|
541
|
+
readonly name: string;
|
|
542
|
+
readonly model: string;
|
|
543
|
+
readonly dimensions: number;
|
|
544
|
+
readonly configHash: string;
|
|
545
|
+
|
|
546
|
+
embedBatch(inputs: EmbeddingInput[]): Promise<EmbeddingVector[]>;
|
|
547
|
+
}
|
|
548
|
+
```
|
|
549
|
+
|
|
550
|
+
Embedding cache invalidation should include:
|
|
551
|
+
|
|
552
|
+
- provider name;
|
|
553
|
+
- model name;
|
|
554
|
+
- model revision or digest when available;
|
|
555
|
+
- dimensions;
|
|
556
|
+
- pooling;
|
|
557
|
+
- normalization;
|
|
558
|
+
- quantization settings;
|
|
559
|
+
- chunk text hash;
|
|
560
|
+
- chunking strategy version.
|
|
561
|
+
|
|
562
|
+
Changing any of these should invalidate only affected embeddings, not the whole
|
|
563
|
+
source parse.
|
|
564
|
+
|
|
565
|
+
## Retrieval Design
|
|
566
|
+
|
|
567
|
+
The retrieval engine should use hybrid search:
|
|
568
|
+
|
|
569
|
+
1. Rewrite or decompose the user's question when useful.
|
|
570
|
+
2. Run FTS5 search for exact terms.
|
|
571
|
+
3. Run vector search for semantic matches.
|
|
572
|
+
4. Run targeted grep for identifiers, package names, CLI flags, and errors.
|
|
573
|
+
5. Merge and deduplicate results by source and chunk.
|
|
574
|
+
6. Boost heading-path matches and exact symbol matches.
|
|
575
|
+
7. Optionally apply reranking to the merged candidate set when the corpus or
|
|
576
|
+
model provider justifies the extra latency.
|
|
577
|
+
8. Penalize low-confidence PDF chunks unless the query explicitly targets them.
|
|
578
|
+
9. Expand around top chunks to include neighboring context.
|
|
579
|
+
10. Validate that final answer citations point to retrieved evidence.
|
|
580
|
+
|
|
581
|
+
This is the core reason Athena should not depend only on an external coding
|
|
582
|
+
agent's native grep/read tools. Documentation RAG needs indexing metadata,
|
|
583
|
+
source-map awareness, chunk scoring, source confidence, and citation validation.
|
|
584
|
+
|
|
585
|
+
## Agent Tool Design
|
|
586
|
+
|
|
587
|
+
The agent should see a small set of precise tools.
|
|
588
|
+
|
|
589
|
+
### `searchDocs`
|
|
590
|
+
|
|
591
|
+
Purpose: hybrid search over indexed docs.
|
|
592
|
+
|
|
593
|
+
Input:
|
|
594
|
+
|
|
595
|
+
```json
|
|
596
|
+
{
|
|
597
|
+
"query": "string",
|
|
598
|
+
"filters": {
|
|
599
|
+
"pathPrefix": "string",
|
|
600
|
+
"kind": "markdown | pdf | html | text"
|
|
601
|
+
},
|
|
602
|
+
"limit": 10
|
|
603
|
+
}
|
|
604
|
+
```
|
|
605
|
+
|
|
606
|
+
Output:
|
|
607
|
+
|
|
608
|
+
```json
|
|
609
|
+
{
|
|
610
|
+
"results": [
|
|
611
|
+
{
|
|
612
|
+
"chunkId": 123,
|
|
613
|
+
"path": "docs/api.md",
|
|
614
|
+
"headingPath": "Authentication > Tokens",
|
|
615
|
+
"snippet": "...",
|
|
616
|
+
"score": 0.82,
|
|
617
|
+
"locator": { "startLine": 10, "endLine": 22 }
|
|
618
|
+
}
|
|
619
|
+
]
|
|
620
|
+
}
|
|
621
|
+
```
|
|
622
|
+
|
|
623
|
+
### `readSourceRange`
|
|
624
|
+
|
|
625
|
+
Purpose: read an exact source range from a non-PDF document.
|
|
626
|
+
|
|
627
|
+
Input:
|
|
628
|
+
|
|
629
|
+
```json
|
|
630
|
+
{
|
|
631
|
+
"path": "docs/api.md",
|
|
632
|
+
"startLine": 10,
|
|
633
|
+
"endLine": 40
|
|
634
|
+
}
|
|
635
|
+
```
|
|
636
|
+
|
|
637
|
+
### `readPdfSource`
|
|
638
|
+
|
|
639
|
+
Purpose: read converted PDF Markdown plus original page-region metadata.
|
|
640
|
+
|
|
641
|
+
Input:
|
|
642
|
+
|
|
643
|
+
```json
|
|
644
|
+
{
|
|
645
|
+
"path": "manual.pdf",
|
|
646
|
+
"page": 17,
|
|
647
|
+
"regionId": "..."
|
|
648
|
+
}
|
|
649
|
+
```
|
|
650
|
+
|
|
651
|
+
### `grepDocs`
|
|
652
|
+
|
|
653
|
+
Purpose: exact string or regex search for identifiers.
|
|
654
|
+
|
|
655
|
+
Input:
|
|
656
|
+
|
|
657
|
+
```json
|
|
658
|
+
{
|
|
659
|
+
"pattern": "AUTH_TOKEN",
|
|
660
|
+
"caseSensitive": false,
|
|
661
|
+
"limit": 20
|
|
662
|
+
}
|
|
663
|
+
```
|
|
664
|
+
|
|
665
|
+
### `listDocs`
|
|
666
|
+
|
|
667
|
+
Purpose: inspect indexed sources and narrow search scope.
|
|
668
|
+
|
|
669
|
+
Input:
|
|
670
|
+
|
|
671
|
+
```json
|
|
672
|
+
{
|
|
673
|
+
"pathPrefix": "docs/",
|
|
674
|
+
"kind": "pdf"
|
|
675
|
+
}
|
|
676
|
+
```
|
|
677
|
+
|
|
678
|
+
### `relatedChunks`
|
|
679
|
+
|
|
680
|
+
Purpose: fetch neighboring or related chunks after a promising hit.
|
|
681
|
+
|
|
682
|
+
Input:
|
|
683
|
+
|
|
684
|
+
```json
|
|
685
|
+
{
|
|
686
|
+
"chunkId": 123,
|
|
687
|
+
"before": 1,
|
|
688
|
+
"after": 2
|
|
689
|
+
}
|
|
690
|
+
```
|
|
691
|
+
|
|
692
|
+
## Custom Agent Loop
|
|
693
|
+
|
|
694
|
+
The agent loop must be small, explicit, and Athena-owned. This is not an SDK
|
|
695
|
+
integration point. It is the core control surface for tool selection, step
|
|
696
|
+
limits, memory policy, streaming, and citation enforcement:
|
|
697
|
+
|
|
698
|
+
```ts
|
|
699
|
+
export type ModelMessage =
|
|
700
|
+
| { role: "system"; content: string }
|
|
701
|
+
| { role: "user"; content: string }
|
|
702
|
+
| { role: "assistant"; content: string; toolCalls?: ToolCall[] }
|
|
703
|
+
| { role: "tool"; toolCallId: string; content: unknown };
|
|
704
|
+
|
|
705
|
+
export type ToolDef = {
|
|
706
|
+
name: string;
|
|
707
|
+
description: string;
|
|
708
|
+
inputSchema: unknown;
|
|
709
|
+
execute(input: unknown): Promise<unknown>;
|
|
710
|
+
};
|
|
711
|
+
|
|
712
|
+
export interface ModelProvider {
|
|
713
|
+
streamResponse(input: {
|
|
714
|
+
model: string;
|
|
715
|
+
messages: ModelMessage[];
|
|
716
|
+
tools: ToolDef[];
|
|
717
|
+
temperature?: number;
|
|
718
|
+
}): AsyncIterable<ModelEvent>;
|
|
719
|
+
}
|
|
720
|
+
```
|
|
721
|
+
|
|
722
|
+
Required loop behavior:
|
|
723
|
+
|
|
724
|
+
- enforce a max-step count;
|
|
725
|
+
- stream assistant output to the UI;
|
|
726
|
+
- execute only registered tools;
|
|
727
|
+
- validate tool inputs before execution;
|
|
728
|
+
- record tool traces for debugging;
|
|
729
|
+
- perform conversation compaction between turns;
|
|
730
|
+
- store final answer citations;
|
|
731
|
+
- reject or warn on unsupported citations;
|
|
732
|
+
- expose deterministic failure messages when retrieval is insufficient.
|
|
733
|
+
|
|
734
|
+
The system prompt should instruct the agent to:
|
|
735
|
+
|
|
736
|
+
- search before answering factual doc questions;
|
|
737
|
+
- cite every claim that depends on documentation;
|
|
738
|
+
- prefer exact source ranges over broad summaries;
|
|
739
|
+
- say when the index has no evidence;
|
|
740
|
+
- use follow-up memory only as conversational context, not as documentary proof.
|
|
741
|
+
|
|
742
|
+
## Follow-Up Memory
|
|
743
|
+
|
|
744
|
+
The chat identified a useful memory policy: keep questions, final answers, and
|
|
745
|
+
citations, but avoid replaying every intermediate search step into the model
|
|
746
|
+
context.
|
|
747
|
+
|
|
748
|
+
A stored turn can look like:
|
|
749
|
+
|
|
750
|
+
```json
|
|
751
|
+
{
|
|
752
|
+
"question": "How do I configure OAuth?",
|
|
753
|
+
"answerSummary": "Use providers.github and set callbackUrl...",
|
|
754
|
+
"citations": [
|
|
755
|
+
{ "path": "docs/auth.md", "startLine": 40, "endLine": 58 }
|
|
756
|
+
],
|
|
757
|
+
"createdAt": "2026-07-03T12:00:00Z"
|
|
758
|
+
}
|
|
759
|
+
```
|
|
760
|
+
|
|
761
|
+
On follow-up, the model can receive:
|
|
762
|
+
|
|
763
|
+
- recent user questions;
|
|
764
|
+
- short answer summaries;
|
|
765
|
+
- citation identifiers;
|
|
766
|
+
- any user-pinned sources;
|
|
767
|
+
- fresh retrieval results for the new question.
|
|
768
|
+
|
|
769
|
+
It should not receive full prior tool traces unless the user asks for audit
|
|
770
|
+
details.
|
|
771
|
+
|
|
772
|
+
## Local Web UI
|
|
773
|
+
|
|
774
|
+
The UI should be simple, but citation-first:
|
|
775
|
+
|
|
776
|
+
- chat column;
|
|
777
|
+
- retrieval trace drawer;
|
|
778
|
+
- source preview panel;
|
|
779
|
+
- clickable citations;
|
|
780
|
+
- line-highlighted Markdown/text viewer;
|
|
781
|
+
- PDF page preview with highlighted regions when available;
|
|
782
|
+
- index status indicator;
|
|
783
|
+
- parser warning badges;
|
|
784
|
+
- "open in editor" links for local files when feasible;
|
|
785
|
+
- controls for reindexing and source filtering.
|
|
786
|
+
|
|
787
|
+
Answer citations should be visible inline and collected at the bottom of each
|
|
788
|
+
answer. Clicking a citation should open the exact source range, not only the
|
|
789
|
+
file.
|
|
790
|
+
|
|
791
|
+
## Optional MCP and OpenCode Integration
|
|
792
|
+
|
|
793
|
+
OpenCode or another agent should be an optional consumer of Athena's index.
|
|
794
|
+
|
|
795
|
+
The best integration is an MCP server exposing:
|
|
796
|
+
|
|
797
|
+
- `searchDocs`;
|
|
798
|
+
- `readSourceRange`;
|
|
799
|
+
- `readPdfSource`;
|
|
800
|
+
- `grepDocs`;
|
|
801
|
+
- `listDocs`;
|
|
802
|
+
- `relatedChunks`.
|
|
803
|
+
|
|
804
|
+
This lets external agents use Athena as a specialized documentation search
|
|
805
|
+
backend while Athena retains responsibility for indexing, ranking, parsing, and
|
|
806
|
+
citations.
|
|
807
|
+
|
|
808
|
+
Athena should also support a normal local chat UI because many documentation
|
|
809
|
+
queries do not require a coding agent.
|
|
810
|
+
|
|
811
|
+
## Configuration
|
|
812
|
+
|
|
813
|
+
Example `athena.config.ts`:
|
|
814
|
+
|
|
815
|
+
```ts
|
|
816
|
+
import { defineConfig } from "@alextis59/athena";
|
|
817
|
+
|
|
818
|
+
export default defineConfig({
|
|
819
|
+
index: {
|
|
820
|
+
include: ["**/*.md", "**/*.mdx", "**/*.pdf", "**/*.html"],
|
|
821
|
+
exclude: ["node_modules/**", "dist/**", ".git/**"]
|
|
822
|
+
},
|
|
823
|
+
embeddings: {
|
|
824
|
+
provider: "transformers-js",
|
|
825
|
+
model: "mixedbread-ai/mxbai-embed-xsmall-v1",
|
|
826
|
+
dimensions: 384,
|
|
827
|
+
pooling: "mean",
|
|
828
|
+
normalize: true
|
|
829
|
+
},
|
|
830
|
+
pdf: {
|
|
831
|
+
parser: "pdf-2-llm",
|
|
832
|
+
pageAnchors: true,
|
|
833
|
+
csvSidecars: true,
|
|
834
|
+
timeoutMs: 30_000,
|
|
835
|
+
minConfidenceForAutoCitation: 0.65
|
|
836
|
+
},
|
|
837
|
+
server: {
|
|
838
|
+
host: "127.0.0.1",
|
|
839
|
+
port: 4317
|
|
840
|
+
}
|
|
841
|
+
});
|
|
842
|
+
```
|
|
843
|
+
|
|
844
|
+
Cloud providers should require explicit configuration:
|
|
845
|
+
|
|
846
|
+
```ts
|
|
847
|
+
embeddings: {
|
|
848
|
+
provider: "openai",
|
|
849
|
+
model: "text-embedding-3-small",
|
|
850
|
+
allowRemoteDocumentText: true
|
|
851
|
+
}
|
|
852
|
+
```
|
|
853
|
+
|
|
854
|
+
The explicit `allowRemoteDocumentText` flag prevents accidental leakage of
|
|
855
|
+
private docs.
|
|
856
|
+
|
|
857
|
+
## Project Structure
|
|
858
|
+
|
|
859
|
+
A practical implementation layout:
|
|
860
|
+
|
|
861
|
+
```txt
|
|
862
|
+
athena/
|
|
863
|
+
src/
|
|
864
|
+
cli/
|
|
865
|
+
main.ts
|
|
866
|
+
commands/
|
|
867
|
+
server/
|
|
868
|
+
app.ts
|
|
869
|
+
routes/
|
|
870
|
+
ui/
|
|
871
|
+
...
|
|
872
|
+
indexer/
|
|
873
|
+
discover.ts
|
|
874
|
+
pipeline.ts
|
|
875
|
+
chunking.ts
|
|
876
|
+
cache.ts
|
|
877
|
+
parsers/
|
|
878
|
+
markdown.ts
|
|
879
|
+
html.ts
|
|
880
|
+
text.ts
|
|
881
|
+
pdf2llm.ts
|
|
882
|
+
embeddings/
|
|
883
|
+
provider.ts
|
|
884
|
+
transformers-js.ts
|
|
885
|
+
ollama.ts
|
|
886
|
+
openai.ts
|
|
887
|
+
retrieval/
|
|
888
|
+
fts.ts
|
|
889
|
+
vector.ts
|
|
890
|
+
hybrid.ts
|
|
891
|
+
rerank.ts
|
|
892
|
+
agent/
|
|
893
|
+
provider.ts
|
|
894
|
+
loop.ts
|
|
895
|
+
tools.ts
|
|
896
|
+
citations.ts
|
|
897
|
+
memory.ts
|
|
898
|
+
storage/
|
|
899
|
+
db.ts
|
|
900
|
+
migrations/
|
|
901
|
+
mcp/
|
|
902
|
+
server.ts
|
|
903
|
+
docs/
|
|
904
|
+
agentic-rag-study.md
|
|
905
|
+
```
|
|
906
|
+
|
|
907
|
+
## MVP Plan
|
|
908
|
+
|
|
909
|
+
### Phase 1: Markdown-Only Local RAG
|
|
910
|
+
|
|
911
|
+
Deliver:
|
|
912
|
+
|
|
913
|
+
- CLI startup.
|
|
914
|
+
- file discovery and hashing.
|
|
915
|
+
- SQLite metadata tables.
|
|
916
|
+
- Markdown parsing with line ranges.
|
|
917
|
+
- FTS5 lexical search.
|
|
918
|
+
- local web chat UI.
|
|
919
|
+
- basic custom agent loop.
|
|
920
|
+
- `searchDocs` and `readSourceRange`.
|
|
921
|
+
- citation validation for Markdown line ranges.
|
|
922
|
+
|
|
923
|
+
This phase proves the end-to-end user experience with the least parser risk.
|
|
924
|
+
|
|
925
|
+
### Phase 2: Local Embeddings and Hybrid Search
|
|
926
|
+
|
|
927
|
+
Deliver:
|
|
928
|
+
|
|
929
|
+
- Transformers.js embedding provider.
|
|
930
|
+
- `mxbai-embed-xsmall-v1` default.
|
|
931
|
+
- embedding cache invalidation.
|
|
932
|
+
- vector storage.
|
|
933
|
+
- hybrid FTS + vector retrieval.
|
|
934
|
+
- neighbor chunk expansion.
|
|
935
|
+
- answer trace display in the UI.
|
|
936
|
+
|
|
937
|
+
This phase proves answer quality beyond exact keyword matching.
|
|
938
|
+
|
|
939
|
+
### Phase 3: PDF Ingestion with `pdf-2-llm`
|
|
940
|
+
|
|
941
|
+
Deliver:
|
|
942
|
+
|
|
943
|
+
- `pdf-2-llm` parser adapter.
|
|
944
|
+
- structured result persistence.
|
|
945
|
+
- PDF Markdown chunking.
|
|
946
|
+
- PDF source-map citations.
|
|
947
|
+
- PDF warning/confidence display.
|
|
948
|
+
- `readPdfSource` tool.
|
|
949
|
+
- table sidecar handling.
|
|
950
|
+
|
|
951
|
+
This phase makes Athena useful for real documentation archives.
|
|
952
|
+
|
|
953
|
+
### Phase 4: Quality Gates and Evaluation
|
|
954
|
+
|
|
955
|
+
Deliver:
|
|
956
|
+
|
|
957
|
+
- fixed evaluation corpus.
|
|
958
|
+
- retrieval recall tests.
|
|
959
|
+
- citation correctness tests.
|
|
960
|
+
- PDF source-map integrity tests.
|
|
961
|
+
- stale-cache invalidation tests.
|
|
962
|
+
- answer faithfulness checks.
|
|
963
|
+
- regression CLI for local runs.
|
|
964
|
+
|
|
965
|
+
This phase prevents the RAG system from becoming a demo that cannot be trusted.
|
|
966
|
+
It should include an evaluation harness that can be run before changing
|
|
967
|
+
chunking, embeddings, ranking, PDF parsing, or citation validation.
|
|
968
|
+
|
|
969
|
+
### Phase 5: MCP/OpenCode Bridge
|
|
970
|
+
|
|
971
|
+
Deliver:
|
|
972
|
+
|
|
973
|
+
- MCP server wrapping Athena tools.
|
|
974
|
+
- external-agent setup docs.
|
|
975
|
+
- trace compatibility between UI and MCP usage.
|
|
976
|
+
- read-only default permissions.
|
|
977
|
+
|
|
978
|
+
This phase lets coding agents consume the index without taking over the core.
|
|
979
|
+
|
|
980
|
+
## Evaluation Plan
|
|
981
|
+
|
|
982
|
+
Athena needs tests at three levels.
|
|
983
|
+
|
|
984
|
+
Parser/index tests:
|
|
985
|
+
|
|
986
|
+
- unchanged files are skipped;
|
|
987
|
+
- changed files are reindexed;
|
|
988
|
+
- line ranges remain stable;
|
|
989
|
+
- PDF source maps point to valid page regions;
|
|
990
|
+
- warning and confidence fields survive storage;
|
|
991
|
+
- table sidecars can be opened from citations.
|
|
992
|
+
|
|
993
|
+
Retrieval tests:
|
|
994
|
+
|
|
995
|
+
- exact symbol query returns the symbol's defining section;
|
|
996
|
+
- semantic query returns relevant conceptual section;
|
|
997
|
+
- hybrid retrieval outperforms FTS-only and vector-only on a small corpus;
|
|
998
|
+
- low-confidence PDF chunks are not over-promoted;
|
|
999
|
+
- source filters work by path and type.
|
|
1000
|
+
|
|
1001
|
+
Answer tests:
|
|
1002
|
+
|
|
1003
|
+
- answer includes citations for doc-derived claims;
|
|
1004
|
+
- unsupported claims are rejected or marked as unsupported;
|
|
1005
|
+
- citations open the expected source range;
|
|
1006
|
+
- follow-up questions use prior context without skipping fresh retrieval;
|
|
1007
|
+
- max-step guard stops runaway tool loops.
|
|
1008
|
+
|
|
1009
|
+
Evaluation datasets should include:
|
|
1010
|
+
|
|
1011
|
+
- synthetic Markdown docs with known answers;
|
|
1012
|
+
- realistic API docs;
|
|
1013
|
+
- docs with repeated terms and ambiguous headings;
|
|
1014
|
+
- table-heavy PDFs;
|
|
1015
|
+
- scanned or OCR-planned PDFs;
|
|
1016
|
+
- malformed or encrypted PDFs;
|
|
1017
|
+
- multilingual or RTL/CJK PDFs if those are target users.
|
|
1018
|
+
|
|
1019
|
+
## Risks and Mitigations
|
|
1020
|
+
|
|
1021
|
+
| Risk | Mitigation |
|
|
1022
|
+
|---|---|
|
|
1023
|
+
| PDF source maps are noisy | Display confidence, warnings, and snippets; gate citations below a threshold. |
|
|
1024
|
+
| Local embeddings are lower quality than cloud | Keep cloud adapters opt-in and add corpus-level evals. |
|
|
1025
|
+
| Vector extension portability issues | Keep vector metadata separate and support rebuilds. |
|
|
1026
|
+
| Agent invents unsupported claims | Citation validator and retrieval-required prompt policy. |
|
|
1027
|
+
| Cache invalidation bugs | Include parser, chunker, model, and source hashes in cache keys. |
|
|
1028
|
+
| Large docs make indexing slow | Incremental hashing, batching, progress UI, and resumable indexing. |
|
|
1029
|
+
| Context flooding in follow-ups | Store summaries and citations, not all tool traces. |
|
|
1030
|
+
| External-agent integration bypasses citations | Expose only citation-aware retrieval tools through MCP. |
|
|
1031
|
+
|
|
1032
|
+
## Final Recommendation
|
|
1033
|
+
|
|
1034
|
+
Build Athena as a local Node/TypeScript documentation RAG system with a custom,
|
|
1035
|
+
SDK-free agent loop and a specialized retrieval engine. Use SQLite for the
|
|
1036
|
+
cache, FTS5 for exact lexical search, an embedded vector extension for semantic
|
|
1037
|
+
search, Transformers.js/ONNX with `mxbai-embed-xsmall-v1` for default local
|
|
1038
|
+
embeddings, and the published `pdf-2-llm` npm package as the PDF ingestion core.
|
|
1039
|
+
|
|
1040
|
+
Do not use OpenCode, LangChain, LlamaIndex, Vercel AI SDK, or any other agent
|
|
1041
|
+
SDK to handle Athena's agentic behavior. They can be useful references, optional
|
|
1042
|
+
integrations, or model-provider adapters, but the hard part of this product is
|
|
1043
|
+
not "calling a model." The hard parts are stable indexing, source-aware
|
|
1044
|
+
chunking, exact citation mapping, PDF extraction quality, cache correctness, and
|
|
1045
|
+
answer validation. Those should remain Athena-owned.
|
|
1046
|
+
|
|
1047
|
+
## References
|
|
1048
|
+
|
|
1049
|
+
- Source chat: https://chatgpt.com/share/6a47e6fe-1124-83ed-b2b6-8d3321dbcfcc
|
|
1050
|
+
- `pdf-2-llm` npm package: https://www.npmjs.com/package/pdf-2-llm
|
|
1051
|
+
- `pdf-2-llm`: https://github.com/alextis59/pdf-2-llm
|
|
1052
|
+
- `mixedbread-ai/mxbai-embed-xsmall-v1`: https://huggingface.co/mixedbread-ai/mxbai-embed-xsmall-v1
|
|
1053
|
+
- Transformers.js: https://huggingface.co/docs/transformers.js/en/index
|
|
1054
|
+
- Ollama embeddings: https://docs.ollama.com/capabilities/embeddings
|
|
1055
|
+
- SQLite FTS5: https://www.sqlite.org/fts5.html
|
|
1056
|
+
- `sqlite-vec`: https://alexgarcia.xyz/sqlite-vec/
|