API Reference rho v#0.1.0

Copy Markdown View Source

Modules

Rho

An agent core for Elixir apps: pi-agent-core's loop (Rho.Loop), omp/pi JSONL sessions (Rho.Session), compaction, tools, and model sign-in (subscription OAuth or API keys, Rho.Auth) on req_llm.

The output budget: a tool result over 16 KiB goes to the model as its head and tail with a marker in the middle; the full text is kept as an artifact the agent reads back with the read_artifact tool, a page of lines at a time.

The model-provider credentials, one per provider, each pi's auth.json entry as JSON in the credential store (Rho.CredentialStore) under "auth:<provider>"

Signing in to a subscription (:anthropic, :openai_codex): the OAuth flow (Rho.Auth.OAuthFlow) with the provider's own localhost redirect, answered by this BEAM.

The OAuth authorization-code flows omp signs in with (/login anthropic, /login openai-codex), built as omp's declarative engine builds them from pi-catalog's rules/auth/anthropic.kdl and openai-codex.kdl

The per-day cost cap: what the model calls cost today, priced by req_llm from each reply's usage (notional on a Claude or ChatGPT subscription), against the host's Rho.Settings.daily_cap/1.

Context compaction, ported from pi's local summary method (pi-agent-core/src/compaction/compaction.ts; omp writes it as method: "soft"). When a request nears the model's context window, the older part of the branch is summarized by the model into a compaction entry (Rho.Session.compaction/4), and Rho.Session.context/1 sends that summary plus the recent tail from then on.

The host's settings for rho, config :rho, ..., each with a default that works on a computer. :data_dir, :default_model and :system_prompt may be given as {module, function, args} (or a 0-arity function), called each time the value is read, so a host can keep them in its own settings and change them at run time.

Where rho keeps secrets: the provider sign-ins and API keys (Rho.Auth), a binary value per string key. A host seam: config :rho, :credential_store, Module picks the backend.

The default backend: one 0600 file per key under <data dir>/secure/, the key made filename-safe. Written through a temp file created 0600 before the secret goes in, then renamed over the old one.

The Dyn layer: code the agent writes, compiles and runs on the device itself (tools and screens), next to the fixed Core (the loop, sessions, sign-in, the core tools, the approval gate and the repair path, which ship in the app and which the agent can't change). The Core changes Dyn only through this module, one generation at a time

The approval gate on changing what runs (the pipeline's step 3, see Rho.Dyn): activating a proposed generation, or reverting to a chosen one by hand.

The production approval: the system screen-lock prompt (fingerprint, face, or the phone's PIN, pattern, password or passcode) behind an approve control the host shows with the change (Operator's approve chip). This small process (Rho.Supervisor starts it with Dyn) holds what the human confirmed; only the host's UI, after the prompt passed, calls confirm/1.

The "approve all" setting: while on, the host's UI activates each candidate generation as it arrives instead of asking for approval (the same activation, so probation and revert-on-crash still apply). Only Dyn activations: reverting still asks.

The static check every staged file passes before it is compiled (the pipeline's step 2, see Rho.Dyn), and beam/2, a second pass over the compiled binaries.

Compiles a source set into generation n (part of the pipeline's step 2, see Rho.Dyn) and loads, unloads and purges generations' modules.

The host's settings for the Dyn layer, config :rho, :dyn, [...] (see Rho.Dyn). Every key has a default; a host that doesn't use Dyn sets none of them.

Unified diff (as diff -u and git print it, three lines of context) between two source sets %{relative_path => source}: what the approval card and the rescue screen show for a generation.

One generation of the Dyn layer: what gens/<n>/manifest.json holds (see Rho.Dyn.Store).

The Core process that decides which Dyn generation runs (activation, probation, revert and safe mode, steps 3–4 in Rho.Dyn). It owns the registry table (Rho.Dyn.Registry) and is the only writer of the current pointer.

A primary :logger filter that keeps Dyn terms away from the logger's formatting, in whatever process logs them.

Reads the Dyn registry: one ETS table owned (and only written) by Rho.Dyn.Keeper, named like the Keeper. It maps logical names to the current generation's modules

Incremental compiles: a proposal recompiles only the files that changed (or depend on a change at compile time) and reuses the rest from its parent generation, renamed. On a Moto G 2021 compiling the default front from source takes about 33 s, and recompiling a module from its stored Erlang code still about 350 ms (the Erlang compiler's passes): 24 s of a one-file change to the 68-module front. So a module is reused by renaming its compiled binary instead; on a Mac that takes 56 ms for the whole front, against 1.9 s through the Erlang compiler.

How Dyn screens plug into the host's UI: a host seam (config :rho, :dyn, screens: Module, Rho.Dyn.Config). rho builds, checks, selftests and watches Dyn screens the same way whatever the UI; this says what a screen is and how the UI tells the Keeper a launch got going

Mob screens as Dyn screens (config :rho, :dyn, screens: Rho.Dyn.Screens.Mob, see Rho.Dyn.Screens)

No screens (the default Rho.Dyn.Screens): Dyn code is tools and modules. Without a UI there is no first frame to wait for, so Rho.Dyn.Screens.after_first_render/1 applies the MFA at once: a launch's clock to "stable" starts at Rho.Dyn.boot/1.

The seed: the Dyn sources an install starts from, which the host ships (Operator's default front: a welcome screen, a component library, the front's settings). The host embeds them at build time (Application.app_dir/2 can't resolve priv/ on the device) and hands them to start/2.

Selftests a compiled generation (the end of the pipeline's step 2, see Rho.Dyn), one module at a time, each in a fresh process with a max_heap_size (killed when exceeded, off-heap binaries included), a timeout and trap_exit (a linked helper that crashes doesn't take the test down unreported).

The Dyn layer as a Rho.ToolRegistry.Source: the current generation's tools, read from the Keeper's registry table on every call, so an activation or a revert (by hand, or automatic) changes the tools the loop offers from its next model call. Every tool call's task is handed to the Keeper first (Rho.ToolRegistry.Source.watch/2), so a Dyn tool's crash, or the kill at its timeout, counts against its generation.

The Dyn layer on disk, under one root dir (<data dir>/dyn by default, Rho.Config.data_dir/0)

An Elixir compiler tracer for Dyn compiles (Code.put_compiler_option(:tracers, ...)): which modules of the generation a file needs at compile time, so Rho.Dyn.Compiler knows when a file compiled for an earlier generation can be reused as it is (Rho.Dyn.Reuse).

What a loop tells its subscribers, as {:rho, session_id, event} messages (plain send, no PubSub). The lifecycle is pi's (pi-agent-core/src/agent-loop.ts)

The model client the loop talks to. Rho.LLM.ReqLLM is the real one (Anthropic, OpenAI, OpenAI Codex and Google through req_llm, with the credential from Rho.Auth); tests inject a scripted fake.

The model calls through req_llm's providers, streamed, with the provider's credential from Rho.Auth (fetched, and for a subscription refreshed when due, per call)

Gives llm_db (the model catalog req_llm resolves models through) a snapshot that works on a phone.

The agent loop for one session: a GenServer with pi's lifecycle (pi-agent-core/src/agent-loop.ts; events in Rho.Events).

The models rho can call right now (like omp's /models): the static catalog filtered to the providers that have a credential (Rho.Auth).

Sessions in omp/pi's JSONL format (pi-coding-agent/src/session/ session-entries.ts, CURRENT_SESSION_VERSION = 3), so a session can move between omp on a computer and a rho host (Operator on a phone, say).

The user settings the loop reads, a host seam (config :rho, :settings, Module)

The default Rho.Settings: settings.json in the data dir (a missing or unreadable file means the defaults; other keys in it are kept, so a host may share the file). Each call reads the file.

rho's processes, for the host to start in its own tree (rho has no OTP application callback: a mob app starts its code by hand)

A tool the agent can call. Implementations are plain modules, so a host can register one at runtime by module, or serve changing sets of them from a source (see Rho.ToolRegistry).

The tools the agent can call, by name, from three places

A source of tools the registry asks on every call, for tool sets that change while the app runs (the Dyn layer's generations, Rho.Dyn.Source: tools the agent writes, compiles and activates on the device). Configure with {Rho.Supervisor, tool_sources: [MySource]} or config :rho, :tool_sources, [MySource].

Runs one tool call in its own task (under a Task.Supervisor, not linked to the loop). The before_tool_call gate runs first, inside the task, so a gate that waits (a biometric approval) never blocks the loop; once the gate allows, the task tells the loop ({:tool_running, pid}), which starts the tool's timeout clock from then.

Core tool: copy a staged Dyn source file to a new path under a new module name (see Rho.Tools.DynTool).

Core tool: delete a staged Dyn source file (see Rho.Tools.DynTool).

Core tool: replace one exact, unique piece of a staged Dyn source file (see Rho.Tools.DynTool).

Core tool: list the Dyn layer's staged source files (see Rho.Tools.DynTool).

Core tool: turn the Dyn staging copy into a candidate generation (Rho.Dyn.propose/2: static check, compile, selftests). It never activates anything itself: the human approves on the phone, or has turned approve all on (Rho.Dyn.AutoApprove), when the host's UI activates it as it arrives. Description and result say which.

Core tool: read a staged Dyn source file with line numbers (see Rho.Tools.DynTool).

Core tool: put the Dyn staging copy back to the current generation's sources (see Rho.Tools.DynTool).

Core tool: what the Dyn layer runs, what's pending, and its recent crash reports (see Rho.Tools.DynTool).

What the dyn_* Core tools share: the agent edits its Dyn layer's staging copy and proposes it (Rho.Dyn). There is deliberately no tool to activate or revert: those need the human's approval (the screen lock).

Core tool: create or overwrite a staged Dyn source file (see Rho.Tools.DynTool).

Core tool: read a page of a tool output that was too big to give whole (Rho.Artifacts). A page stays under the output budget, so reading never spills again.

Core tool: the session's todo list (omp's todo), kept in todos/<session_id>.json in the app's data dir. Writing the steps of a multi-step job down and ticking them off keeps the agent from stopping halfway, or forgetting a step after a compaction; and the list survives a restart of the app. One item at a time is in progress: the first open one, moved on whenever an item is done or removed.

Text and data from code rho doesn't trust, the Dyn layer's (Rho.Dyn), for the processes it does: the loop, the Keeper, the caller of a compile or a selftest.

How much of each provider subscription is left, and what the model calls used, for the host's usage page and status line.

Asks a provider's OAuth usage endpoint what its subscription has left, with the sign-in's access token (Rho.Auth.credential/1), as omp does (subscriptions only: an API key has no such endpoint)