| 1 | # ed-agent |
| 2 | |
| 3 | **Embed Ed.** Your app gets a brain. Your users keep their data. |
| 4 | |
| 5 | Ed is a chat agent you drop into an application. It sits one layer above the |
| 6 | [`onde`](https://crates.io/crates/onde) inference SDK: `onde` gives you an |
| 7 | engine to load a model into and send messages to, and Ed adds the part every |
| 8 | application built on it was writing for itself. |
| 9 | |
| 10 | - **Lifecycle orchestration.** Loading a model is a sequence of state changes, |
| 11 | not one call. Ed drives `Loading -> Ready` (or `-> Error`) and reports each |
| 12 | transition. |
| 13 | - **Notification.** `onde` exposes status by polling. A UI needs to be told. Ed |
| 14 | pushes transitions, tool activity, and replies to an `EventSink` you implement. |
| 15 | - **Tool calling.** The host registers an explicit JSON-schema capability list. |
| 16 | Read-only tools run automatically; mutating tools require host approval. |
| 17 | - **A flattened reply.** Inference returns `Result<InferenceResult, _>`; a |
| 18 | frontend wants one shape covering both outcomes. That's `ChatReply`. |
| 19 | |
| 20 | Ed deliberately does not own sessions, persistence, model catalogues, or |
| 21 | downloads. Those differ per application and belong to you. |
| 22 | |
| 23 | ## Usage |
| 24 | |
| 25 | ```rust |
| 26 | use ed_agent::{Ed, GgufModelConfig}; |
| 27 | |
| 28 | let ed = Ed::new(); |
| 29 | ed.load(GgufModelConfig::qwen25_1_5b(), None, None).await?; |
| 30 | |
| 31 | let reply = ed.send("Summarise this thread.").await; |
| 32 | println!("{}", reply.reply.unwrap_or_default()); |
| 33 | ``` |
| 34 | |
| 35 | Use `Ed::with_agent` to provide a `ToolExecutor`, `ApprovalHandler`, and |
| 36 | `EventSink`. For Tauri applications, |
| 37 | [`ed-agent-tauri`](https://crates.io/crates/ed-agent-tauri) provides that |
| 38 | implementation plus the commands a webview invokes. |
| 39 | |
| 40 | ## Any load path, same transitions |
| 41 | |
| 42 | `Ed::load` covers GGUF. For anything else — an assigned model from a server, a |
| 43 | UQFF build, a load API that doesn't exist yet — hand the call to `load_with` and |
| 44 | Ed reports the transitions around it: |
| 45 | |
| 46 | ```rust |
| 47 | ed.load_with("Onde model", |engine| { |
| 48 | engine.load_assigned_model(env, app_id, secret, prompt, sampling) |
| 49 | }) |
| 50 | .await?; |
| 51 | ``` |
| 52 | |
| 53 | Ed takes a closure rather than wrapping each path by name on purpose. Those APIs |
| 54 | take types that would drag their crates into Ed's public signature and pin every |
| 55 | host to Ed's version of them — and at least one of those types has already moved |
| 56 | between `onde` releases. A closure keeps Ed's dependency surface to `onde` alone. |
| 57 | |
| 58 | ## Platforms |
| 59 | |
| 60 | Ed has no `cfg(target_os)` gates. `onde` already splits real and fallback |
| 61 | implementations internally, so this crate compiles everywhere and surfaces |
| 62 | `onde`'s error on a platform it can't run inference on. |
| 63 | |
| 64 | Be careful adding platform gates in a host: a gate narrower than `onde`'s own |
| 65 | support list silently disables chat on a platform that would have worked. |
| 66 | |
| 67 | ## Copyright |
| 68 | |
| 69 | © 2026 [Onde Inference](https://ondeinference.com). |
| 70 | Licensed under MIT or Apache-2.0. |