| 1 | # onde-ed – Agent Instructions |
| 2 | |
| 3 | ## What this repo is |
| 4 | `onde-ed` is a **Rust Cargo workspace** providing the embeddable agentic core |
| 5 | used across Onde products (macOS, iOS, Tauri, CLI). The workspace is organised |
| 6 | into focused crates; do not merge them. |
| 7 | |
| 8 | --- |
| 9 | |
| 10 | ## Crate map |
| 11 | |
| 12 | | Crate | Purpose | |
| 13 | |---|---| |
| 14 | | `ed-agent` | Core library: `Ed` struct, lifecycle, `send`/`stream`/`run`, tool approval, `EventSink`, `SessionStore` seams | |
| 15 | | `ed-acp` | ACP v1 server: `Profile` trait, `serve`/`serve_stdio`, sessions, LLM streaming, tool routing, MCP | |
| 16 | | `ed-acp-tauri` | Hosts an `ed-acp` agent in a Tauri app: in-process `serve` over `Channel::duplex`, permission prompts, `fs/*` inside per-session roots, URL-elicitation sign-in via the system browser, session and config-option commands | |
| 17 | | `ed-mcp` | MCP integration over `rmcp`: tool namespacing (`mcp__<server>__<tool>`, max 64 bytes), `SharedServer`, `McpExecutor`, timeouts, cancellation | |
| 18 | | `ed-acp-tui` | ratatui TUI frontend for ACP agents over stdio | |
| 19 | | `ed-acp-testkit` | Mock Onde Cloud endpoint + conformance test suite | |
| 20 | | `ed-agent-ffi` | UniFFI bridge → Apple XCFramework (`FfiEd`, `FfiToolExecutor`, `FfiApprovalHandler`, `FfiEventListener`) | |
| 21 | | `ed-agent-tauri` | Tauri command bindings that emit events to the webview | |
| 22 | |
| 23 | --- |
| 24 | |
| 25 | ## Non-negotiable architecture rules |
| 26 | |
| 27 | 1. **One `Ed` instance, one engine, one model.** `Ed` is `Send + Sync`, not |
| 28 | `Clone`. Never try to clone or share it across threads without `Arc`. |
| 29 | |
| 30 | 2. **`Profile` is the product seam.** A product crate (or binary) implements |
| 31 | `Profile` (`info`, `system_prompt`, `toolsets`, `builtin_mcp_servers`). |
| 32 | `ed-acp` handles the ACP server, session management, and LLM glue. Do not |
| 33 | put product-specific logic inside `ed-acp`. |
| 34 | |
| 35 | 3. **Tool risk gates approval.** `ToolRisk::ReadOnly` vs `ToolRisk::Mutating` |
| 36 | determines whether the approval handler is invoked. Per-session grants are |
| 37 | tracked via `ApprovalDecision::AllowForSession`. Do not bypass this gate. |
| 38 | |
| 39 | 4. **MCP tool names ≤ 64 bytes.** Names are sanitised to |
| 40 | `mcp__<server>__<tool>`. Anything longer is truncated. Do not assume the |
| 41 | original tool name survived intact. |
| 42 | |
| 43 | 5. **FFI types are wrappers, not source of truth.** `FfiEd` delegates to |
| 44 | `Ed`; `FfiToolExecutor` bridges a foreign callback into the |
| 45 | `ToolExecutor` trait. Keep FFI types thin. |
| 46 | |
| 47 | 6. **Tauri and FFI are separate bindings.** `ed-agent-tauri` and |
| 48 | `ed-agent-ffi` both wrap `ed-agent` but must not depend on each other. |
| 49 | `ed-acp-tauri` is the Tauri host for `ed-acp` agents; it does not depend on |
| 50 | `ed-agent-tauri`. |
| 51 | |
| 52 | 7. **A product's own sign-in goes through `Profile::auth`.** `ed-acp` knows |
| 53 | terminal auth and the `<PREFIX>_*` env file. A product with a browser or |
| 54 | session sign-in supplies an `AuthProvider`; do not put its flow in `ed-acp`. |
| 55 | |
| 56 | --- |
| 57 | |
| 58 | ## LLM providers |
| 59 | |
| 60 | Configured via `ed-acp/src/llm.rs`. Three variants: `Onde`, `OpenAi`, `Local`. |
| 61 | |
| 62 | | Env var | Effect | |
| 63 | |---|---| |
| 64 | | `<PREFIX>_PROVIDER` | `onde` / `openai` / `local` | |
| 65 | | `ONDE_API_KEY` | Cloud key for `Onde` provider | |
| 66 | | `OPENAI_API_KEY` | Key for `OpenAi` provider | |
| 67 | |
| 68 | Default cloud model: `onde-kkk`. |
| 69 | Default local model: `Qwen3-4B` (desktop) / `Qwen3-1.7B` (mobile). |
| 70 | |
| 71 | `LlmEnv` drives config-dir, env-file loading, and variable namespacing per |
| 72 | product. Do not hard-code env vars; always go through `LlmEnv`. |
| 73 | |
| 74 | --- |
| 75 | |
| 76 | ## Working in this repo |
| 77 | |
| 78 | ### Before changing anything |
| 79 | - Read the relevant `lib.rs` and the trait/struct you are modifying first. |
| 80 | - Check whether a public API is re-exported from `ed-agent`'s `lib.rs` before |
| 81 | adding a new one; keep the public surface minimal. |
| 82 | - If touching `ed-agent-ffi`, remember it must compile for Apple targets |
| 83 | (macOS + iOS); avoid anything that does not cross the FFI boundary cleanly. |
| 84 | |
| 85 | ### Making changes |
| 86 | - Keep crate boundaries: adding a dependency from a lower-level crate |
| 87 | (`ed-agent`) on a higher-level one (`ed-acp`) is forbidden. |
| 88 | - New tool definitions belong in the crate that owns the domain, exposed via |
| 89 | `ToolExecutor` or MCP server, not inlined into `Ed`. |
| 90 | - Prefer `async_trait` + `Arc<dyn Trait>` for seams; avoid generics on `Ed` |
| 91 | itself. |
| 92 | - When adding an FFI type, add the matching `From<Ffi*>` / `From<*>` impls |
| 93 | immediately so both sides stay in sync. |
| 94 | |
| 95 | ### After changes |
| 96 | - Run `cargo check --workspace` to catch compile errors across all crates. |
| 97 | - Run `cargo test --workspace` before committing. |
| 98 | - For FFI changes, note that a full XCFramework build requires the Apple SDK |
| 99 | toolchain; CI handles that — local `cargo check` is sufficient to validate |
| 100 | Rust correctness. |
| 101 | |
| 102 | ### Branches |
| 103 | - Base branch is **`development`**. Open all PRs against `development`, not `main`. |
| 104 | - Feature branches: `feature/<short-description>`. |
| 105 | - Stacked PRs are fine; always target the parent feature branch, not `development`, when stacking. |
| 106 | |
| 107 | ### Commit messages |
| 108 | - Scope to the affected crate(s): `ed-agent: …`, `ed-acp: …`, `ed-mcp: …`. |
| 109 | - Keep the subject line under 72 chars. |
| 110 | - Always end the commit with: |
| 111 | ``` |
| 112 | Co-Authored-By: siGit Code v1.6.6-acp <noreply@sigit.si> |
| 113 | ``` |
| 114 | |
| 115 | --- |
| 116 | |
| 117 | ## Things to avoid |
| 118 | - Do not add `#[allow(unused)]` blanket suppressions — fix the unused item. |
| 119 | - Do not store `tokio::runtime::Runtime` inside an FFI object; use `block_on` |
| 120 | only at the outermost FFI boundary. |
| 121 | - Do not panic inside async tasks — convert errors to `AgentError` and surface |
| 122 | them through the event sink or return type. |
| 123 | - Do not hard-code model names anywhere outside `ed-acp/src/llm.rs`. |
| 124 | - Do not bypass `ToolRisk` approval for any mutating tool, even in tests — use |
| 125 | an `AllowAll` test double instead. |