v3 — typed I/O, modal editing, step-level runs, toolbar layout #2

Open
opened 2026-04-14 07:18:03 +00:00 by timur · 5 comments
Owner

Why

The authoring UX still has rough edges after v1/v2:

  • Node cards are visually tall (inline textareas) but the cytoscape hit-box is smaller than the rendered card, so edge arrows dead-end under the card instead of meeting the edge.
  • You can't scan a workflow at a glance — each step is a wall of input fields.
  • I/O is untyped: workflow inputs are {{placeholders}} scraped from prompts; outputs are raw strings and routing lives in free-form string expressions. No way to say "this step outputs a list of selected services" and branch on outputs.services contains 'hero_books'.
  • Model dropdown shows ~3 entries despite aibroker having an OpenRouter key → full catalog isn't reaching the UI.
  • No way to test a single step in isolation.
  • Left palette column steals horizontal canvas space.

Decisions (aligned)

  • Type system: full JSON Schema subset. Inputs/outputs are JSON Schemas; we get a schema editor UI per node.
  • Output formats v1: raw text, JSON, TOML. (YAML deferred.)
  • Per-step play: ephemeral. Runs live in a modal; no persistent Play record.
  • Aibroker model list: investigate first — look at what models.list actually returns on this instance, whether provider catalogs are being merged, whether OpenRouter /models is exposed — then decide whether to fix upstream (aibroker) or in the hero_logic_ui proxy.

Changes

1. Tight cards + edit-in-modal

  • Fixed compact card height (~160px).
  • Card contents: title • description • input chips (name:type) • output chip (type · format) • model/retry/timeout tags.
  • Pencil icon on each long-form field (system prompt, user prompt, script) opens a modal with a real code editor (monospace textarea + autosize + apply/cancel footer).
  • View-mode cards open the same modal read-only.
  • Card width/height = cytoscape node w/h via box-sizing: border-box. Edge arrows terminate on the card edge again.

2. Typed inputs + outputs on NodeConfig

Add to the oschema:

NodeConfig {
  ...existing fields...
  input_schema:  str  // JSON Schema describing this node's inputs
  output_schema: str  // JSON Schema for parsed output
  output_format: str  // "json" | "toml" | "text" | "raw"
}
  • AI nodes: prompt template references {{inputs.foo}}; the engine substitutes from parsed workflow input.
  • Engine parses each completed action's output_data according to output_format. On success, a parsed JSON value is stored; on failure, only the raw text is carried.
  • Downstream edges get access to parsed output via {{outputs.field}}.
  • Workflow inputs = aggregate of root action nodes' input schemas. Start-play form auto-generates from that.

3. Condition builder on edges

When the source node declares an output schema, the edge drawer shows:

[output field ▾] [operator ▾] [value]

followed by a "+ AND" button and a "Raw expression" escape hatch. Field dropdown is populated from the source's declared outputs.

4. Step-level play

  • Per-node ▶ button on the card.
  • Modal layout: left = typed input form (widget per type: text, textarea, list, number, checkbox), right = output preview (formatted per the declared output_format), bottom = live logs streamed from hero_proc.
  • New RPC: node_play(workflow_sid: str, node_id: str, input_data: str) -> NodeRun. Creates a one-shot hero_proc job scoped to that step, streams logs via polling.
  • No persistent Play record is created.

5. Full model list

Investigation plan:

  1. Call models.list against aibroker manually; log what comes back.
  2. Inspect aibroker source for catalog merging / OpenRouter integration.
  3. If upstream fix is small, patch aibroker; otherwise add a secondary fetch in hero_logic_ui's /api/aibroker/models proxy.
  4. Group models by provider in the dropdown.

6. Top toolbar for step insertion

  • Move palette from left column to a horizontal toolbar above the canvas.
  • Toolbar groups: Steps (AI/Python/Bash/Rhai), Control (Transform/Wait/Human input), View (zoom/fit/re-layout).
  • Left meta sidebar (title/description/plays) stays.

Phases

  1. UX fundamentals — tight cards, cytoscape↔CSS sizing fix, field edit modals, top toolbar. No schema change.
  2. Typed inputs — schema addition, start-play form generation from inputs, per-step play modal (left form / right output / bottom logs).
  3. Typed outputs — engine parsing per format, {{outputs.*}} in edge conditions.
  4. Condition builder — field/operator/value UI over the source's output schema, raw-expression fallback retained.
  5. Model list fix — investigate, then patch aibroker or the UI proxy, group by provider.

Out of scope (for this issue)

  • Full visual schema designer (v1 uses a JSON Schema textarea + validation).
  • YAML parsing.
  • Saving per-step test runs as Plays.
# Why The authoring UX still has rough edges after v1/v2: - Node cards are visually tall (inline textareas) but the cytoscape hit-box is smaller than the rendered card, so edge arrows dead-end under the card instead of meeting the edge. - You can't scan a workflow at a glance — each step is a wall of input fields. - I/O is untyped: workflow inputs are `{{placeholders}}` scraped from prompts; outputs are raw strings and routing lives in free-form string expressions. No way to say "this step outputs a list of selected services" and branch on `outputs.services contains 'hero_books'`. - Model dropdown shows ~3 entries despite aibroker having an OpenRouter key → full catalog isn't reaching the UI. - No way to test a single step in isolation. - Left palette column steals horizontal canvas space. # Decisions (aligned) - **Type system:** full JSON Schema subset. Inputs/outputs are JSON Schemas; we get a schema editor UI per node. - **Output formats v1:** raw text, JSON, TOML. (YAML deferred.) - **Per-step play:** ephemeral. Runs live in a modal; no persistent Play record. - **Aibroker model list:** investigate first — look at what `models.list` actually returns on this instance, whether provider catalogs are being merged, whether OpenRouter `/models` is exposed — then decide whether to fix upstream (aibroker) or in the hero_logic_ui proxy. # Changes ## 1. Tight cards + edit-in-modal - Fixed compact card height (~160px). - Card contents: title • description • input chips (name:type) • output chip (type · format) • model/retry/timeout tags. - Pencil icon on each long-form field (system prompt, user prompt, script) opens a modal with a real code editor (monospace textarea + autosize + apply/cancel footer). - View-mode cards open the same modal read-only. - Card `width`/`height` = cytoscape node `w`/`h` via `box-sizing: border-box`. Edge arrows terminate on the card edge again. ## 2. Typed inputs + outputs on NodeConfig Add to the oschema: ``` NodeConfig { ...existing fields... input_schema: str // JSON Schema describing this node's inputs output_schema: str // JSON Schema for parsed output output_format: str // "json" | "toml" | "text" | "raw" } ``` - AI nodes: prompt template references `{{inputs.foo}}`; the engine substitutes from parsed workflow input. - Engine parses each completed action's `output_data` according to `output_format`. On success, a parsed JSON value is stored; on failure, only the raw text is carried. - Downstream edges get access to parsed output via `{{outputs.field}}`. - Workflow inputs = aggregate of root action nodes' input schemas. Start-play form auto-generates from that. ## 3. Condition builder on edges When the source node declares an output schema, the edge drawer shows: [output field ▾] [operator ▾] [value] followed by a "+ AND" button and a "Raw expression" escape hatch. Field dropdown is populated from the source's declared outputs. ## 4. Step-level play - Per-node ▶ button on the card. - Modal layout: **left** = typed input form (widget per type: text, textarea, list, number, checkbox), **right** = output preview (formatted per the declared output_format), **bottom** = live logs streamed from hero_proc. - New RPC: `node_play(workflow_sid: str, node_id: str, input_data: str) -> NodeRun`. Creates a one-shot hero_proc job scoped to that step, streams logs via polling. - No persistent Play record is created. ## 5. Full model list Investigation plan: 1. Call `models.list` against aibroker manually; log what comes back. 2. Inspect aibroker source for catalog merging / OpenRouter integration. 3. If upstream fix is small, patch aibroker; otherwise add a secondary fetch in hero_logic_ui's `/api/aibroker/models` proxy. 4. Group models by provider in the dropdown. ## 6. Top toolbar for step insertion - Move palette from left column to a horizontal toolbar above the canvas. - Toolbar groups: **Steps** (AI/Python/Bash/Rhai), **Control** (Transform/Wait/Human input), **View** (zoom/fit/re-layout). - Left meta sidebar (title/description/plays) stays. # Phases 1. **UX fundamentals** — tight cards, cytoscape↔CSS sizing fix, field edit modals, top toolbar. No schema change. 2. **Typed inputs** — schema addition, start-play form generation from inputs, per-step play modal (left form / right output / bottom logs). 3. **Typed outputs** — engine parsing per format, `{{outputs.*}}` in edge conditions. 4. **Condition builder** — field/operator/value UI over the source's output schema, raw-expression fallback retained. 5. **Model list fix** — investigate, then patch aibroker or the UI proxy, group by provider. # Out of scope (for this issue) - Full visual schema designer (v1 uses a JSON Schema textarea + validation). - YAML parsing. - Saving per-step test runs as Plays.
Author
Owner

Read through the spec and explored the repo. A few clarifying questions before I start implementation — mostly to lock down scope boundaries for v1 so we don't re-litigate them mid-phase:

JSON Schema support

  1. What subset are we committing to for v1? My proposal: primitives (string/number/integer/boolean), array (with items), object (with properties + required), and enum. No $ref, allOf/oneOf, pattern, format. Agree?
  2. Which validator? Rust side: jsonschema crate. JS side (for start-play form + per-step play): a small hand-rolled form generator that walks the schema — no full validator, rely on server echo for errors. OK?
  3. Editor UI in v1: JSON Schema textarea with syntax highlight + "validate" button (per the out-of-scope note)? Or a minimal field-list editor (name / type / required)? The issue says textarea — confirming.

output_format semantics
4. raw vs text — what's the intended distinction? My read: raw = pass through untouched bytes; text = UTF-8 string, no parsing; json/toml = parse into structured value. Confirm?
5. On parse failure, spec says "only the raw text is carried". Does the node_run still succeed (status=success, parsed=null), or fail? I'd lean success-with-warning so downstream {{outputs.raw}} still works.

Placeholder substitution
6. Migration: existing workflows use {{placeholder}} scraped from prompts. Do we (a) keep both syntaxes working, (b) auto-migrate to {{inputs.*}} on first save, or (c) hard-break and require manual edit? I'd go (a) — treat bare {{foo}} as {{inputs.foo}}.
7. Where does substitution live? Currently the engine passes input_data JSON to hero_proc and the AI interpreter does the substitution. For {{outputs.*}} we need engine-side substitution before dispatch since outputs aren't known to hero_proc. Plan: do all substitution engine-side, send final resolved strings to hero_proc. Confirm.

Per-step play (node_play)
8. Persistence: spec says "ephemeral, no Play record". Do we persist the NodeRun itself, or is even that in-memory only? Log streaming needs some job_id to poll against.
9. Should node_play respect the node's retry/timeout config, or strip those for test runs?
10. Can it run even if the workflow is unsaved (dirty editor state)? I'd say yes — serialize current editor state and pass it in.

Condition builder
11. Operator set for v1: ==, !=, >, >=, <, <=, contains (string/array), in (value in array), exists. Add matches (regex)? Skip it?
12. Value side: literal only, or also {{outputs.*}} from other upstream nodes? Latter is more powerful but needs scope analysis.

UX details
13. Card size: ~160px height mentioned. Width? I'll use 280px unless you have a preference.
14. Toolbar: full-width above canvas (meta sidebar still left), or toolbar spans only the canvas area? Drag-to-add kept, or switch to click-to-add-at-center?
15. Step-play modal: blocks the editor underneath (modal-style), or side panel that lets you keep editing? Spec says "modal" — confirming full-screen overlay.

Model list (phase 5)
16. Investigation first, or just add a secondary OpenRouter /models fetch in the UI proxy and ship it? If the former, I'll open a separate issue with findings before patching. Preference?

I'll wait for answers before writing any code. Will likely tackle in the order of phases in the spec (1 → 5).

Read through the spec and explored the repo. A few clarifying questions before I start implementation — mostly to lock down scope boundaries for v1 so we don't re-litigate them mid-phase: **JSON Schema support** 1. What subset are we committing to for v1? My proposal: primitives (`string`/`number`/`integer`/`boolean`), `array` (with `items`), `object` (with `properties` + `required`), and `enum`. No `$ref`, `allOf`/`oneOf`, `pattern`, `format`. Agree? 2. Which validator? Rust side: `jsonschema` crate. JS side (for start-play form + per-step play): a small hand-rolled form generator that walks the schema — no full validator, rely on server echo for errors. OK? 3. Editor UI in v1: JSON Schema textarea with syntax highlight + "validate" button (per the out-of-scope note)? Or a minimal field-list editor (name / type / required)? The issue says textarea — confirming. **output_format semantics** 4. `raw` vs `text` — what's the intended distinction? My read: `raw` = pass through untouched bytes; `text` = UTF-8 string, no parsing; `json`/`toml` = parse into structured value. Confirm? 5. On parse failure, spec says "only the raw text is carried". Does the node_run still succeed (status=success, parsed=null), or fail? I'd lean success-with-warning so downstream `{{outputs.raw}}` still works. **Placeholder substitution** 6. Migration: existing workflows use `{{placeholder}}` scraped from prompts. Do we (a) keep both syntaxes working, (b) auto-migrate to `{{inputs.*}}` on first save, or (c) hard-break and require manual edit? I'd go (a) — treat bare `{{foo}}` as `{{inputs.foo}}`. 7. Where does substitution live? Currently the engine passes `input_data` JSON to hero_proc and the AI interpreter does the substitution. For `{{outputs.*}}` we need engine-side substitution before dispatch since outputs aren't known to hero_proc. Plan: do all substitution engine-side, send final resolved strings to hero_proc. Confirm. **Per-step play (`node_play`)** 8. Persistence: spec says "ephemeral, no Play record". Do we persist the `NodeRun` itself, or is even that in-memory only? Log streaming needs *some* job_id to poll against. 9. Should `node_play` respect the node's retry/timeout config, or strip those for test runs? 10. Can it run even if the workflow is unsaved (dirty editor state)? I'd say yes — serialize current editor state and pass it in. **Condition builder** 11. Operator set for v1: `==`, `!=`, `>`, `>=`, `<`, `<=`, `contains` (string/array), `in` (value in array), `exists`. Add `matches` (regex)? Skip it? 12. Value side: literal only, or also `{{outputs.*}}` from *other* upstream nodes? Latter is more powerful but needs scope analysis. **UX details** 13. Card size: ~160px height mentioned. Width? I'll use 280px unless you have a preference. 14. Toolbar: full-width above canvas (meta sidebar still left), or toolbar spans only the canvas area? Drag-to-add kept, or switch to click-to-add-at-center? 15. Step-play modal: blocks the editor underneath (modal-style), or side panel that lets you keep editing? Spec says "modal" — confirming full-screen overlay. **Model list (phase 5)** 16. Investigation first, or just add a secondary OpenRouter `/models` fetch in the UI proxy and ship it? If the former, I'll open a separate issue with findings before patching. Preference? I'll wait for answers before writing any code. Will likely tackle in the order of phases in the spec (1 → 5).
Author
Owner

1 - ok, 2 - ok, 3 - form from fields. 4 - ok, 5 - encoding / decoding should fail because its a node execution failure. if someone wants to protect and logic against they can get it raw then pass a script to decode than if doesnt work give it to ai to reprompt etc. 6. a, 7. the hero_logic should template in variables and decode outputs. we keep hero_proc a simple ai executor. 8. yes persist. in fact lets not make node runs ephemeral, probably simpler to keep all unified and treat single node run as a regular play but with one node. 9, yes should respect. goal is to reduce exceptions to keep things uniform. 10 maybe, but sounds like might add complexity. 11 - sure add matches. in fact, connector should also have configuration on which of the outputs from the previous step to map to inputs of next. this also answers 12 - such that maybe a node that came two steps before can still pass output as input via connector. 13: no preference, 14 spans canvas, 15, modal. 16 investigate: hero_logic uses hero_proc uses hero_aibroker. hero aibroker should work perfectly, no hardcoded mock models should be displayed. everything that can be configured for a hero_aibroker can be there in the ai node, and how we want to do this is we can map inputs to values and provide defaults so maybe user can provide a temperature value for flow as input or not and we need this mapping functionality.

1 - ok, 2 - ok, 3 - form from fields. 4 - ok, 5 - encoding / decoding should fail because its a node execution failure. if someone wants to protect and logic against they can get it raw then pass a script to decode than if doesnt work give it to ai to reprompt etc. 6. a, 7. the hero_logic should template in variables and decode outputs. we keep hero_proc a simple ai executor. 8. yes persist. in fact lets not make node runs ephemeral, probably simpler to keep all unified and treat single node run as a regular play but with one node. 9, yes should respect. goal is to reduce exceptions to keep things uniform. 10 maybe, but sounds like might add complexity. 11 - sure add matches. in fact, connector should also have configuration on which of the outputs from the previous step to map to inputs of next. this also answers 12 - such that maybe a node that came two steps before can still pass output as input via connector. 13: no preference, 14 spans canvas, 15, modal. 16 investigate: hero_logic uses hero_proc uses hero_aibroker. hero aibroker should work perfectly, no hardcoded mock models should be displayed. everything that can be configured for a hero_aibroker can be there in the ai node, and how we want to do this is we can map inputs to values and provide defaults so maybe user can provide a temperature value for flow as input or not and we need this mapping functionality.
Author
Owner

All 5 phases implemented and deployed. Summary + aibroker investigation below.

Phase 1 — UX fundamentals

  • Left palette removed; horizontal toolbar above the canvas (Steps / Control / View groups).
  • Compact cards (300×180 AI, 300×160 script kinds, smaller for orchestration). Strict box-sizing: border-box; overflow: hidden + CSS size = cy node size → edge arrows terminate on the visible card edge.
  • Long-form fields (system prompt, user prompt, script) collapsed to preview chips with pencil icons opening a Bootstrap modal with a monospace textarea.
  • Model / temp / timeout / retry render as tag chips on a bottom row.
  • Node drawer re-enabled for structured fields.

Phase 2 — Typed inputs + AI param mapping

  • OSchema: NodeConfig gained input_schema, output_schema, output_format (string JSON Schema + format enum: raw|text|json|toml).
  • Engine templating rebuilt: {{inputs.X}}, {{outputs.X}}, dotted paths ({{outputs.foo.bar}}), legacy {{X}} falls through to inputs.X.
  • build_node_input produces structured ctx: { inputs, outputs, ...legacy flat keys }.
  • Server aggregates workflow-level input_schema on every save — union of root nodes' input schemas, persisted on Workflow.
  • Drawer: field-list schema editor (name / type / required ✓) for input_schema and output_schema; output_format as a select.
  • Start-play modal: schema-driven form with per-type widgets (string / integer / number / boolean / array / enum), falls back to legacy placeholder scraping for old workflows, then to raw JSON.
  • AI param mapping: if resolved input contains model / temperature / max_tokens, those override the action's defaults — so a workflow input can drive temperature across a branch.

Phase 3 — Typed outputs + parsing

  • Engine parses each action's stdout per output_format. json / toml parse failures flip the node to Failed with the parser error + raw output as the error message. raw / text carry the string through.
  • Successfully-parsed values are re-serialized as canonical JSON in NodeRun.output_data so downstream {{outputs.*}} resolves against structured data rather than double-escaped strings.

Phase 4 — Condition builder + cross-hop connectors

  • OSchema: DataMapping gained source_node — a connector can now pull a field from any upstream ancestor, not just the direct parent.
  • Engine: build_node_input honors source_node; falls back to edge's from_node when empty (back-compat).
  • Condition evaluator rewritten. Operators: ==, !=, >, >=, <, <=, contains, in, matches, exists. Clauses joined by AND. Bare field = truthy check. Dotted paths resolve against the ctx (so outputs.foo.bar works).
  • Edge drawer: Builder / Raw toggle. Builder renders [field ▾][op ▾][value] rows with + AND; fields populate from the source node's declared output_schema via a <datalist>; raw toggle retains the freeform textarea.
  • Connector mapping rows got a source-node dropdown listing all upstream ancestors of the target node.
  • matches currently falls back to substring for patterns with regex metacharacters — the regex crate wasn't pulled in to keep the dep tree tight. Real regex can ride in when needed.

Phase 5 — Step-level play + aibroker (partial)

  • New service method: node_play(workflow_sid, node_id, input_data) -> Play. Creates a normal Play with workflow_sid pointing at the parent and exactly one NodeRun — so it shows up in the workflow's play list, and play_status / node_logs work unchanged (per your 'unify, don't make ephemeral' answer).
  • Respects the node's retry_count / retry_delay_secs / timeout_secs exactly like a full play.
  • button on each action card (edit mode only). Click → modal with: left = input form generated from the node's input_schema, right = output preview, bottom = hero_proc logs streamed via polling every 800 ms.
  • Requires the workflow to be saved — unsaved editor state isn't allowed (per your answer; avoids scope creep).

aibroker investigation (Q16)

Confirmed the /api/aibroker/models proxy forwards whatever aibroker returns verbatim — no caching or hardcoded mocks in hero_logic_ui.

Live response from hero_aibroker::models.list on this host:

claude-sonnet    (anthropic/claude-sonnet-4.5 via openrouter)
gpt-4o-mini      (openai/gpt-4o-mini via openrouter)
gpt-4o           (openai/gpt-4o via openrouter)

That's 3 models — matching your earlier observation. The OpenRouter catalog (~300 models) isn't being merged in. The gap lives in hero_aibroker itself, not the hero_logic_ui proxy. Recommendation: open a separate ticket against hero_aibroker along the lines of 'pull full OpenRouter /models catalog when an OpenRouter key is configured, expose them under the same models.list response'. Once that lands, the grouping-by-provider UX in the AI node's model dropdown is a ~10-line change here — worth deferring until the catalog actually has multiple providers to group.

The mappable-param side of the answer (temperature / max_tokens / model as workflow inputs) is already live via Phase 2's engine override.

Deployed

hero_logic service restarted on this host with all phases active. Ready for visual review.

All 5 phases implemented and deployed. Summary + aibroker investigation below. ## Phase 1 — UX fundamentals ✅ - Left palette removed; horizontal toolbar above the canvas (Steps / Control / View groups). - Compact cards (300×180 AI, 300×160 script kinds, smaller for orchestration). Strict `box-sizing: border-box; overflow: hidden` + CSS size = cy node size → edge arrows terminate on the visible card edge. - Long-form fields (system prompt, user prompt, script) collapsed to preview chips with pencil icons opening a Bootstrap modal with a monospace textarea. - Model / temp / timeout / retry render as tag chips on a bottom row. - Node drawer re-enabled for structured fields. ## Phase 2 — Typed inputs + AI param mapping ✅ - OSchema: `NodeConfig` gained `input_schema`, `output_schema`, `output_format` (string JSON Schema + format enum: `raw|text|json|toml`). - Engine templating rebuilt: `{{inputs.X}}`, `{{outputs.X}}`, dotted paths (`{{outputs.foo.bar}}`), legacy `{{X}}` falls through to `inputs.X`. - `build_node_input` produces structured ctx: `{ inputs, outputs, ...legacy flat keys }`. - Server aggregates workflow-level `input_schema` on every save — union of root nodes' input schemas, persisted on `Workflow`. - Drawer: field-list schema editor (name / type / required ✓) for input_schema and output_schema; `output_format` as a select. - Start-play modal: schema-driven form with per-type widgets (string / integer / number / boolean / array / enum), falls back to legacy placeholder scraping for old workflows, then to raw JSON. - AI param mapping: if resolved input contains `model` / `temperature` / `max_tokens`, those override the action's defaults — so a workflow input can drive temperature across a branch. ## Phase 3 — Typed outputs + parsing ✅ - Engine parses each action's stdout per `output_format`. `json` / `toml` parse failures flip the node to `Failed` with the parser error + raw output as the error message. `raw` / `text` carry the string through. - Successfully-parsed values are re-serialized as canonical JSON in `NodeRun.output_data` so downstream `{{outputs.*}}` resolves against structured data rather than double-escaped strings. ## Phase 4 — Condition builder + cross-hop connectors ✅ - OSchema: `DataMapping` gained `source_node` — a connector can now pull a field from any upstream ancestor, not just the direct parent. - Engine: `build_node_input` honors `source_node`; falls back to edge's `from_node` when empty (back-compat). - Condition evaluator rewritten. Operators: `==`, `!=`, `>`, `>=`, `<`, `<=`, `contains`, `in`, `matches`, `exists`. Clauses joined by `AND`. Bare field = truthy check. Dotted paths resolve against the ctx (so `outputs.foo.bar` works). - Edge drawer: `Builder` / `Raw` toggle. Builder renders `[field ▾][op ▾][value]` rows with `+ AND`; fields populate from the source node's declared output_schema via a `<datalist>`; raw toggle retains the freeform textarea. - Connector mapping rows got a source-node dropdown listing all upstream ancestors of the target node. - `matches` currently falls back to substring for patterns with regex metacharacters — the `regex` crate wasn't pulled in to keep the dep tree tight. Real regex can ride in when needed. ## Phase 5 — Step-level play + aibroker ✅ (partial) - New service method: `node_play(workflow_sid, node_id, input_data) -> Play`. Creates a normal `Play` with `workflow_sid` pointing at the parent and exactly one `NodeRun` — so it shows up in the workflow's play list, and `play_status` / `node_logs` work unchanged (per your 'unify, don't make ephemeral' answer). - Respects the node's `retry_count` / `retry_delay_secs` / `timeout_secs` exactly like a full play. - `▶` button on each action card (edit mode only). Click → modal with: left = input form generated from the node's `input_schema`, right = output preview, bottom = hero_proc logs streamed via polling every 800 ms. - Requires the workflow to be saved — unsaved editor state isn't allowed (per your answer; avoids scope creep). ### aibroker investigation (Q16) Confirmed the `/api/aibroker/models` proxy forwards whatever aibroker returns verbatim — no caching or hardcoded mocks in hero_logic_ui. Live response from `hero_aibroker::models.list` on this host: ``` claude-sonnet (anthropic/claude-sonnet-4.5 via openrouter) gpt-4o-mini (openai/gpt-4o-mini via openrouter) gpt-4o (openai/gpt-4o via openrouter) ``` That's 3 models — matching your earlier observation. The OpenRouter catalog (~300 models) isn't being merged in. The gap lives in **hero_aibroker itself**, not the hero_logic_ui proxy. Recommendation: open a separate ticket against `hero_aibroker` along the lines of 'pull full OpenRouter `/models` catalog when an OpenRouter key is configured, expose them under the same `models.list` response'. Once that lands, the grouping-by-provider UX in the AI node's model dropdown is a ~10-line change here — worth deferring until the catalog actually has multiple providers to group. The mappable-param side of the answer (temperature / max_tokens / model as workflow inputs) is already live via Phase 2's engine override. ## Deployed `hero_logic` service restarted on this host with all phases active. Ready for visual review.
Author
Owner

aibroker investigation — root cause of the 3-model limit

Found the gap. Two separate problems in hero_aibroker:

1. Model filtering is too strict

  • Catalog is loaded from a static YAML at ~/hero/var/hero_aibroker/modelsconfig.yml — not fetched from OpenRouter live. The file has ~55 model entries.
  • At registry load (hero_aibroker_lib/src/registry/mod.rs:54-78), each model's backends are filtered against the configured ProviderMap — a provider instance must match the backend's provider string. If no backend matches, the whole model is silently skipped.
  • Only 3 of the 55 have a matching backend against the one openrouter provider instance that gets created when OPENROUTER_API_KEY is set. The rest expect provider instances we never create.

2. system_prompt is ignored by the chat handler

  • hero_aibroker_server/src/api/chat.rs:22-52 respects temperature, top_p, max_tokens, stream.
  • It does not apply the system_prompt field to the outgoing LLM request. That field is only used in the admin UI.
  • This directly breaks hero_logic AI nodes — our system-prompt textarea is set, sent through hero_proc → hero_aibroker, and then silently dropped.

Recommended fixes (separate hero_aibroker tickets)

  • Auto-register OpenRouter's full /models catalog (~300 entries) when an OpenRouter key is present, instead of relying only on the hand-curated YAML. Or at minimum, rewrite the filter to accept any model whose backend provider name exists in the ProviderMap regardless of the backend's model_id matching specific instances.
  • Wire system_prompt through the chat API: prepend it as a system-role message to the messages array. Straightforward change at the chat handler.

Hero_proc UI staleness — mentioned in passing but same ticket flavour: hero_proc's job detail UI should surface the ai_config fields (temp/max_tokens/system_prompt) from the job's ActionSpec. Currently it only shows the raw script. Separate hero_proc UI ticket.

**aibroker investigation — root cause of the 3-model limit** Found the gap. Two separate problems in `hero_aibroker`: **1. Model filtering is too strict** - Catalog is loaded from a static YAML at `~/hero/var/hero_aibroker/modelsconfig.yml` — not fetched from OpenRouter live. The file has ~55 model entries. - At registry load (`hero_aibroker_lib/src/registry/mod.rs:54-78`), each model's backends are filtered against the configured `ProviderMap` — a provider instance must match the backend's `provider` string. If no backend matches, the whole model is silently skipped. - Only 3 of the 55 have a matching backend against the one `openrouter` provider instance that gets created when `OPENROUTER_API_KEY` is set. The rest expect provider instances we never create. **2. `system_prompt` is ignored by the chat handler** - `hero_aibroker_server/src/api/chat.rs:22-52` respects `temperature`, `top_p`, `max_tokens`, `stream`. - It does **not** apply the `system_prompt` field to the outgoing LLM request. That field is only used in the admin UI. - This directly breaks hero_logic AI nodes — our system-prompt textarea is set, sent through hero_proc → hero_aibroker, and then silently dropped. **Recommended fixes (separate hero_aibroker tickets)** - Auto-register OpenRouter's full `/models` catalog (~300 entries) when an OpenRouter key is present, instead of relying only on the hand-curated YAML. Or at minimum, rewrite the filter to accept any model whose backend provider name exists in the ProviderMap regardless of the backend's `model_id` matching specific instances. - Wire `system_prompt` through the chat API: prepend it as a `system`-role message to the messages array. Straightforward change at the chat handler. **Hero_proc UI staleness** — mentioned in passing but same ticket flavour: hero_proc's job detail UI should surface the ai_config fields (temp/max_tokens/system_prompt) from the job's ActionSpec. Currently it only shows the raw script. Separate hero_proc UI ticket.
Author
Owner

Refactor plan — nodes as pointers to hero_proc actions

Filed as lhumina_code/hero_proc#47 on the hero_proc side. The idea is to strip action configuration out of the workflow card entirely: a node becomes just { input_schema, output_schema, action_name, action_context }, and the card shows a read-only preview + Configure in hero_proc ↗ button. New nodes render as dashed placeholders with a Create in hero_proc ↗ jump-out. Single + Step in the toolbar — interpreter lives on the action, not the node.

The blocker

Workflow inputs need a route to the action's script/prompt/ai_config. Today hero_logic does its own {{var}} rendering in engine/node_executors.rs::execute_action (fetch ActionSpec → mutate → re-push to job.create). Once nodes become pointers, that indirection doesn't fit — we want plain job.create(action_name, inputs).

Proposed fix (on hero_proc)

  1. input_schema: Option<String> on ActionSpec — JSON Schema, same shape hero_logic already uses.
  2. inputs: Option<Value> on JobCreateInput.
  3. hero_proc does the {{var}} / {{nested.path}} templating at dispatch time, across script, ai_config.system_prompt, ai_config.model / temperature / max_tokens, and env values. Port interpolate_template from here.
  4. Shell interpreters: also expose inputs as HERO_INPUT_* env vars for scripts that don't use {{}}.
  5. Validate inputs against input_schema at job.create, reject on mismatch.

The hero_aibroker system_prompt drop is still a separate blocker — without that, AI actions can't actually use the templated system prompt even after the hero_proc work lands.

Once #47 ships, this side becomes

  • Delete ai_config / script / interpreter editors from the card body.
  • Replace with action-preview + Configure ↗.
  • Swap execute_action to job.create(action_name, context, inputs: <ctx>).
  • Drop per-interpreter palette buttons; single + Step.
  • Remove interpolate_template (lives in hero_proc then).

Estimated ~40% surface-area cut on the editor. Waiting on hero_proc#47.

**Refactor plan — nodes as pointers to hero_proc actions** Filed as https://forge.ourworld.tf/lhumina_code/hero_proc/issues/47 on the hero_proc side. The idea is to strip action configuration out of the workflow card entirely: a node becomes just `{ input_schema, output_schema, action_name, action_context }`, and the card shows a read-only preview + `Configure in hero_proc ↗` button. New nodes render as dashed placeholders with a `Create in hero_proc ↗` jump-out. Single `+ Step` in the toolbar — interpreter lives on the action, not the node. **The blocker** Workflow inputs need a route to the action's script/prompt/ai_config. Today hero_logic does its own `{{var}}` rendering in `engine/node_executors.rs::execute_action` (fetch ActionSpec → mutate → re-push to `job.create`). Once nodes become pointers, that indirection doesn't fit — we want plain `job.create(action_name, inputs)`. **Proposed fix (on hero_proc)** 1. `input_schema: Option<String>` on `ActionSpec` — JSON Schema, same shape hero_logic already uses. 2. `inputs: Option<Value>` on `JobCreateInput`. 3. hero_proc does the `{{var}}` / `{{nested.path}}` templating at dispatch time, across `script`, `ai_config.system_prompt`, `ai_config.model` / `temperature` / `max_tokens`, and `env` values. Port `interpolate_template` from here. 4. Shell interpreters: also expose inputs as `HERO_INPUT_*` env vars for scripts that don't use `{{}}`. 5. Validate inputs against `input_schema` at `job.create`, reject on mismatch. The hero_aibroker `system_prompt` drop is still a separate blocker — without that, AI actions can't actually use the templated system prompt even after the hero_proc work lands. **Once #47 ships, this side becomes** - Delete `ai_config` / `script` / interpreter editors from the card body. - Replace with action-preview + `Configure ↗`. - Swap `execute_action` to `job.create(action_name, context, inputs: <ctx>)`. - Drop per-interpreter palette buttons; single `+ Step`. - Remove `interpolate_template` (lives in hero_proc then). Estimated ~40% surface-area cut on the editor. Waiting on hero_proc#47.
Sign in to join this conversation.
No labels
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_logic#2
No description provided.