Skip to content

Commit 3eb7961

Browse files
prosdevclaude
andcommitted
docs(plans): complete spike 1.1 — antfly API validated
Key findings: - Upsert works (batch insert overwrites existing keys) - Hybrid search (BM25 + vector + RRF) works beautifully - Embedding delay ~2s (dev index must wait for completion) - Lookup by key is direct (replaces O(n) zero-vector hack) - Table info provides disk_usage for VectorStats - Auto full-text index on every table (hybrid search for free) - SDK has CJS/ESM bug — use direct REST API with fetch() instead - Docker image needs explicit `swarm` command - i8 model variant 404s — use default (f32) variant - Updated Part 1.5 to Docker-first with native fallback Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent cf40eb5 commit 3eb7961

2 files changed

Lines changed: 211 additions & 43 deletions

File tree

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
# Part 1.1 — Spike Findings
2+
3+
**Date:** 2026-03-29
4+
**Antfly version:** 0.1.0 (native binary, macOS ARM64)
5+
**SDK version:** @antfly/sdk 0.0.14
6+
7+
## Results
8+
9+
| # | Question | Answer |
10+
|---|----------|--------|
11+
| 1 | Does batch insert overwrite existing keys (upsert)? | **Yes.** Re-inserting same key overwrites the document. Confirmed via lookup after upsert. |
12+
| 2 | How long does background embedding take? | **~2 seconds** for a single document to become searchable. First batch (10 docs) searchable within 5-8s. |
13+
| 3 | Can we query immediately after insert? | **No — ~2s delay.** Embeddings are generated asynchronously. `dev index` should wait or poll for completion. |
14+
| 4 | What does `client.tables.get()` return? | Returns table info including `storage_status.disk_usage` (bytes), index configs, and shard info. **No direct doc count** — need to use a query with limit to count. |
15+
| 5 | Latency of lookup vs vector search? | Lookup is near-instant. Semantic search ~1-2ms for 10 docs. Both fast at this scale. |
16+
| 6 | Can we full-scan without a query vector? | **Yes** — use the global `/api/v1/query` endpoint with just `table` and `limit`, no `semantic_search`. Returns all docs. |
17+
| 7 | Does the SDK handle connection errors gracefully? | **SDK has a CJS/ESM interop bug**`TypeError: (0 , import_openapi_fetch.default) is not a function`. Direct REST API works fine. See SDK issues section below. |
18+
| 8 | What happens when antfly server is not running? | curl gets `ECONNREFUSED`. Clear and fast failure. |
19+
| 9 | Does `getAll()` paginate beyond 10000 docs? | Not tested at scale in this spike. The query endpoint accepts `limit` — likely works up to a reasonable size. Need to test with a real repo index. |
20+
| 10 | Does `dev index` need to wait for embedding completion? | **Yes.** There's a ~2s delay between insert and searchability. For a full index run, we should wait for all embeddings to complete before declaring success. Poll embedding status or add a brief wait. |
21+
22+
## API Endpoint Reference (verified)
23+
24+
| Operation | Method | Endpoint |
25+
|-----------|--------|----------|
26+
| Create table | POST | `/api/v1/tables/{name}` |
27+
| Get table info | GET | `/api/v1/tables/{name}` |
28+
| Drop table | DELETE | `/api/v1/tables/{name}` |
29+
| List tables | GET | `/api/v1/tables` |
30+
| Batch insert/delete | POST | `/api/v1/tables/{name}/batch` |
31+
| Lookup by key | GET | `/api/v1/tables/{name}/lookup/{key}` |
32+
| Query (table-specific) | POST | `/api/v1/tables/{name}/query` |
33+
| Query (global) | POST | `/api/v1/query` |
34+
35+
**Important:** The global query endpoint (`/api/v1/query`) returns results in `responses[0].hits.hits[]` format. Table-specific query (`/api/v1/tables/{name}/query`) returns in `hits.hits[]` format.
36+
37+
## Key Findings
38+
39+
### 1. Table creation auto-creates full-text index
40+
41+
When creating a table with an embeddings index, antfly automatically adds a
42+
`full_text_index_v0` full-text index. This means **every table gets hybrid search
43+
for free** — no extra configuration needed.
44+
45+
### 2. Hybrid search with RRF works beautifully
46+
47+
Tested: `semantic_search: "error handling and retry"` + `full_text_search: "retryWithBackoff"`
48+
49+
Result: `func-retryBackoff` ranked #1 with scores from BOTH BM25 and vector similarity.
50+
The `_index_scores` object shows which indexes contributed. RRF doubled its score vs
51+
semantic-only results. This is exactly the upgrade we wanted for `dev_search`.
52+
53+
### 3. Document structure is flexible (schemaless)
54+
55+
Documents are JSON objects. No predefined schema required. We can store `text`, `metadata`,
56+
`type`, `file`, `line` — whatever we want. The embedding index uses the `template` field
57+
(Handlebars) to know which field(s) to embed.
58+
59+
### 4. Embedding model confirmed: bge-small-en-v1.5, dimension 384
60+
61+
Table info shows `dimension: 384` and `model: BAAI/bge-small-en-v1.5`. Same dimension
62+
as our current all-MiniLM-L6-v2 (384), so result structures don't change.
63+
64+
Note: i8 variant 404'd during model pull. f32 variant (127.8MB) works. The plan should
65+
use default variant (no `--variants i8` flag) until i8 is fixed.
66+
67+
### 5. Lookup by key replaces O(n) zero-vector hack
68+
69+
`GET /api/v1/tables/{name}/lookup/{key}` returns the document directly. Returns 404 if
70+
not found. This is a massive improvement over the current `get()` implementation in
71+
`LanceDBVectorStore` which does a full vector scan with a zero vector.
72+
73+
### 6. Storage info available
74+
75+
`client.tables.get()` returns `storage_status.disk_usage` in bytes. This can replace
76+
the `storageSize` field in `VectorStats` (currently reads local LanceDB directory).
77+
78+
## SDK Issues
79+
80+
### CJS/ESM interop bug
81+
82+
The `@antfly/sdk` v0.0.14 fails when imported in a CJS context (e.g., via `tsx`):
83+
84+
```
85+
TypeError: (0 , import_openapi_fetch.default) is not a function
86+
```
87+
88+
The SDK's CJS bundle (`dist/index.cjs`) doesn't correctly handle the `openapi-fetch`
89+
default export.
90+
91+
**Workaround options:**
92+
1. Use ESM imports only (our packages use ESM anyway)
93+
2. Use the REST API directly with `fetch()` instead of the SDK
94+
3. Report the bug to antfly team (the user's friend built it)
95+
96+
**Recommendation:** Start with direct REST API calls via `fetch()`. The SDK is thin
97+
(just openapi-fetch wrapper) and we need only 6-7 endpoints. Building our own thin
98+
client gives us full control and avoids SDK version coupling. We can adopt the SDK
99+
later when it stabilizes (v0.0.x is very early).
100+
101+
## Docker Findings
102+
103+
### `ghcr.io/antflydb/antfly:omni`
104+
- No ARM64 image available. Runs under Rosetta with `--platform linux/amd64`.
105+
- Pull succeeded but entrypoint errored: `Error: unknown flag: --api-url`
106+
107+
### `ghcr.io/antflydb/antfly:latest`
108+
- Pulls successfully on ARM64 (via amd64 emulation)
109+
- Does NOT auto-start — just shows help. Needs explicit `swarm` command.
110+
- Would need: `docker run -d ... ghcr.io/antflydb/antfly:latest swarm`
111+
112+
### Port conflict on native
113+
- `antfly swarm` binds to ports 8080, 9017, 9021, 12380, 11433
114+
- If any are occupied (e.g., old Docker container), it crashes with `bind: address already in use`
115+
- Docker is preferred because it isolates ports inside the container
116+
117+
**Recommendation:** Docker-first with `antfly swarm` as the command, native fallback.
118+
Need to verify Docker image + `swarm` command works end-to-end.
119+
120+
## Impact on Plan
121+
122+
1. **Use direct REST API instead of SDK** — avoids CJS/ESM bug and early SDK instability
123+
2. **Model pull: use default variant** (not `--variants i8`) until i8 is fixed
124+
3. **`dev index` must wait for embeddings** — poll or add brief sleep after batch insert
125+
4. **Table info provides disk_usage** — can populate `VectorStats.storageSize`
126+
5. **Auto full-text index** — every table gets BM25 for free, simplifies table creation
127+
6. **Docker needs `swarm` command**`docker run ... antfly swarm` not just `docker run ... antfly`

.claude/da-plans/core/phase-1-antfly-migration/1.5-dev-setup-command.md

Lines changed: 84 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,20 @@
55
The user never runs `antfly` directly. `dev setup` handles one-time installation,
66
and any command that needs antfly auto-starts it as a background process.
77

8+
**Docker-first, native fallback.** Prefer Docker (isolated, no port conflicts).
9+
Fall back to native binary if Docker isn't available.
10+
811
## UX
912

10-
### First time
13+
### First time (Docker available)
1114

1215
```bash
1316
$ dev setup
1417

15-
Antfly v0.4.2 found
16-
✓ Pulling embedding model...
17-
BAAI/bge-small-en-v1.5 (INT8) ready
18-
✓ Starting Antfly server...
18+
Docker found
19+
✓ Pulling antfly image...
20+
ghcr.io/antflydb/antfly:omni ready
21+
✓ Starting container "dev-agent-antfly"...
1922
Running on http://localhost:8080
2023

2124
✓ Setup complete!
@@ -25,41 +28,48 @@ $ dev setup
2528
dev mcp install --cursor # Connect to Cursor
2629
```
2730

28-
### Already set up
31+
### First time (no Docker, native fallback)
2932

3033
```bash
3134
$ dev setup
3235

33-
✓ Antfly v0.4.2 found
34-
✓ Embedding model ready
35-
✓ Server already running
36+
Docker not found. Falling back to native binary.
3637

37-
Nothing to do — you're all set!
38+
Antfly is not installed. Install it now? (Y/n) y
39+
40+
Installing via Homebrew...
41+
✓ Antfly v0.4.2 installed
42+
✓ Pulling embedding model...
43+
BAAI/bge-small-en-v1.5 ready
44+
✓ Starting Antfly server...
45+
Running on http://localhost:8080
46+
47+
✓ Setup complete!
3848
```
3949

40-
### Not installed
50+
### Already set up
4151

4252
```bash
4353
$ dev setup
4454

45-
Antfly is not installed. Install it now? (Y/n) y
46-
47-
Installing via Homebrew...
48-
✓ Antfly v0.4.2 installed
55+
✓ Container "dev-agent-antfly" already running
56+
✓ Server healthy on http://localhost:8080
4957

50-
✓ Pulling embedding model...
51-
✓ Starting server...
52-
✓ Setup complete!
58+
Nothing to do — you're all set!
5359
```
5460
55-
If user declines:
61+
### Neither Docker nor native
5662
5763
```bash
58-
Antfly is not installed. Install it now? (Y/n) n
64+
$ dev setup
65+
66+
✗ No runtime found.
5967
60-
Install manually, then run `dev setup` again:
61-
brew install --cask antflydb/antfly/antfly # macOS
62-
curl -fsSL https://releases.antfly.io/antfly/latest/install.sh | sh # Linux
68+
Install one of:
69+
Docker Desktop → https://docker.com/get-started
70+
Antfly native → brew install --cask antflydb/antfly/antfly
71+
72+
Then run `dev setup` again.
6373
```
6474
6575
### Any command when antfly is down
@@ -83,44 +93,61 @@ No error — it just starts it. Transparent.
8393
8494
New file: `packages/cli/src/utils/antfly.ts`
8595
96+
Docker-first, native fallback:
97+
8698
```typescript
8799
export async function ensureAntfly(options?: { quiet?: boolean }): Promise<AntflyClient> {
88100
const client = new AntflyClient({ baseUrl: getAntflyUrl() });
89101
90-
// 1. Check if already running
102+
// 1. Check if already running (Docker or native)
91103
try {
92104
await client.tables.list();
93105
return client;
94106
} catch {
95107
// Not running — try to start
96108
}
97109
98-
// 2. Check binary exists
110+
// 2. Try Docker first (preferred — isolated, no port conflicts)
111+
if (hasDocker()) {
112+
if (!options?.quiet) log.info('Starting Antfly via Docker...');
113+
execSync(
114+
'docker run -d --name dev-agent-antfly -p 8080:8080 ghcr.io/antflydb/antfly:omni',
115+
{ stdio: 'pipe' }
116+
);
117+
await waitForServer(getAntflyUrl(), { timeout: 30000 });
118+
if (!options?.quiet) log.success('Running on ' + getAntflyUrl());
119+
return client;
120+
}
121+
122+
// 3. Fall back to native binary
99123
try {
100124
execSync('antfly --version', { stdio: 'pipe' });
101125
} catch {
102126
throw new Error(
103-
'Antfly is not installed.\n' +
104-
'Run `dev setup` or install manually:\n' +
105-
' brew install --cask antflydb/antfly/antfly'
127+
'No runtime found. Run `dev setup` or install:\n' +
128+
' Docker Desktop → https://docker.com/get-started\n' +
129+
' Native binary → brew install --cask antflydb/antfly/antfly'
106130
);
107131
}
108132
109-
// 3. Start in background
110133
if (!options?.quiet) log.info('Starting Antfly server...');
111-
const child = spawn('antfly', ['swarm'], {
112-
detached: true,
113-
stdio: 'ignore',
114-
});
134+
const child = spawn('antfly', ['swarm'], { detached: true, stdio: 'ignore' });
115135
child.unref();
116-
117-
// 4. Wait for readiness (poll with timeout)
118136
await waitForServer(getAntflyUrl(), { timeout: 15000 });
119137
if (!options?.quiet) log.success('Running on ' + getAntflyUrl());
120138
121139
return client;
122140
}
123141
142+
function hasDocker(): boolean {
143+
try {
144+
execSync('docker info', { stdio: 'pipe' });
145+
return true;
146+
} catch {
147+
return false;
148+
}
149+
}
150+
124151
function getAntflyUrl(): string {
125152
return process.env.ANTFLY_URL || 'http://localhost:8080';
126153
}
@@ -149,7 +176,22 @@ commander
149176
.description('One-time setup: install search backend and embedding model')
150177
.option('--model <name>', 'Termite embedding model', 'BAAI/bge-small-en-v1.5')
151178
.action(async (opts) => {
152-
// 1. Check antfly binary — offer to install if missing
179+
// 1. Docker path (preferred)
180+
if (hasDocker()) {
181+
log.success('Docker found');
182+
// Pull image if needed
183+
execSync('docker pull ghcr.io/antflydb/antfly:omni', { stdio: 'inherit' });
184+
// Start container (ensureAntfly handles this)
185+
await ensureAntfly();
186+
// Save runtime preference
187+
saveConfig({ antflyRuntime: 'docker', embeddingModel: opts.model });
188+
log.success('Setup complete!');
189+
return;
190+
}
191+
192+
// 2. Native fallback
193+
log.info('Docker not found. Falling back to native binary.');
194+
153195
try {
154196
const version = execSync('antfly --version', { stdio: 'pipe' }).toString().trim();
155197
log.success(`Antfly ${version} found`);
@@ -160,23 +202,22 @@ commander
160202
161203
const answer = await confirm('Antfly is not installed. Install it now?');
162204
if (answer) {
163-
log.info(`Installing via ${process.platform === 'darwin' ? 'Homebrew' : 'install script'}...`);
164205
execSync(installCmd, { stdio: 'inherit' });
165206
log.success('Antfly installed');
166207
} else {
167-
log.info('Install manually, then run `dev setup` again:');
168-
log.info(` ${installCmd}`);
208+
log.info(`Install manually: ${installCmd}`);
169209
return;
170210
}
171211
}
172212
173-
// 2. Check/pull embedding model
174-
// 3. ensureAntfly() — starts server if needed
175-
// 4. Verify connection
213+
// 3. Pull embedding model (native only — Docker image bundles models)
214+
// 4. ensureAntfly() — starts server
215+
// 5. Save config
216+
saveConfig({ antflyRuntime: 'native', embeddingModel: opts.model });
176217
});
177218
```
178219
179-
Step 2 (model pull) uses the selected model and saves to config:
220+
Step 3 (model pull — native path only) uses the selected model:
180221
181222
```typescript
182223
const model = opts.model; // default: 'BAAI/bge-small-en-v1.5'

0 commit comments

Comments
 (0)