Phase 2: Examples use typed flow inputs #6

Open
opened 2026-04-19 16:32:20 +00:00 by timur · 2 comments
Owner

Context

Phase 1 (#5) introduces typed flow inputs on Workflow. Currently Example.input_data is a free-form JSON string — whatever the user typed into the run form as raw JSON. Once inputs are declared with types, examples should be structured fixtures: one value per declared input.

Goal

Each Example is a named set of values, one per flow input. Loading an example populates the play-start form field-by-field. Saving an example captures the current form values.

Depends on

  • #5 must be merged first (needs Workflow.inputs: [FlowInput] and typed run form)

OSchema changes

File: crates/hero_logic/schemas/logic/logic.oschema

Replace input_data on Example:

# Example is a named input fixture for a workflow. [rootobject]
Example = {
    sid: str
    workflow_sid: str               # parent workflow
    workflow_version_sid: str       # optional pin to a specific version
    name: str
    description: str
    input_values: {str: str}        # map from flow input name to serialized JSON value
    created_at: int
    updated_at: int
    # REMOVED: input_data (free-form JSON string)
}

Why {str: str} with serialized JSON values: OSchema's map-of-Value is more complex than needed; storing each value as a JSON-encoded string keeps it simple and the Python/JS sides parse per-input by looking at the input's declared type.

Code changes

crates/hero_logic/src/logic/server/rpc.rs

  • example_create(workflow_sid, name, description, input_values) -> Example: validates that provided input_values keys are a subset of the workflow's declared input names; each value can be parsed according to the input's declared type. Unknown keys are dropped with a warning.
  • example_apply(example_sid) -> {input_values: {str: str}}: fetches the example and returns its values — useful for the UI to load them.
  • Existing play_start optionally accepts example_sid instead of input_data; if given, resolves the example's input_values and composes them into the input_data JSON.

Migration

For existing Example records with input_data:

  • Detect legacy shape on read; parse the JSON; best-effort map each top-level key to input_values[key] (values serialized back to JSON strings).
  • Migrated examples write back on next save.
  • Provide one-shot example_migrate_legacy(example_sid) RPC.

UI changes

File: crates/hero_logic_ui/templates/workflow_editor.html + JS

  • "Save as example" modal: instead of a free-form JSON textarea, capture values from the current play-start form fields (one per declared input). Save as input_values: {name: JSON.stringify(value)}.
  • "Load example" button on each example row: populates play-start form fields from example.input_values (parse per input type).
  • Example card / list item: shows name + a compact preview of input values (e.g., prompt="ping hero_proc", num_tests=3).
  • Example validation: if the workflow's input schema changed after the example was created, show a warning icon indicating stale inputs (e.g., input foo no longer exists, or required input bar was added after).

Acceptance criteria

  • Example record has input_values: {str: str} field; input_data removed
  • Creating an example from the UI captures structured per-input values
  • Loading an example populates each play-start form field correctly
  • Legacy examples (with input_data) still load (auto-migrated on read)
  • Validation warning shown when an example's keys don't match the workflow's current input schema
  • Example count per workflow shown in the editor

Out of scope

  • Example versioning (pinning an example to a specific workflow version — could be done via workflow_version_sid but not strictly required for this phase)
  • Import/export examples as .json files
  • Bulk example operations
## Context Phase 1 (#5) introduces typed flow inputs on Workflow. Currently `Example.input_data` is a free-form JSON string — whatever the user typed into the run form as raw JSON. Once inputs are declared with types, examples should be structured fixtures: one value per declared input. ## Goal Each Example is a named set of values, one per flow input. Loading an example populates the play-start form field-by-field. Saving an example captures the current form values. ## Depends on - **#5 must be merged first** (needs `Workflow.inputs: [FlowInput]` and typed run form) ## OSchema changes File: `crates/hero_logic/schemas/logic/logic.oschema` Replace `input_data` on `Example`: ``` # Example is a named input fixture for a workflow. [rootobject] Example = { sid: str workflow_sid: str # parent workflow workflow_version_sid: str # optional pin to a specific version name: str description: str input_values: {str: str} # map from flow input name to serialized JSON value created_at: int updated_at: int # REMOVED: input_data (free-form JSON string) } ``` Why `{str: str}` with serialized JSON values: OSchema's map-of-Value is more complex than needed; storing each value as a JSON-encoded string keeps it simple and the Python/JS sides parse per-input by looking at the input's declared `type`. ## Code changes ### `crates/hero_logic/src/logic/server/rpc.rs` - `example_create(workflow_sid, name, description, input_values) -> Example`: validates that provided `input_values` keys are a subset of the workflow's declared input names; each value can be parsed according to the input's declared `type`. Unknown keys are dropped with a warning. - `example_apply(example_sid) -> {input_values: {str: str}}`: fetches the example and returns its values — useful for the UI to load them. - Existing `play_start` optionally accepts `example_sid` instead of `input_data`; if given, resolves the example's `input_values` and composes them into the input_data JSON. ### Migration For existing Example records with `input_data`: - Detect legacy shape on read; parse the JSON; best-effort map each top-level key to `input_values[key]` (values serialized back to JSON strings). - Migrated examples write back on next save. - Provide one-shot `example_migrate_legacy(example_sid)` RPC. ## UI changes File: `crates/hero_logic_ui/templates/workflow_editor.html` + JS - **"Save as example" modal**: instead of a free-form JSON textarea, capture values from the current play-start form fields (one per declared input). Save as `input_values: {name: JSON.stringify(value)}`. - **"Load example" button** on each example row: populates play-start form fields from `example.input_values` (parse per input type). - **Example card / list item**: shows name + a compact preview of input values (e.g., `prompt="ping hero_proc", num_tests=3`). - **Example validation**: if the workflow's input schema changed after the example was created, show a warning icon indicating stale inputs (e.g., input `foo` no longer exists, or required input `bar` was added after). ## Acceptance criteria - [ ] `Example` record has `input_values: {str: str}` field; `input_data` removed - [ ] Creating an example from the UI captures structured per-input values - [ ] Loading an example populates each play-start form field correctly - [ ] Legacy examples (with `input_data`) still load (auto-migrated on read) - [ ] Validation warning shown when an example's keys don't match the workflow's current input schema - [ ] Example count per workflow shown in the editor ## Out of scope - Example versioning (pinning an example to a specific workflow version — could be done via `workflow_version_sid` but not strictly required for this phase) - Import/export examples as .json files - Bulk example operations
Author
Owner

Backend landed: 671d026

Phase 2 schema + RPC changes are in. UI changes (per-input form fields in save/load modals, structured preview) remain.

What's live

OSchema:

  • Example.input_values: JSON-serialized {input_name: value} map
  • Example.input_data: kept for legacy (auto-migrated on fetch)
  • Example.workflow_version_sid: optional pin

RPC methods:

  • example_upsert(sid, workflow_sid, workflow_version_sid, name, description, input_values_json) — validates keys against Workflow.inputs, rejects unknowns with a clear error
  • example_fetch(sid) — auto-migrates legacy examples (copies input_datainput_values when the latter is empty)
  • example_to_input_data(example_sid) — new helper that resolves input_values into a play_start-ready input JSON, merging in declared defaults for any unset optional inputs

Verified

example_upsert({prompt:'ping hero_proc'})  sid ok 
example_upsert({prompt:'x', unknown_key:'y'})  "Unknown input keys: ['unknown_key']" 
example_to_input_data(sid)  '{\"prompt\":\"ping hero_proc\"}' 
example_fetch(sid)  returns input_values + workflow_version_sid 

Remaining for this issue

  • Schema: input_values, workflow_version_sid
  • RPC: validation, auto-migration, to_input_data helper
  • UI: "Save as example" modal captures per-input form values
  • UI: "Load example" button fills play-start fields per input type
  • UI: example row shows structured preview (prompt=..., num_tests=...)
  • UI: stale-schema warning when example keys don't match current inputs

Moving on to Phase 3 (#7) next — benchmark flow.

## Backend landed: 671d026 Phase 2 schema + RPC changes are in. UI changes (per-input form fields in save/load modals, structured preview) remain. ### What's live **OSchema:** - `Example.input_values`: JSON-serialized `{input_name: value}` map - `Example.input_data`: kept for legacy (auto-migrated on fetch) - `Example.workflow_version_sid`: optional pin **RPC methods:** - `example_upsert(sid, workflow_sid, workflow_version_sid, name, description, input_values_json)` — validates keys against `Workflow.inputs`, rejects unknowns with a clear error - `example_fetch(sid)` — auto-migrates legacy examples (copies `input_data` → `input_values` when the latter is empty) - `example_to_input_data(example_sid)` — new helper that resolves `input_values` into a `play_start`-ready input JSON, merging in declared defaults for any unset optional inputs ### Verified ```python example_upsert({prompt:'ping hero_proc'}) → sid ok ✓ example_upsert({prompt:'x', unknown_key:'y'}) → "Unknown input keys: ['unknown_key']" ✓ example_to_input_data(sid) → '{\"prompt\":\"ping hero_proc\"}' ✓ example_fetch(sid) → returns input_values + workflow_version_sid ✓ ``` ### Remaining for this issue - [x] Schema: `input_values`, `workflow_version_sid` - [x] RPC: validation, auto-migration, to_input_data helper - [ ] **UI: "Save as example" modal captures per-input form values** - [ ] **UI: "Load example" button fills play-start fields per input type** - [ ] **UI: example row shows structured preview (`prompt=..., num_tests=...`)** - [ ] **UI: stale-schema warning when example keys don't match current inputs** Moving on to Phase 3 (#7) next — benchmark flow.
Author
Owner

Phase 2 UI landed (commit c062bbf)

Example save/load now uses structured input_values:

  • Save-as-example modal renders typed form fields (one per declared FlowInput) when the workflow has declared inputs; falls back to the legacy capture-current-start-node-run_inputs behavior for pre-Phase-2 workflows.
  • example_upsert now submits input_values_json + workflow_version_sid (full Phase 2 signature) so saved Examples bind to declared inputs and pin to the version that was current at save time.
  • loadExampleInputs prefers ex.input_values over legacy ex.input_data when applying values into the start-node run_inputs.
  • Extracted collectTypedInputValues(inputs, prefix) JS helper that both the Start-play and Save-as-example modals share — single source of truth for type coercion (string/number/integer/boolean/object/array) and required-field validation.

Verified end-to-end:

  • example_upsert("ping","gpt-4o-mini") against workflow 00dz/version 00e0 → saved Example 00e1 with input_values="{...}" and the legacy input_data left empty.
  • example_to_input_data(00e1){"model":"gpt-4o-mini","prompt":"ping"} — ready to feed into play_start.
  • example_fetch(00e1) returns both input_values and workflow_version_sid so the UI can warn on stale-schema mismatch.

Still open for this issue:

  • Detect and visibly flag when a loaded Example was saved against a version whose declared inputs have since changed (mismatch warning in the sidebar).
  • Bulk-apply: one-click "run all examples against current version" (bridges to Phase 3 benchmarking).
### Phase 2 UI landed (commit c062bbf) Example save/load now uses structured `input_values`: - **Save-as-example modal** renders typed form fields (one per declared `FlowInput`) when the workflow has declared inputs; falls back to the legacy capture-current-start-node-run_inputs behavior for pre-Phase-2 workflows. - `example_upsert` now submits `input_values_json` + `workflow_version_sid` (full Phase 2 signature) so saved Examples bind to declared inputs and pin to the version that was current at save time. - `loadExampleInputs` prefers `ex.input_values` over legacy `ex.input_data` when applying values into the start-node `run_inputs`. - Extracted `collectTypedInputValues(inputs, prefix)` JS helper that both the Start-play and Save-as-example modals share — single source of truth for type coercion (string/number/integer/boolean/object/array) and required-field validation. **Verified end-to-end:** - `example_upsert("ping","gpt-4o-mini")` against workflow `00dz`/version `00e0` → saved Example `00e1` with `input_values="{...}"` and the legacy `input_data` left empty. - `example_to_input_data(00e1)` → `{"model":"gpt-4o-mini","prompt":"ping"}` — ready to feed into `play_start`. - `example_fetch(00e1)` returns both `input_values` and `workflow_version_sid` so the UI can warn on stale-schema mismatch. **Still open for this issue:** - Detect and visibly flag when a loaded Example was saved against a version whose declared inputs have since changed (mismatch warning in the sidebar). - Bulk-apply: one-click "run all examples against current version" (bridges to Phase 3 benchmarking).
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#6
No description provided.