151 lines
5.1 KiB
Markdown
151 lines
5.1 KiB
Markdown
# aether-tools-rs — atom + 4 binaries
|
||
|
||
The unified rewrite of aether-tools onto a single atomic primitive.
|
||
**1517 production LOC across 4 binaries** (vs ~7000 LOC in the
|
||
original 7-crate suite) — driven by one core idea:
|
||
|
||
> An answer, once given, is true until forgotten.
|
||
|
||
Concretely: a content-addressed memo table that lives on disk as an
|
||
append-only JSONL ledger. Every binary in this workspace is a thin
|
||
binding around three operations:
|
||
|
||
1. **`canonicalize(input)`** — collapse equivalent inputs to a stable
|
||
byte form (regex-based Rust-error abstraction by default; identity
|
||
otherwise).
|
||
2. **`record(sig, scope, body, worked?, ts)`** — append a fact to the
|
||
audit log.
|
||
3. **`ask(sig) → value | miss`** — cache-or-compute.
|
||
|
||
## Crates
|
||
|
||
| Crate | LOC | Role |
|
||
|---|---:|---|
|
||
| `aether-atom` | 577 | The atomic core: fingerprint, canonicalize, lattice, ask/demote/remember |
|
||
| `aether-recall-mcp` | 311 | MCP-over-stdio server (the 4 tools that drive opencode's recall loop) |
|
||
| `aether-net-mini` | 245 | TCP JSON-RPC daemon: `put`/`get` over the atom |
|
||
| `aether-ask` | 326 | HTTP front-end: cache-or-fallback to ollama, dark-mode UI |
|
||
| `aec-atom` | 250 | REPL: `lookup`/`remember`/`stats`/`compact`/`replay`/`fingerprint` |
|
||
| `examples/library_usage` | ~80 | Drop-in pattern for any third-party crate that wants memory |
|
||
|
||
Total **1517 production LOC** (excluding the example).
|
||
|
||
## Tests
|
||
|
||
```
|
||
atom 28 unit
|
||
aether-recall-mcp 1 integration (spawns binary, full lifecycle)
|
||
aether-net-mini 5 unit
|
||
aether-ask 2 unit + 4 integration (HTTP routes + cache hit assertion)
|
||
aec-atom 10 unit
|
||
─────
|
||
50 passing
|
||
```
|
||
|
||
`cargo test --workspace` runs all of them in ~0.30 s.
|
||
|
||
## Clippy
|
||
|
||
```
|
||
$ cargo clippy --workspace --all-targets
|
||
(clean — no warnings, no errors)
|
||
```
|
||
|
||
## Configuration
|
||
|
||
Every binary takes its data directory from a single helper in the atom:
|
||
|
||
```rust
|
||
aether_atom::data_dir("AETHER_RECALL_DIR")
|
||
```
|
||
|
||
Resolution order: env var → `$HOME/.aether-recall/` → `./.aether-recall/`.
|
||
Override with `AETHER_RECALL_DIR=/path/to/ledger`.
|
||
|
||
Per-binary tunables:
|
||
|
||
| Binary | Env var | Default |
|
||
|---|---|---|
|
||
| `aether-recall-mcp` | `AETHER_RECALL_LOOKUP_RETENTION_DAYS` | 30 |
|
||
| `aether-ask` | `AETHER_ASK_PORT`, `OLLAMA_HOST`, `OLLAMA_PORT`, `OLLAMA_MODEL` | 8082 / 127.0.0.1 / 11434 / dolphin-llama3:8b |
|
||
| `aether-net-mini` | `AETHER_NET_DATA_DIR` | `$HOME/.aether-recall/` |
|
||
| `aec-atom` | `AEC_COMPACT_DAYS` | 30 |
|
||
|
||
## Run
|
||
|
||
```bash
|
||
cargo build --release --workspace
|
||
|
||
# Stand up an MCP server (used by opencode)
|
||
AETHER_RECALL_DIR=~/.aether-recall ./target/release/aether-recall-mcp &
|
||
|
||
# Run an HTTP gateway alongside
|
||
AETHER_ASK_PORT=8082 ./target/release/aether-ask &
|
||
|
||
# Optionally stand up a DHT node
|
||
./target/release/aether-net-mini daemon --listen 0.0.0.0:9001 &
|
||
|
||
# Or poke at the ledger live
|
||
./target/release/aec-atom
|
||
ae> remember "error[E0308]: expected i32, found &str" "use .to_string()"
|
||
ae> lookup "error[E0308]: expected i32, found &str"
|
||
ae> stats
|
||
ae> replay
|
||
ae> quit
|
||
```
|
||
|
||
## Use the atom in your own code
|
||
|
||
```toml
|
||
[dependencies]
|
||
aether-atom = { path = "/home/cosmos/aether-atom" } # or git / crates.io
|
||
```
|
||
|
||
```rust
|
||
use aether_atom::{data_dir, fingerprint, rust_canonicalize, Lattice};
|
||
|
||
let mut lat = Lattice::open(&data_dir("MY_DATA_DIR"))?;
|
||
let sig = fingerprint(rust_canonicalize(my_input).as_slice());
|
||
match lat.lookup(&sig, "global") {
|
||
Some(hit) => return hit.value, // cache hit
|
||
None => { let v = compute(my_input); // cache miss
|
||
lat.record(sig, "global", &v, Some(true));
|
||
return v; }
|
||
}
|
||
```
|
||
|
||
See `examples/library_usage/` for a 50-LOC working program with tests.
|
||
|
||
## Bench approx (in-process, sdb fs)
|
||
|
||
```
|
||
10,000 cached ask() ops (incl. disk write): 801 ms = 80 µs/op
|
||
100,000 raw lookup() ops (incl. disk write): 8.5 s = 85 µs/op
|
||
```
|
||
|
||
vs the original aether-recall README's claim of ~200 µs per cached
|
||
lookup — about **2.5×** faster, chiefly because bodies are Arc-shared
|
||
on the hit path so there's never a memcpy of the cached value.
|
||
|
||
## What this is NOT
|
||
|
||
- Not a chat product. A primitive your tools build on.
|
||
- Not a peer-to-peer replication mesh. `replicate` is a placeholder.
|
||
Both peers inherit the same atom ledger if they share the data dir.
|
||
- Not a model registry. `aether-ask` delegates to whatever ollama is
|
||
reachable; it doesn't manage model storage.
|
||
|
||
## Built from this premise
|
||
|
||
Today the aether suite was 7 crates, ~7000 LOC. The original had
|
||
*three* divergent fingerprint schemes, *three* append-only ledgers,
|
||
~900 ms subprocess overhead per ask, a hardcoded WSL2 path in three
|
||
places, two dead kelp of dependencies (`sha3`, `hex`, `rand`, `chrono`)
|
||
and two stubbed modules. This rewrite collapses that to one
|
||
fingerprint, one ledger, one canonical linker (the atom), and four
|
||
bindings — each adding a real surface (MCP tool, TCP verb, HTTP verb,
|
||
or REPL command).
|
||
|
||
The atom is the most essential layer: prove it works, prove it
|
||
benchmarks, and every wrapper is an obligation.
|