# oino: complete SDK reference for LLMs > Coding-agent runtime in Rust: a small core (`oino-core`), extensions and providers as WASM components in any language targeting wasm32-wasip2 (WIT `oino:extension@2.0.0`), clients for terminal, stdio protocols and embedding. 19 crates on crates.io (1.x line). MIT OR Apache-2.0. Source: https://github.com/Neuron-Mr-White/Oino This file is self-contained: install, crate map, embedding API with working code, extension authoring end to end, provider/model configuration, RPC and ACP essentials, and the pitfalls that cost time. All Rust samples compile against the published crates (versions: oino-core 1.0, oino-client 1.0, oino-extension 1.0). ## Contents 1. Install and first run 2. Crate map (all 19 crates) 3. Architecture in one page 4. Embedding oino-core (working code) 5. Session/event model 6. Extension authoring, end to end (Rust) 7. WIT world essentials (oino:extension@2.0.0) 8. Packaging, permissions, installing 9. Providers and models (credentials, models.json) 10. RPC protocol essentials 11. ACP essentials 12. TUI and CLI essentials 13. Settings and environment variables 14. Common pitfalls ## 1. Install and first run macOS/Linux: curl -fsSL https://oino.dev/install.sh | sh Windows (PowerShell): irm https://oino.dev/install.ps1 | iex Or from Rust: `cargo install oino-cli` (builds the binary only; no extension packages are bundled, so no models until provider packages are installed: `oino install `). Prefer the installer for a complete setup. First prompt: export ANTHROPIC_API_KEY=sk-ant-... # or OPENAI_API_KEY, OPENROUTER_API_KEY, ... oino -p "Summarize this repository" Modes: `oino` (TUI), `oino -p` (print; automatic when stdio is not a TTY), `oino --mode json` (JSON event stream), `oino --mode rpc` (JSONL commands on stdio), `oino --mode acp` (Agent Client Protocol v1, e.g. Zed). Useful first commands in the TUI: `/model`, `/settings`, `/info` (loaded extensions), `/hotkeys`, `ctrl+d` quit. Credential resolution per provider: (1) `--api-key` flag, (2) `~/.oino/auth.json`, (3) provider environment variables. `oino --list-models [search]` lists usable models. Uninstall: delete the `oino` binary; `~/.oino` holds settings, credentials (`auth.json`, `secrets.json`), sessions and extension state. ## 2. Crate map (19 crates, all on crates.io) Runtime layer (bottom-up): - oino-ai: message, content, model, streaming types; fixed JSON wire shapes; builds on wasm32-wasip2. docs.rs/oino-ai - oino-agent: provider-agnostic agent loop; turns, steering/follow-up queues, tool execution, hooks. docs.rs/oino-agent - oino-tools: built-in tools read, write, edit, bash, grep, find, ls (+ powershell on Windows). docs.rs/oino-tools - oino-session: session format v3; entries, migration, context projection, SessionManager. docs.rs/oino-session - oino-settings: settings manager; global/project merge, typed getters, locking. docs.rs/oino-settings - oino-resources: context files (AGENTS.md/CLAUDE.md), skills, prompt templates, system prompt, project trust. docs.rs/oino-resources - oino-models: model registry; models.json, model resolution, auth storage, virtual models. docs.rs/oino-models - oino-core: composition root; Services, AgentSessionRuntime, extension runner, compaction, retry, export. docs.rs/oino-core Extension host: - oino-extension-api: contracts between core and runtimes; instances, host services, manifests, permissions. docs.rs/oino-extension-api - oino-wasm-host: wasmtime component host for oino:extension@2.0.0; CoreLoader bridges into oino-core. docs.rs/oino-wasm-host Guest SDK (for writing extensions): - oino-extension: guest SDK wrapping the WIT bindings; Extension trait, Init, typed host modules; bundles the WIT world. docs.rs/oino-extension - oino-extension-macros: #[tool] proc macro. docs.rs/oino-extension-macros Clients: - oino-client: SessionBackend contract, RPC command records, InProcessBackend, FakeBackend. docs.rs/oino-client - oino-rpc: JSONL RPC codec, RpcServer over any backend, RpcClient/RpcBackend. docs.rs/oino-rpc - oino-acp: ACP v1 agent adapter over oino-core sessions. docs.rs/oino-acp - oino-view: frontend-agnostic view models (transcript, tree, sessions, composer, keybindings). docs.rs/oino-view - oino-tui: interactive terminal client over oino-client. docs.rs/oino-tui - oino-cli: the `oino` binary (TUI, print, JSON, RPC, ACP). docs.rs/oino-cli Shared: - oino-ui: wire types + builder for the extension UI protocol v2; serde-only, wasip2-safe; shared by host and guests. docs.rs/oino-ui Layering rules: clients depend on core; core depends on the runtime crates; nothing in the runtime depends on wasmtime. Core reaches extensions only through the ExtensionLoader trait. ## 3. Architecture in one page - Agent loop (oino-agent): send conversation to provider, stream assistant message, execute tool calls (sequential or parallel per tool), append results, repeat until a no-tool answer or abort. Steering messages deliver after the current tool call; follow-ups wait for the run to settle. - Sessions: JSONL files, format v3, one JSON object per line; a tree of entries (fork or navigate back creates a branch). Stored under ~/.oino/sessions/ organised by cwd. - Extensions: WASM components implementing WIT world oino:extension/guest@2.0.0, run by wasmtime in per-instance sandboxes; capabilities granted per manifest. - Providers are extensions exporting the provider interface; core dispatches each request by the model's `api` field. - Modes: one binary. Interactive when stdio is a TTY, else print. json/rpc/acp by flag. Extensions see the mode in init context. - Frontends embed in process (InProcessBackend over AgentSessionRuntime) or out of process (RpcBackend over oino --mode rpc). The SessionBackend contract is identical: send(RpcCommand) -> response data; events() -> ordered ClientEvent stream; ui_respond() answers dialogs; shutdown(). ## 4. Embedding oino-core (working code) Cargo.toml: [dependencies] oino-core = "1" oino-client = "1" indexmap = "2" anyhow = "1" tokio = { version = "1", features = ["rt-multi-thread", "macros"] } futures = "0.3" Minimal embed (no extensions; compiles as-is against published 1.x): use std::{path::PathBuf, sync::Arc}; use futures::StreamExt; use indexmap::IndexMap; use oino_client::{AgentCommand, InProcessBackend, RpcCommand, SessionBackend}; use oino_core::extensions::api::ExtensionMode; use oino_core::runtime::{AgentSessionRuntime, SessionOptions}; use oino_core::services::{ResourceFlags, Services, ServicesOptions, StaticLoader}; #[tokio::main] async fn main() -> anyhow::Result<()> { let services = Services::create(ServicesOptions { cwd: std::env::current_dir()?, agent_dir: PathBuf::from( std::env::var("OINO_AGENT_DIR").unwrap_or_else(|_| { format!("{}/.oino", std::env::var("HOME").unwrap()) }), ), docs_root: PathBuf::new(), mode: ExtensionMode::Print, // Tui | Print | Json | Rpc | Acp | Desktop | Mobile has_ui: false, flags: ResourceFlags::default(), extension_flag_values: IndexMap::new(), loader: Arc::new(StaticLoader(Vec::new())), // no components -> no providers approve: None, // project trust decision models: None, // override; default loads auth.json + models.json settings: None, }) .await?; let runtime = Arc::new( AgentSessionRuntime::create(services, SessionOptions::default()).await?, ); let backend = InProcessBackend::new(runtime); backend .send(RpcCommand::Agent(AgentCommand::Prompt { message: "Summarize this repository".into(), images: Vec::new(), streaming_behavior: None, })) .await?; let mut events = backend.events(); while let Some(event) = events.next().await { println!("{event:?}"); // ClientEvent::Session/Ui/ExtensionError/Oino } backend.shutdown().await; Ok(()) } SessionOptions fields: source (SessionSource::New | NewWithId(String) | Open(PathBuf) | ContinueRecent | Resume(String) | Fork(path/id)), session_dir, name, model (resolved oino_ai::Model), thinking_level, scoped_models, tools (ToolSelection), exclude_tools, builtin_tools, custom_tools. Default: New session, default tool selection. Loading real extension packages (gives you tools, commands and providers): use oino_wasm_host::{HostConfig, WasmRuntime, WasmRuntimeOptions}; use oino_wasm_host::core_bridge::{CoreLoader, SourcedPackage}; use oino_wasm_host::packages::discover; let (packages, problems) = discover(&[agent_dir.join("extensions")], true); let packages: Vec = packages .into_iter() .map(|p| SourcedPackage { package: p, project: false }) .collect(); let wasm = WasmRuntime::new(WasmRuntimeOptions::new(HostConfig::new( agent_dir.clone(), cwd, )))?; let loader = Arc::new(CoreLoader::new(Arc::new(wasm), packages)); // pass `loader` as ServicesOptions::loader; check `problems` and surface them Distribution packages (bundled providers) come from the installer's share dir; the CLI resolves it relative to the binary (override with OINO_DIST_EXTENSIONS). The CLI's own loader (crates/oino/src/loader.rs + startup.rs in the repo) is the complete worked example. Out of process (spawn oino --mode rpc): use oino_client::protocol::ClientInfo; use oino_rpc::{ChildCommand, RpcBackend, Transport}; let backend = RpcBackend::connect( Transport::StdioChild(ChildCommand { program: "oino".into(), args: vec!["--mode".into(), "rpc".into()], cwd: Some(project_dir), env: vec![("ANTHROPIC_API_KEY".into(), key)], }), ClientInfo { name: "my-app".into(), version: "0.1.0".into() }, None, // optional frontend capabilities JSON ) .await?; RpcBackend implements the same SessionBackend trait; RpcClient alone exposes raw JSON events. FrontendApi (oino-client) adds typed helpers for completions, panels, shortcuts, settings, sign-in, rewind. FakeBackend (oino_client::fake) scripts events/replies for tests. ## 5. Session and event model Session file: one JSON object per line, entries with `type`, 8-hex `id`, `parentId`, ISO timestamps; message shapes from oino-ai (AgentMessage). Tree: fork or navigate back creates a branch; all branches resumable. Older format files migrate on first access (originals move to ~/.oino/sessions/.legacy/). Files from other tools speaking v3 open directly (`--session path.jsonl`, `/import`). ClientEvent (oino-core::events) variants: Session(AgentSessionEvent), Ui(UiRequest), ExtensionError, Oino(OinoEvent). Session events include agent_start, message_update, message_end, tool_execution_start, tool_execution_end, agent_settled, and lifecycle events for sessions/models. These are the same events as `--mode json` output. Compaction: when the context fills (settings compaction.*) or on `/compact [instructions]`, older turns are replaced by a summary entry. Branch summaries can summarise abandoned branches when navigating. ## 6. Extension authoring, end to end (Rust) Setup: cargo new --lib hello-ext && cd hello-ext rustup target add wasm32-wasip2 Cargo.toml: [package] name = "hello-ext" version = "0.1.0" edition = "2021" [lib] crate-type = ["cdylib"] [dependencies] oino-extension = "1" serde = { version = "1", features = ["derive"] } schemars = "1.2" serde and schemars must be direct dependencies (derive macros emit paths into them). schemars must be 1.x (the SDK is built against it). extension.json (manifest, next to Cargo.toml; goes into the package): { "apiVersion": "oino.extension/2", "id": "acme.hello", "name": "Hello", "version": "0.1.0", "kind": "extension", "description": "Greets people and reacts to the agent." } src/lib.rs (complete, compiles against oino-extension 1.0): use std::time::Duration; use oino_extension::command::{Command, Flag, Shortcut}; use oino_extension::prelude::*; #[derive(Default)] struct Hello; #[derive(Deserialize, JsonSchema)] struct HelloArgs { /// Name of the person to greet. name: String, } impl Hello { /// Say hello to someone. #[oino_extension::tool(label = "Say hello", kind = Other)] async fn hello(&'static self, args: HelloArgs, cx: ToolCtx) -> Result { cx.progress(&ToolResult::text(format!("greeting {}", args.name))); Ok(ToolResult::text(format!("Hello, {}!", args.name))) } } impl Extension for Hello { async fn init(&'static self, cx: &mut Init) -> Result<()> { // Macro-generated builder from the method above: cx.tool(Self::hello_tool()); // Plain builder form: cx.tool( Tool::new("shout") .description("SHOUT A MESSAGE") .args::() .run(|_ext, args: HelloArgs, _cx| async move { Ok(ToolResult::text(args.name.to_uppercase())) }), ); // Events; handlers of one extension run concurrently (component-model async). cx.on(EventKind::AgentEnd, |_ext, _ev: Event, _cx| async move { host::info_log("run finished"); session::append_entry("hello.seen", Some(&json!({"at": timers::now_ms()})))?; Ok(None::) }); // Slash command (appears as /hello in every client): cx.command( Command::new("hello") .description("Say hello to the team") .run_value(|_ext, _args, _cx| async move { Ok(Some(json!({"hello": true}))) }), ); // Keyboard shortcut, CLI flag, cross-extension service, repeating timer: cx.shortcut( Shortcut::new("hello.shortcut", &["ctrl+e"]) .description("Greet on keypress") .run(|_ext, _cx| async { ui::notify("hi", "info") }), ); cx.flag(Flag::boolean("hello-verbose", "Verbose hello output")); cx.service("hello.ping", |_ext, payload, caller, _cx| async move { Ok(json!({"pong": payload, "from": caller})) }); cx.timer("hello.tick", Duration::from_secs(60), true); Ok(()) } } oino_extension::export_extension!(Hello); Tool handler details: doc comment -> model-facing description; `label` is UI text; `kind` maps to editor tool kinds (Read, Edit, Execute, Other, ...). ToolResult::text/error/image; .with_details(json) for renderers; .terminate() ends the run. Tool builder options: name, label, description, args::() (JSON schema from T: JsonSchema), parameters(&Value), output_schema, prompt_snippet, guideline, exposure, inactive(), sequential(), kind, replay, namespace, reaches(...) (nested tool calls), render_hints. Dynamic registration after init: tool.register(&'static ext). Event handling: `cx.on(kind, |ext, ev: Event, cx| async move { ... })` returns Ok(None) to pass or Ok(Some(json)) to answer. For EventKind::ToolCall, `{"block": true, "reason": "..."}` vetoes the call. Handlers of one extension run in registration order; last non-None result wins. EventKind values include: ProjectTrust, ResourcesDiscover, SessionStart, SessionInfoChanged, SessionBeforeSwitch, SessionBeforeFork, SessionBeforeCompact, SessionCompact, SessionShutdown, SessionBeforeTree, SessionTree, Context, ContextWithSystem, BeforeProviderHeaders, BeforeProviderRequest, AfterProviderResponse, ProviderStreamEvent, BeforeAgentStart, AgentStart, AgentEnd, AgentBeforeSettle, AgentSettled, TurnStart, TurnEnd, MessageStart, MessageUpdate, MessageEnd, ToolExecutionStart, ToolExecutionUpdate, ToolExecutionEnd, ModelSelect, ThinkingLevelSelect, ToolCall, ToolResult, UserBash, Input, OinoTimer, OinoBus, OinoTask. Host capability modules (prelude): session (entries, append_entry, send_message, sessions index), control (is_idle, thinking_level, set_model, compact, new_session, ...), models (complete, stream, summarize_default, credential_for), ui (notify, dialogs select/confirm/input/editor/form/multiselect/approval/custom, status, widgets, Panel builder, os_notify, clipboard, open_url), agents (Agent::spawn for nested sessions), process (exec, Child::spawn streaming stdio), tasks (background tasks start/list/logs/kill), http (get/post/request(..).send(), loopback Listener), fs (preopens /ext-global, /project, /tmp, /cache, /logs; atomic_write, Lock, Watcher; plain std::fs works on preopen paths), timers (set/cancel with oino_timer events, sleep, now_ms), kv (Scope::Global/Workspace/Session), secrets, bus (publish/subscribe, oino_bus events), services (call other extensions), host_service (embedding-host services like "browser", "cron"), host (info, platform, workspace, env, logging). Raw WIT types: oino_extension::wit. State across .await: use oino_extension::sync (Mutex) or SessionMap; a std::sync::Mutex must not be held across .await. Handlers receive &'static self (host leaks one Default instance per component). Build and run: cargo build --target wasm32-wasip2 # package: # acme.hello/extension.json # acme.hello/component.wasm (from target/wasm32-wasip2/debug/hello_ext.wasm) oino -e ./acme.hello cargo build --target wasm32-wasip2 emits the component directly (wit-bindgen + wasip2 target); no wasm-tools step. In the oino repository workspace: cargo xtask wasm -p hello-ext builds and stages extensions/dist//. Testing: host-side integration tests load built components via oino-wasm-host's `testing` feature (Harness) against recording host services. Worked examples in extensions/fixtures/ of the repo: echo (every contribution + import probe), async-probe (concurrency, cancellation), ui-probe (UI), provider-faux (provider). Providers: same crate with oino-extension's `provider` feature; implement Extension::provider returning a Provider (stream, list-models, OAuth hooks); manifest kind "provider" with provides.apis. oino-provider-kit (extensions/sdk/) implements the wire protocols; shipping providers live in extensions/providers/. ## 7. WIT world essentials (oino:extension@2.0.0) WIT package oino:extension@2.0.0, defined in wit/oino-extension/ in the repo and bundled in the oino-extension crate. Two worlds: `extension` (exports guest) and `provider-extension` (adds provider export + provider-host import). WASI 0.2.12 basics are imported; everything else is oino interfaces. Data values (messages, models, events, tool results) cross the boundary as canonical JSON with fixed wire shapes (oino-ai shapes). Host capabilities are typed interfaces: - guest (export): init, on-event, run-tool, run-command, shortcuts, renderers, route-model, arbiter hooks (propose-nudge, wait-source), services - provider (export): stream, list-models, OAuth login/refresh - registry: register tools, commands, shortcuts, flags, renderers, settings namespaces, providers, virtual models, services - session: read entries / branch / tree, append entries, send messages, sessions index - control: model + thinking level, run state, context usage, compaction, tool control, command invocation, nested tool calls, settings snapshot - ui: dialogs, notify, status, widgets, frame, panels, editor text - models: complete, stream, summarize-default, classify (unsupported), credentials - agents: nested in-process agent sessions - process, tasks: subprocesses, background tasks - http: outbound HTTP through the host (allowlists, proxy, credential injection); loopback listener - fs, kv, secrets, timers, bus, services, log, host-info: state, storage, timers, pub/sub, inter-extension calls, diagnostics Known host gaps: models.classify and models.generate-image return "unsupported"; control.shutdown is recorded but no client exits on it. Non-Rust languages: any toolchain that produces a wasm32-wasip2 component exporting oino:extension/guest@2.0.0 works (component-model toolchains: wasm-tools, wit-bindgen variants, jco, componentize-py, ...). The manifest and permissions are language independent. ## 8. Packaging, permissions, installing Package directory: acme.hello/ extension.json # manifest component.wasm # name from "component", default component.wasm skills/ prompts/ themes/ docs/ # optional, declared under "resources" bin/ # optional native binaries (declared under "binaries") Manifest fields: apiVersion (must be exactly "oino.extension/2"; 1.x manifests rejected), id, name, version, description, license, authors, kind ("extension" | "provider"), component, checksums (file -> "sha256:", verified on install), minOinoVersion, provides {apis, providers} (providers), resources {skills, prompts, themes, docs}, binaries, limits {memoryMiB (default 512, max 2048), hookTimeoutMs (5000), toolCallHookTimeoutMs}, ui {reserved, settings}, permissions. Permissions: everything denied except ui. Fields: sessionControl (bool), sessions {readAll}, tools {override: [names]}, models {complete}, credentials [provider ids or "*"], agents {spawn, maxDepth}, process {spawn, allow: [programs], detached}, network {hosts, listenLoopback}, fs {project: "read"|"write", agentDir}, env [var names], secrets (bool), ui (bool, default true), osNotify, openUrl, clipboard {read, write}, editExternally, services {call: [ids or "*"]}, hostServices [names]. A missing grant fails the import with "permission denied: ". Load order (later replaces earlier same-id): distribution share dir (OINO_DIST_EXTENSIONS overrides; empty disables) -> ~/.oino/extensions/ -> /.oino/extensions/ (trusted projects) -> -e (wins). Flags: -ne disables extensions (providers stay, explicit -e loads), --exclude-extension drops ids, disabledExtensions setting persists. Install routes: `oino -e ./pkg` (one run), copy/symlink into ~/.oino/extensions/ (global) or /.oino/extensions/ (project), or `oino install ` with source a directory, archive, https archive or git+https URL (flags -l/--project, -y/--yes). `oino list` (dist/global/project), `oino remove [-l]`, `oino update ` / `oino update --extensions` (reinstall from recorded source), `oino config` (print settings paths). Distribution packages cannot be removed. Safe mode: after two consecutive failed starts, next start runs extensions off, providers on; banner names suspects; success resets. State: ~/.oino/state/oino.safe-mode.json. Extension state: ~/.oino/state//{global, workspace/, session/}. ## 9. Providers and models Provider components and their wire APIs: - oino.anthropic -> anthropic-messages: anthropic (API key or Claude Pro/Max OAuth), minimax, minimax-cn - oino.openai-responses -> openai-responses: openai, xai, meta - oino.openai-compat -> openai-completions: openrouter, groq, deepseek, together, fireworks, cerebras, huggingface, nvidia, moonshotai, kimi-coding, qwen-token-plan*, xiaomi*, zai, opencode, baseten, ant-ling, vercel-ai-gateway, ollama, lmstudio, mistral, deepinfra, chutes, perplexity - oino.openai-codex -> openai-codex-responses: openai-codex (ChatGPT sign-in) - oino.google -> google-generative-ai, google-vertex: google, google-vertex - oino.azure -> azure-openai-responses: azure - oino.bedrock -> bedrock-converse-stream: amazon-bedrock Dispatch is per model `api` field, not per provider. Credential resolution per provider: (1) --api-key with --model, (2) ~/.oino/auth.json (mode 0600), (3) environment variables. auth.json format: { "anthropic": { "type": "api_key", "key": "sk-ant-..." }, "openrouter": { "type": "api_key", "key": "sk-or-..." } } Environment variables: ANTHROPIC_AUTH_TOKEN / ANTHROPIC_OAUTH_TOKEN / ANTHROPIC_API_KEY (anthropic), OPENAI_API_KEY (openai), XAI_API_KEY, META_API_KEY, OPENROUTER_API_KEY, _API_KEY (groq, deepseek, together, fireworks, cerebras, mistral, deepinfra, chutes, perplexity, nvidia), HF_TOKEN (huggingface), MOONSHOT_API_KEY / KIMI_API_KEY, ZAI_API_KEY, MINIMAX_API_KEY, AI_GATEWAY_API_KEY (vercel-ai-gateway). Ollama (localhost:11434) and LM Studio (localhost:1234) need no key. OAuth flows (/login in the TUI; RPC _oino/login): Anthropic Claude Pro/Max, GitHub Copilot, OpenAI Codex, OpenAI Sign in with ChatGPT. models.json (~/.oino/models.json) adds or overrides providers: { "providers": { "my-gateway": { "baseUrl": "https://llm.example.com/v1", "api": "openai-completions", "apiKey": "MY_GATEWAY_KEY", "authHeader": true, "models": [ { "id": "qwen3-coder", "name": "Qwen3 Coder", "contextWindow": 262144, "maxTokens": 32768, "reasoning": true, "input": ["text"], "cost": { "input": 0.3, "output": 1.2 } } ] }, "openrouter": { "baseUrl": "https://my-proxy.example.com/api/v1" } } } api must be one of openai-completions, openai-responses, anthropic-messages, openai-codex-responses, google-generative-ai, azure-openai-responses, bedrock-converse-stream (i.e. implemented by a loaded component). apiKey is a literal, an env var name, or "!command". authHeader true sends Authorization: Bearer. An entry with only baseUrl/headers/models patches an existing provider. Model selection: --model accepts provider/id, bare id, fuzzy pattern, optional ":thinking" suffix; --provider narrows; --models a,b,c scopes (globs ok, ctrl+p cycles); defaults from settings defaultProvider/defaultModel then first available. Thinking levels: off, minimal, low, medium, high, xhigh, max (clamped to model support). Dynamic model lists (Ollama, LM Studio, Mistral, DeepInfra, Chutes, Perplexity) at startup; --offline / OINO_OFFLINE=1 skips. Virtual models: extensions register models whose route is chosen per request (route-model WIT export); footer shows the routed model. ## 10. RPC protocol essentials `oino --mode rpc`: JSONL on stdin/stdout, one JSON object per line. Commands carry optional id; every command gets {"id","type":"response","command","success","data"|"error"}; session events stream interleaved (same shapes as --mode json). One-shot example: printf '%s\n' '{"id":"1","type":"prompt","message":"hello"}' | oino --mode rpc Commands: prompt, steer, follow_up, abort, new_session, get_state, set_model, cycle_model, get_available_models, set_thinking_level, cycle_thinking_level, get_available_thinking_levels, set_steering_mode, set_follow_up_mode, compact, set_auto_compaction, set_auto_retry, abort_retry, bash, abort_bash, get_session_stats, export_html, switch_session, fork, clone, get_fork_messages, get_entries, get_tree, get_last_assistant_text, set_session_name, get_messages, get_commands, clear_queue. Oino extensions (prefix _oino/): _oino/hello (capability handshake; advertise frontend capabilities such as images, panels), _oino/ui_snapshot, _oino/navigate_tree, _oino/set_label, _oino/get_context_usage, _oino/import_jsonl, _oino/export_jsonl. Extension UI sub-protocol: extension_ui_request events carry dialogs (select, confirm, input, editor, form, multiselect, approval, custom) expecting extension_ui_response with the same id; fire-and-forget requests (notify, setStatus, setWidget, setFrame, setTitle, panels, setPlan) need no answer. Unknown methods may be ignored. Not implemented yet: `oino serve` (TCP + TLS + pairing) and `--remote`. Rust client: oino_rpc::RpcClient (connect over Transport::StdioChild(ChildCommand)) or RpcBackend::connect (adds the _oino/hello handshake and the SessionBackend trait). ## 11. ACP essentials `oino --mode acp`: ACP v1 agent on stdio (protocol via the agent-client-protocol SDK; logs to stderr). Every ACP session loads the same extensions as the TUI. Zed settings.json: { "agent_servers": { "Oino": { "type": "custom", "command": "oino", "args": ["--mode", "acp"], "env": {} } } } Supported: initialize; authenticate/logout (no-ops; credentials from auth.json/env); session/new, session/load (replay), session/resume, session/fork, session/list (pages of 50), session/close, session/delete (moves file to ~/.oino/trash); session/prompt (text, images, embedded resources) streaming agent_message_chunk, agent_thought_chunk, tool calls with kind/title/locations/diffs (edit/write) and terminal output (bash), plans, usage, compaction notices; stop reasons end_turn, cancelled, max_tokens; session/cancel; session/set_config_option (model, thinking, more); session/set_mode (thinking level); slash commands published as available commands; approval dialogs -> session/request_permission; other dialogs -> elicitation/create; client fs/terminal capabilities route file tools and bash through the client; _oino/rpc and _oino/ run any RPC command for a sessionId. Gaps: MCP servers passed in session/new are not forwarded to the MCP extension; refusal stop reason never returned; extension tools report kind "other" (unless kind set). ## 12. TUI and CLI essentials TUI input: enter sends (steers while running), alt+enter queues follow-up, escape interrupts, escape escape opens tree/forks (setting doubleEscapeAction), !cmd runs shell command adding output to context (!! without adding), ctrl+l model picker, ctrl+p cycle scoped models, shift+tab thinking level, ctrl+d quit (empty editor). Built-in commands: /new /resume /name /session /tree /fork /clone /compact /import /copy /export /reload /model /thinking /scoped-models /settings /hotkeys /changelog /login /logout /quit; extension commands, prompt templates and /skill:* join the menu. CLI (usage: oino [options] [--] [@files...] [messages...]): - Modes: --mode text|json|rpc|acp; -p/--print; --tui-mode fullscreen|regular - Models: --model , --provider, --models a,b,c, --thinking off|minimal|low|medium|high|xhigh|max, --api-key, --list-models [search], --offline - Sessions: -c/--continue, -r/--resume, --session , --session-id , --fork , --no-session, -n/--name, --session-dir, --export [out.html] - Prompt: --system-prompt, --append-system-prompt (repeatable), -nc (no AGENTS.md/CLAUDE.md) - Tools: -t/--tools, -xt/--exclude-tools, -nt (none), -nbt (no built-ins) - Extensions: -e (repeatable, builtin: test components), -ne, --exclude-extension ; resources: --skill, -ns, --prompt-template, -np, --theme, --no-themes, --use-theme - Projects: -a/--approve, -na/--no-approve, --agent-dir, --remote (not implemented), --verbose, -v, -h - Subcommands: oino install [-l] [-y] | remove [-l] | uninstall | update [--check] | update |--extensions | list | config | auth | serve (not implemented) Unknown long flags are passed to extension-registered flags. Multi-letter short flags combine (-nbt). ## 13. Settings and environment variables Settings files: ~/.oino/settings.json (global), /.oino/settings.json (project, wins, trusted projects only). Atomic writes, migration on load. Extension secrets in ~/.oino/secrets.json (0600), never in settings.json. Core keys: defaultProvider, defaultModel, defaultThinkingLevel, modelThinkingLevels, enabledModels, thinkingBudgets, steeringMode, followUpMode (all | one-at-a-time), compaction.{enabled,reserveTokens,keepRecentTokens}, branchSummary.{reserveTokens,skipPrompt}, retry.{enabled,maxRetries,baseDelayMs,maxAgentDelayMs}, transport, httpIdleTimeoutMs, cacheWarming, showCacheMissNotices, images.{autoResize,blockImages}, shellPath, shellCommandPrefix, enableSkillCommands, sessionDir, defaultProjectTrust, packages, quietStartup, hideThinking, theme, doubleEscapeAction, treeFilter, tuiMode. Extension keys: extensionSettings. (deep-merged global->project), disabledExtensions, ui.density, serve.* (reserved). Value hygiene: a stored value failing its field validation (type/enum/range/pattern) is ignored for the layer below and reported as a diagnostic; writing a value equal to the layer below deletes the key. Environment variables: OINO_AGENT_DIR (config dir, default ~/.oino), OINO_SESSION_DIR, OINO_OFFLINE, OINO_CACHE_RETENTION (none|short|long), OINO_DIST_EXTENSIONS (share dir override; empty disables), OINO_DOCS_ROOT. Exported to bash-tool commands and extension processes: OINO_SESSION_ID, OINO_SESSION_FILE, OINO_PROVIDER, OINO_MODEL, OINO_REASONING_LEVEL (PI_* aliases exported for compatibility). Project trust: project-local files only after trust; -a/-na for one run; decisions in ~/.oino/trust.json. Resources: context files AGENTS.md (candidates per directory: AGENTS.override.md, AGENTS.md, AGENTS.MD, CLAUDE.md, CLAUDE.MD), SYSTEM.md (system prompt override), APPEND_SYSTEM.md (addendum). Skills: folder with SKILL.md (front matter name, description), listed in the system prompt; /skill: with enableSkillCommands; opt-in skills via default-enabled: false + registry switches. Prompt templates: ~/.oino/prompts/*.md -> slash commands; $1 $2 $@ ${@:2} argument splicing. Discovery order: bundled extension resources -> ~/.oino -> ~/.agents (skills) -> project -> explicit flags. ## 14. Common pitfalls - cargo install oino-cli ships no extension packages (providers are packages): use the installer, or install provider packages, or pass models explicitly. - Guest crates need serde (derive) and schemars 1.2 as direct dependencies; schemars 0.8 breaks the JsonSchema derive. - Event handler returning bare Ok(None) does not infer: write Ok(None::) or return Ok(Some(json!(...))). - Manifest apiVersion must be "oino.extension/2". A 1.x manifest is rejected at load. - Every capability except ui is denied by default; the call fails at runtime with "permission denied: ", not at build time. - Plain std::sync::Mutex must not be held across .await in handlers; use oino_extension::sync::Mutex. - Tool doc comments are the model-facing description; a tool without a description is hard for the model to use. - -e paths load in addition to discovered packages; a same-id package replaces earlier ones in place (explicit -e wins). - StaticLoader(Vec::new()) means no providers: prompts fail with no available model until you load provider packages or supply a registry. - RPC over stdio: one JSON object per line; responses interleave with events; answer extension_ui_request with the same id or the dialog hangs. - ACP MCP servers in session/new are not forwarded yet; extension tools may report kind "other". - Settings values that fail validation are silently ignored in favour of the layer below (a diagnostic is reported); check diagnostics when a setting seems ignored. - Session files are per-cwd trees; --continue looks in the current directory's sessions only. - First start extracts bundled resources and compiles WASM (~/.oino/cache/wasmtime/), so it takes a few seconds longer. ## Links - Documentation: https://oino.dev/docs - Repository: https://github.com/Neuron-Mr-White/Oino - Crates: https://crates.io/search?q=oino (oino-core, oino-extension, ...) - Rustdoc: https://docs.rs/oino-core (each crate opens with an API map) - Guest SDK: https://docs.rs/oino-extension