Python client generation and AI agent execution mode for all services #18

Open
opened 2026-04-07 13:48:32 +00:00 by timur · 2 comments
Owner

Summary

Add two new capabilities to hero_router:

  1. Auto-generated Python clients for every discovered service (from OpenRPC spec)
  2. AI agent execution mode that takes a user prompt, generates a Python script using the client, executes it, and retries on error

hero_router already discovers all services, caches their OpenRPC specs, and proxies RPC/MCP/admin traffic. These two features make it the central gateway for programmatic and AI-driven service interaction.


Feature 1: Python Client Generation

How It Works

When hero_router discovers a service and fetches its OpenRPC spec, it also generates a typed Python client. This is deterministic (template-based, no LLM needed).

Generation Pipeline

Service discovered → OpenRPC spec cached → Python client generated → cached + served
                                         → Python interface file generated (for LLM consumption)

What Gets Generated

For each service, two files:

1. {service}_client.py — Full typed Python client

import json
import socket
import http.client

class HeroBooksClient:
    """Auto-generated client for hero_books (v0.2.0)
    
    Generated from OpenRPC spec. Do not edit manually.
    """
    
    def __init__(self, socket_path: str = None):
        self.socket_path = socket_path or os.path.expanduser(
            "~/hero/var/sockets/hero_books/rpc.sock"
        )
        self._id = 0
    
    def _call(self, method: str, params: dict = None) -> dict:
        """Send JSON-RPC 2.0 request over Unix socket."""
        self._id += 1
        payload = {"jsonrpc": "2.0", "method": method, "params": params or {}, "id": self._id}
        # HTTP-over-Unix-socket using http.client + socket override
        ...
    
    def book_list(self, workspace: str = "default") -> dict:
        """List all books in a workspace.
        
        Args:
            workspace: Workspace name (default: "default")
        
        Returns:
            dict with 'books' array
        """
        return self._call("book.list", {"workspace": workspace})
    
    def book_get(self, name: str, workspace: str = "default") -> dict:
        """Get a specific book by name."""
        return self._call("book.get", {"name": name, "workspace": workspace})
    
    # ... one method per OpenRPC method, with docstrings from spec

2. {service}_interface.py — Lightweight stub for LLM consumption

# hero_books interface — 12 methods
# Generated from OpenRPC spec v0.2.0

# book.list(workspace: str = "default") -> {books: [{name, title, path}]}
# book.get(name: str, workspace: str = "default") -> {name, title, chapters: [...]}
# book.search(query: str) -> {results: [{book, chapter, snippet, score}]}
# ...

Serving

Access Method Endpoint
HTTP download GET /:service/python/client.py
HTTP download GET /:service/python/interface.py
RPC method router.python_client {service} → returns client source
RPC method router.python_interface {service} → returns interface source
Filesystem cache ~/.hero/var/router/python/{service}_client.py

Auto-Update

  • Client is regenerated when the OpenRPC spec hash changes (detected during periodic scan)
  • Version comment in generated file tracks spec hash
  • No manual rebuild needed

Implementation Options

Option A: Pure template-based (recommended for client)

  • Parse OpenRPC JSON in Rust
  • Walk methods array, extract params/result schemas
  • Emit Python code from Rust string templates
  • Zero dependencies, deterministic, fast
  • Handles: method names, params with types, return types, docstrings, default values

Option B: LLM-assisted generation

  • Feed spec to LLM, ask for Python client
  • More flexible but non-deterministic
  • Overkill for structured spec → code translation
  • Reserve LLM for the agent feature instead

Option C: Runtime translation (no pre-generation)

  • Python client is a thin generic wrapper
  • Methods are created dynamically from spec at import time
  • client = HeroClient("hero_books") → fetches spec → creates methods
  • Pros: always up to date, single generic client
  • Cons: runtime dependency on router being available, slower startup

Recommendation: Option A for the static client + Option C as a convenience wrapper. The static client is reliable and works offline. The dynamic wrapper is nice for interactive/notebook use.


Feature 2: AI Agent Execution Mode

How It Works

User sends a prompt describing what they want to do with a service. hero_router:

  1. Loads the service's Python client + interface
  2. Sends prompt + interface to LLM
  3. LLM generates a Python script that uses the client
  4. Executes the script via uv (Python runner)
  5. On error: feeds error output back to LLM → generates corrected script → retries
  6. Returns final result

Endpoint

POST /:service/agent
Content-Type: application/json

{
  "prompt": "List all books and find any that mention 'kubernetes'",
  "max_retries": 3,
  "model": "auto"
}

Or via JSON-RPC:

{"jsonrpc": "2.0", "method": "router.agent.run", "params": {
  "service": "hero_books",
  "prompt": "List all books and find any that mention 'kubernetes'",
  "max_retries": 3
}, "id": 1}

Response

{
  "success": true,
  "result": "Found 3 books mentioning kubernetes:\n- Infrastructure Guide (ch. 4)\n- ...",
  "script": "from hero_books_client import HeroBooksClient\n...",
  "attempts": 1,
  "duration_ms": 2340
}

Execution Pipeline

User prompt
    ↓
Load {service}_interface.py (lightweight spec for LLM context)
    ↓
System prompt: "Generate a Python script using the provided client to accomplish the user's goal.
               Import from {service}_client. The client connects via Unix socket automatically.
               Print results to stdout. Handle errors gracefully."
    ↓
LLM generates Python script
    ↓
Write script to temp file alongside {service}_client.py
    ↓
Execute: `uv run --no-project script.py` (30s timeout)
    ↓
┌─ Success? → return stdout
└─ Error? → feed stderr + script back to LLM
              → "The script failed with this error. Fix it and return the corrected script."
              → retry (up to max_retries)

LLM Integration

  • Uses hero_aibroker for LLM calls (already available in the ecosystem)
  • Fallback: direct API call to configured provider
  • Model selection: auto (fast model for simple tasks, capable model for complex)

Prior Art

This pattern is already proven in hero_aibroker/crates/mcp/mcp_hero/:

  • codegen.rs:generate_client_from_spec() — Python client from OpenRPC
  • codegen.rs:generate_code() — LLM script generation with error context for retries
  • executor.rs:execute_with_retry() — Python execution with LLM-driven error recovery
  • executor.rs — manages Python venv at ~/.hero/var/aibroker/python

The implementation should port and adapt this logic into hero_router.


Implementation Plan

Phase 1: Python Client Generator

  1. Add python_codegen module to herolib_router
  2. Template-based client generation from OpenRPC spec
  3. Interface file generation (lightweight stubs)
  4. Cache in ~/.hero/var/router/python/
  5. Regenerate on spec hash change during scan
  6. Serve via HTTP endpoints and RPC methods

Phase 2: Agent Executor

  1. Add agent module to herolib_router
  2. Port executor logic from mcp_hero (Python venv management, uv runner)
  3. Port codegen logic (LLM prompt construction, code extraction)
  4. Add retry loop with error-fed regeneration
  5. Wire up HTTP endpoint POST /:service/agent
  6. Wire up RPC method router.agent.run
  7. Add to router admin UI (agent tab per service)

Phase 3: Admin UI Integration

  1. Agent tab in router dashboard per service
  2. Prompt input → execute → show result
  3. Show generated script, execution log, retry history
  4. Download Python client button

  • hero_aibroker mcp_hero crate — prior art for Python codegen + execution
  • hero_shrimp agent loop — retry and checkpoint patterns
  • hero_claude — Python ACP server pattern
  • hero_agent — Rust native agent with tool execution
## Summary Add two new capabilities to hero_router: 1. **Auto-generated Python clients** for every discovered service (from OpenRPC spec) 2. **AI agent execution mode** that takes a user prompt, generates a Python script using the client, executes it, and retries on error hero_router already discovers all services, caches their OpenRPC specs, and proxies RPC/MCP/admin traffic. These two features make it the central gateway for programmatic and AI-driven service interaction. --- ## Feature 1: Python Client Generation ### How It Works When hero_router discovers a service and fetches its OpenRPC spec, it also generates a typed Python client. This is **deterministic** (template-based, no LLM needed). ### Generation Pipeline ``` Service discovered → OpenRPC spec cached → Python client generated → cached + served → Python interface file generated (for LLM consumption) ``` ### What Gets Generated For each service, two files: **1. `{service}_client.py`** — Full typed Python client ```python import json import socket import http.client class HeroBooksClient: """Auto-generated client for hero_books (v0.2.0) Generated from OpenRPC spec. Do not edit manually. """ def __init__(self, socket_path: str = None): self.socket_path = socket_path or os.path.expanduser( "~/hero/var/sockets/hero_books/rpc.sock" ) self._id = 0 def _call(self, method: str, params: dict = None) -> dict: """Send JSON-RPC 2.0 request over Unix socket.""" self._id += 1 payload = {"jsonrpc": "2.0", "method": method, "params": params or {}, "id": self._id} # HTTP-over-Unix-socket using http.client + socket override ... def book_list(self, workspace: str = "default") -> dict: """List all books in a workspace. Args: workspace: Workspace name (default: "default") Returns: dict with 'books' array """ return self._call("book.list", {"workspace": workspace}) def book_get(self, name: str, workspace: str = "default") -> dict: """Get a specific book by name.""" return self._call("book.get", {"name": name, "workspace": workspace}) # ... one method per OpenRPC method, with docstrings from spec ``` **2. `{service}_interface.py`** — Lightweight stub for LLM consumption ```python # hero_books interface — 12 methods # Generated from OpenRPC spec v0.2.0 # book.list(workspace: str = "default") -> {books: [{name, title, path}]} # book.get(name: str, workspace: str = "default") -> {name, title, chapters: [...]} # book.search(query: str) -> {results: [{book, chapter, snippet, score}]} # ... ``` ### Serving | Access Method | Endpoint | |---|---| | HTTP download | `GET /:service/python/client.py` | | HTTP download | `GET /:service/python/interface.py` | | RPC method | `router.python_client {service}` → returns client source | | RPC method | `router.python_interface {service}` → returns interface source | | Filesystem cache | `~/.hero/var/router/python/{service}_client.py` | ### Auto-Update - Client is regenerated when the OpenRPC spec hash changes (detected during periodic scan) - Version comment in generated file tracks spec hash - No manual rebuild needed ### Implementation Options **Option A: Pure template-based (recommended for client)** - Parse OpenRPC JSON in Rust - Walk methods array, extract params/result schemas - Emit Python code from Rust string templates - Zero dependencies, deterministic, fast - Handles: method names, params with types, return types, docstrings, default values **Option B: LLM-assisted generation** - Feed spec to LLM, ask for Python client - More flexible but non-deterministic - Overkill for structured spec → code translation - Reserve LLM for the agent feature instead **Option C: Runtime translation (no pre-generation)** - Python client is a thin generic wrapper - Methods are created dynamically from spec at import time - `client = HeroClient("hero_books")` → fetches spec → creates methods - Pros: always up to date, single generic client - Cons: runtime dependency on router being available, slower startup Recommendation: **Option A for the static client + Option C as a convenience wrapper**. The static client is reliable and works offline. The dynamic wrapper is nice for interactive/notebook use. --- ## Feature 2: AI Agent Execution Mode ### How It Works User sends a prompt describing what they want to do with a service. hero_router: 1. Loads the service's Python client + interface 2. Sends prompt + interface to LLM 3. LLM generates a Python script that uses the client 4. Executes the script via `uv` (Python runner) 5. On error: feeds error output back to LLM → generates corrected script → retries 6. Returns final result ### Endpoint ``` POST /:service/agent Content-Type: application/json { "prompt": "List all books and find any that mention 'kubernetes'", "max_retries": 3, "model": "auto" } ``` Or via JSON-RPC: ```json {"jsonrpc": "2.0", "method": "router.agent.run", "params": { "service": "hero_books", "prompt": "List all books and find any that mention 'kubernetes'", "max_retries": 3 }, "id": 1} ``` ### Response ```json { "success": true, "result": "Found 3 books mentioning kubernetes:\n- Infrastructure Guide (ch. 4)\n- ...", "script": "from hero_books_client import HeroBooksClient\n...", "attempts": 1, "duration_ms": 2340 } ``` ### Execution Pipeline ``` User prompt ↓ Load {service}_interface.py (lightweight spec for LLM context) ↓ System prompt: "Generate a Python script using the provided client to accomplish the user's goal. Import from {service}_client. The client connects via Unix socket automatically. Print results to stdout. Handle errors gracefully." ↓ LLM generates Python script ↓ Write script to temp file alongside {service}_client.py ↓ Execute: `uv run --no-project script.py` (30s timeout) ↓ ┌─ Success? → return stdout └─ Error? → feed stderr + script back to LLM → "The script failed with this error. Fix it and return the corrected script." → retry (up to max_retries) ``` ### LLM Integration - Uses hero_aibroker for LLM calls (already available in the ecosystem) - Fallback: direct API call to configured provider - Model selection: auto (fast model for simple tasks, capable model for complex) ### Prior Art This pattern is already proven in `hero_aibroker/crates/mcp/mcp_hero/`: - `codegen.rs:generate_client_from_spec()` — Python client from OpenRPC - `codegen.rs:generate_code()` — LLM script generation with error context for retries - `executor.rs:execute_with_retry()` — Python execution with LLM-driven error recovery - `executor.rs` — manages Python venv at `~/.hero/var/aibroker/python` The implementation should port and adapt this logic into hero_router. --- ## Implementation Plan ### Phase 1: Python Client Generator 1. Add `python_codegen` module to herolib_router 2. Template-based client generation from OpenRPC spec 3. Interface file generation (lightweight stubs) 4. Cache in `~/.hero/var/router/python/` 5. Regenerate on spec hash change during scan 6. Serve via HTTP endpoints and RPC methods ### Phase 2: Agent Executor 1. Add `agent` module to herolib_router 2. Port executor logic from mcp_hero (Python venv management, uv runner) 3. Port codegen logic (LLM prompt construction, code extraction) 4. Add retry loop with error-fed regeneration 5. Wire up HTTP endpoint `POST /:service/agent` 6. Wire up RPC method `router.agent.run` 7. Add to router admin UI (agent tab per service) ### Phase 3: Admin UI Integration 1. Agent tab in router dashboard per service 2. Prompt input → execute → show result 3. Show generated script, execution log, retry history 4. Download Python client button --- ## Related - hero_aibroker `mcp_hero` crate — prior art for Python codegen + execution - hero_shrimp agent loop — retry and checkpoint patterns - hero_claude — Python ACP server pattern - hero_agent — Rust native agent with tool execution
Author
Owner

Implementation Strategy

Starting work on branch development_18.

Phase 1: Python Client Generator (template-based, no LLM)

Key design decision: Pure template-based generation in Rust (Option A from the issue). This is deterministic, fast, zero-dependency, and doesn't require hero_aibroker to be running. The mcp_hero prior art uses LLM for client generation, but since OpenRPC specs are highly structured, template-based is the right approach here.

Module: crates/hero_router/src/python_codegen.rs

Client generation pipeline:

  1. Parse OpenRPC JSON → extract info (title, version, description) and methods array
  2. For each method: extract name, params (with JSON Schema types), result schema, description
  3. Emit Python class with:
    • __init__(socket_path) with default path to service socket
    • _call(method, params) using HTTP-over-Unix-socket (stdlib only: http.client + socket)
    • One typed method per RPC method with docstrings and type hints
    • JSON-RPC 2.0 error response handling
  4. Type mapping: JSON Schema → Python type hints (string→str, integer→int, boolean→bool, array→list, object→dict)

Interface generation: Lightweight comment-based stub file, one line per method — designed for LLM context windows.

Caching: Files cached at ~/.hero/var/router/python/{service}_client.py and {service}_interface.py. Regenerated when spec hash changes (detected during scanner refresh).

Endpoints:

  • GET /:service/python/client.py → serve cached client
  • GET /:service/python/interface.py → serve cached interface
  • RPC: router.python_client {service} and router.python_interface {service}

Phase 2: AI Agent Executor

Module: crates/hero_router/src/server/agent.rs

Ported from mcp_hero patterns:

  • Python venv management at ~/.hero/var/router/python/venv/ using uv
  • Script execution with 30s timeout, stdout/stderr capture
  • LLM integration via hero_aibroker Unix socket ($HERO_SOCKET_DIR/hero_aibroker/rpc.sock) using ai.chat method
  • Retry loop: execute → on failure, feed error to LLM → regenerate → retry (up to max_retries)

Endpoints:

  • POST /:service/agent with {prompt, max_retries}
  • RPC: router.agent.run {service, prompt, max_retries}

Response: {success, result, script, attempts, duration_ms, error}

Implementation Order

  1. python_codegen.rs — core generation logic
  2. Integration with scanner/cache + HTTP/RPC endpoints
  3. agent.rs — executor + LLM integration
  4. Update openrpc.json with new methods
  5. Test against hero_router's own OpenRPC spec
## Implementation Strategy Starting work on branch `development_18`. ### Phase 1: Python Client Generator (template-based, no LLM) **Key design decision:** Pure template-based generation in Rust (Option A from the issue). This is deterministic, fast, zero-dependency, and doesn't require hero_aibroker to be running. The mcp_hero prior art uses LLM for client generation, but since OpenRPC specs are highly structured, template-based is the right approach here. **Module:** `crates/hero_router/src/python_codegen.rs` **Client generation pipeline:** 1. Parse OpenRPC JSON → extract `info` (title, version, description) and `methods` array 2. For each method: extract name, params (with JSON Schema types), result schema, description 3. Emit Python class with: - `__init__(socket_path)` with default path to service socket - `_call(method, params)` using HTTP-over-Unix-socket (stdlib only: `http.client` + `socket`) - One typed method per RPC method with docstrings and type hints - JSON-RPC 2.0 error response handling 4. Type mapping: JSON Schema → Python type hints (`string→str`, `integer→int`, `boolean→bool`, `array→list`, `object→dict`) **Interface generation:** Lightweight comment-based stub file, one line per method — designed for LLM context windows. **Caching:** Files cached at `~/.hero/var/router/python/{service}_client.py` and `{service}_interface.py`. Regenerated when spec hash changes (detected during scanner refresh). **Endpoints:** - `GET /:service/python/client.py` → serve cached client - `GET /:service/python/interface.py` → serve cached interface - RPC: `router.python_client {service}` and `router.python_interface {service}` ### Phase 2: AI Agent Executor **Module:** `crates/hero_router/src/server/agent.rs` Ported from mcp_hero patterns: - Python venv management at `~/.hero/var/router/python/venv/` using `uv` - Script execution with 30s timeout, stdout/stderr capture - LLM integration via hero_aibroker Unix socket (`$HERO_SOCKET_DIR/hero_aibroker/rpc.sock`) using `ai.chat` method - Retry loop: execute → on failure, feed error to LLM → regenerate → retry (up to max_retries) **Endpoints:** - `POST /:service/agent` with `{prompt, max_retries}` - RPC: `router.agent.run {service, prompt, max_retries}` **Response:** `{success, result, script, attempts, duration_ms, error}` ### Implementation Order 1. `python_codegen.rs` — core generation logic 2. Integration with scanner/cache + HTTP/RPC endpoints 3. `agent.rs` — executor + LLM integration 4. Update `openrpc.json` with new methods 5. Test against hero_router's own OpenRPC spec
Author
Owner

Implementation Complete — Phase 1 & Phase 2

Branch: development_18 — pushed to remote.

Phase 1: Python Client Generator

New file: crates/hero_router/src/python_codegen.rs (540 lines)

Template-based generation (no LLM required):

  • generate_python_client() — Full typed Python client class
    • HTTP-over-Unix-socket transport using stdlib only (http.client + socket)
    • One typed method per RPC method with docstrings and type hints
    • JSON Schema → Python type mapping (string→str, integer→int, etc.)
    • Required params before optional with defaults
    • JSON-RPC 2.0 error handling
  • generate_python_interface() — Lightweight stub for LLM context
    • One comment line per method with signature and return type
    • Compact enough for LLM prompts
  • Cache at ~/.hero/var/router/python/ with hash-based invalidation
  • Auto-regeneration during scanner refresh
  • 5 unit tests passing

Endpoints:

  • GET /:service/python/client.py → serve typed client
  • GET /:service/python/interface.py → serve interface stub
  • RPC: router.python_client {service} → return client source
  • RPC: router.python_interface {service} → return interface source

Phase 2: AI Agent Executor

New file: crates/hero_router/src/server/agent.rs (370 lines)

Ported from mcp_hero patterns:

  • Python venv management (uv preferred, python3 -m venv fallback)
  • Script execution with 30s timeout, stdout/stderr capture
  • LLM integration via hero_aibroker Unix socket (ai.chat method)
  • Retry loop: execute → on failure, feed error to LLM → regenerate → retry
  • Stages client library in scripts dir for import

Endpoints:

  • POST /:service/agent with body {"prompt": "...", "max_retries": 3}
  • RPC: router.agent.run {service, prompt, max_retries}
  • Response: {success, result, script, attempts, duration_ms, error}

Modified files

  • lib.rs — registered python_codegen module
  • server/mod.rs — registered agent module
  • scanner.rs — added refresh_python_cache() after each scan
  • server/routes.rs — added python and agent webname handlers
  • server/rpc.rs — added 3 new RPC methods
  • static/openrpc.json — added method specs for all 3 new methods

Build status

  • Clean compilation (no warnings)
  • All 5 tests passing
  • Full build verified
## Implementation Complete — Phase 1 & Phase 2 Branch: `development_18` — pushed to remote. ### Phase 1: Python Client Generator **New file:** `crates/hero_router/src/python_codegen.rs` (540 lines) Template-based generation (no LLM required): - `generate_python_client()` — Full typed Python client class - HTTP-over-Unix-socket transport using stdlib only (`http.client` + `socket`) - One typed method per RPC method with docstrings and type hints - JSON Schema → Python type mapping (`string→str`, `integer→int`, etc.) - Required params before optional with defaults - JSON-RPC 2.0 error handling - `generate_python_interface()` — Lightweight stub for LLM context - One comment line per method with signature and return type - Compact enough for LLM prompts - Cache at `~/.hero/var/router/python/` with hash-based invalidation - Auto-regeneration during scanner refresh - 5 unit tests passing **Endpoints:** - `GET /:service/python/client.py` → serve typed client - `GET /:service/python/interface.py` → serve interface stub - RPC: `router.python_client {service}` → return client source - RPC: `router.python_interface {service}` → return interface source ### Phase 2: AI Agent Executor **New file:** `crates/hero_router/src/server/agent.rs` (370 lines) Ported from mcp_hero patterns: - Python venv management (`uv` preferred, `python3 -m venv` fallback) - Script execution with 30s timeout, stdout/stderr capture - LLM integration via hero_aibroker Unix socket (`ai.chat` method) - Retry loop: execute → on failure, feed error to LLM → regenerate → retry - Stages client library in scripts dir for import **Endpoints:** - `POST /:service/agent` with body `{"prompt": "...", "max_retries": 3}` - RPC: `router.agent.run {service, prompt, max_retries}` - Response: `{success, result, script, attempts, duration_ms, error}` ### Modified files - `lib.rs` — registered `python_codegen` module - `server/mod.rs` — registered `agent` module - `scanner.rs` — added `refresh_python_cache()` after each scan - `server/routes.rs` — added `python` and `agent` webname handlers - `server/rpc.rs` — added 3 new RPC methods - `static/openrpc.json` — added method specs for all 3 new methods ### Build status - Clean compilation (no warnings) - All 5 tests passing - Full build verified
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_router#18
No description provided.