diff --git a/Cargo.toml b/Cargo.toml index c4a095faf..57a785148 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -64,7 +64,7 @@ tinyjuice = { version = "0.1", default-features = false } # Durable graph checkpoints still use `SqlRunLedgerCheckpointer` until the # migration re-points those rows to the crate checkpointer. # The `repl` feature adds the embedded Rhai `.ragsh` session runtime powering -# the `rlm` language-workflow tool (`src/openhuman/rlm/`). +# the `rhai_workflows` language-workflow tool (`src/openhuman/rhai_workflows/`). tinyagents = { version = "1.7", features = ["sqlite", "repl"] } # TinyCortex — Rust core for the memory engine (store/chunks/tree/retrieval/ # queue/ingest/score + long tail), vendored as a git submodule and patched diff --git a/docs/plans/tinyflows-integration/README.md b/docs/plans/tinyflows-integration/README.md index bdb8e6ef7..6112a9e05 100644 --- a/docs/plans/tinyflows-integration/README.md +++ b/docs/plans/tinyflows-integration/README.md @@ -69,7 +69,7 @@ The repo has **three** "workflow" systems. This plan touches only the first: 1. `flows::` / `openhuman.flows_*` — **tinyflows typed graphs** (this plan). 2. `workflows::` / `openhuman.workflows_*` — WORKFLOW.md/SKILL.md bundle discovery/install (separate product surface under `/skills`). **Slated for decommission** — it is essentially the skills feature wearing the "workflows" name; see Phase 8. Retiring it frees the "Workflows" branding for tinyflows (the `/flows` nav tab already reads "Workflows"). -3. `rlm::` — Rhai `.ragsh` language workflows (`docs/plans/rlm-workflows/`), positioned in `gitbooks/features/orchestration.md` as the _next_ layer on the same substrate. tinyflows remains the shipping visual/typed product; rlm does not replace it. +3. `rhai::` — Rhai `.ragsh` language workflows (`docs/plans/rlm-workflows/`), positioned in `gitbooks/features/orchestration.md` as the _next_ layer on the same substrate. tinyflows remains the shipping visual/typed product; Rhai workflows do not replace it. --- @@ -113,16 +113,16 @@ Every workflow is, at run time, a **unique tinyagents state graph**: `compile()` Audit (2026-07-04): - ✅ **Single unified copy** — both Cargo worlds (`Cargo.toml:355`, `app/src-tauri/Cargo.toml:214`) patch `tinyagents` to `vendor/tinyagents`; exactly one `tinyagents 1.5.0` in both lockfiles. tinyflows' `tinyagents = "1.2"` requirement unifies onto the same copy (semver-compatible). No duplication, no type-identity risk. -- ⚠️ **Not on a tag** — the submodule is pinned at `df391c4` = `v1.5.0-13-gdf391c4`: 13 untagged commits past v1.5.0 (REPL host-embedding / cancel work taken early for the rlm feature). The crate still self-reports `1.5.0`, so the version string understates the vendored code. +- ⚠️ **Not on a tag** — the submodule is pinned at `df391c4` = `v1.5.0-13-gdf391c4`: 13 untagged commits past v1.5.0 (REPL host-embedding / cancel work taken early for the Rhai workflow feature). The crate still self-reports `1.5.0`, so the version string understates the vendored code. - ⚠️ **Two minor versions behind** — upstream is at **v1.7.1** (60 commits past v1.5.0). The 13 early-adopted commits all landed upstream (via PR #19 etc.; `ReplSession::set_cancel_flag` verified present in v1.7.1), so retagging loses nothing. - ⚠️ Same pattern on tinyflows itself: submodule at `v0.3.0-1-g438f8fc` (one commit past tag). Work items: 1. Bump `vendor/tinyagents` submodule to the **v1.7.1 tag**; bump root requirement `tinyagents = { version = "1.7", features = ["sqlite", "repl"] }` (verify both features still exist in 1.7); `cargo update -p tinyagents` in both lockfiles. -2. Review the v1.5→v1.7 changelog for API breaks in the seams we touch: `Checkpointer`/`DurabilityMode`, `GraphEventJournal`/`GraphObservation`, interrupt/resume semantics (used by `flows::ops` + `src/openhuman/tinyagents/` + rlm). +2. Review the v1.5→v1.7 changelog for API breaks in the seams we touch: `Checkpointer`/`DurabilityMode`, `GraphEventJournal`/`GraphObservation`, interrupt/resume semantics (used by `flows::ops` + `src/openhuman/tinyagents/` + Rhai workflows). 3. Retag `vendor/tinyflows` on a proper release (v0.3.1) whose `Cargo.toml` requires `tinyagents = "1.7"` so the version story is coherent end-to-end. -4. Gate with `cargo check` both worlds, `pnpm test:rust`, and the flows/rlm unit suites; adopt a standing rule: **vendored tiny\* submodules pin release tags, never floating commits** (early-adopting an upstream PR requires a pre-release tag). +4. Gate with `cargo check` both worlds, `pnpm test:rust`, and the flows/Rhai unit suites; adopt a standing rule: **vendored tiny\* submodules pin release tags, never floating commits** (early-adopting an upstream PR requires a pre-release tag). ### Phase 1 — Backend completion (triggers, lifecycle, observability) @@ -255,7 +255,7 @@ Tracked separately since it's a submodule with its own release cadence (host pin The `workflows::` domain (WORKFLOW.md/SKILL.md bundle discovery/install, RPC `openhuman.workflows_*`) predates tinyflows and is functionally the **skills** feature under a different name. Keeping two things called "workflows" confuses users, agents, and contributors alike. Plan: fold what's unique into `skills`, delete the rest, and hand the name to tinyflows. -- **Audit consumers first**: `agent/tools/run_workflow.rs` (agent tool that runs WORKFLOW.md bundles — decide: retire, or repoint to `flows_run`/skills), the `/skills` UI surfaces (`WorkflowsTab`, `CreateWorkflowForm`, `WorkflowRunnerBody`, `WorkflowNew.tsx`, `WorkflowsRun.tsx`, `DevWorkflowPanel`, `workflowsApi.ts`), `about_app`, gitbooks, and the rlm plan's references to `run_workflow` as a composition surface. +- **Audit consumers first**: `agent/tools/run_workflow.rs` (agent tool that runs WORKFLOW.md bundles — decide: retire, or repoint to `flows_run`/skills), the `/skills` UI surfaces (`WorkflowsTab`, `CreateWorkflowForm`, `WorkflowRunnerBody`, `WorkflowNew.tsx`, `WorkflowsRun.tsx`, `DevWorkflowPanel`, `workflowsApi.ts`), `about_app`, gitbooks, and the Rhai workflow plan's references to `run_workflow` as a composition surface. - **Migrate**: bundle discovery/install semantics that skills doesn't already cover move into the `skills` domain (it is metadata-only post-QuickJS-removal, so this is mostly file-format and registry work). - **Deprecate then delete**: mark `openhuman.workflows_*` deprecated for one release (RPC responses carry a deprecation notice), then remove `src/openhuman/workflows/`, its controllers from `src/core/all.rs`, the frontend clients/pages, and the `/workflows/new`//`workflows/run` routes (bare `/workflows` already redirects to `/settings/automations`). - **Not in scope**: `openhuman.workflow_run_*` (`agent_orchestration`'s declarative run ledger) is a different system and untouched here — though its name should also be revisited once "Workflows" ≡ tinyflows. diff --git a/gitbooks/developing/architecture/agent-harness.md b/gitbooks/developing/architecture/agent-harness.md index f32e2cc3e..c293508e9 100644 --- a/gitbooks/developing/architecture/agent-harness.md +++ b/gitbooks/developing/architecture/agent-harness.md @@ -54,7 +54,7 @@ OpenHuman pins `tinyagents = { version = "1.5.0", features = ["sqlite", "repl"] - **OpenHuman-owned providers only.** We do **not** enable any bundled provider feature. OpenHuman owns provider transport, credentials, OAuth, and billing classification, so the live model is always OpenHuman's `Provider` wrapped as [`ProviderModel`](../../../src/openhuman/tinyagents/model.rs), never an SDK-owned provider client. The `ChatModel` adapter is the seam that replaces feature-gated SDK providers. - **`sqlite` feature enabled with one native sqlite chain.** OpenHuman's root and Tauri Cargo worlds pin `rusqlite = "=0.40.0"` and patch `rusqlite` / `libsqlite3-sys` locally to avoid the upstream `cfg_select!` build break on the current toolchain. Both worlds resolve to a single `libsqlite3-sys v0.38.0` chain. Durable graph checkpoints still run through [`SqlRunLedgerCheckpointer`](../../../src/openhuman/tinyagents/checkpoint.rs) until the migration re-points those rows to the crate checkpointer. - **WhatsApp Web storage bridge.** `whatsapp-rust`'s Diesel-backed `sqlite-storage` feature links sqlite separately from rusqlite 0.40, so the optional `whatsapp-web` feature currently builds against `wacore::store::InMemoryBackend` and logs that sessions are not durable. A rusqlite-backed durable WhatsApp store is required before treating Web sessions as persistent again. -- **`repl` feature enabled for language workflows; `.rag` expressive language unused.** OpenHuman still drives *graphs* from Rust (`GraphBuilder`), not the declarative `.rag` language. But the `repl` feature (the imperative Rhai `.ragsh` session runtime) is enabled to power the `rlm` language-workflow tool ([`openhuman::rlm`](../../../src/openhuman/rlm/README.md), see "Language workflows (`rlm`)" below). +- **`repl` feature enabled for language workflows; `.rag` expressive language unused.** OpenHuman still drives *graphs* from Rust (`GraphBuilder`), not the declarative `.rag` language. But the `repl` feature (the imperative Rhai `.ragsh` session runtime) is enabled to power the `rhai_workflows` language-workflow tool ([`openhuman::rhai_workflows`](../../../src/openhuman/rhai_workflows/README.md), see "Language workflows (Rhai)" below). - **Adapter map (feature-gated SDK piece → OpenHuman replacement):** provider clients → `ProviderModel`; crate SQLite checkpointer rows not yet adopted → `SqlRunLedgerCheckpointer`; task/status stores not yet controller-canonical → OpenHuman SQL/JSON run ledgers (`running_subagents`, `workflow_runs`, `agent_teams`, `command_center`). The generic harness/graph/middleware/event primitives are used as-is. The agent harness is the runtime that turns a user message (or a webhook fire, or a cron tick) into a complete, tool-using LLM interaction. It owns the tool-call loop, sub-agent dispatch, the trigger-triage pipeline, and the hook surface around them. It does **not** own provider HTTP transport, tool implementations, prompt-section assembly, or memory storage - those are separate domains the harness composes. @@ -282,20 +282,20 @@ Each `AgentDefinition` carries an `agent_tier` field (`chat` / `reasoning` / `wo For Composio toolkits with hundreds of actions (GitHub alone has 500+), loading every action into the sub-agent's tool set balloons prompt size. The harness ranks the toolkit's actions against the parent-refined task prompt with a cheap CPU-only filter (verb detection, token overlap, verb-alignment boost) and only loads the top-ranked subset into the sub-agent. No model call, pure heuristic - fast and explainable. -## Language workflows (`rlm`) +## Language workflows (Rhai) -The fixed delegation primitives (`spawn_subagent`, `spawn_parallel_agents`, `run_workflow`) can't express *ad-hoc control flow* — "spawn N readers, dedupe their findings, verify each survivor with 3 refuters, loop until dry". The **`rlm` tool** closes that gap: it exposes TinyAgents' Rhai-backed `.ragsh` REPL (the `repl` cargo feature) so the orchestrator can author and run its own workflow scripts. +The fixed delegation primitives (`spawn_subagent`, `spawn_parallel_agents`, `run_workflow`) can't express *ad-hoc control flow* — "spawn N readers, dedupe their findings, verify each survivor with 3 refuters, loop until dry". The **`rhai_workflows` tool** closes that gap: it exposes TinyAgents' Rhai-backed `.ragsh` REPL (the `repl` cargo feature) so the orchestrator can author and run its own workflow scripts. **One tool call = one `eval_cell`.** The orchestrator's normal tool-call loop *is* the CodeAct driver loop: the model writes a Rhai cell, the cell runs against a persistent per-session namespace (top-level `let` bindings survive into the next cell via an optional `session_id`), and the structured result flows back as the tool result. Scripts reach the host only through capability functions — `tool_call`, `agent_query`, `model_query`, their `*_batched` fan-out variants, `emit`, and `answer`. -The domain lives in [`src/openhuman/rlm/`](../../../src/openhuman/rlm/README.md): +The domain lives in [`src/openhuman/rhai_workflows/`](../../../src/openhuman/rhai_workflows/README.md): - **`policy.rs`** maps the autonomy tier + `tool_timeout` clamps onto a `tinyagents::ReplPolicy` (always bounded, never unbounded; `readonly` refused; `full` may raise call-count limits to a hard 2× ceiling). -- **`bridge.rs`** builds the `CapabilityRegistry`: the parent's visible tools (each re-wrapped so the **approval gate runs in the bridge** — it is *not* on the repl path, which bypasses the harness `wrap_tool` middleware), the turn's provider model, and a sub-agent capability per `allowed_subagent_ids`. Recursion/duplication hazards (`rlm`, `spawn_*`, workflow tools, `CliRpcOnly`-scoped tools) are excluded. Because `eval_cell` runs on `spawn_blocking` + `block_on`, the `agent_query` adapter re-installs the `PARENT_CONTEXT` task-local that `run_subagent` resolves. +- **`bridge.rs`** builds the `CapabilityRegistry`: the parent's visible tools (each re-wrapped so the **approval gate runs in the bridge** — it is *not* on the repl path, which bypasses the harness `wrap_tool` middleware), the turn's provider model, and a sub-agent capability per `allowed_subagent_ids`. Recursion/duplication hazards (`rhai`, legacy `rlm`, `spawn_*`, workflow tools, `CliRpcOnly`-scoped tools) are excluded. Because `eval_cell` runs on `spawn_blocking` + `block_on`, the `agent_query` adapter re-installs the `PARENT_CONTEXT` task-local that `run_subagent` resolves. - **`sessions.rs`** is a bounded (LRU + idle-TTL) manager of persistent sessions, one cell at a time (a concurrent call on a busy session returns a typed "busy" error). - **`ops.rs`** runs the cell on `spawn_blocking` under a layered time bound (rhai `on_progress` deadline → `bridge_block_on` timer race → outer `tokio::timeout` backstop → harness `ToolTimeout`), wires the run-cancellation token to a fresh per-cell `ReplCancelFlag`, and maps every failure mode to a model-consumable result. -The tool is registered for the orchestrator on `supervised`/`full` tiers only, behind the `OPENHUMAN_RLM=0` kill switch. The TinyAgents-side host-embedding support (external cancellation, live capability events) landed in that crate's `repl` feature. +The tool is registered for the orchestrator on `supervised`/`full` tiers only, behind the `OPENHUMAN_RHAI_WORKFLOWS=0` kill switch; `OPENHUMAN_RHAI=0` and `OPENHUMAN_RLM=0` remain legacy aliases. The TinyAgents-side host-embedding support (external cancellation, live capability events) landed in that crate's `repl` feature. ## Triage - handling external triggers diff --git a/gitbooks/features/orchestration.md b/gitbooks/features/orchestration.md index a903ddc13..483ce6722 100644 --- a/gitbooks/features/orchestration.md +++ b/gitbooks/features/orchestration.md @@ -38,7 +38,7 @@ Because the transport is plain Signal-encrypted messaging, the other side doesn' ## What's next: RLMs -The direction we're building toward: **RLM-style language-based workflows**. These are agents that express orchestration as small programs in a sandboxed REPL, rather than a fixed graph, so control flow itself becomes something the model writes, inspects, and repairs. The graph engine, checkpointing, and trust model above are the substrate for it. +The direction we're building toward: **Rhai-backed language workflows**. These are agents that express orchestration as small programs in a sandboxed REPL, rather than a fixed graph, so control flow itself becomes something the model writes, inspects, and repairs. The graph engine, checkpointing, and trust model above are the substrate for it. *** diff --git a/src/openhuman/about_app/catalog_data.rs b/src/openhuman/about_app/catalog_data.rs index fff46e414..31ea849ab 100644 --- a/src/openhuman/about_app/catalog_data.rs +++ b/src/openhuman/about_app/catalog_data.rs @@ -622,11 +622,11 @@ pub(super) const CAPABILITIES: &[Capability] = &[ }, Capability { id: "intelligence.language_workflows", - name: "Language Workflows (RLM)", + name: "Language Workflows (Rhai)", domain: "intelligence", category: CapabilityCategory::Intelligence, description: "The orchestrator can author and run small Rhai workflow scripts to express ad-hoc control flow over delegated work — parallel fan-out, loops, and dedup-then-verify pipelines that fixed spawn/parallel primitives cannot. Each script runs bounded and fail-closed (per-cell timeout, per-session caps on tool/model/agent calls and recursion depth), and every effectful step still passes the same approval and permission gates as a direct tool call. Progress rides the existing tool-call timeline.", - how_to: "Runs automatically when the orchestrator chooses the `rlm` tool; disable with OPENHUMAN_RLM=0 or the read-only autonomy tier", + how_to: "Runs automatically when the orchestrator chooses the `rhai_workflows` tool; disable with OPENHUMAN_RHAI_WORKFLOWS=0 or the read-only autonomy tier", status: CapabilityStatus::Beta, privacy: DERIVED_TO_BACKEND, }, diff --git a/src/openhuman/agent_registry/agents/orchestrator/prompt.md b/src/openhuman/agent_registry/agents/orchestrator/prompt.md index 9d1518b63..7cbc69452 100644 --- a/src/openhuman/agent_registry/agents/orchestrator/prompt.md +++ b/src/openhuman/agent_registry/agents/orchestrator/prompt.md @@ -103,11 +103,11 @@ their refs separate by `subagent_session_id` or `task_id` (`agentId` is only the worker type), tick or wait on each independently, and synthesise only completed outputs. Never fabricate a result for a worker that is still running or failed. -## Language workflows (rlm) +## Language workflows (Rhai) When a task needs **ad-hoc control flow** over delegated work — loops, conditionals, a dedup-then-verify pipeline, "spawn N, filter, then verify each survivor with M checks" — that -the fixed `spawn_parallel_agents` / `delegate_*` primitives can't express, use the `rlm` tool. +the fixed `spawn_parallel_agents` / `delegate_*` primitives can't express, use the `rhai_workflows` tool. It evaluates a small **Rhai workflow cell** whose only side effects are capability calls: `tool_call`, `agent_query`, `model_query`, and their `*_batched` fan-out variants (plus `emit`/`answer`/`print`). @@ -115,11 +115,11 @@ It evaluates a small **Rhai workflow cell** whose only side effects are capabili - **One call = one cell.** Top-level `let` bindings persist within a `session_id`, so pass the same `session_id` back to continue a namespace across calls (`let findings = …` in cell 1, reference it in cell 2). Omit `session_id` for a fresh session; set `close_session: true` when done. -- **Prefer `rlm`** over `spawn_parallel_agents` when you need iteration, branching, or a +- **Prefer `rhai_workflows`** over `spawn_parallel_agents` when you need iteration, branching, or a reduce/verify step over results — not for a single delegation (use the matching `delegate_*` or `spawn_subagent` for that). - **It stays inside the gates.** Every effectful inner `tool_call` still hits the approval gate; - `agent_query` only reaches sub-agents already in your allowlist. `rlm` itself, `spawn_*`, and + `agent_query` only reaches sub-agents already in your allowlist. `rhai` itself, `spawn_*`, and workflow tools are not callable from a cell. - **It is bounded and fail-closed.** Cells have a wall-clock timeout and per-session caps on model/tool/agent calls and recursion depth. Exceeding one returns an error you can fix and diff --git a/src/openhuman/mod.rs b/src/openhuman/mod.rs index e786d7754..7d8774c47 100644 --- a/src/openhuman/mod.rs +++ b/src/openhuman/mod.rs @@ -98,7 +98,7 @@ pub mod provider_surfaces; pub mod recall_calendar; pub mod redirect_links; pub mod referral; -pub mod rlm; +pub mod rhai_workflows; pub mod routing; pub mod runtime_node; pub mod runtime_python; diff --git a/src/openhuman/rlm/README.md b/src/openhuman/rhai_workflows/README.md similarity index 76% rename from src/openhuman/rlm/README.md rename to src/openhuman/rhai_workflows/README.md index 6e687df68..8b8217f1b 100644 --- a/src/openhuman/rlm/README.md +++ b/src/openhuman/rhai_workflows/README.md @@ -1,7 +1,7 @@ -# `rlm` — language-based workflows (Rhai `.ragsh` REPL) +# `rhai` — language-based workflows (Rhai `.ragsh` REPL) Exposes TinyAgents' Rhai-backed `.ragsh` session runtime (the `repl` cargo -feature, `tinyagents::ReplSession`) as a first-class **`rlm` tool** so the +feature, `tinyagents::ReplSession`) as a first-class **`rhai_workflows` tool** so the orchestrator model can author and execute its own workflow scripts — fan-out over subagents, batched tool/model calls, loops, dedup/verify pipelines — and run them deterministically, in the spirit of Claude Code Workflows and @@ -17,12 +17,12 @@ the tool result. The orchestrator's own turn loop *is* the CodeAct driver loop. | File | Role | | ---- | ---- | | `mod.rs` | Exports only (no controller schemas in v1). | -| `types.rs` | `RlmSessionId`, `RlmEvalRequest`/`RlmEvalResponse`, `RlmLimitsOverride`, `RlmCallSummary`, serde types. | +| `types.rs` | `RhaiSessionId`, `RhaiEvalRequest`/`RhaiEvalResponse`, `RhaiLimitsOverride`, `RhaiCallSummary`, serde types. | | `policy.rs` | Maps openhuman autonomy tier + `tool_timeout` clamps → `tinyagents::ReplPolicy` (fail-closed, bounded). | | `bridge.rs` | Builds the `CapabilityRegistry<()>`: openhuman tools (approval-gated, scope-filtered) + provider models + subagents. | -| `sessions.rs` | `RlmSessionManager`: LRU + idle-TTL bounded map of persistent `ReplSession`s, keyed `:`, one cell at a time. | -| `ops.rs` | `eval_rlm_cell`: spawn_blocking + outer timeout, cancellation wiring, event forwarding, error → model-consumable result. | -| `tools.rs` | `RlmTool` (the `rlm` tool: schema, permission, scope, timeout, display). | +| `sessions.rs` | `RhaiSessionManager`: LRU + idle-TTL bounded map of persistent `ReplSession`s, keyed `:`, one cell at a time. | +| `ops.rs` | `eval_rhai_cell`: spawn_blocking + outer timeout, cancellation wiring, event forwarding, error → model-consumable result. | +| `tools.rs` | `RhaiTool` (the `rhai_workflows` tool: schema, permission, scope, timeout, display). | ## Fail-closed guarantees @@ -47,8 +47,8 @@ never a hung turn: ## Approval & security (bridged tools keep their own gates) -The RLM bridge restricts callable tools to the parent turn's -`visible_tool_names`, and **excludes** recursion/duplication hazards: `rlm` +The Rhai bridge restricts callable tools to the parent turn's +`visible_tool_names`, and **excludes** recursion/duplication hazards: `rhai` itself, `spawn_subagent`/`spawn_parallel_agents` (use `agent_query` instead), and `run_workflow`/`await_workflow`. `ToolScope::CliRpcOnly` tools are denied. @@ -66,5 +66,6 @@ and `answer`. Graph authoring/execution (`graph_*`) is **out of scope for v1** ## Kill switch & rollout The tool is **not registered** when the autonomy tier is `readonly` or when -`OPENHUMAN_RLM=0`; default-on for `supervised`/`full`. Reverting the -registration line disables the surface without touching the domain. +`OPENHUMAN_RHAI_WORKFLOWS=0`; default-on for `supervised`/`full`. +`OPENHUMAN_RHAI=0` and `OPENHUMAN_RLM=0` are still honoured as legacy aliases. +Reverting the registration line disables the surface without touching the domain. diff --git a/src/openhuman/rlm/bridge.rs b/src/openhuman/rhai_workflows/bridge.rs similarity index 92% rename from src/openhuman/rlm/bridge.rs rename to src/openhuman/rhai_workflows/bridge.rs index 1891e6116..fa44950fd 100644 --- a/src/openhuman/rlm/bridge.rs +++ b/src/openhuman/rhai_workflows/bridge.rs @@ -4,7 +4,7 @@ //! //! Three capability kinds are wired, each keeping openhuman's own gates: //! -//! - **Tools** — one [`RlmToolAdapter`] per visible, non-excluded, agent-scoped +//! - **Tools** — one [`RhaiToolAdapter`] per visible, non-excluded, agent-scoped //! tool. Approval is **not** on the tinyagents repl path (it lives in the //! harness `wrap_tool` middleware the REPL bypasses), so the adapter itself //! invokes the [`ApprovalGate`] for any tool whose `external_effect_with_args` @@ -15,7 +15,7 @@ //! `allowed_subagent_ids`, so `agent_query("", ...)` spawns a real //! openhuman sub-agent through `run_subagent`. //! -//! Recursion/duplication hazards are **excluded** from the tool surface: `rlm` +//! Recursion/duplication hazards are **excluded** from the tool surface: `rhai` //! itself (no REPL-in-REPL), `spawn_*` (use `agent_query`), and //! `run_workflow`/`await_workflow`. `ToolScope::CliRpcOnly` tools are excluded //! too. @@ -49,7 +49,9 @@ use crate::openhuman::tools::Tool as OhTool; /// re-entering the REPL) and capability duplication (spawn/workflow primitives /// the script models with `agent_query` instead). fn is_excluded_tool(name: &str) -> bool { - name == "rlm" + name == "rhai_workflows" + || name == "rhai" + || name == "rlm" || name == "run_workflow" || name == "await_workflow" || name.starts_with("spawn_") @@ -62,6 +64,8 @@ mod tests { #[test] fn recursion_and_duplication_hazards_are_excluded() { for name in [ + "rhai", + "rhai_workflows", "rlm", "run_workflow", "await_workflow", @@ -81,7 +85,7 @@ mod tests { /// turn's execution context. /// /// Reads the parent's visible tool set, provider/model, and sub-agent -/// allowlist. The returned registry carries no `rlm`, `spawn_*`, or workflow +/// allowlist. The returned registry carries no `rhai`, `spawn_*`, or workflow /// tools, and no `CliRpcOnly`-scoped tools. pub(super) fn build_capability_registry(parent: &ParentExecutionContext) -> CapabilityRegistry<()> { let mut registry = CapabilityRegistry::<()>::new(); @@ -107,7 +111,7 @@ pub(super) fn build_capability_registry(parent: &ParentExecutionContext) -> Capa if matches!(tool.scope(), ToolScope::CliRpcOnly) { continue; } - registry.replace_tool(Arc::new(RlmToolAdapter::new( + registry.replace_tool(Arc::new(RhaiToolAdapter::new( parent.all_tools.clone(), tool.as_ref(), ))); @@ -128,7 +132,7 @@ pub(super) fn build_capability_registry(parent: &ParentExecutionContext) -> Capa tools = tool_count, agents = agent_count, model = %parent.model_name, - "[rlm] built capability registry" + "[rhai_workflows] built capability registry" ); registry } @@ -137,7 +141,7 @@ pub(super) fn build_capability_registry(parent: &ParentExecutionContext) -> Capa /// in the parent's shared tool set on each call (the set is `Arc`-shared, not /// cloned). Adds the approval gate the harness `wrap_tool` middleware would /// otherwise apply — absent on the repl bridge path. -pub(super) struct RlmToolAdapter { +pub(super) struct RhaiToolAdapter { tools: Arc>>, name: String, description: String, @@ -145,7 +149,7 @@ pub(super) struct RlmToolAdapter { policy: TaToolPolicy, } -impl RlmToolAdapter { +impl RhaiToolAdapter { fn new(tools: Arc>>, tool: &dyn OhTool) -> Self { let schema = TaToolSchema { name: tool.name().to_string(), @@ -171,7 +175,7 @@ impl RlmToolAdapter { match found { Some(tool) => gated_execute(tool.as_ref(), call, context).await, None => { - tracing::warn!(tool = %self.name, "[rlm] bridged tool not found at call time"); + tracing::warn!(tool = %self.name, "[rhai_workflows] bridged tool not found at call time"); TaToolResult { call_id: call.id, name: call.name, @@ -186,7 +190,7 @@ impl RlmToolAdapter { } #[async_trait] -impl TaTool<()> for RlmToolAdapter { +impl TaTool<()> for RhaiToolAdapter { fn name(&self) -> &str { &self.name } @@ -229,12 +233,12 @@ async fn gated_execute( if let Some(gate) = ApprovalGate::try_global() { let summary = summarize_action(&call.name, &call.arguments); let redacted = redact_args(&call.arguments); - tracing::debug!(tool = %call.name, "[rlm] external-effect tool — routing through approval gate"); + tracing::debug!(tool = %call.name, "[rhai_workflows] external-effect tool — routing through approval gate"); let (outcome, request_id) = gate.intercept_audited(&call.name, &summary, redacted).await; match outcome { GateOutcome::Deny { reason } => { - tracing::info!(tool = %call.name, %reason, "[rlm] tool denied by approval gate"); + tracing::info!(tool = %call.name, %reason, "[rhai_workflows] tool denied by approval gate"); return TaToolResult { content: format!("Denied by approval gate: {reason}"), error: Some(format!("approval denied: {reason}")), @@ -309,7 +313,7 @@ impl HarnessAgent for SubagentCapability { ..Default::default() }; - tracing::debug!(agent = %self.agent_id, "[rlm] agent_query — spawning sub-agent"); + tracing::debug!(agent = %self.agent_id, "[rhai_workflows] agent_query — spawning sub-agent"); let outcome = with_parent_context( self.parent.clone(), run_subagent(definition, &input.prompt, options), diff --git a/src/openhuman/rlm/mod.rs b/src/openhuman/rhai_workflows/mod.rs similarity index 72% rename from src/openhuman/rlm/mod.rs rename to src/openhuman/rhai_workflows/mod.rs index 5207f2cde..f6574f34a 100644 --- a/src/openhuman/rlm/mod.rs +++ b/src/openhuman/rhai_workflows/mod.rs @@ -1,7 +1,7 @@ -//! `rlm` — language-based workflows over the TinyAgents Rhai `.ragsh` REPL. +//! `rhai` — language-based workflows over the TinyAgents Rhai `.ragsh` REPL. //! //! Exposes `tinyagents::ReplSession` (the `repl` feature) as a first-class -//! [`RlmTool`], so the orchestrator can author and run its own workflow scripts +//! [`RhaiTool`], so the orchestrator can author and run its own workflow scripts //! — fan-out, batched calls, loops, dedup/verify pipelines — bounded and //! fail-closed. See [`README.md`](https://github.com/tinyhumansai/openhuman) //! (module `README.md`) for the design. @@ -17,4 +17,4 @@ mod types; pub mod tools; -pub use tools::RlmTool; +pub use tools::RhaiTool; diff --git a/src/openhuman/rlm/ops.rs b/src/openhuman/rhai_workflows/ops.rs similarity index 74% rename from src/openhuman/rlm/ops.rs rename to src/openhuman/rhai_workflows/ops.rs index 105be0e8f..a0f28a23e 100644 --- a/src/openhuman/rlm/ops.rs +++ b/src/openhuman/rhai_workflows/ops.rs @@ -17,9 +17,9 @@ use crate::openhuman::security::policy::AutonomyLevel; use crate::openhuman::tinyagents::run_cancellation_context::current_run_cancellation; use super::bridge::build_capability_registry; -use super::policy::{resolve_policy, DEFAULT_RLM_TIMEOUT_SECS}; -use super::sessions::RlmSessionManager; -use super::types::{RlmCallSummary, RlmEvalRequest, RlmEvalResponse, RlmLimitsRemaining}; +use super::policy::{resolve_policy, DEFAULT_RHAI_TIMEOUT_SECS}; +use super::sessions::RhaiSessionManager; +use super::types::{RhaiCallSummary, RhaiEvalRequest, RhaiEvalResponse, RhaiLimitsRemaining}; /// Grace added to the inner policy timeout for the outer `spawn_blocking` /// backstop — the inner deadline should always fire first; this defends against @@ -28,12 +28,12 @@ const OUTER_TIMEOUT_GRACE: Duration = Duration::from_secs(5); /// Hard cap on the characters returned to the model in `stdout`, so a runaway /// print cannot flood the context window even within the byte policy. -const MAX_RLM_RESULT_CHARS: usize = 24_000; +const MAX_RHAI_RESULT_CHARS: usize = 24_000; -/// A typed RLM failure, each mapped to a model-consumable tool result by -/// [`RlmError::message`] (with a fix hint) and [`RlmError::kind`]. +/// A typed Rhai failure, each mapped to a model-consumable tool result by +/// [`RhaiError::message`] (with a fix hint) and [`RhaiError::kind`]. #[derive(Debug, Clone)] -pub(crate) enum RlmError { +pub(crate) enum RhaiError { /// The autonomy tier or policy refused the session. Denied(String), /// Script failed to compile or raised a runtime error. @@ -56,60 +56,62 @@ pub(crate) enum RlmError { Internal(String), } -impl RlmError { +impl RhaiError { /// A stable, snake_case kind tag for logging and the tool result. pub(crate) fn kind(&self) -> &'static str { match self { - RlmError::Denied(_) => "denied", - RlmError::Script(_) => "script_error", - RlmError::Timeout(_) => "timeout", - RlmError::LimitExceeded(_) => "limit_exceeded", - RlmError::UnknownCapability(_) => "unknown_capability", - RlmError::Depth(_) => "recursion_depth", - RlmError::CapabilityError(_) => "capability_error", - RlmError::Cancelled => "cancelled", - RlmError::SessionBusy(_) => "session_busy", - RlmError::Internal(_) => "internal", + RhaiError::Denied(_) => "denied", + RhaiError::Script(_) => "script_error", + RhaiError::Timeout(_) => "timeout", + RhaiError::LimitExceeded(_) => "limit_exceeded", + RhaiError::UnknownCapability(_) => "unknown_capability", + RhaiError::Depth(_) => "recursion_depth", + RhaiError::CapabilityError(_) => "capability_error", + RhaiError::Cancelled => "cancelled", + RhaiError::SessionBusy(_) => "session_busy", + RhaiError::Internal(_) => "internal", } } /// The model-facing message, including a concrete fix hint. pub(crate) fn message(&self) -> String { match self { - RlmError::Denied(m) => m.clone(), - RlmError::Script(m) => { - format!("rlm script error: {m}\nFix the script and retry — reuse the same session_id to keep your bindings.") + RhaiError::Denied(m) => m.clone(), + RhaiError::Script(m) => { + format!("rhai_workflows script error: {m}\nFix the script and retry — reuse the same session_id to keep your bindings.") } - RlmError::Timeout(m) => { - format!("rlm cell timed out: {m}\nSplit the work across cells, lower fan-out, or raise timeout_secs.") + RhaiError::Timeout(m) => { + format!("rhai_workflows cell timed out: {m}\nSplit the work across cells, lower fan-out, or raise timeout_secs.") } - RlmError::LimitExceeded(m) => { - format!("rlm limit exceeded: {m}\nSplit the work across cells, or (full tier) raise the relevant limit in `limits`.") + RhaiError::LimitExceeded(m) => { + format!("rhai_workflows limit exceeded: {m}\nSplit the work across cells, or (full tier) raise the relevant limit in `limits`.") } - RlmError::UnknownCapability(m) => format!("rlm unknown capability: {m}"), - RlmError::Depth(m) => format!("rlm recursion depth exceeded: {m}"), - RlmError::CapabilityError(m) => { - format!("rlm capability call failed: {m}\nInspect the failing call's arguments and retry.") + RhaiError::UnknownCapability(m) => { + format!("rhai_workflows unknown capability: {m}") } - RlmError::Cancelled => { - "rlm cell was cancelled by the user. The session is intact and resumable." + RhaiError::Depth(m) => format!("rhai_workflows recursion depth exceeded: {m}"), + RhaiError::CapabilityError(m) => { + format!("rhai_workflows capability call failed: {m}\nInspect the failing call's arguments and retry.") + } + RhaiError::Cancelled => { + "rhai_workflows cell was cancelled by the user. The session is intact and resumable." .to_string() } - RlmError::SessionBusy(m) => m.clone(), - RlmError::Internal(m) => format!("rlm internal error: {m}"), + RhaiError::SessionBusy(m) => m.clone(), + RhaiError::Internal(m) => format!("rhai_workflows internal error: {m}"), } } } /// The registered capability names, snapshotted for unknown-capability error /// messages so the model sees the live surface it can actually call. -struct RlmAvailable { +struct RhaiAvailable { tools: Vec, agents: Vec, models: Vec, } -impl RlmAvailable { +impl RhaiAvailable { fn from_registry(registry: &CapabilityRegistry<()>) -> Self { Self { tools: registry.names(ComponentKind::Tool), @@ -130,17 +132,18 @@ enum CellRun { Poisoned, } -/// Evaluates one RLM cell against a (possibly new) session, fail-closed. +/// Evaluates one Rhai cell against a (possibly new) session, fail-closed. /// /// Resolves the parent turn context, maps autonomy/timeout to a `ReplPolicy`, /// resolves or creates the session, wires user cancellation to a fresh per-cell /// flag, runs `eval_cell` on `spawn_blocking` under an outer `tokio::timeout` -/// backstop, and maps every outcome to [`RlmEvalResponse`] or a typed -/// [`RlmError`]. -pub(crate) async fn eval_rlm_cell(req: RlmEvalRequest) -> Result { +/// backstop, and maps every outcome to [`RhaiEvalResponse`] or a typed +/// [`RhaiError`]. +pub(crate) async fn eval_rhai_cell(req: RhaiEvalRequest) -> Result { let parent = current_parent().ok_or_else(|| { - RlmError::Internal( - "no parent execution context — the rlm tool must run inside an agent turn".to_string(), + RhaiError::Internal( + "no parent execution context — the rhai_workflows tool must run inside an agent turn" + .to_string(), ) })?; @@ -148,7 +151,7 @@ pub(crate) async fn eval_rlm_cell(req: RlmEvalRequest) -> Result Result, thread_scope: &str, - manager: &RlmSessionManager, -) -> Result { + manager: &RhaiSessionManager, +) -> Result { let session_id = req .session_id .clone() .filter(|s| !s.trim().is_empty()) - .unwrap_or_else(|| format!("rlm-{}", uuid::Uuid::new_v4())); - let key = RlmSessionManager::session_key(thread_scope, &session_id); + .unwrap_or_else(|| format!("rhai_workflows-{}", uuid::Uuid::new_v4())); + let key = RhaiSessionManager::session_key(thread_scope, &session_id); // Snapshot the registry's names for error messages, then hand it to the // session builder (used only on a fresh session; a reused session keeps its // own registry). - let available = RlmAvailable::from_registry(®istry); + let available = RhaiAvailable::from_registry(®istry); let policy_for_build = policy.clone(); tracing::info!( @@ -192,7 +195,7 @@ async fn run_cell( thread_id = %thread_scope, session_id = %session_id, script_bytes = req.script.len(), - "[rlm] eval_rlm_cell: start" + "[rhai_workflows] eval_rhai_cell: start" ); let handle = manager.get_or_create(&key, move || { @@ -209,7 +212,9 @@ async fn run_cell( let flag = cell_flag.clone(); tokio::spawn(async move { token.cancelled().await; - tracing::debug!("[rlm] run cancellation observed — tripping cell cancel flag"); + tracing::debug!( + "[rhai_workflows] run cancellation observed — tripping cell cancel flag" + ); flag.cancel(); }); } @@ -219,7 +224,7 @@ async fn run_cell( let flag_for_cell = cell_flag.clone(); let outer_bound = policy .timeout - .unwrap_or(Duration::from_secs(DEFAULT_RLM_TIMEOUT_SECS)) + .unwrap_or(Duration::from_secs(DEFAULT_RHAI_TIMEOUT_SECS)) + OUTER_TIMEOUT_GRACE; let join = tokio::task::spawn_blocking(move || { @@ -238,9 +243,9 @@ async fn run_cell( Ok(Err(join_err)) => { // The blocking task panicked — the session may be poisoned; drop it. manager.close(&key); - tracing::error!(session_key = %key, %join_err, "[rlm] cell task panicked — session dropped"); - return Err(RlmError::Internal(format!( - "rlm cell task failed: {join_err}" + tracing::error!(session_key = %key, %join_err, "[rhai_workflows] cell task panicked — session dropped"); + return Err(RhaiError::Internal(format!( + "rhai_workflows cell task failed: {join_err}" ))); } Err(_elapsed) => { @@ -250,10 +255,10 @@ async fn run_cell( tracing::error!( session_key = %key, outer_secs = outer_bound.as_secs(), - "[rlm] outer wall-clock backstop fired — session dropped (inner deadline should have fired first)" + "[rhai_workflows] outer wall-clock backstop fired — session dropped (inner deadline should have fired first)" ); - return Err(RlmError::Timeout( - "the rlm cell exceeded its outer wall-clock backstop".to_string(), + return Err(RhaiError::Timeout( + "the rhai_workflows cell exceeded its outer wall-clock backstop".to_string(), )); } }; @@ -261,15 +266,15 @@ async fn run_cell( let eval = match run { CellRun::Completed(eval) => eval, CellRun::Busy => { - tracing::info!(session_key = %key, "[rlm] session busy — concurrent cell rejected"); - return Err(RlmError::SessionBusy(format!( - "rlm session '{session_id}' is already evaluating a cell; wait for it to finish or use a different session_id" + tracing::info!(session_key = %key, "[rhai_workflows] session busy — concurrent cell rejected"); + return Err(RhaiError::SessionBusy(format!( + "rhai_workflows session '{session_id}' is already evaluating a cell; wait for it to finish or use a different session_id" ))); } CellRun::Poisoned => { manager.close(&key); - return Err(RlmError::Internal( - "the rlm session was poisoned by a prior panic and has been dropped; start a fresh session".to_string(), + return Err(RhaiError::Internal( + "the rhai_workflows session was poisoned by a prior panic and has been dropped; start a fresh session".to_string(), )); } }; @@ -278,7 +283,7 @@ async fn run_cell( Ok(result) => result, Err(err) => { let mapped = map_eval_error(err, &available); - tracing::info!(session_key = %key, kind = mapped.kind(), "[rlm] cell failed"); + tracing::info!(session_key = %key, kind = mapped.kind(), "[rhai_workflows] cell failed"); return Err(mapped); } }; @@ -288,16 +293,16 @@ async fn run_cell( manager.close(&key); } - let response = RlmEvalResponse { + let response = RhaiEvalResponse { session_id, - stdout: truncate_chars(&result.stdout, MAX_RLM_RESULT_CHARS), + stdout: truncate_chars(&result.stdout, MAX_RHAI_RESULT_CHARS), value: result.value.as_ref().map(|v| v.to_json()), variables_changed: result.variables_changed, calls: summarize_calls(&result.calls), final_answer: result.final_answer, elapsed_ms: result.elapsed.as_millis() as u64, cells_used: stats.cells, - limits_remaining: RlmLimitsRemaining { + limits_remaining: RhaiLimitsRemaining { cells: policy.max_iterations.saturating_sub(stats.cells), model_calls: policy.max_model_calls.saturating_sub(stats.model_calls), tool_calls: policy.max_tool_calls.saturating_sub(stats.tool_calls), @@ -310,45 +315,45 @@ async fn run_cell( elapsed_ms = response.elapsed_ms, calls = response.calls.len(), cells_used = response.cells_used, - "[rlm] eval_rlm_cell: done" + "[rhai_workflows] eval_rhai_cell: done" ); Ok(response) } -/// Maps a raw `TinyAgentsError` from `eval_cell` onto a typed [`RlmError`], +/// Maps a raw `TinyAgentsError` from `eval_cell` onto a typed [`RhaiError`], /// enriching unknown-capability errors with the live registered names. -fn map_eval_error(err: TinyAgentsError, available: &RlmAvailable) -> RlmError { +fn map_eval_error(err: TinyAgentsError, available: &RhaiAvailable) -> RhaiError { match err { - TinyAgentsError::Validation(m) => RlmError::Script(m), - TinyAgentsError::Timeout(m) => RlmError::Timeout(m), - TinyAgentsError::LimitExceeded(m) => RlmError::LimitExceeded(m), - TinyAgentsError::Cancelled => RlmError::Cancelled, - TinyAgentsError::SubAgentDepth(n) => RlmError::Depth(format!( + TinyAgentsError::Validation(m) => RhaiError::Script(m), + TinyAgentsError::Timeout(m) => RhaiError::Timeout(m), + TinyAgentsError::LimitExceeded(m) => RhaiError::LimitExceeded(m), + TinyAgentsError::Cancelled => RhaiError::Cancelled, + TinyAgentsError::SubAgentDepth(n) => RhaiError::Depth(format!( "sub-agent recursion exceeded the maximum depth of {n}" )), - TinyAgentsError::ModelNotFound(name) => RlmError::UnknownCapability(format!( + TinyAgentsError::ModelNotFound(name) => RhaiError::UnknownCapability(format!( "model `{name}` is not registered. Available models: {}", join_or_none(&available.models) )), - TinyAgentsError::ToolNotFound(name) => RlmError::UnknownCapability(format!( + TinyAgentsError::ToolNotFound(name) => RhaiError::UnknownCapability(format!( "tool `{name}` is not registered. Available tools: {}", join_or_none(&available.tools) )), - TinyAgentsError::Capability(m) => RlmError::UnknownCapability(format!( + TinyAgentsError::Capability(m) => RhaiError::UnknownCapability(format!( "{m}. Available agents: {}", join_or_none(&available.agents) )), - TinyAgentsError::Tool(m) => RlmError::CapabilityError(m), - other => RlmError::Script(other.to_string()), + TinyAgentsError::Tool(m) => RhaiError::CapabilityError(m), + other => RhaiError::Script(other.to_string()), } } /// Summarizes recorded calls into the model-visible shape — kind, name, timing, /// success only, never raw arguments/payloads. -fn summarize_calls(calls: &[ReplCallRecord]) -> Vec { +fn summarize_calls(calls: &[ReplCallRecord]) -> Vec { calls .iter() - .map(|c| RlmCallSummary { + .map(|c| RhaiCallSummary { kind: call_kind_str(c.kind).to_string(), name: c.name.clone(), elapsed_ms: c.elapsed.as_millis() as u64, @@ -383,7 +388,7 @@ fn truncate_chars(s: &str, max: usize) -> String { return s.to_string(); } let mut out: String = s.chars().take(max).collect(); - out.push_str("\n… (rlm output truncated)"); + out.push_str("\n… (rhai_workflows output truncated)"); out } @@ -410,16 +415,16 @@ mod tests { #[test] fn error_kinds_are_stable() { - assert_eq!(RlmError::Cancelled.kind(), "cancelled"); - assert_eq!(RlmError::Timeout("x".into()).kind(), "timeout"); - assert!(RlmError::Script("boom".into()) + assert_eq!(RhaiError::Cancelled.kind(), "cancelled"); + assert_eq!(RhaiError::Timeout("x".into()).kind(), "timeout"); + assert!(RhaiError::Script("boom".into()) .message() .contains("Fix the script")); } #[test] fn every_taxonomy_variant_maps_to_the_right_kind() { - let avail = RlmAvailable { + let avail = RhaiAvailable { tools: vec!["read_file".into(), "grep".into()], agents: vec!["researcher".into()], models: vec!["chat".into()], @@ -494,8 +499,8 @@ mod tests { registry } - fn req(script: &str) -> RlmEvalRequest { - RlmEvalRequest { + fn req(script: &str) -> RhaiEvalRequest { + RhaiEvalRequest { script: script.to_string(), ..Default::default() } @@ -503,7 +508,7 @@ mod tests { #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn happy_path_runs_a_tool_call_and_reports_usage() { - let manager = RlmSessionManager::new_for_test(); + let manager = RhaiSessionManager::new_for_test(); let resp = run_cell( req(r#"tool_call(#{ tool: "echo", arguments: #{ msg: "hi" } })"#), ReplPolicy::default(), @@ -526,7 +531,7 @@ mod tests { #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn parse_error_is_a_script_error() { - let manager = RlmSessionManager::new_for_test(); + let manager = RhaiSessionManager::new_for_test(); let err = run_cell( req("let x = ;"), ReplPolicy::default(), @@ -541,7 +546,7 @@ mod tests { #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn unknown_tool_lists_registered_names() { - let manager = RlmSessionManager::new_for_test(); + let manager = RhaiSessionManager::new_for_test(); let err = run_cell( req(r#"tool_call(#{ tool: "does_not_exist" })"#), ReplPolicy::default(), @@ -557,7 +562,7 @@ mod tests { #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn call_count_limit_fails_closed() { - let manager = RlmSessionManager::new_for_test(); + let manager = RhaiSessionManager::new_for_test(); let policy = ReplPolicy { max_tool_calls: 1, ..ReplPolicy::default() @@ -576,7 +581,7 @@ mod tests { #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn script_loop_times_out_within_the_bound() { - let manager = RlmSessionManager::new_for_test(); + let manager = RhaiSessionManager::new_for_test(); let policy = ReplPolicy { timeout: Some(Duration::from_millis(200)), max_operations: 0, @@ -596,7 +601,7 @@ mod tests { #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn oversized_output_fails_closed() { - let manager = RlmSessionManager::new_for_test(); + let manager = RhaiSessionManager::new_for_test(); let policy = ReplPolicy { max_output_bytes: 64, ..ReplPolicy::default() @@ -615,9 +620,9 @@ mod tests { #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn namespace_persists_and_close_session_drops_it() { - let manager = RlmSessionManager::new_for_test(); + let manager = RhaiSessionManager::new_for_test(); let first = run_cell( - RlmEvalRequest { + RhaiEvalRequest { script: "let acc = 10; acc".into(), session_id: Some("keep".into()), ..Default::default() @@ -632,7 +637,7 @@ mod tests { assert_eq!(first.session_id, "keep"); let second = run_cell( - RlmEvalRequest { + RhaiEvalRequest { script: "acc + 5".into(), session_id: Some("keep".into()), close_session: true, @@ -652,14 +657,14 @@ mod tests { #[tokio::test(flavor = "current_thread")] async fn concurrent_cell_on_a_busy_session_is_rejected() { - let manager = RlmSessionManager::new_for_test(); - let key = RlmSessionManager::session_key("t", "busy"); + let manager = RhaiSessionManager::new_for_test(); + let key = RhaiSessionManager::session_key("t", "busy"); let handle = manager.get_or_create(&key, || tinyagents::ReplSession::<()>::new()); // Hold the session lock to simulate an in-flight cell. let _guard = handle.session.lock().unwrap(); let res = run_cell( - RlmEvalRequest { + RhaiEvalRequest { script: "1".into(), session_id: Some("busy".into()), ..Default::default() @@ -670,6 +675,6 @@ mod tests { &manager, ) .await; - assert!(matches!(res, Err(RlmError::SessionBusy(_))), "got {res:?}"); + assert!(matches!(res, Err(RhaiError::SessionBusy(_))), "got {res:?}"); } } diff --git a/src/openhuman/rlm/policy.rs b/src/openhuman/rhai_workflows/policy.rs similarity index 80% rename from src/openhuman/rlm/policy.rs rename to src/openhuman/rhai_workflows/policy.rs index d0780f236..a54aecfa5 100644 --- a/src/openhuman/rlm/policy.rs +++ b/src/openhuman/rhai_workflows/policy.rs @@ -1,7 +1,7 @@ //! Maps openhuman's autonomy tier and tool-timeout clamps onto a //! [`tinyagents::ReplPolicy`], fail-closed. //! -//! Every RLM session is bounded: the resolved policy is always based on the +//! Every Rhai session is bounded: the resolved policy is always based on the //! conservative crate default, the wall-clock timeout is always set (never //! unbounded), the `readonly` tier is refused outright, and caller-supplied //! limit overrides are clamped — a `full`-tier caller may raise call-count @@ -14,38 +14,38 @@ use tinyagents::ReplPolicy; use crate::openhuman::security::policy::AutonomyLevel; use crate::openhuman::tool_timeout; -use super::types::RlmLimitsOverride; +use super::types::RhaiLimitsOverride; /// Default per-cell wall-clock timeout when the caller does not specify one. -/// Matches the `rlm` tool's default `ToolTimeout::Secs(300)` so the inner +/// Matches the `rhai_workflows` tool's default `ToolTimeout::Secs(300)` so the inner /// deadline and the harness backstop agree. -pub const DEFAULT_RLM_TIMEOUT_SECS: u64 = 300; +pub const DEFAULT_RHAI_TIMEOUT_SECS: u64 = 300; /// Hard upper bound on batched concurrency, regardless of tier or override. -pub const MAX_RLM_CONCURRENCY: usize = 8; +pub const MAX_RHAI_CONCURRENCY: usize = 8; /// Ceiling multiplier applied to the crate default call-count limits when a /// `full`-tier caller raises them via the tool's `limits` argument. pub const LIMIT_CEILING_MULTIPLIER: usize = 2; -/// Maximum sub-agent recursion depth an RLM session may drive. Mirrors the +/// Maximum sub-agent recursion depth an Rhai session may drive. Mirrors the /// harness `MAX_SPAWN_DEPTH` (kept in lock-step by intent; a divergence only /// tightens one side, never opens an unbounded path). -pub const RLM_MAX_DEPTH: usize = 3; +pub const RHAI_MAX_DEPTH: usize = 3; /// Resolves a [`ReplPolicy`] for a session opened at autonomy `tier`, with an /// optional caller `timeout_secs` and `overrides`. /// /// Returns `Err(reason)` — a model-consumable string — when the tier forbids -/// RLM entirely (`readonly`). The `reason` is surfaced verbatim to the model. +/// Rhai entirely (`readonly`). The `reason` is surfaced verbatim to the model. pub fn resolve_policy( tier: AutonomyLevel, timeout_secs: Option, - overrides: Option<&RlmLimitsOverride>, + overrides: Option<&RhaiLimitsOverride>, ) -> Result { if tier == AutonomyLevel::ReadOnly { return Err( - "the `rlm` tool is disabled at the read-only autonomy tier (it can drive tools, \ + "the `rhai_workflows` tool is disabled at the read-only autonomy tier (it can drive tools, \ models, and sub-agents); raise autonomy to `supervised` or `full` to use it" .to_string(), ); @@ -55,15 +55,15 @@ pub fn resolve_policy( let base = ReplPolicy::default(); // Wall-clock timeout: clamp the caller's request to [1, 3600] and cap it, - // defaulting to DEFAULT_RLM_TIMEOUT_SECS. Never unbounded. + // defaulting to DEFAULT_RHAI_TIMEOUT_SECS. Never unbounded. let secs = tool_timeout::explicit_call_timeout_secs(timeout_secs, tool_timeout::MAX_TIMEOUT_SECS) - .unwrap_or(DEFAULT_RLM_TIMEOUT_SECS); + .unwrap_or(DEFAULT_RHAI_TIMEOUT_SECS); let ov = overrides.cloned().unwrap_or_default(); let policy = ReplPolicy { timeout: Some(Duration::from_secs(secs)), - max_depth: base.max_depth.min(RLM_MAX_DEPTH), + max_depth: base.max_depth.min(RHAI_MAX_DEPTH), max_model_calls: clamp_limit(base.max_model_calls, ov.max_model_calls, allow_raise), max_agent_calls: clamp_limit(base.max_agent_calls, ov.max_agent_calls, allow_raise), max_tool_calls: clamp_limit(base.max_tool_calls, ov.max_tool_calls, allow_raise), @@ -79,7 +79,7 @@ pub fn resolve_policy( max_tool_calls = policy.max_tool_calls, max_concurrency = policy.max_concurrency, max_depth = policy.max_depth, - "[rlm] resolved ReplPolicy" + "[rhai_workflows] resolved ReplPolicy" ); Ok(policy) } @@ -103,16 +103,16 @@ fn clamp_limit(default: usize, requested: Option, allow_raise: bool) -> u } } -/// Clamps batched concurrency to `[1, MAX_RLM_CONCURRENCY]`, and to the default +/// Clamps batched concurrency to `[1, MAX_RHAI_CONCURRENCY]`, and to the default /// unless the tier allows raising it. fn clamp_concurrency(default: usize, requested: Option, allow_raise: bool) -> usize { let ceiling = if allow_raise { - MAX_RLM_CONCURRENCY + MAX_RHAI_CONCURRENCY } else { - default.min(MAX_RLM_CONCURRENCY) + default.min(MAX_RHAI_CONCURRENCY) }; match requested { - None => default.min(MAX_RLM_CONCURRENCY), + None => default.min(MAX_RHAI_CONCURRENCY), Some(n) => n.clamp(1, ceiling.max(1)), } } @@ -136,7 +136,7 @@ mod tests { let p = resolve_policy(AutonomyLevel::Supervised, None, None).expect("policy"); assert_eq!( p.timeout, - Some(Duration::from_secs(DEFAULT_RLM_TIMEOUT_SECS)) + Some(Duration::from_secs(DEFAULT_RHAI_TIMEOUT_SECS)) ); // A caller request is clamped to [1, 3600]. @@ -145,14 +145,14 @@ mod tests { let p = resolve_policy(AutonomyLevel::Supervised, Some(0), None).expect("policy"); assert_eq!( p.timeout, - Some(Duration::from_secs(DEFAULT_RLM_TIMEOUT_SECS)) + Some(Duration::from_secs(DEFAULT_RHAI_TIMEOUT_SECS)) ); } #[test] fn supervised_may_only_tighten_limits() { let base = ReplPolicy::default(); - let ov = RlmLimitsOverride { + let ov = RhaiLimitsOverride { max_tool_calls: Some(base.max_tool_calls * 10), // request a raise ..Default::default() }; @@ -162,7 +162,7 @@ mod tests { "supervised cannot raise above the default" ); - let ov = RlmLimitsOverride { + let ov = RhaiLimitsOverride { max_tool_calls: Some(1), ..Default::default() }; @@ -173,7 +173,7 @@ mod tests { #[test] fn full_may_raise_up_to_the_ceiling() { let base = ReplPolicy::default(); - let ov = RlmLimitsOverride { + let ov = RhaiLimitsOverride { max_model_calls: Some(base.max_model_calls * 100), // way over ceiling ..Default::default() }; @@ -187,17 +187,17 @@ mod tests { #[test] fn concurrency_is_hard_capped() { - let ov = RlmLimitsOverride { + let ov = RhaiLimitsOverride { max_concurrency: Some(1000), ..Default::default() }; let p = resolve_policy(AutonomyLevel::Full, None, Some(&ov)).expect("policy"); - assert_eq!(p.max_concurrency, MAX_RLM_CONCURRENCY); + assert_eq!(p.max_concurrency, MAX_RHAI_CONCURRENCY); } #[test] fn depth_respects_the_spawn_ceiling() { let p = resolve_policy(AutonomyLevel::Full, None, None).expect("policy"); - assert!(p.max_depth <= RLM_MAX_DEPTH); + assert!(p.max_depth <= RHAI_MAX_DEPTH); } } diff --git a/src/openhuman/rlm/sessions.rs b/src/openhuman/rhai_workflows/sessions.rs similarity index 85% rename from src/openhuman/rlm/sessions.rs rename to src/openhuman/rhai_workflows/sessions.rs index 65eabfbdf..f82721743 100644 --- a/src/openhuman/rlm/sessions.rs +++ b/src/openhuman/rhai_workflows/sessions.rs @@ -60,16 +60,16 @@ pub(super) struct CellStats { } /// The process-global session manager. -pub(super) struct RlmSessionManager { +pub(super) struct RhaiSessionManager { inner: Mutex>, } -static MANAGER: OnceLock = OnceLock::new(); +static MANAGER: OnceLock = OnceLock::new(); -impl RlmSessionManager { +impl RhaiSessionManager { /// Returns the process-global manager, initialising it on first use. - pub(super) fn global() -> &'static RlmSessionManager { - MANAGER.get_or_init(|| RlmSessionManager { + pub(super) fn global() -> &'static RhaiSessionManager { + MANAGER.get_or_init(|| RhaiSessionManager { inner: Mutex::new(HashMap::new()), }) } @@ -97,7 +97,10 @@ impl RlmSessionManager { let now = Instant::now(); if let Some(slot) = map.get_mut(key) { slot.last_access = now; - tracing::debug!(session_key = key, "[rlm] reusing existing session"); + tracing::debug!( + session_key = key, + "[rhai_workflows] reusing existing session" + ); return SlotHandle { session: slot.session.clone(), cancel: slot.cancel.clone(), @@ -122,7 +125,7 @@ impl RlmSessionManager { tracing::debug!( session_key = key, live_sessions = map.len(), - "[rlm] created new session" + "[rhai_workflows] created new session" ); SlotHandle { session, @@ -159,7 +162,7 @@ impl RlmSessionManager { /// session that must never be reused). pub(super) fn close(&self, key: &str) { if self.lock().remove(key).is_some() { - tracing::debug!(session_key = key, "[rlm] closed session"); + tracing::debug!(session_key = key, "[rhai_workflows] closed session"); } } @@ -171,7 +174,7 @@ impl RlmSessionManager { map.retain(|k, slot| { let keep = now.duration_since(slot.last_access) < IDLE_TTL; if !keep { - tracing::debug!(session_key = %k, "[rlm] evicting idle session (TTL)"); + tracing::debug!(session_key = %k, "[rhai_workflows] evicting idle session (TTL)"); } keep }); @@ -185,7 +188,7 @@ impl RlmSessionManager { .map(|(k, _)| k.clone()) { map.remove(&lru_key); - tracing::debug!(session_key = %lru_key, "[rlm] evicting LRU session (cap)"); + tracing::debug!(session_key = %lru_key, "[rhai_workflows] evicting LRU session (cap)"); } } @@ -199,7 +202,7 @@ impl RlmSessionManager { /// the process-global singleton and from each other. #[cfg(test)] pub(super) fn new_for_test() -> Self { - RlmSessionManager { + RhaiSessionManager { inner: Mutex::new(HashMap::new()), } } @@ -216,8 +219,8 @@ mod tests { #[test] fn namespace_persists_across_cells_in_one_session() { - let manager = RlmSessionManager::new_for_test(); - let key = RlmSessionManager::session_key("t", "s1"); + let manager = RhaiSessionManager::new_for_test(); + let key = RhaiSessionManager::session_key("t", "s1"); let handle = manager.get_or_create(&key, build_session); assert!(handle.fresh); @@ -243,20 +246,20 @@ mod tests { #[test] fn distinct_session_keys_are_isolated() { - let manager = RlmSessionManager::new_for_test(); - let a = manager.get_or_create(&RlmSessionManager::session_key("t", "a"), build_session); + let manager = RhaiSessionManager::new_for_test(); + let a = manager.get_or_create(&RhaiSessionManager::session_key("t", "a"), build_session); a.session.lock().unwrap().eval_cell("let x = 1;").unwrap(); // A different key has its own namespace, so `x` is undefined there. - let b = manager.get_or_create(&RlmSessionManager::session_key("t", "b"), build_session); + let b = manager.get_or_create(&RhaiSessionManager::session_key("t", "b"), build_session); assert!(b.session.lock().unwrap().eval_cell("x").is_err()); } #[test] fn thread_scope_isolates_the_same_session_id() { - let manager = RlmSessionManager::new_for_test(); - let k1 = RlmSessionManager::session_key("thread-1", "shared"); - let k2 = RlmSessionManager::session_key("thread-2", "shared"); + let manager = RhaiSessionManager::new_for_test(); + let k1 = RhaiSessionManager::session_key("thread-1", "shared"); + let k2 = RhaiSessionManager::session_key("thread-2", "shared"); assert_ne!(k1, k2); manager.get_or_create(&k1, build_session); let h2 = manager.get_or_create(&k2, build_session); @@ -268,10 +271,10 @@ mod tests { #[test] fn lru_cap_bounds_the_number_of_live_sessions() { - let manager = RlmSessionManager::new_for_test(); + let manager = RhaiSessionManager::new_for_test(); for i in 0..(MAX_SESSIONS + 5) { manager.get_or_create( - &RlmSessionManager::session_key("t", &format!("s{i}")), + &RhaiSessionManager::session_key("t", &format!("s{i}")), build_session, ); } @@ -284,8 +287,8 @@ mod tests { #[test] fn close_drops_the_session() { - let manager = RlmSessionManager::new_for_test(); - let key = RlmSessionManager::session_key("t", "closeme"); + let manager = RhaiSessionManager::new_for_test(); + let key = RhaiSessionManager::session_key("t", "closeme"); manager.get_or_create(&key, build_session); assert_eq!(manager.len(), 1); manager.close(&key); @@ -296,8 +299,8 @@ mod tests { #[test] fn finish_cell_accumulates_and_bounds_are_reported() { - let manager = RlmSessionManager::new_for_test(); - let key = RlmSessionManager::session_key("t", "stats"); + let manager = RhaiSessionManager::new_for_test(); + let key = RhaiSessionManager::session_key("t", "stats"); let policy = ReplPolicy::default(); let handle = manager.get_or_create(&key, || { ReplSession::<()>::new().with_policy(policy.clone()) diff --git a/src/openhuman/rlm/tools.rs b/src/openhuman/rhai_workflows/tools.rs similarity index 81% rename from src/openhuman/rlm/tools.rs rename to src/openhuman/rhai_workflows/tools.rs index f31bbc7b7..febdbcc44 100644 --- a/src/openhuman/rlm/tools.rs +++ b/src/openhuman/rhai_workflows/tools.rs @@ -1,39 +1,39 @@ -//! The first-class `rlm` tool — the orchestrator's language-workflow surface. +//! The first-class `rhai_workflows` tool — the orchestrator's language-workflow surface. //! //! One tool call evaluates one Rhai cell against a persistent session //! namespace. All effectful work a cell performs goes through the bridged inner //! tools/models/sub-agents (each carrying its own approval/permission gates in -//! [`super::bridge`]), so the `rlm` tool itself declares no external effect. +//! [`super::bridge`]), so the `rhai_workflows` tool itself declares no external effect. use async_trait::async_trait; use serde_json::{json, Value}; use crate::openhuman::tools::traits::{PermissionLevel, Tool, ToolResult, ToolScope, ToolTimeout}; -use super::policy::DEFAULT_RLM_TIMEOUT_SECS; -use super::types::RlmEvalRequest; +use super::policy::DEFAULT_RHAI_TIMEOUT_SECS; +use super::types::RhaiEvalRequest; -/// The `rlm` language-workflow tool. Stateless: it resolves the parent turn +/// The `rhai_workflows` language-workflow tool. Stateless: it resolves the parent turn /// context, autonomy tier, and cancellation per call. -pub struct RlmTool; +pub struct RhaiTool; -impl RlmTool { +impl RhaiTool { /// Builds the tool. pub fn new() -> Self { Self } } -impl Default for RlmTool { +impl Default for RhaiTool { fn default() -> Self { Self::new() } } #[async_trait] -impl Tool for RlmTool { +impl Tool for RhaiTool { fn name(&self) -> &str { - "rlm" + "rhai_workflows" } fn description(&self) -> &str { @@ -52,7 +52,7 @@ In-cell built-ins:\n\ \n\ Bounds (fail-closed): per-cell wall-clock timeout, and per-session caps on model/tool/agent \ calls, iterations, output bytes, and recursion depth. Exceeding one returns an error you can \ -fix and retry in the same session. Excluded from cells: `rlm` itself, `spawn_*`, and workflow tools.\n\ +fix and retry in the same session. Excluded from cells: `rhai_workflows` itself, `spawn_*`, and workflow tools.\n\ \n\ Example — parallel fan-out over sub-agents, then reduce:\n\ ```\n\ @@ -71,7 +71,7 @@ for r in reads { if r.ok && r.content.contains(\"TODO\") { hits.push(r.content); hits\n\ ```\n\ \n\ -Prefer `rlm` over spawn_parallel_agents when you need loops, conditionals, or a dedup/verify \ +Prefer `rhai_workflows` over spawn_parallel_agents when you need loops, conditionals, or a dedup/verify \ pipeline over results. Pass a `session_id` to continue a prior cell's bindings; set \ `close_session: true` when done." } @@ -86,7 +86,7 @@ pipeline over results. Pass a `session_id` to continue a prior cell's bindings; }, "session_id": { "type": "string", - "description": "Continue a prior RLM session's namespace; omit for a fresh session (its generated id is returned)." + "description": "Continue a prior Rhai session's namespace; omit for a fresh session (its generated id is returned)." }, "timeout_secs": { "type": "integer", @@ -132,12 +132,12 @@ pipeline over results. Pass a `session_id` to continue a prior cell's bindings; requested, crate::openhuman::tool_timeout::MAX_TIMEOUT_SECS, ) - .unwrap_or(DEFAULT_RLM_TIMEOUT_SECS); + .unwrap_or(DEFAULT_RHAI_TIMEOUT_SECS); ToolTimeout::Secs(secs) } fn display_label(&self, _args: &Value) -> Option { - Some("running RLM workflow".to_string()) + Some("running Rhai workflow".to_string()) } fn display_detail(&self, args: &Value) -> Option { @@ -155,30 +155,33 @@ pipeline over results. Pass a `session_id` to continue a prior cell's bindings; } async fn execute(&self, args: Value) -> anyhow::Result { - let req: RlmEvalRequest = match serde_json::from_value(args) { + let req: RhaiEvalRequest = match serde_json::from_value(args) { Ok(req) => req, Err(err) => { return Ok(ToolResult::error(format!( - "rlm: invalid arguments: {err}. Required: `script` (string). \ + "rhai_workflows: invalid arguments: {err}. Required: `script` (string). \ Optional: session_id, timeout_secs (1–3600), limits, close_session." ))); } }; if req.script.trim().is_empty() { return Ok(ToolResult::error( - "rlm: `script` is required and must be a non-empty Rhai cell.", + "rhai_workflows: `script` is required and must be a non-empty Rhai cell.", )); } - match super::ops::eval_rlm_cell(req).await { + match super::ops::eval_rhai_cell(req).await { Ok(response) => match serde_json::to_value(&response) { Ok(value) => Ok(ToolResult::json(value)), Err(err) => Ok(ToolResult::error(format!( - "rlm: internal error rendering result: {err}" + "rhai_workflows: internal error rendering result: {err}" ))), }, Err(err) => { - log::info!("[rlm] tool returning error result (kind={})", err.kind()); + log::info!( + "[rhai_workflows] tool returning error result (kind={})", + err.kind() + ); Ok(ToolResult::error(err.message())) } } @@ -191,8 +194,8 @@ mod tests { #[test] fn metadata_is_stable() { - let tool = RlmTool::new(); - assert_eq!(tool.name(), "rlm"); + let tool = RhaiTool::new(); + assert_eq!(tool.name(), "rhai_workflows"); assert_eq!(tool.permission_level(), PermissionLevel::Execute); assert!(matches!(tool.scope(), ToolScope::AgentOnly)); assert!(!tool.external_effect()); @@ -200,10 +203,10 @@ mod tests { #[test] fn timeout_is_always_bounded() { - let tool = RlmTool::new(); + let tool = RhaiTool::new(); assert_eq!( tool.timeout_policy(&json!({})), - ToolTimeout::Secs(DEFAULT_RLM_TIMEOUT_SECS) + ToolTimeout::Secs(DEFAULT_RHAI_TIMEOUT_SECS) ); assert_eq!( tool.timeout_policy(&json!({ "timeout_secs": 42 })), @@ -218,7 +221,7 @@ mod tests { #[test] fn display_detail_elides_first_script_line() { - let tool = RlmTool::new(); + let tool = RhaiTool::new(); let detail = tool .display_detail(&json!({ "script": "\n\nlet x = 1;\nlet y = 2;" })) .expect("detail"); @@ -227,7 +230,7 @@ mod tests { #[tokio::test] async fn empty_script_is_a_model_consumable_error() { - let tool = RlmTool::new(); + let tool = RhaiTool::new(); let result = tool .execute(json!({ "script": " " })) .await @@ -237,7 +240,7 @@ mod tests { #[tokio::test] async fn missing_script_is_a_model_consumable_error() { - let tool = RlmTool::new(); + let tool = RhaiTool::new(); // No `script` field at all → invalid arguments, not a panic. let result = tool .execute(json!({ "session_id": "x" })) @@ -249,7 +252,7 @@ mod tests { #[test] fn schema_requires_script() { - let schema = RlmTool::new().parameters_schema(); + let schema = RhaiTool::new().parameters_schema(); assert_eq!(schema["required"], json!(["script"])); } } diff --git a/src/openhuman/rlm/types.rs b/src/openhuman/rhai_workflows/types.rs similarity index 83% rename from src/openhuman/rlm/types.rs rename to src/openhuman/rhai_workflows/types.rs index 25c94dbfb..a3edd58d7 100644 --- a/src/openhuman/rlm/types.rs +++ b/src/openhuman/rhai_workflows/types.rs @@ -1,6 +1,6 @@ -//! Serde domain types for the `rlm` language-workflow tool. +//! Serde domain types for the `rhai` language-workflow tool. //! -//! These cross the tool boundary (parsed from the `rlm` tool call, and rendered +//! These cross the tool boundary (parsed from the `rhai_workflows` tool call, and rendered //! back into its [`ToolResult`](crate::openhuman::tools::ToolResult)). The //! runtime types that wire a session to openhuman's tools/models/subagents live //! in [`super::bridge`] and [`super::sessions`]; the tinyagents-side limit and @@ -8,32 +8,32 @@ use serde::{Deserialize, Serialize}; -/// A caller-supplied RLM session id. Continuing a prior `session_id` reuses that +/// A caller-supplied Rhai session id. Continuing a prior `session_id` reuses that /// session's persistent namespace (`let` bindings survive across cells); an /// absent id starts a fresh session. Namespaces are additionally scoped by the /// parent thread inside [`super::sessions`], so two chats never collide. #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] -pub struct RlmSessionId(pub String); +pub struct RhaiSessionId(pub String); -impl RlmSessionId { +impl RhaiSessionId { /// The session id as a string slice. pub fn as_str(&self) -> &str { &self.0 } } -impl std::fmt::Display for RlmSessionId { +impl std::fmt::Display for RhaiSessionId { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.write_str(&self.0) } } -/// Per-call limit overrides a caller may pass in the `rlm` tool's `limits` +/// Per-call limit overrides a caller may pass in the `rhai_workflows` tool's `limits` /// argument. Each is clamped in [`super::policy`] to a hard ceiling (never /// unbounded); a `full`-tier caller may raise them, others are capped at the /// conservative defaults. #[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] -pub struct RlmLimitsOverride { +pub struct RhaiLimitsOverride { /// Requested `max_tool_calls` for the session. #[serde(default, skip_serializing_if = "Option::is_none")] pub max_tool_calls: Option, @@ -48,9 +48,9 @@ pub struct RlmLimitsOverride { pub max_concurrency: Option, } -/// A parsed `rlm` tool call — one cell to evaluate against a session. +/// A parsed `rhai_workflows` tool call — one cell to evaluate against a session. #[derive(Debug, Clone, Default, Deserialize)] -pub struct RlmEvalRequest { +pub struct RhaiEvalRequest { /// The Rhai workflow cell to evaluate. pub script: String, /// Continue a prior session's namespace; `None` starts a fresh session. @@ -61,7 +61,7 @@ pub struct RlmEvalRequest { pub timeout_secs: Option, /// Optional per-session limit overrides. #[serde(default)] - pub limits: Option, + pub limits: Option, /// Close (drop) the session after this cell. #[serde(default)] pub close_session: bool, @@ -72,7 +72,7 @@ pub struct RlmEvalRequest { /// level and on the live event stream), so a model-visible result cannot leak a /// large or sensitive payload back into the context window. #[derive(Debug, Clone, PartialEq, Serialize)] -pub struct RlmCallSummary { +pub struct RhaiCallSummary { /// `model` | `tool` | `agent` | `graph` | `emit`. pub kind: String, /// The capability or event name. @@ -87,7 +87,7 @@ pub struct RlmCallSummary { /// The remaining per-session budget after a cell, surfaced so the model can plan /// how much more fan-out it can do before splitting work across sessions. #[derive(Debug, Clone, Default, PartialEq, Serialize)] -pub struct RlmLimitsRemaining { +pub struct RhaiLimitsRemaining { /// Cells left before `max_iterations`. pub cells: usize, /// `model_query` calls left. @@ -98,10 +98,10 @@ pub struct RlmLimitsRemaining { pub agent_calls: usize, } -/// The structured result of evaluating one RLM cell, rendered into the JSON +/// The structured result of evaluating one Rhai cell, rendered into the JSON /// content of the tool result. #[derive(Debug, Clone, PartialEq, Serialize)] -pub struct RlmEvalResponse { +pub struct RhaiEvalResponse { /// The session the cell ran in (echoed so a fresh session's generated id is /// discoverable, and a later cell can continue it). pub session_id: String, @@ -113,7 +113,7 @@ pub struct RlmEvalResponse { /// Names of persistent variables the cell created or changed. pub variables_changed: Vec, /// Summarized capability calls the cell performed. - pub calls: Vec, + pub calls: Vec, /// The final answer, if the cell called `answer(...)`. #[serde(skip_serializing_if = "Option::is_none")] pub final_answer: Option, @@ -122,7 +122,7 @@ pub struct RlmEvalResponse { /// Number of cells this session has evaluated so far. pub cells_used: usize, /// Remaining per-session budget. - pub limits_remaining: RlmLimitsRemaining, + pub limits_remaining: RhaiLimitsRemaining, /// Whether the session was closed after this cell. #[serde(default)] pub closed: bool, diff --git a/src/openhuman/tinyagents/model.rs b/src/openhuman/tinyagents/model.rs index 86da67628..acf6eb964 100644 --- a/src/openhuman/tinyagents/model.rs +++ b/src/openhuman/tinyagents/model.rs @@ -366,7 +366,7 @@ pub(super) struct ProviderModel { /// Builds a `ChatModel<()>` capability over an openhuman [`Provider`], pinned to /// `model`/`temperature`, ready to register into a `tinyagents` /// [`CapabilityRegistry`](tinyagents::registry::CapabilityRegistry) — e.g. the -/// `.ragsh` REPL bridge (`crate::openhuman::rlm`). [`ProviderModel::new`] is +/// `.ragsh` REPL bridge (`crate::openhuman::rhai_workflows`). [`ProviderModel::new`] is /// `pub(super)`; this is the crate-visible factory for that one bridge use. pub(crate) fn provider_chat_model( provider: Arc, diff --git a/src/openhuman/tools/mod.rs b/src/openhuman/tools/mod.rs index 6c2bbbd99..030fc0b01 100644 --- a/src/openhuman/tools/mod.rs +++ b/src/openhuman/tools/mod.rs @@ -38,7 +38,7 @@ pub use crate::openhuman::monitor::tools::*; pub use crate::openhuman::orchestration::tools::*; pub use crate::openhuman::people::tools::*; pub use crate::openhuman::referral::tools::*; -pub use crate::openhuman::rlm::tools::*; +pub use crate::openhuman::rhai_workflows::tools::*; pub use crate::openhuman::screen_intelligence::tools::*; pub use crate::openhuman::search::tools::*; pub use crate::openhuman::security::tools::*; diff --git a/src/openhuman/tools/ops.rs b/src/openhuman/tools/ops.rs index b716c7bb2..6f3440834 100644 --- a/src/openhuman/tools/ops.rs +++ b/src/openhuman/tools/ops.rs @@ -966,26 +966,28 @@ pub fn all_tools_with_runtime( tracing::debug!("[lsp] capability gate off (set OPENHUMAN_LSP_ENABLED=1 to register)"); } - // Language-workflow `rlm` tool (RLM / `.ragsh` REPL, `openhuman::rlm`): lets + // Language-workflow `rhai_workflows` tool (`.ragsh` REPL, `openhuman::rhai_workflows`): lets // the orchestrator author and run its own Rhai workflow cells (fan-out, // loops, dedup/verify pipelines). Registered on the `supervised`/`full` // tiers only — dark on `readonly` (it can drive effectful tools/sub-agents) - // and behind the `OPENHUMAN_RLM=0` kill switch. Every effectful inner call - // still re-gates itself in the RLM bridge, so this surface adds no new + // and behind the `OPENHUMAN_RHAI_WORKFLOWS=0` kill switch. Every effectful inner call + // still re-gates itself in the Rhai bridge, so this surface adds no new // ungated capability. - let rlm_enabled = std::env::var("OPENHUMAN_RLM") + let rhai_workflows_enabled = std::env::var("OPENHUMAN_RHAI_WORKFLOWS") + .or_else(|_| std::env::var("OPENHUMAN_RHAI")) + .or_else(|_| std::env::var("OPENHUMAN_RLM")) .map(|v| v != "0") .unwrap_or(true); - if rlm_enabled + if rhai_workflows_enabled && security.autonomy != crate::openhuman::security::policy::AutonomyLevel::ReadOnly { - tools.push(Box::new(crate::openhuman::rlm::RlmTool::new())); - tracing::debug!("[rlm] registered rlm language-workflow tool"); + tools.push(Box::new(crate::openhuman::rhai_workflows::RhaiTool::new())); + tracing::debug!("[rhai_workflows] registered rhai_workflows language-workflow tool"); } else { tracing::debug!( - rlm_enabled, + rhai_workflows_enabled, tier = ?security.autonomy, - "[rlm] rlm tool not registered (readonly tier or OPENHUMAN_RLM=0)" + "[rhai_workflows] rhai_workflows tool not registered (readonly tier or OPENHUMAN_RHAI_WORKFLOWS=0)" ); }