Hero Agent should start using service python generation flow #2

Closed
opened 2026-04-13 07:06:24 +00:00 by timur · 5 comments
Owner

In hero_logic, we have a flow that allows us to generate python scripts for working against openrpc services. We want hero agent to use this hero_logic flow so it can works against hero services

In [hero_logic](https://forge.ourworld.tf/lhumina_code/hero_logic), we have a flow that allows us to generate python scripts for working against openrpc services. We want hero agent to use this hero_logic flow so it can works against hero services
Author
Owner

Plan & deliberation

Current state

  • hero_logic is an OpenRPC/RPC service with a DAG workflow engine, not an MCP server today. Its service_agent.json template is a 5-node DAG:
    service_selectioncode_generationscript_execution (Python3 via hero_proc) → error_debugresult_summary.
    Entry points: play_start(workflow_sid, input_data), play_status(play_sid).
  • hero_agent already has an MCP client (crates/hero_agent/src/mcp_client.rs) + 56 built-in Rust tools (hero_agent_tools). No python-generation flow today — it routes everything through LLM + atomic tool calls.

So "use the hero_logic mcp" is aspirational. Two integration shapes are possible.

Option A — Native RPC tool in hero_agent_tools

Add a service_agent tool that calls hero_logic directly over its OpenRPC socket:

  1. Tool input: NL task + optional service hints.
  2. Calls play_start(workflow_sid=service_agent, input_data={...}).
  3. Polls play_status(play_sid) and streams progress into the agent's SSE channel (per-node updates: selection → code → exec → summary).
  4. Returns final result + generated Python + execution trace to the LLM.

Pros: ship fast; tight SSE integration; no extra hop.
Cons: couples hero_agent to hero_logic's RPC shape.

Option B — MCP wrapper around hero_logic

Expose hero_logic as an MCP server so any MCP-compatible client (hero_agent, hero_shrimp, Claude Desktop, future agents) picks it up through tool discovery.

Pros: generic, reusable, zero hero_agent code once the MCP server exists.
Cons: extra surface to maintain; one more hop; harder to stream node-level progress cleanly over MCP today.

Recommendation

Start with Option A, extract to Option B once shape stabilizes. Ship a service_agent native tool in hero_agent_tools now; once it's proven, lift it into a thin MCP wrapper crate (hero_logic_mcp) that any agent can consume.

What the agent adds on top of service_agent

service_agent is single-shot and headless. The agent contributes:

  • Conversation & memory — multi-turn refinement, carried context, OSIS-backed history.
  • Dispatch judgment — decide when a query belongs to service_agent vs. a built-in tool (file ops, git, web, shell) vs. direct LLM answer. service_agent alone cannot make that call.
  • Input shaping — NL → structured input_data for the workflow.
  • Streaming UX — bridge play_status polling into the agent's SSE stream so the user sees node-level progress.
  • Composition — chain the Python script's output into subsequent tool calls (write to file, open a PR, register a service, etc.).
  • Error recovery beyond one workflow — if service_agent gives up after its error_debug node, the agent can re-prompt, adjust inputs, or fall back to atomic tools.

Phased plan

Phase 1 — native integration (this issue):

  1. Add hero_logic SDK dep to hero_agent (or reuse openrpc_client! against its spec).
  2. New tool crates/hero_agent_tools/src/service_agent.rs implementing Tool:
    • Schema: { task: string, service_hints?: string[], timeout_ms?: u64 }.
    • execute(): play_start → loop play_status with backoff → stream node results via tool context → return { result, python_code, logs }.
  3. Register in all_tools().
  4. System prompt update: guidance on when to prefer service_agent over atomic tools.
  5. Propagate claims/context through the call so downstream services see the right auth.

Phase 2 — generalize (follow-up issue):

  • Extract hero_logic_mcp crate that re-exposes play_start/play_status + template listing over MCP. Agent then discovers it via existing MCP client; native tool becomes optional/removable.

Open questions before I start

  1. Should the 56 built-in tools stay as a fast path for non-service work, or do we eventually collapse service-shaped tools into service_agent? My default: keep built-ins as fast path.
  2. Claim/auth propagation: hero_agent → hero_logic → target service — does hero_context flow end-to-end today, or do we need wiring?
  3. Routing policy: always offer service_agent as a tool and let the LLM choose, or add a lightweight classifier that hard-routes OpenRPC-service intents? I'd start with LLM-chooses (simpler, observable) and only add a classifier if selection is unreliable.
  4. Is a hero_logic_mcp crate already planned elsewhere? If so, skip Option A and wait for it.

Happy to start on Phase 1 once the above are settled.

## Plan & deliberation ### Current state - **hero_logic** is an OpenRPC/RPC service with a DAG workflow engine, **not** an MCP server today. Its `service_agent.json` template is a 5-node DAG: `service_selection` → `code_generation` → `script_execution` (Python3 via hero_proc) → `error_debug` → `result_summary`. Entry points: `play_start(workflow_sid, input_data)`, `play_status(play_sid)`. - **hero_agent** already has an MCP client (`crates/hero_agent/src/mcp_client.rs`) + 56 built-in Rust tools (`hero_agent_tools`). No python-generation flow today — it routes everything through LLM + atomic tool calls. So "use the hero_logic mcp" is aspirational. Two integration shapes are possible. ### Option A — Native RPC tool in `hero_agent_tools` Add a `service_agent` tool that calls `hero_logic` directly over its OpenRPC socket: 1. Tool input: NL task + optional service hints. 2. Calls `play_start(workflow_sid=service_agent, input_data={...})`. 3. Polls `play_status(play_sid)` and streams progress into the agent's SSE channel (per-node updates: selection → code → exec → summary). 4. Returns final result + generated Python + execution trace to the LLM. **Pros:** ship fast; tight SSE integration; no extra hop. **Cons:** couples hero_agent to hero_logic's RPC shape. ### Option B — MCP wrapper around hero_logic Expose hero_logic as an MCP server so any MCP-compatible client (hero_agent, hero_shrimp, Claude Desktop, future agents) picks it up through tool discovery. **Pros:** generic, reusable, zero hero_agent code once the MCP server exists. **Cons:** extra surface to maintain; one more hop; harder to stream node-level progress cleanly over MCP today. ### Recommendation **Start with Option A, extract to Option B once shape stabilizes.** Ship a `service_agent` native tool in `hero_agent_tools` now; once it's proven, lift it into a thin MCP wrapper crate (`hero_logic_mcp`) that any agent can consume. ### What the agent adds on top of `service_agent` `service_agent` is single-shot and headless. The agent contributes: - **Conversation & memory** — multi-turn refinement, carried context, OSIS-backed history. - **Dispatch judgment** — decide when a query belongs to `service_agent` vs. a built-in tool (file ops, git, web, shell) vs. direct LLM answer. `service_agent` alone cannot make that call. - **Input shaping** — NL → structured `input_data` for the workflow. - **Streaming UX** — bridge `play_status` polling into the agent's SSE stream so the user sees node-level progress. - **Composition** — chain the Python script's output into subsequent tool calls (write to file, open a PR, register a service, etc.). - **Error recovery beyond one workflow** — if `service_agent` gives up after its `error_debug` node, the agent can re-prompt, adjust inputs, or fall back to atomic tools. ### Phased plan **Phase 1 — native integration (this issue):** 1. Add `hero_logic` SDK dep to `hero_agent` (or reuse `openrpc_client!` against its spec). 2. New tool `crates/hero_agent_tools/src/service_agent.rs` implementing `Tool`: - Schema: `{ task: string, service_hints?: string[], timeout_ms?: u64 }`. - `execute()`: `play_start` → loop `play_status` with backoff → stream node results via tool context → return `{ result, python_code, logs }`. 3. Register in `all_tools()`. 4. System prompt update: guidance on when to prefer `service_agent` over atomic tools. 5. Propagate claims/context through the call so downstream services see the right auth. **Phase 2 — generalize (follow-up issue):** - Extract `hero_logic_mcp` crate that re-exposes `play_start/play_status` + template listing over MCP. Agent then discovers it via existing MCP client; native tool becomes optional/removable. ### Open questions before I start 1. Should the 56 built-in tools stay as a fast path for non-service work, or do we eventually collapse service-shaped tools into `service_agent`? My default: **keep built-ins as fast path.** 2. Claim/auth propagation: hero_agent → hero_logic → target service — does `hero_context` flow end-to-end today, or do we need wiring? 3. Routing policy: always offer `service_agent` as a tool and let the LLM choose, or add a lightweight classifier that hard-routes OpenRPC-service intents? I'd start with **LLM-chooses** (simpler, observable) and only add a classifier if selection is unreliable. 4. Is a `hero_logic_mcp` crate already planned elsewhere? If so, skip Option A and wait for it. Happy to start on Phase 1 once the above are settled.
Author
Owner
  • hero_router already provides mcp wrappers around openrpc services so we can use that.
  • yes the hero agent remains what it is.
  • about claim auth propogation, the headers should be carried, not sure it flows end to end already.
  • llm chooses
- hero_router already provides mcp wrappers around openrpc services so we can use that. - yes the hero agent remains what it is. - about claim auth propogation, the headers should be carried, not sure it flows end to end already. - llm chooses
Author
Owner

Got it — revised plan with hero_router's MCP gateway as the integration point.

Revised architecture

hero_router already exposes every OpenRPC service as MCP at POST /mcp/{service_name}tools/list maps to the service's OpenRPC methods, tools/call maps to JSON-RPC invocations. It also injects X-Hero-Context / X-Hero-Claims / X-Forwarded-Prefix on every forwarded request.

So there is no hero_logic_mcp crate to write and no native RPC tool in hero_agent_tools. hero_agent just points its existing MCP client at the router's /mcp/{service} endpoint, and hero_logic (plus every other service) shows up as tools.

Phase 1 — wire hero_agent's MCP client to hero_router

  1. Transport: crates/hero_agent/src/mcp_client.rs currently supports UDS-HTTP and stdio. hero_router exposes MCP over TCP-HTTP at /mcp/{service} — add a TCP-HTTP transport variant so the MCP client can POST to http://{router_host}:{router_port}/mcp/{service}.
  2. Config: add entries to the agent's MCP config for the services we want the LLM to see — at minimum hero_logic (for play_start / play_status → the service_agent workflow), and likely a curated set of others (hero_proc, osis, etc.) so the LLM can either call a service directly or delegate to hero_logic when it wants python-generation.
  3. Header propagation: pass incoming X-Hero-Context / X-Hero-Claims through the MCP client into the outbound HTTP request to hero_router so claims flow end-to-end (agent → router → service). This is the main wiring gap today. Will handle as part of this issue.
  4. System prompt: brief the LLM on the two shapes now available — direct tool calls per service vs. hero_logic.play_start(service_agent, …) for multi-service python-generation tasks. LLM chooses.
  5. Streaming: play_status is poll-based; simplest shape is the LLM polls play_status as repeat tool calls and watches node-level progress (service_selectioncode_generationscript_execution → …). A thin wrapper that polls and emits SSE progress is a possible follow-up.

Phase 2 — nice-to-haves (follow-ups)

  • Auto-discover services from hero_router rather than a static MCP config, so new services light up automatically.
  • Classifier / router-hinting if LLM tool selection gets noisy (only if needed).

Decisions locked in from your response

  • Use hero_router's MCP gateway over TCP-HTTP; no new wrapper crate.
  • hero_agent stays as-is (56 built-in tools kept as fast path for non-service work).
  • Claim/auth propagation: headers are carried by router; agent needs wiring to forward its incoming headers into the MCP client's HTTP requests. I'll add this.
  • LLM chooses between service_agent (via hero_logic MCP) and direct service tool calls and built-in tools.

Starting on Phase 1.

Got it — revised plan with `hero_router`'s MCP gateway as the integration point. ## Revised architecture `hero_router` already exposes every OpenRPC service as MCP at `POST /mcp/{service_name}` — `tools/list` maps to the service's OpenRPC methods, `tools/call` maps to JSON-RPC invocations. It also injects `X-Hero-Context` / `X-Hero-Claims` / `X-Forwarded-Prefix` on every forwarded request. So there is **no `hero_logic_mcp` crate to write** and **no native RPC tool in `hero_agent_tools`**. hero_agent just points its existing MCP client at the router's `/mcp/{service}` endpoint, and `hero_logic` (plus every other service) shows up as tools. ## Phase 1 — wire hero_agent's MCP client to hero_router 1. **Transport:** `crates/hero_agent/src/mcp_client.rs` currently supports UDS-HTTP and stdio. hero_router exposes MCP over **TCP-HTTP at `/mcp/{service}`** — add a TCP-HTTP transport variant so the MCP client can POST to `http://{router_host}:{router_port}/mcp/{service}`. 2. **Config:** add entries to the agent's MCP config for the services we want the LLM to see — at minimum `hero_logic` (for `play_start` / `play_status` → the `service_agent` workflow), and likely a curated set of others (hero_proc, osis, etc.) so the LLM can either call a service directly or delegate to `hero_logic` when it wants python-generation. 3. **Header propagation:** pass incoming `X-Hero-Context` / `X-Hero-Claims` through the MCP client into the outbound HTTP request to hero_router so claims flow end-to-end (agent → router → service). This is the main wiring gap today. Will handle as part of this issue. 4. **System prompt:** brief the LLM on the two shapes now available — direct tool calls per service vs. `hero_logic.play_start(service_agent, …)` for multi-service python-generation tasks. LLM chooses. 5. **Streaming:** `play_status` is poll-based; simplest shape is the LLM polls `play_status` as repeat tool calls and watches node-level progress (`service_selection` → `code_generation` → `script_execution` → …). A thin wrapper that polls and emits SSE progress is a possible follow-up. ## Phase 2 — nice-to-haves (follow-ups) - Auto-discover services from hero_router rather than a static MCP config, so new services light up automatically. - Classifier / router-hinting if LLM tool selection gets noisy (only if needed). ## Decisions locked in from your response - Use hero_router's MCP gateway over TCP-HTTP; no new wrapper crate. - hero_agent stays as-is (56 built-in tools kept as fast path for non-service work). - Claim/auth propagation: headers are carried by router; agent needs wiring to forward its incoming headers into the MCP client's HTTP requests. I'll add this. - LLM chooses between `service_agent` (via hero_logic MCP) and direct service tool calls and built-in tools. Starting on Phase 1.
Author
Owner

Phase 1 implemented on development (unstaged — ready for review).

Changes

crates/hero_agent/src/mcp_client.rs

  • New Transport::Http { url } variant alongside existing Socket and Stdio.
  • url field in McpServerConfig with ${VAR} / ${VAR:-default} env expansion (so the same config works across dev / CI / tfgrid).
  • http_post() helper using the already-present reqwest dep.
  • tokio::task_local! FORWARDED_HEADERS that carries an Arc<HashMap<String,String>>. Both unix_post and http_post read it and attach headers on every outbound MCP call. Stdio transport ignores it (subprocess has no HTTP surface).
  • Unit tests for env expansion.

crates/hero_agent_server/src/routes.rs

  • rpc_endpoint and chat now take axum::http::HeaderMap. They extract X-Hero-Context / X-Hero-Claims / X-Forwarded-Prefix (lowercased, axum-style) and scope them into FORWARDED_HEADERS for the duration of the request.
  • Because tokio::spawn doesn't carry task-locals across the boundary, rpc_chat and chat snapshot the scope before spawning and re-scope inside the spawned task, so mcp.call_tool inside handle_message sees the headers.

examples/mcp.json — example config showing the hero_router /mcp/{service} pattern for hero_logic, hero_osis, hero_proc.

docs/spec.md — config docs updated with all three transports and a note that url (via hero_router) is preferred for Hero services since it's the only path that propagates claims.

How hero_logic becomes usable

  1. Drop examples/mcp.json into $HERO_AGENT_DATA_DIR/mcp.json (default ~/hero/var/agent/mcp.json).
  2. Start hero_router (default 127.0.0.1:9988) with hero_logic running behind it.
  3. Start hero_agent_server. On startup it loads the MCP config, tools/list hits http://127.0.0.1:9988/mcp/hero_logic, and hero_logic's OpenRPC methods (play_start, play_status, template_list, workflow_from_template, …) appear as MCP tools.
  4. The LLM can now call play_start with workflow_sid=service_agent + input_data to kick off the python-generation workflow, and poll play_status for progress.

Claims flow end-to-end: hero_router → hero_agent (with X-Hero-Context / X-Hero-Claims) → hero_agent → hero_router /mcp/hero_logic (same headers re-attached) → hero_router → hero_logic.

Build status

  • cargo build --workspace
  • cargo test -p hero_agent --lib (59 existing + 3 new)

Not committed — let me know if you want me to squash into a branch + PR, or tweak anything first (e.g. system prompt nudges, auto-discovery from hero_router's service list as Phase 2).

Phase 1 implemented on `development` (unstaged — ready for review). ## Changes **`crates/hero_agent/src/mcp_client.rs`** - New `Transport::Http { url }` variant alongside existing `Socket` and `Stdio`. - `url` field in `McpServerConfig` with `${VAR}` / `${VAR:-default}` env expansion (so the same config works across dev / CI / tfgrid). - `http_post()` helper using the already-present `reqwest` dep. - `tokio::task_local!` `FORWARDED_HEADERS` that carries an `Arc<HashMap<String,String>>`. Both `unix_post` and `http_post` read it and attach headers on every outbound MCP call. Stdio transport ignores it (subprocess has no HTTP surface). - Unit tests for env expansion. **`crates/hero_agent_server/src/routes.rs`** - `rpc_endpoint` and `chat` now take `axum::http::HeaderMap`. They extract `X-Hero-Context` / `X-Hero-Claims` / `X-Forwarded-Prefix` (lowercased, axum-style) and scope them into `FORWARDED_HEADERS` for the duration of the request. - Because `tokio::spawn` doesn't carry task-locals across the boundary, `rpc_chat` and `chat` snapshot the scope before spawning and re-scope inside the spawned task, so `mcp.call_tool` inside `handle_message` sees the headers. **`examples/mcp.json`** — example config showing the hero_router `/mcp/{service}` pattern for `hero_logic`, `hero_osis`, `hero_proc`. **`docs/spec.md`** — config docs updated with all three transports and a note that `url` (via hero_router) is preferred for Hero services since it's the only path that propagates claims. ## How `hero_logic` becomes usable 1. Drop `examples/mcp.json` into `$HERO_AGENT_DATA_DIR/mcp.json` (default `~/hero/var/agent/mcp.json`). 2. Start `hero_router` (default 127.0.0.1:9988) with `hero_logic` running behind it. 3. Start `hero_agent_server`. On startup it loads the MCP config, `tools/list` hits `http://127.0.0.1:9988/mcp/hero_logic`, and hero_logic's OpenRPC methods (`play_start`, `play_status`, `template_list`, `workflow_from_template`, …) appear as MCP tools. 4. The LLM can now call `play_start` with `workflow_sid=service_agent` + `input_data` to kick off the python-generation workflow, and poll `play_status` for progress. Claims flow end-to-end: `hero_router → hero_agent` (with `X-Hero-Context` / `X-Hero-Claims`) → `hero_agent → hero_router /mcp/hero_logic` (same headers re-attached) → `hero_router → hero_logic`. ## Build status - `cargo build --workspace` ✅ - `cargo test -p hero_agent --lib` ✅ (59 existing + 3 new) Not committed — let me know if you want me to squash into a branch + PR, or tweak anything first (e.g. system prompt nudges, auto-discovery from hero_router's service list as Phase 2).
timur closed this issue 2026-04-13 09:30:36 +00:00
Author
Owner

Landed in development as 573dac2.

Built on the existing HTTP MCP transport (cf9ca97) — this adds hero context header forwarding and env-var expansion for url, so pointing an mcpServers entry at http://${HERO_ROUTER_HOST:-127.0.0.1}:${HERO_ROUTER_PORT:-9988}/mcp/hero_logic now works end-to-end with claims propagating via X-Hero-Context / X-Hero-Claims / X-Forwarded-Prefix.

See examples/mcp.json for the router-fronted config pattern.

Closing — Phase 2 (auto-discovery of services from hero_router, system-prompt nudges) can be separate follow-up issues.

Landed in `development` as `573dac2`. Built on the existing HTTP MCP transport (cf9ca97) — this adds hero context header forwarding and env-var expansion for `url`, so pointing an `mcpServers` entry at `http://${HERO_ROUTER_HOST:-127.0.0.1}:${HERO_ROUTER_PORT:-9988}/mcp/hero_logic` now works end-to-end with claims propagating via `X-Hero-Context` / `X-Hero-Claims` / `X-Forwarded-Prefix`. See `examples/mcp.json` for the router-fronted config pattern. Closing — Phase 2 (auto-discovery of services from hero_router, system-prompt nudges) can be separate follow-up issues.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
lhumina_code/hero_agent#2
No description provided.