# Areev Rust SDK
Rust client library for the [Areev](https://areev.ai) knowledge database.
A **grain** is one typed memory fact — the atomic unit Areev stores (e.g. the
Fact "John likes coffee"). Everything in this SDK ultimately reads or writes
grains.
## Installation
```toml
[dependencies]
areev = "1"
```
For gRPC transport:
```toml
[dependencies]
areev = { version = "1", features = ["grpc"] }
```
## Quick Start
```rust
use areev::Areev;
#[tokio::main]
async fn main() -> areev::Result<()> {
let areev = Areev::from_env(); // reads AREEV_API_KEY, AREEV_URL from env
areev.remember("John likes coffee").await?;
let results = areev.recall("what does John like?").await?;
for hit in &results.results {
println!("{}: {:?}", hit.grain_type, hit.fields);
}
Ok(())
}
```
### Configuration
The client reads from environment variables by default:
| `AREEV_API_KEY` | — | API key (sent as `Authorization: Bearer <key>`) |
| `AREEV_URL` | `https://app.areev.ai` | Server endpoint |
| `AREEV_MEMORY_ID` | `default` | Memory database ID |
Or pass them explicitly:
```rust
let areev = Areev::new(
Some("ar_..."), // api_key
Some("https://dub.areev.ai"), // url
Some("my-memory"), // memory_id
);
```
## API
The top-level [`Areev`] client exposes two bare verbs (`remember` /
`recall`) plus a resource accessor for every domain. The namespaces split
into two groups.
> **Known limitation (v1.0).** Most resource-layer methods return
> `serde_json::Value` rather than typed response structs — reach into the
> `Value` for fields. A handful of surfaces are already typed (e.g.
> `recall()` results, `connectors()`). Typed response models for the
> remaining resources (recall/chat/harness → structs) land in **v1.1**.
**Core** — what most integrations use day to day:
| `remember(text)` / `recall(query)` | Store NL memory (LLM extracts structure) / search it |
| `grains()` | Typed grain CRUD: `add`, `get`, `supersede`, `forget`, `recall`, `cal`, … |
| `harness()` | Flow-A chat: `chat`, `chat_resume`, `chat_cancel`, `chat_session`/`chat_interactive` |
| `tools()` | v1 tools surface — bind/list/invoke tools on a harness |
| `connectors()` | Third-party connector catalog + OAuth (Axtion-backed): `list`, `actions`, `authorize`, `poll_oauth`, `store_credentials` |
| `memories()` | Memory-database lifecycle + stats |
| `system()` | `health`, `stats`, `config`, `models` |
**Advanced / Compliance** — governance, identity, and ingestion surfaces
you reach for as you scale: `compliance()` (GDPR/CCPA audit, export,
verify), `consent()`, `scope()`, `policy()`, `authz()`,
`agent_identities()`, `sessions()`, `namespaces()`, `provenance()`,
`preferences()`, `hooks()`, `goals()`, `chat()`, `connections()`,
`knowledge_sources()`, `imports()`.
```rust
let areev = areev::Areev::from_env();
// Typed grain ops via the grains resource.
let hash = areev.grains().add(
"fact",
serde_json::json!({"subject": "john", "relation": "likes", "object": "coffee"}),
None,
).await?;
let grain = areev.grains().get(&hash).await?;
// System ops.
let _health = areev.system().health().await?;
```
### Full flow
connect → remember/recall → create a harness and chat → register a
client-side tool → connect a third-party connector and bind one of its
actions as a tool — without hand-writing a single JSON Schema:
```rust
use std::pin::Pin;
use areev::{Areev, ChatExecutors};
use serde_json::json;
#[tokio::main]
async fn main() -> areev::Result<()> {
let areev = Areev::new(
Some("ap_local_..."), // ap_*/ar_* key
Some("http://localhost:4210"), // your cell
Some("my-first-memory"),
);
// 1. Remember + recall
areev.remember("Ada prefers email over Slack").await?;
let hits = areev.recall("how should I contact Ada?").await?;
for h in &hits.results {
println!("{} {:?}", h.grain_type, h.fields);
}
// 2. Create a harness wired to a provider, then chat
areev
.harness()
.create("Sales Bot", "sales-bot")
.description("Outbound sales assistant")
.llm_config("openai", "gpt-4o-mini")
// provider_type defaults to provider_id ("openai") automatically.
.send()
.await?;
let reply = areev.harness().chat("sales-bot", "Say hi").send().await?;
println!("{reply}");
// 3. Add a client-side tool (executed in your process)
let mut executors: ChatExecutors = ChatExecutors::new();
executors.insert("get_weather", Box::new(|_name, args| {
Pin::from(Box::new(async move {
let city = args.get("city").and_then(|v| v.as_str()).unwrap_or("?");
Ok(json!({ "city": city, "temp_c": 22 }))
}))
}));
areev
.harness()
.chat_session("sales-bot")
.message("What's the weather in Paris?")
.executors(&executors)
.send()
.await?;
// 4. Connect a third-party connector (OAuth), then poll for completion
let flow = areev
.connectors()
.authorize("gmail", "https://yourapp.example/oauth/cb")
.await?;
println!("Open this URL to authorize: {}", flow.authorize_url);
// ... user consents in the browser, provider redirects back ...
// Poll until the flow completes — sleep between attempts so you don't
// hammer the endpoint.
loop {
let status = areev.connectors().poll_oauth("gmail", &flow.state).await?;
match status.get("status").and_then(|s| s.as_str()) {
Some("success") => break,
Some("error") | Some("expired") => {
eprintln!("oauth flow failed: {status}");
break;
}
_ => tokio::time::sleep(std::time::Duration::from_secs(2)).await,
}
}
// 5. Bind a connector action as a tool — schema fetched for you
areev
.tools()
.bind_axtion("sales-bot", "gmail", "send-email") // action key from connectors().actions()
.description("Send an email via Gmail")
.send()
.await?;
Ok(())
}
```
### Connectors
`connectors()` is the catalog + OAuth surface (backed by Axtion). All
methods return clean, parsed data — provider tokens never touch the SDK.
> **Naming, disambiguated.** `connectors()` is the third-party connector
> *catalog* + OAuth (Gmail, Slack, …); `connections()` manages a memory's
> live data-source *connections* (Knowledge Sources); `chat()` is the
> stateless completions endpoint; and `harness()` is the stateful
> LLM-plus-tools runtime you create, bind tools to, and converse with.
| `connectors().list()` | `Vec<ConnectorSpec>` — `{name, display_name, category, version}` |
| `connectors().get(name)` | Raw connector detail (metadata + action descriptors) |
| `connectors().actions(name)` | `Vec<ConnectorAction>` — `{name, display_name, description, param_schema, required}` |
| `connectors().authorize(name, redirect_uri)` | `AuthorizeResponse` — `{authorize_url, state, ..}` |
| `connectors().poll_oauth(name, state)` | Raw `{status: "pending"\|"success"\|"error"\|"expired", ..}` |
| `connectors().store_credentials(body)` | Store API-key credentials (non-OAuth connectors) |
```rust
let catalog = areev.connectors().list().await?;
// → [ConnectorSpec { name: "gmail", display_name: "Google Gmail",
// category: "Communication", version: "1.0.0" }, ...]
let actions = areev.connectors().actions("gmail").await?;
// → [ConnectorAction { name: "send-email", display_name: "Send Email",
// param_schema: {...}, required: ["to", "subject", "body"] }, ...]
```
> **Prerequisite — redirect-uri allowlist.** The `redirect_uri` you pass to
> `connectors().authorize(...)` must be **pre-registered on your org** by an
> admin. An unregistered URI is rejected before any provider redirect — the
> call raises [`AreevError::Validation`] (HTTP 400). Register your callback
> URL(s) in org settings before going live.
`tools().bind_axtion(slug, connector, action)` then looks up the action's
parameter schema from `connectors().actions()` and binds it for you — so
the agent gets a fully-typed tool without you writing any JSON Schema.
### Harness Chat
Use `harness().chat_session(slug)` to drive a harness (Areev's
LLM-plus-tools runtime) with client-side tool executors. The builder runs
the pause/resume loop for you when the model calls a `client://` tool, and
names each argument so the call site stays readable:
```rust
use std::pin::Pin;
use areev::{Areev, ChatExecutors};
use serde_json::json;
let areev = Areev::from_env();
let mut executors: ChatExecutors = ChatExecutors::new();
.harness()
.chat_session("weather-harness")
.conversation_id("conv-1") // omit for a fresh single-shot turn
.message("What's the weather in Paris?")
.executors(&executors)
.send()
.await?;
println!("{}", response.text);
```
`harness().chat_interactive(slug, conversation_id, message, &executors)`
is the equivalent positional form. `harness().chat`,
`harness().chat_resume`, and `harness().chat_cancel` are the low-level
primitives if you want to run the loop yourself.
## Transports
| HTTP/REST | `http` | Yes |
| gRPC | `grpc` | No |
## License
BUSL-1.1