An analytical database built for agents to use directly: columnar storage, a vectorized query engine, and MCP as a first-class interface
Rust
3
14 commits
updated Sep 24, 2026
A database your AI agent can talk to, that tells you how it read the question and says so when it can't answer.
AI agents collect things as they work: leads, prices, support tickets, research notes. AgeDB gives them somewhere to keep those records and lets them ask questions in plain English, such as "which companies look most likely to convert?". It runs on your machine as one small program. There is no SQL to write, and no extra AI call is made to understand the question.
Agent: "Create a table for the leads I'm collecting."
Agent: "Store these 4,000 leads."
Agent: "Which companies look most likely to convert?"
AgeDB: acme 0.99, company 210 0.79, company 139 0.74, ...
reading leads (grouped by company; avg(score); top 10)
Agent: "Which of these leads became customers?"
AgeDB: unsupported: that request spans 2 tables (customers and leads), which needs a
join; v0.1 queries one table at a time
The last answer is the point. A system that answered from one table would return a confident, incomplete number. AgeDB refuses, says why, and the agent can ask a narrower question.
Status: v0.1, single node, 308 tests, written in Rust. AgeDB is a working name and may change.
There is no language model inside. Plain-language questions are parsed in-process by deterministic rules. That takes about 17 microseconds per question on a laptop (Apple M1, release build). The same question over the same schema always gives the same plan, and nothing is sent anywhere. The agent calling AgeDB is usually already a language model, so AgeDB does not add a second one to every question.
There are two starting points. Neither is proven yet.
If an embedded database your agent already uses serves you well, that is a legitimate answer. See How it compares.
| Guaranteed | How |
|---|---|
| A plan can only use tables and columns that exist | Every plan is validated against the live schema before anything runs. An unknown name comes back as not_found, with the real names listed |
| Meaningless aggregations are refused | Columns carry a semantic_type, so sum(customer_id) is rejected: "customer_id is a id column, not a measure" |
| You can see how a question was read | Every response carries the plan that ran, a one-line interpretation, and what was scanned and pruned |
| A question that needs two tables is refused, never half-answered | The translator refuses multi-table and "rows missing from another table" questions as unsupported. No model is in the path to substitute one table for two |
| One tenant cannot read another's rows | The tenant comes from the API key and selects the storage prefix. There is no WHERE tenant_id = ? for anyone to forget |
| A key does only what it is scoped to | database:read, schema:write, data:insert and so on live on the key, never in the request |
| Result size and data read are bounded | Row and byte budgets are enforced inside execution. A truncated result says so |
What AgeDB does not guarantee:
Business correctness. A valid plan can still:
Column descriptions and default aggregations reduce this, and the echoed plan is how you catch it. AgeDB knows only the business definitions your schema states.
Column-level authorization. sensitive hides a column from select *, from schema
context and from error suggestions. Any key that may query the table can still select
the column by name. Keep data a caller must never see in a separate table, database or
tenant.
A hard time limit. The time budget is checked between batches and between execution stages. It is cooperative: a single sort or merge step already running finishes first, so a query can overrun its budget by the length of that step.
High availability. AgeDB v0.1 runs on a single node.
A well-built existing stack can do much of this. PostgreSQL or DuckDB, with schema discovery, a read-only role and validated SQL, also rejects unknown columns and bounds queries. There is also direct competition:
MCP plus analytics plus semantics is not, on its own, a reason to choose AgeDB.
The bet is narrower:
Models will keep getting better at writing SQL, so "models struggle with SQL" is not a durable reason to exist. Enforced permissions, consistent definitions and predictable behaviour across models might be. Whether they are worth adopting a new dependency for is what the evidence plan tests.
Most of what is distinctive about AgeDB sits above storage.
The engine exists for three reasons:
Those are easier to guarantee when the executor is ours. That is a design reason, not evidence that users need it. Adopting AgeDB today also means adopting its recovery, upgrade and maintenance story, which is younger than any established database's.
So the next experiment is to put the same validated interface over an established engine (DuckDB) as a second backend, and see which one users choose. The custom engine stays only if users show they need what it does differently.
Requires Rust 1.90 or newer (rustup). There are no other dependencies, no server to install, and no Docker.
git clone https://github.com/sivsivsree/agedb
cd agedb
cargo build --release
See the whole thing work in about 40 seconds:
examples/demo.sh
The demo:
An agent launches the process and talks JSON-RPC over the pipe:
{
"mcpServers": {
"agedb": {
"command": "/absolute/path/to/agedb",
"args": ["--data-dir", "/absolute/path/to/data",
"serve", "--transport", "stdio", "--tenant", "acme"]
}
}
}
./target/release/agedb --data-dir ./data \
serve --transport http --port 8080 --api-key dev-key --tenant acme
{
"mcpServers": {
"agedb": {
"type": "http",
"url": "http://localhost:8080/mcp",
"headers": { "Authorization": "Bearer dev-key" }
}
}
}
/mcp implements the MCP Streamable HTTP transport (revision 2025-06-18):
| Method | What it does |
|---|---|
POST | Answers a request as a server-sent event stream, or as plain JSON if the client does not accept streams |
GET | Opens an event stream for server-to-client messages |
DELETE | Ends a session |
Two more behaviours matter:
initialize issues an Mcp-Session-Id.--allow-origin.
Allowed origins get CORS, so a browser client works.Every request still needs its API key.
The same server also speaks REST:
H=(-H 'authorization: Bearer dev-key' -H 'content-type: application/json')
curl -sS -X POST localhost:8080/v1/databases "${H[@]}" -d '{"database":"sales"}'
curl -sS -X POST localhost:8080/v1/databases/sales/tables "${H[@]}" -d '{
"table":"orders","primary_key":["id"],
"columns":[
{"name":"id","type":"int64","nullable":false,"semantic_type":"id"},
{"name":"customer_id","type":"int64","semantic_type":"id"},
{"name":"country","type":"utf8","semantic_type":"country"},
{"name":"amount","type":"float64","semantic_type":"currency","default_aggregation":"sum"}]}'
curl -sS -X POST localhost:8080/v1/databases/sales/tables/orders/rows "${H[@]}" -d '{
"rows":[{"id":1,"customer_id":1,"country":"uae","amount":12000},
{"id":2,"customer_id":2,"country":"usa","amount":8400},
{"id":3,"customer_id":1,"country":"uae","amount":19300}]}'
curl -sS -X POST localhost:8080/v1/databases/sales/query "${H[@]}" \
-d '{"request":"total amount by country in orders"}'
# 200 {"rows":[{"country":"uae","sum_amount":31300.0},{"country":"usa","sum_amount":8400.0}],
# "interpretation":"reading orders (grouped by country; sum(amount))", "plan":{...}, ...}
The operator guide, including config files, scopes, limits and troubleshooting, is in
docs/usage.md.
There are fourteen tools, no query language, and no escape hatch. There is deliberately no
execute_arbitrary_query:
database_create table_create data_insert data_query
database_list table_list data_upsert
database_delete table_describe data_get
table_drop data_delete
schema_get
schema_update
data_query accepts plain language or a structured plan:
{ "request": "total revenue by country in the last 30 days" }
{
"plan": {
"operation": "aggregate",
"table": "orders",
"filters": [{ "column": "created_at", "op": "gte", "value": "2026-08-01" }],
"group_by": ["country"],
"metrics": [{ "function": "sum", "column": "amount", "alias": "revenue" }],
"order_by": [{ "column": "revenue", "direction": "desc" }],
"limit": 20
}
}
Either way, the response carries the plan that ran, so an agent can check the interpretation and reuse or adjust it:
{
"rows": [{ "country": "uae", "revenue": 3458839.12 }],
"interpretation": "reading orders (grouped by country; sum(amount))",
"plan": { "operation": "aggregate", "...": "..." },
"stats": { "elapsed_ms": 1, "rows_scanned": 4000, "segments_pruned": 24 },
"warnings": []
}
And when it cannot answer, it says so with a code the agent can branch on:
{ "error": { "code": "unsupported",
"message": "unsupported in v0.1: that request spans 2 tables (customers and orders), which needs a join; v0.1 queries one table at a time" } }
{ "error": { "code": "bad_request",
"message": "bad request: sum(customer_id) is not meaningful: customer_id is a id column, not a measure" } }
The plain-language layer handles:
Anything else is refused with the available columns listed. That refusal is the cue for the calling agent to send a structured plan instead.
Every front end converges on one validated Query IR. Natural language, structured plans and any future front end lower into the same representation. It is validated once, in one place, before anything executes.
Agents / LLMs
│
MCP (stdio or Streamable HTTP) REST
│ │
┌────────▼────────▼────────┐
│ adb-mcp / adb-api │ auth, scopes, sessions, per-key limits
└────────────┬─────────────┘
│
┌────────────▼─────────────┐
│ adb-engine │ catalog, tables, request context
└────────────┬─────────────┘
│
┌──────────────────────┼──────────────────────┐
│ │ │
┌───────▼────────┐ ┌─────────▼────────┐ ┌──────────▼─────────┐
│ adb-query │ │ adb-planner │ │ adb-exec │
│ NL to plan JSON│──▶│ plan to IR to │──▶│ vectorized Arrow │
│ (local rules) │ │ validated plan │ │ operators, budgets │
└────────────────┘ └──────────────────┘ └──────────┬─────────┘
│
┌──────────▼─────────┐
│ adb-storage │
│ WAL, memtable, │
│ segments, manifest │
└──────────┬─────────┘
│
filesystem (S3-shaped keys)
| Crate | Responsibility |
|---|---|
adb-core | Identifiers, type system, semantic schema, versioned catalog, request context |
adb-storage | WAL, memtable, Parquet segments, manifests, key index, compaction |
adb-planner | Query IR, validator, optimizer, physical plan |
adb-exec | Vectorized operators over Arrow, budgets and statistics |
adb-query | Natural language to structured plan (deterministic rules), schema retrieval |
adb-engine | The facade: catalog, tables and the query path |
adb-mcp | MCP tools, JSON-RPC, API keys and scopes, stdio transport |
adb-api | REST, plus MCP over Streamable HTTP |
adb-server | The agedb binary |
Underneath:
ARCHITECTURE.md has the design rationale, on-disk formats, the
concurrency model, and the list of what is deliberately missing.
The numbers in ARCHITECTURE.md track the engine
against itself from change to change. Ingest is measured with WAL fsync off, and there is no
competitor run alongside it. They are useful for catching regressions. They prove nothing
about adoption.
The benchmark that matters is not built yet. It asks whether an agent using AgeDB completes real analytical tasks more accurately, cheaply and safely than the same agent using a well-configured alternative. The plan is to use the same model, the same data and the same task set on both sides, with equivalent metadata and reasonable configuration for the alternative, and to measure:
The harness and the results will be published whichever way they come out.
AgeDB has solid engineering and no customer evidence yet. No external team uses it every week. The next 30 days are about finding out whether one should, not about adding features.
The decision gate is:
If the gate is not met, the direction changes. The most likely change is the validated interface over an established engine.
The questions the evidence has to answer:
Deferred until a user is blocked on it. Each of these is a substantial programme of work:
HAVING;LogStore seam is ready for a Raft log, but snapshots, membership
changes, read consistency, recovery and failure testing are each their own design work.Known limitations today:
float64 with semantic_type: currency, not an exact decimal;Every change must keep these green. CI runs all of them on each pull request:
cargo fmt --all --check # formatting
cargo clippy --workspace --all-targets -- -D warnings # no warnings, at all
cargo test --workspace # 308 tests, about 15 seconds
examples/demo.sh # end-to-end smoke test
The suite is built around four kinds of test, and a change is expected to extend whichever applies:
| Kind | Where | What it protects |
|---|---|---|
| Unit tests | next to the code, in #[cfg(test)] | Behaviour of one function, including its error cases |
| Correctness oracle | crates/adb-exec/tests/query.rs | The vectorized engine agreeing with a naive row-at-a-time implementation over the same data |
| Durability | crates/adb-engine/tests/engine.rs | Acknowledged writes surviving a hard kill, verified by aborting a child process mid-write |
| Protocol | crates/adb-mcp/tests/, crates/adb-api/tests/ | Real JSON-RPC frames, real HTTP requests and SSE streams, not internal function calls |
What a contribution is expected to bring:
ARCHITECTURE.md if the
on-disk format moved.cargo run --release -p bench -- --rows 2000000.Tests must be deterministic. Where data is generated, seed it (see Rng in
crates/adb-exec/tests/query.rs), and never depend on wall-clock time. The natural language
layer takes an injected clock for exactly this reason.
Start with CONTRIBUTING.md. The short version: fork, branch, make the
four commands above pass, and open a pull request that explains why.
The most useful contributions right now are evidence, not features:
crates/adb-query/tests/nl.rs. Every
question an agent asks that gets refused, or read wrongly, is a small, self-contained
improvement.One repository rule worth stating up front: no em dashes anywhere, in code, comments, documentation or commit messages. CI enforces it.
Apache License 2.0. See LICENSE.
14 commits
Rust
100.0%
An analytical database built for agents to use directly: columnar storage, a vectorized query engine, and MCP as a first-class interface
Rust
3
14 commits
updated Sep 24, 2026
A database your AI agent can talk to, that tells you how it read the question and says so when it can't answer.
AI agents collect things as they work: leads, prices, support tickets, research notes. AgeDB gives them somewhere to keep those records and lets them ask questions in plain English, such as "which companies look most likely to convert?". It runs on your machine as one small program. There is no SQL to write, and no extra AI call is made to understand the question.
Agent: "Create a table for the leads I'm collecting."
Agent: "Store these 4,000 leads."
Agent: "Which companies look most likely to convert?"
AgeDB: acme 0.99, company 210 0.79, company 139 0.74, ...
reading leads (grouped by company; avg(score); top 10)
Agent: "Which of these leads became customers?"
AgeDB: unsupported: that request spans 2 tables (customers and leads), which needs a
join; v0.1 queries one table at a time
The last answer is the point. A system that answered from one table would return a confident, incomplete number. AgeDB refuses, says why, and the agent can ask a narrower question.
Status: v0.1, single node, 308 tests, written in Rust. AgeDB is a working name and may change.
There is no language model inside. Plain-language questions are parsed in-process by deterministic rules. That takes about 17 microseconds per question on a laptop (Apple M1, release build). The same question over the same schema always gives the same plan, and nothing is sent anywhere. The agent calling AgeDB is usually already a language model, so AgeDB does not add a second one to every question.
There are two starting points. Neither is proven yet.
If an embedded database your agent already uses serves you well, that is a legitimate answer. See How it compares.
| Guaranteed | How |
|---|---|
| A plan can only use tables and columns that exist | Every plan is validated against the live schema before anything runs. An unknown name comes back as not_found, with the real names listed |
| Meaningless aggregations are refused | Columns carry a semantic_type, so sum(customer_id) is rejected: "customer_id is a id column, not a measure" |
| You can see how a question was read | Every response carries the plan that ran, a one-line interpretation, and what was scanned and pruned |
| A question that needs two tables is refused, never half-answered | The translator refuses multi-table and "rows missing from another table" questions as unsupported. No model is in the path to substitute one table for two |
| One tenant cannot read another's rows | The tenant comes from the API key and selects the storage prefix. There is no WHERE tenant_id = ? for anyone to forget |
| A key does only what it is scoped to | database:read, schema:write, data:insert and so on live on the key, never in the request |
| Result size and data read are bounded | Row and byte budgets are enforced inside execution. A truncated result says so |
What AgeDB does not guarantee:
Business correctness. A valid plan can still:
Column descriptions and default aggregations reduce this, and the echoed plan is how you catch it. AgeDB knows only the business definitions your schema states.
Column-level authorization. sensitive hides a column from select *, from schema
context and from error suggestions. Any key that may query the table can still select
the column by name. Keep data a caller must never see in a separate table, database or
tenant.
A hard time limit. The time budget is checked between batches and between execution stages. It is cooperative: a single sort or merge step already running finishes first, so a query can overrun its budget by the length of that step.
High availability. AgeDB v0.1 runs on a single node.
A well-built existing stack can do much of this. PostgreSQL or DuckDB, with schema discovery, a read-only role and validated SQL, also rejects unknown columns and bounds queries. There is also direct competition:
MCP plus analytics plus semantics is not, on its own, a reason to choose AgeDB.
The bet is narrower:
Models will keep getting better at writing SQL, so "models struggle with SQL" is not a durable reason to exist. Enforced permissions, consistent definitions and predictable behaviour across models might be. Whether they are worth adopting a new dependency for is what the evidence plan tests.
Most of what is distinctive about AgeDB sits above storage.
The engine exists for three reasons:
Those are easier to guarantee when the executor is ours. That is a design reason, not evidence that users need it. Adopting AgeDB today also means adopting its recovery, upgrade and maintenance story, which is younger than any established database's.
So the next experiment is to put the same validated interface over an established engine (DuckDB) as a second backend, and see which one users choose. The custom engine stays only if users show they need what it does differently.
Requires Rust 1.90 or newer (rustup). There are no other dependencies, no server to install, and no Docker.
git clone https://github.com/sivsivsree/agedb
cd agedb
cargo build --release
See the whole thing work in about 40 seconds:
examples/demo.sh
The demo:
An agent launches the process and talks JSON-RPC over the pipe:
{
"mcpServers": {
"agedb": {
"command": "/absolute/path/to/agedb",
"args": ["--data-dir", "/absolute/path/to/data",
"serve", "--transport", "stdio", "--tenant", "acme"]
}
}
}
./target/release/agedb --data-dir ./data \
serve --transport http --port 8080 --api-key dev-key --tenant acme
{
"mcpServers": {
"agedb": {
"type": "http",
"url": "http://localhost:8080/mcp",
"headers": { "Authorization": "Bearer dev-key" }
}
}
}
/mcp implements the MCP Streamable HTTP transport (revision 2025-06-18):
| Method | What it does |
|---|---|
POST | Answers a request as a server-sent event stream, or as plain JSON if the client does not accept streams |
GET | Opens an event stream for server-to-client messages |
DELETE | Ends a session |
Two more behaviours matter:
initialize issues an Mcp-Session-Id.--allow-origin.
Allowed origins get CORS, so a browser client works.Every request still needs its API key.
The same server also speaks REST:
H=(-H 'authorization: Bearer dev-key' -H 'content-type: application/json')
curl -sS -X POST localhost:8080/v1/databases "${H[@]}" -d '{"database":"sales"}'
curl -sS -X POST localhost:8080/v1/databases/sales/tables "${H[@]}" -d '{
"table":"orders","primary_key":["id"],
"columns":[
{"name":"id","type":"int64","nullable":false,"semantic_type":"id"},
{"name":"customer_id","type":"int64","semantic_type":"id"},
{"name":"country","type":"utf8","semantic_type":"country"},
{"name":"amount","type":"float64","semantic_type":"currency","default_aggregation":"sum"}]}'
curl -sS -X POST localhost:8080/v1/databases/sales/tables/orders/rows "${H[@]}" -d '{
"rows":[{"id":1,"customer_id":1,"country":"uae","amount":12000},
{"id":2,"customer_id":2,"country":"usa","amount":8400},
{"id":3,"customer_id":1,"country":"uae","amount":19300}]}'
curl -sS -X POST localhost:8080/v1/databases/sales/query "${H[@]}" \
-d '{"request":"total amount by country in orders"}'
# 200 {"rows":[{"country":"uae","sum_amount":31300.0},{"country":"usa","sum_amount":8400.0}],
# "interpretation":"reading orders (grouped by country; sum(amount))", "plan":{...}, ...}
The operator guide, including config files, scopes, limits and troubleshooting, is in
docs/usage.md.
There are fourteen tools, no query language, and no escape hatch. There is deliberately no
execute_arbitrary_query:
database_create table_create data_insert data_query
database_list table_list data_upsert
database_delete table_describe data_get
table_drop data_delete
schema_get
schema_update
data_query accepts plain language or a structured plan:
{ "request": "total revenue by country in the last 30 days" }
{
"plan": {
"operation": "aggregate",
"table": "orders",
"filters": [{ "column": "created_at", "op": "gte", "value": "2026-08-01" }],
"group_by": ["country"],
"metrics": [{ "function": "sum", "column": "amount", "alias": "revenue" }],
"order_by": [{ "column": "revenue", "direction": "desc" }],
"limit": 20
}
}
Either way, the response carries the plan that ran, so an agent can check the interpretation and reuse or adjust it:
{
"rows": [{ "country": "uae", "revenue": 3458839.12 }],
"interpretation": "reading orders (grouped by country; sum(amount))",
"plan": { "operation": "aggregate", "...": "..." },
"stats": { "elapsed_ms": 1, "rows_scanned": 4000, "segments_pruned": 24 },
"warnings": []
}
And when it cannot answer, it says so with a code the agent can branch on:
{ "error": { "code": "unsupported",
"message": "unsupported in v0.1: that request spans 2 tables (customers and orders), which needs a join; v0.1 queries one table at a time" } }
{ "error": { "code": "bad_request",
"message": "bad request: sum(customer_id) is not meaningful: customer_id is a id column, not a measure" } }
The plain-language layer handles:
Anything else is refused with the available columns listed. That refusal is the cue for the calling agent to send a structured plan instead.
Every front end converges on one validated Query IR. Natural language, structured plans and any future front end lower into the same representation. It is validated once, in one place, before anything executes.
Agents / LLMs
│
MCP (stdio or Streamable HTTP) REST
│ │
┌────────▼────────▼────────┐
│ adb-mcp / adb-api │ auth, scopes, sessions, per-key limits
└────────────┬─────────────┘
│
┌────────────▼─────────────┐
│ adb-engine │ catalog, tables, request context
└────────────┬─────────────┘
│
┌──────────────────────┼──────────────────────┐
│ │ │
┌───────▼────────┐ ┌─────────▼────────┐ ┌──────────▼─────────┐
│ adb-query │ │ adb-planner │ │ adb-exec │
│ NL to plan JSON│──▶│ plan to IR to │──▶│ vectorized Arrow │
│ (local rules) │ │ validated plan │ │ operators, budgets │
└────────────────┘ └──────────────────┘ └──────────┬─────────┘
│
┌──────────▼─────────┐
│ adb-storage │
│ WAL, memtable, │
│ segments, manifest │
└──────────┬─────────┘
│
filesystem (S3-shaped keys)
| Crate | Responsibility |
|---|---|
adb-core | Identifiers, type system, semantic schema, versioned catalog, request context |
adb-storage | WAL, memtable, Parquet segments, manifests, key index, compaction |
adb-planner | Query IR, validator, optimizer, physical plan |
adb-exec | Vectorized operators over Arrow, budgets and statistics |
adb-query | Natural language to structured plan (deterministic rules), schema retrieval |
adb-engine | The facade: catalog, tables and the query path |
adb-mcp | MCP tools, JSON-RPC, API keys and scopes, stdio transport |
adb-api | REST, plus MCP over Streamable HTTP |
adb-server | The agedb binary |
Underneath:
ARCHITECTURE.md has the design rationale, on-disk formats, the
concurrency model, and the list of what is deliberately missing.
The numbers in ARCHITECTURE.md track the engine
against itself from change to change. Ingest is measured with WAL fsync off, and there is no
competitor run alongside it. They are useful for catching regressions. They prove nothing
about adoption.
The benchmark that matters is not built yet. It asks whether an agent using AgeDB completes real analytical tasks more accurately, cheaply and safely than the same agent using a well-configured alternative. The plan is to use the same model, the same data and the same task set on both sides, with equivalent metadata and reasonable configuration for the alternative, and to measure:
The harness and the results will be published whichever way they come out.
AgeDB has solid engineering and no customer evidence yet. No external team uses it every week. The next 30 days are about finding out whether one should, not about adding features.
The decision gate is:
If the gate is not met, the direction changes. The most likely change is the validated interface over an established engine.
The questions the evidence has to answer:
Deferred until a user is blocked on it. Each of these is a substantial programme of work:
HAVING;LogStore seam is ready for a Raft log, but snapshots, membership
changes, read consistency, recovery and failure testing are each their own design work.Known limitations today:
float64 with semantic_type: currency, not an exact decimal;Every change must keep these green. CI runs all of them on each pull request:
cargo fmt --all --check # formatting
cargo clippy --workspace --all-targets -- -D warnings # no warnings, at all
cargo test --workspace # 308 tests, about 15 seconds
examples/demo.sh # end-to-end smoke test
The suite is built around four kinds of test, and a change is expected to extend whichever applies:
| Kind | Where | What it protects |
|---|---|---|
| Unit tests | next to the code, in #[cfg(test)] | Behaviour of one function, including its error cases |
| Correctness oracle | crates/adb-exec/tests/query.rs | The vectorized engine agreeing with a naive row-at-a-time implementation over the same data |
| Durability | crates/adb-engine/tests/engine.rs | Acknowledged writes surviving a hard kill, verified by aborting a child process mid-write |
| Protocol | crates/adb-mcp/tests/, crates/adb-api/tests/ | Real JSON-RPC frames, real HTTP requests and SSE streams, not internal function calls |
What a contribution is expected to bring:
ARCHITECTURE.md if the
on-disk format moved.cargo run --release -p bench -- --rows 2000000.Tests must be deterministic. Where data is generated, seed it (see Rng in
crates/adb-exec/tests/query.rs), and never depend on wall-clock time. The natural language
layer takes an injected clock for exactly this reason.
Start with CONTRIBUTING.md. The short version: fork, branch, make the
four commands above pass, and open a pull request that explains why.
The most useful contributions right now are evidence, not features:
crates/adb-query/tests/nl.rs. Every
question an agent asks that gets refused, or read wrongly, is a small, self-contained
improvement.One repository rule worth stating up front: no em dashes anywhere, in code, comments, documentation or commit messages. CI enforces it.
Apache License 2.0. See LICENSE.
14 commits
Rust
100.0%