Skip to content

Commit 53104fb

Browse files
prosdevclaude
andcommitted
docs(plans): add Phase 1 antfly migration plan
Detailed plan for replacing LanceDB + @xenova/transformers with Antfly: - Overview with architecture, API mapping, decisions, risks - 6 implementation parts: spike, AntflyVectorStore, facade update, dep swap + CI, dev setup command, documentation - Docker-based integration tests (CI and local) - graphweave as end-to-end test corpus - CLI owns antfly lifecycle (dev setup, auto-start) - User-selectable embedding models via Termite Reviewed by plan-reviewer agent; all blockers resolved. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 4697859 commit 53104fb

8 files changed

Lines changed: 1389 additions & 1 deletion

File tree

.claude/da-plans/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ Implementation deviations are logged at the bottom of each plan file.
99

1010
| Track | Description | Status |
1111
|-------|-------------|--------|
12-
| [Core](core/) | Scanner, vector storage, services, indexer | Not started |
12+
| [Core](core/) | Scanner, vector storage, services, indexer | Phase 1: Draft |
1313
| [CLI](cli/) | Command-line interface | Not started |
1414
| [MCP Server](mcp-server/) | Model Context Protocol server + adapters | Not started |
1515
| [Subagents](subagents/) | Coordinator, explorer, planner, GitHub agents | Not started |
Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
# Part 1.1 — Spike: Validate Antfly API
2+
3+
## Goal
4+
5+
Install antfly locally and confirm it can satisfy every operation our `VectorStore` interface
6+
needs. This is a throwaway spike — no code is committed.
7+
8+
## Prerequisites
9+
10+
```bash
11+
brew install --cask antflydb/antfly/antfly
12+
antfly termite pull --variants i8 BAAI/bge-small-en-v1.5
13+
antfly swarm
14+
```
15+
16+
## Tasks
17+
18+
### 1. Create a table with embedding index
19+
20+
```typescript
21+
import { AntflyClient } from '@antfly/sdk';
22+
23+
const client = new AntflyClient({ baseUrl: 'http://localhost:8080' });
24+
25+
await client.tables.create('spike-code', {
26+
indexes: {
27+
content: {
28+
type: 'embeddings',
29+
template: '{{text}}',
30+
embedder: {
31+
provider: 'termite',
32+
model: 'BAAI/bge-small-en-v1.5',
33+
},
34+
},
35+
},
36+
});
37+
```
38+
39+
**Confirm:** Table created, index active.
40+
41+
### 2. Batch insert documents
42+
43+
Insert 100 code documents matching our `EmbeddingDocument` shape:
44+
45+
```typescript
46+
const inserts: Record<string, any> = {};
47+
for (const doc of documents) {
48+
inserts[doc.id] = {
49+
text: doc.text,
50+
metadata: JSON.stringify(doc.metadata),
51+
};
52+
}
53+
await client.tables.batch('spike-code', { inserts });
54+
```
55+
56+
**Confirm:** Documents inserted. Check background embedding progress:
57+
```bash
58+
antfly index list --table spike-code
59+
```
60+
61+
### 3. Test upsert behavior
62+
63+
Re-insert a document with the same key but different text.
64+
65+
**Confirm:** Does it overwrite? Or error? We need overwrite (upsert) semantics.
66+
67+
### 4. Run hybrid search
68+
69+
```typescript
70+
const results = await client.query({
71+
table: 'spike-code',
72+
semantic_search: 'authentication middleware',
73+
full_text_search: { query: 'validateUser' },
74+
indexes: ['content'],
75+
fields: ['text', 'metadata'],
76+
limit: 10,
77+
});
78+
```
79+
80+
**Confirm:** Results come back. Both semantic and keyword matches appear.
81+
82+
### 5. Test semantic-only search
83+
84+
```typescript
85+
const results = await client.query({
86+
table: 'spike-code',
87+
semantic_search: 'error handling patterns',
88+
indexes: ['content'],
89+
limit: 10,
90+
});
91+
```
92+
93+
**Confirm:** Pure semantic search works (our current default path).
94+
95+
### 6. Test key lookup
96+
97+
```typescript
98+
const doc = await client.tables.lookup('spike-code', 'some-doc-id');
99+
```
100+
101+
**Confirm:** Returns the document by key. Fast (not a vector scan).
102+
103+
### 7. Test batch delete
104+
105+
```typescript
106+
await client.tables.batch('spike-code', { deletes: ['doc-1', 'doc-2'] });
107+
```
108+
109+
**Confirm:** Documents removed. Search no longer returns them.
110+
111+
### 8. Test count / table stats
112+
113+
```typescript
114+
const info = await client.tables.get('spike-code');
115+
```
116+
117+
**Confirm:** Can we get document count from table info?
118+
119+
### 9. Test full scan (no query vector)
120+
121+
```typescript
122+
const all = await client.tables.query('spike-code', { limit: 1000 });
123+
// or
124+
const all = await client.query({ table: 'spike-code', limit: 1000 });
125+
```
126+
127+
**Confirm:** Can we retrieve all documents without a search query? This maps to `getAll()`.
128+
129+
### 10. Test embedding availability timing
130+
131+
Insert a batch, then immediately search for it.
132+
133+
**Confirm:** How long until newly-inserted docs appear in search results?
134+
If there's a delay, we need to handle this in the index command (wait for embedding completion).
135+
136+
## Questions to answer
137+
138+
| # | Question | Answer |
139+
|---|----------|--------|
140+
| 1 | Does batch insert overwrite existing keys (upsert)? | |
141+
| 2 | How long does background embedding take for 100/1000/10000 docs? | |
142+
| 3 | Can we query immediately after insert? | |
143+
| 4 | What does `client.tables.get()` return? (need count) | |
144+
| 5 | Latency of `client.tables.lookup()` vs vector search? | |
145+
| 6 | Can we full-scan without a query vector? | |
146+
| 7 | Does the SDK handle connection errors gracefully? | |
147+
| 8 | What happens when antfly server is not running? | |
148+
149+
## Exit criteria
150+
151+
All 8 questions answered. If any answer blocks the migration, document it and reassess.
152+
If all answers are compatible, proceed to Part 1.2.
Lines changed: 197 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,197 @@
1+
# Part 1.2 — Implement AntflyVectorStore
2+
3+
## Goal
4+
5+
Create `AntflyVectorStore` class that implements the `VectorStore` interface using `@antfly/sdk`.
6+
This is the core swap — everything else builds on it.
7+
8+
## New file
9+
10+
`packages/core/src/vector/antfly-store.ts`
11+
12+
## Interface to implement
13+
14+
From `types.ts`:
15+
16+
```typescript
17+
interface VectorStore {
18+
readonly path: string;
19+
initialize(): Promise<void>;
20+
add(documents: EmbeddingDocument[], embeddings: number[][]): Promise<void>;
21+
search(queryEmbedding: number[], options?: SearchOptions): Promise<SearchResult[]>;
22+
get(id: string): Promise<EmbeddingDocument | null>;
23+
delete(ids: string[]): Promise<void>;
24+
count(): Promise<number>;
25+
optimize(): Promise<void>;
26+
close(): Promise<void>;
27+
}
28+
```
29+
30+
Plus the concrete-only methods on the current `LanceDBVectorStore`:
31+
- `getAll(): Promise<EmbeddingDocument[]>`
32+
- `searchByDocumentId(id: string, options?): Promise<SearchResult[]>`
33+
- `clear(): Promise<void>`
34+
35+
## Design
36+
37+
### Constructor
38+
39+
```typescript
40+
interface AntflyConfig {
41+
baseUrl: string; // default: 'http://localhost:8080'
42+
table: string; // e.g., 'dev-agent-code', 'dev-agent-git', 'dev-agent-github'
43+
indexName: string; // e.g., 'content'
44+
template?: string; // Handlebars template for embedding, default: '{{text}}'
45+
model?: string; // Termite model — read from config, default: 'BAAI/bge-small-en-v1.5'
46+
}
47+
```
48+
49+
The `model` field comes from `~/.dev-agent/config.json`, set by `dev setup --model`.
50+
This flows into the antfly table creation (embedding index config).
51+
52+
### Search interface design (BLOCKER resolution)
53+
54+
The `VectorStore.search()` interface takes `queryEmbedding: number[]`, but antfly needs
55+
query text, not a pre-computed vector.
56+
57+
**Decision:** Add `searchText()` to `AntflyVectorStore` as a concrete method (not on the
58+
`VectorStore` interface). The `VectorStorage` facade calls `searchText()` directly since
59+
it already receives the query as a string.
60+
61+
The old `search(queryEmbedding: number[])` remains on the interface for type compatibility
62+
but throws `Error('Use searchText() — antfly handles embeddings')` if called directly.
63+
In practice it's never called directly — only the facade calls it, and the facade is
64+
updated in Part 1.3 to call `searchText()` instead.
65+
66+
```typescript
67+
class AntflyVectorStore implements VectorStore {
68+
// Interface method — kept for compatibility, not called in practice
69+
async search(queryEmbedding: number[], options?: SearchOptions): Promise<SearchResult[]> {
70+
throw new Error('Use searchText() — antfly handles embeddings internally');
71+
}
72+
73+
// The real search method — called by VectorStorage facade
74+
async searchText(query: string, options?: SearchOptions): Promise<SearchResult[]> {
75+
const results = await this.client.query({
76+
table: this.config.table,
77+
semantic_search: query,
78+
indexes: [this.config.indexName],
79+
limit: options?.limit ?? 10,
80+
});
81+
return this.mapHitsToSearchResults(results.hits);
82+
}
83+
}
84+
```
85+
86+
### searchByDocumentId behavioral change
87+
88+
**Acknowledged tradeoff:** Currently, `searchByDocumentId` fetches the stored embedding
89+
vector and does a vector-space nearest-neighbor search. After migration, it becomes
90+
"lookup doc → search with its text." This may produce slightly different results because
91+
text-based search goes through antfly's tokenization + embedding pipeline rather than using
92+
the exact stored vector.
93+
94+
In practice this should be **equivalent or better** — the text goes through the same
95+
embedding model, and hybrid search (BM25 + vector) adds keyword matching that pure
96+
vector search lacked. The `dev_inspect` tool (primary consumer) finds similar code files,
97+
where text-based similarity is a natural fit.
98+
99+
### Method implementations
100+
101+
**`initialize()`**
102+
- Create table with embedding index if not exists
103+
- Handle "already exists" gracefully (idempotent)
104+
105+
**`add(documents, embeddings)`**
106+
- Ignore `embeddings` parameter — antfly auto-embeds
107+
- Convert `EmbeddingDocument[]` to antfly batch format: `{ [id]: { text, metadata } }`
108+
- Batch in chunks of 500 (antfly may have payload limits)
109+
110+
**`searchText(query, options)`**
111+
- Use `client.query()` with `semantic_search` (and optionally `full_text_search`)
112+
- Map antfly `hits` to `SearchResult[]`
113+
114+
**`get(id)`**
115+
- Use `client.tables.lookup(table, id)`
116+
- Map to `EmbeddingDocument | null`
117+
118+
**`delete(ids)`**
119+
- Use `client.tables.batch(table, { deletes: ids })`
120+
121+
**`count()`**
122+
- Use `client.tables.get(table)` and extract doc count from stats
123+
124+
**`getAll()`**
125+
- Use `client.tables.query(table, { limit: 10000 })` or paginate
126+
- If more than 10000 docs, paginate with offset (test this in spike)
127+
128+
**`searchByDocumentId(id)`**
129+
- Lookup document by key → get its text → run `searchText()` with that text
130+
- Note: behavioral change from vector-based to text-based similarity (see above)
131+
132+
**`clear()`**
133+
- Drop and recreate the table
134+
135+
**`optimize()`** — No-op (antfly manages compaction)
136+
**`close()`** — No-op (SDK is stateless HTTP)
137+
138+
**`path` (readonly property)**
139+
- Return the antfly base URL + table name as identifier (e.g., `http://localhost:8080/dev-agent-code`)
140+
- Used for logging and stats, not for file I/O
141+
142+
### Stats support
143+
144+
`VectorStorage.getStats()` currently reads `dimension` and `modelName` from the embedder,
145+
and `storageSize` from the local LanceDB directory. After migration:
146+
147+
- `dimension` — read from antfly config (known at table creation time from model)
148+
- `modelName` — read from antfly config (stored in `AntflyConfig.model`)
149+
- `storageSize` — antfly manages storage; report 0 or get from `client.tables.get()` if
150+
it exposes size stats. Spike will confirm.
151+
- `totalDocuments` — from `count()`
152+
153+
Add a `getModelInfo()` method to `AntflyVectorStore`:
154+
155+
```typescript
156+
getModelInfo(): { dimension: number; modelName: string } {
157+
return {
158+
dimension: MODEL_DIMENSIONS[this.config.model] ?? 384,
159+
modelName: this.config.model ?? 'BAAI/bge-small-en-v1.5',
160+
};
161+
}
162+
```
163+
164+
## Tests
165+
166+
New file: `packages/core/src/vector/__tests__/antfly-store.test.ts`
167+
168+
Tests require running antfly server. Tagged with `describe.runIf(process.env.ANTFLY_URL)`
169+
so CI runs them in the docker-based job and local devs can skip them.
170+
171+
Use a dedicated test table (`test-antfly-{random}`), clean up after each test.
172+
173+
| Test | Description |
174+
|------|-------------|
175+
| creates table on initialize | Idempotent table creation |
176+
| inserts and retrieves documents | batch insert → lookup by key |
177+
| upserts on duplicate key | insert key X, re-insert with different text → second version stored |
178+
| searches by semantic query | insert → searchText → verify relevance |
179+
| handles hybrid search | BM25 + vector returns combined results |
180+
| deletes documents | insert → delete → lookup returns null |
181+
| counts documents | insert N → count returns N |
182+
| gets all documents | insert → getAll → verify all returned |
183+
| paginates getAll for large sets | insert 100+ → getAll returns all |
184+
| searches by document ID | insert A,B → searchByDocumentId(A) → B appears if similar |
185+
| clears all data | insert → clear → count returns 0 |
186+
| returns model info | getModelInfo() returns dimension + model name |
187+
| handles empty table search | search on empty table → returns [] |
188+
| handles missing server gracefully | Connection refused → meaningful error |
189+
| search(embedding) throws | Direct call to search() with vector → throws with guidance |
190+
191+
## Exit criteria
192+
193+
- `AntflyVectorStore` passes all tests
194+
- `searchText()` is the primary search method, `search()` throws
195+
- `searchByDocumentId` uses text-based similarity (behavioral change documented)
196+
- `getModelInfo()` provides dimension + model for stats
197+
- No antfly-specific concepts leak above this layer

0 commit comments

Comments
 (0)