feat(#39): Logic/LogicVersion/LogicExample/Play foundation (phases 1–3 + cleanup) #40

Open
timur wants to merge 15 commits from feat/39-logic-model into development
Owner

Closes / refs

Partially implements #39 — schema, SDK, and runtime foundation. UI rebuild + transport auto-trace + pause-chain propagation + static parser deferred to follow-up PRs (see bottom).

What landed (commit per phase)

Phase 1 — schema reshape (5b5a36b)

  • WorkflowLogic, WorkflowVersionLogicVersion, ExampleLogicExample
  • inputs / outputs moved from Logic onto LogicVersion so different versions can evolve their signatures independently
  • Dropped entirely: Span, SpanKind, SpanStatus value types; Benchmark + PerRunResult rootobjects; pick_version, span_push, benchmark_* RPCs
  • Play.spans / Play.parent_span_id replaced with Play.parent_play_sid + Play.sub_play_sids — the Play tree now mirrors the call tree
  • flow_library_searchlogic_library_search; full workflow_*logic_* RPC rename
  • span_socket.rs slimmed to a minimal per-Play event listener (log / output / step_output / pause / cost events). The Span concept is gone.
  • python_executor.rs field renames: parent_span_idparent_play_sid, workflow_namelogic_name, workflow_version_sidlogic_version_sid. New HERO_LOGIC_* env vars (HERO_FLOW_* kept as aliases for one release).
  • seed.rs emits the new RPC method names; inputs/outputs go onto LogicVersion
  • Wipe-and-reseed migration — no AST splitter, no data preservation (existing OSIS data is dev-only)

Phase 2 — SDK rename (736c7fa)

  • New logic namespace (alias of flow for one release so existing source keeps loading)
  • New logic.log(text) — emits a {"type":"log",...} event the per-Play listener routes to play.logs
  • Module docstring rewritten to reflect the issue #39 event protocol
  • flow.step / flow.span / flow.current_span / instrument() retained as a back-compat surface; their span events are silently dropped by the new listener (the Span concept is gone)

Phase 3 — runtime: child Plays per logic.invoke (eb06676)

  • Every logic.invoke(name, **kwargs) now registers a child Play row whose parent_play_sid points back to the caller and whose sid is appended to the caller's sub_play_sids. Same subprocess — no fork per sub-Play.
  • _current_play_sid contextvar tracks the currently-executing Play; nested invokes correctly attribute parent.
  • _invoke_sid_cache mirrors the existing entry-function cache so we don't re-do the library-search lookup on every call.
  • New SDK helpers _create_child_play / _append_sub_play_sid / _finalize_child_play use the auto-generated play.set / play.get RPCs (no new server methods needed).
  • Memoization short-circuits when the step_outputs cache holds (parent_path, name, args).
  • All SDK RPC method names updated for the Phase 1 rename.

Phase 10 (partial) — cleanups (0f3ccd7, d10ac9b)

  • Deleted 6 dead admin handlers + helpers flagged by cargo (build_node_preview, fetch_referenced_actions, build_graph_and_panel, play_detail_handler + PlayDetailTemplate, pretty_json_or_string, rpc_call) — net −416 LOC
  • Removed the python_executor::integration_tests module and standalone tests/e2e_create_event.rs (span-based, referenced a service_agent_v3.py that doesn't exist)

Deferred to follow-up PRs

  • Phase 4 — transport-level auto-trace (aibroker token cost lifted onto the current Play's totals)
  • Phase 5 — pause-chain propagation (play_wait returns when any descendant Play enters awaiting_resume)
  • Phase 6 — static Python source parser → flow graph (used by the idle flow view in the new UI)
  • Phase 7 — the full Logic-view + play-bar UI rebuild (left info / middle flow-or-code / right stats + bottom 3-col play bar; dashboard at /)
  • Phase 8 — sub-Logic navigation (/logics/{sid}?play={play_sid} with breadcrumbs)
  • Phase 9 — stats derived from Play queries (drop in the idle right-sidebar card)
  • Phase 10 remainder — delete workflows.html / plays.html / play_detail.html / examples.html templates + their routes, dashboard creation, 301 redirects

The UI templates still reference the old names (workflow_sid etc.) and call old RPC method names over JSON — they compile but will fail at runtime against this new server. The UI rebuild lands those changes coherently rather than patch them piecemeal.

Test plan

  • cargo build — green workspace-wide (2 harmless warnings about backend_online template field, will clear in Phase 7)
  • cargo test --lib -p hero_logic — 29 passed / 0 failed
  • SDK smoke test: import hero_tracing; logic = hero_tracing.logic; @logic(...) def f(...): ... — works standalone (no executor)
  • Server smoke test (post-merge): restart hero_logic, verify seeding creates Logic + LogicVersion records, verify a basic play_start runs to completion
  • UI smoke test — deferred, expected to be broken until Phase 7 lands

🤖 Generated with Claude Code

## Closes / refs Partially implements #39 — schema, SDK, and runtime foundation. UI rebuild + transport auto-trace + pause-chain propagation + static parser deferred to follow-up PRs (see bottom). ## What landed (commit per phase) ### Phase 1 — schema reshape (`5b5a36b`) - `Workflow` → `Logic`, `WorkflowVersion` → `LogicVersion`, `Example` → `LogicExample` - `inputs` / `outputs` moved from `Logic` onto `LogicVersion` so different versions can evolve their signatures independently - Dropped entirely: `Span`, `SpanKind`, `SpanStatus` value types; `Benchmark` + `PerRunResult` rootobjects; `pick_version`, `span_push`, `benchmark_*` RPCs - `Play.spans` / `Play.parent_span_id` replaced with `Play.parent_play_sid` + `Play.sub_play_sids` — the Play tree now mirrors the call tree - `flow_library_search` → `logic_library_search`; full `workflow_*` → `logic_*` RPC rename - `span_socket.rs` slimmed to a minimal per-Play event listener (log / output / step_output / pause / cost events). The Span concept is gone. - `python_executor.rs` field renames: `parent_span_id` → `parent_play_sid`, `workflow_name` → `logic_name`, `workflow_version_sid` → `logic_version_sid`. New `HERO_LOGIC_*` env vars (`HERO_FLOW_*` kept as aliases for one release). - `seed.rs` emits the new RPC method names; `inputs`/`outputs` go onto LogicVersion - **Wipe-and-reseed migration** — no AST splitter, no data preservation (existing OSIS data is dev-only) ### Phase 2 — SDK rename (`736c7fa`) - New `logic` namespace (alias of `flow` for one release so existing source keeps loading) - New `logic.log(text)` — emits a `{"type":"log",...}` event the per-Play listener routes to `play.logs` - Module docstring rewritten to reflect the issue #39 event protocol - `flow.step` / `flow.span` / `flow.current_span` / `instrument()` retained as a back-compat surface; their span events are silently dropped by the new listener (the Span concept is gone) ### Phase 3 — runtime: child Plays per `logic.invoke` (`eb06676`) - Every `logic.invoke(name, **kwargs)` now registers a child Play row whose `parent_play_sid` points back to the caller and whose sid is appended to the caller's `sub_play_sids`. Same subprocess — no fork per sub-Play. - `_current_play_sid` contextvar tracks the currently-executing Play; nested invokes correctly attribute parent. - `_invoke_sid_cache` mirrors the existing entry-function cache so we don't re-do the library-search lookup on every call. - New SDK helpers `_create_child_play` / `_append_sub_play_sid` / `_finalize_child_play` use the auto-generated `play.set` / `play.get` RPCs (no new server methods needed). - Memoization short-circuits when the step_outputs cache holds `(parent_path, name, args)`. - All SDK RPC method names updated for the Phase 1 rename. ### Phase 10 (partial) — cleanups (`0f3ccd7`, `d10ac9b`) - Deleted 6 dead admin handlers + helpers flagged by cargo (`build_node_preview`, `fetch_referenced_actions`, `build_graph_and_panel`, `play_detail_handler` + `PlayDetailTemplate`, `pretty_json_or_string`, `rpc_call`) — net −416 LOC - Removed the `python_executor::integration_tests` module and standalone `tests/e2e_create_event.rs` (span-based, referenced a `service_agent_v3.py` that doesn't exist) ## Deferred to follow-up PRs - **Phase 4** — transport-level auto-trace (aibroker token cost lifted onto the current Play's totals) - **Phase 5** — pause-chain propagation (`play_wait` returns when any descendant Play enters `awaiting_resume`) - **Phase 6** — static Python source parser → flow graph (used by the idle flow view in the new UI) - **Phase 7** — the full Logic-view + play-bar UI rebuild (left info / middle flow-or-code / right stats + bottom 3-col play bar; dashboard at `/`) - **Phase 8** — sub-Logic navigation (`/logics/{sid}?play={play_sid}` with breadcrumbs) - **Phase 9** — stats derived from Play queries (drop in the idle right-sidebar card) - **Phase 10 remainder** — delete `workflows.html` / `plays.html` / `play_detail.html` / `examples.html` templates + their routes, dashboard creation, 301 redirects The UI templates still reference the old names (`workflow_sid` etc.) and call old RPC method names over JSON — they compile but will fail at runtime against this new server. The UI rebuild lands those changes coherently rather than patch them piecemeal. ## Test plan - [x] `cargo build` — green workspace-wide (2 harmless warnings about `backend_online` template field, will clear in Phase 7) - [x] `cargo test --lib -p hero_logic` — 29 passed / 0 failed - [x] SDK smoke test: `import hero_tracing; logic = hero_tracing.logic; @logic(...) def f(...): ...` — works standalone (no executor) - [ ] Server smoke test (post-merge): restart hero_logic, verify seeding creates Logic + LogicVersion records, verify a basic `play_start` runs to completion - [ ] UI smoke test — **deferred**, expected to be broken until Phase 7 lands 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Rename and reshape the schema per issue #39:

- Workflow → Logic, WorkflowVersion → LogicVersion, Example → LogicExample
- Move inputs/outputs from Logic to LogicVersion (per-version signatures)
- Drop Span/SpanKind/SpanStatus value types entirely
- Drop Benchmark/PerRunResult rootobjects (stats derived from queries
  over Plays in Phase 9)
- Replace Play.spans / Play.parent_span_id with parent_play_sid +
  sub_play_sids; every logic.invoke(...) will create a child Play
- Drop pick_version + span_push + flow_library_search → renamed to
  logic_library_search; benchmark_* RPCs gone

Rust side:
- rpc.rs handlers ported to the new method names
- span_socket.rs slimmed to a minimal per-Play event listener:
  log → play.logs, output → play.output_data, step_output →
  play.step_outputs, pause → play.pending_resumes, cost → play
  cost aggregates. The Span concept is gone.
- python_executor.rs field renames: parent_span_id → parent_play_sid,
  workflow_name → logic_name, workflow_version_sid → logic_version_sid.
  HERO_LOGIC_* env vars (HERO_FLOW_* kept as aliases for one release).
- seed.rs emits the new logic.set / logicversion.set / logic_example_*
  RPCs; inputs/outputs go onto LogicVersion now.
- build.rs: drop the FlowField alias patch (no back-compat needed
  since existing in-dev OSIS data is wiped).

Wipe-and-reseed migration: no AST splitter, no data preservation.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Introduce `logic` as the canonical SDK namespace (alias of `flow`
  for one release so existing python_source keeps loading).
- Add `logic.log(text)` — appends a line to the current Play's
  `logs` field via the new `{"type":"log",...}` event the per-Play
  event listener understands.
- Rewrite the SDK module docstring to reflect issue #39's per-Play
  event model (Span concept gone; structure via parent_play_sid /
  sub_play_sids); document the five event types the wire protocol
  now carries.

`flow.step` / `flow.span` / `flow.current_span` / `instrument()` are
retained as a back-compat surface; the events they emit are silently
dropped by the new event listener since the Span concept is gone.
A follow-up commit (Phase 3) will rewrite seed_flows and the
runtime so `logic.invoke(...)` creates child Plays.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Make every `logic.invoke(name, **kwargs)` register a child Play row
whose `parent_play_sid` points back to the caller and whose sid is
appended to the caller's `sub_play_sids`. Execution stays in-process
— no subprocess fork per sub-Play (only the top-level Play runs in
its own subprocess, as before). The Play tree mirrors the call tree.

SDK changes (hero_tracing.py):
- `_current_play_sid` contextvar tracks the currently-executing Play
  sid; `_ambient_play_sid()` reads it (or falls back to the env var
  for the top-level Play).
- `_invoke_sid_cache` mirrors `_invoke_cache` and remembers the
  (logic_sid, version_sid) for each resolved name so we don't redo
  the library-search lookup on every call.
- `_create_child_play` directly writes a new Play row via the auto-
  generated `play.set` RPC (no new RPC method needed). Best-effort:
  RPC failures fall back to running the child without registration
  so a flaky storage doesn't break the parent's run.
- `_append_sub_play_sid` does the read-modify-write to keep
  parent.sub_play_sids consistent.
- `_finalize_child_play` closes the child row with output_data,
  status, error_message, and timing.
- `_Flow.invoke()` orchestrates: create row → push contextvar → run
  body → pop contextvar → finalize. Memoization short-circuits when
  the step_outputs cache has the (parent_path, name, args) key.
- All SDK RPC method names updated for the Phase 1 rename:
  workflow.get → logic.get, logicservice.flow_library_search →
  logicservice.logic_library_search, logicservice.workflow_version_fetch
  → logicservice.logic_version_fetch.

The `spawn=True` escape hatch is retained for back-compat; new code
should not pass it (every invoke is now visible in the Play tree by
default, which was the whole point of `spawn=True` previously).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Delete six unused functions flagged by cargo's dead_code warning:

- routes.rs: build_node_preview, fetch_referenced_actions,
  build_graph_and_panel — DAG-era helpers superseded by the Python
  flow model.
- routes.rs: play_detail_handler + PlayDetailTemplate +
  pretty_json_or_string — orphaned by an earlier route cleanup; the
  template file (play_detail.html) is kept for the moment since the
  Phase 7 UI rebuild will replace it wholesale.
- seed.rs: rpc_call — generic RPC helper with no remaining callers.

Net -416 LOC of dead code, two warnings down. Remaining warnings:
two duplicates of `field 'backend_online' is never read` in template
DTOs, harmless and will be cleared in the Phase 7 UI rebuild.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The python_executor's `integration_tests` module and the standalone
`tests/e2e_create_event.rs` driver both asserted on `Play.spans`
and referenced a `service_agent_v3.py` flow file that no longer
exists. With the schema reshape both are unbuildable as-is; rather
than maintain a half-broken test surface they're removed wholesale
and will be re-written in the Phase 7 UI rebuild against the new
Play-tree model.

Also rename the `sanitized_env` test that checks the parent-play
env var so it tests the new `HERO_PARENT_PLAY_SID` name (not the
old `HERO_FLOW_PARENT_SPAN`).

`cargo test --lib -p hero_logic`: 29 passed / 0 failed.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Drop the auto-trace plan. Per discussion: don't lift cost from RPC
responses at the transport layer; instead service-client wrapper Logics
(model_call, embed, …) report their own consumption explicitly.

- Schema: drop Play.total_tokens_prompt / total_tokens_completion /
  total_cost_usd. Add Play.stats (JSON string) with a documented
  reusable shape: {tokens_prompt, tokens_completion, cost_usd,
  duration_ms, calls}. Missing keys = 0 to aggregators; extra keys
  preserved.
- Event protocol: drop `cost`, add `stats` (last-write-wins per key).
- SDK: logic.record_stats(tokens_prompt=…, cost_usd=…, …) merges into
  the current Play's stats field.
- Seed model_call.py: rewrite to use `logic`, lazy-load aibroker
  models.config pricing once per subprocess, compute cost_usd, return
  it in outputs AND call logic.record_stats() so subtree summaries
  pick it up.

The UI sums stats across sub_play_sids on read (Phase 9) — Plays don't
auto-aggregate children.
`play_wait` now walks `sub_play_sids` recursively each poll and stays
parked when any descendant is in `awaiting_resume`, even if the named
Play itself is otherwise terminal. Without this, a background-spawned
child's pause would be invisible to a parent's wait.

- Add `subtree_has_awaiting_resume()` helper (bounded walk, cycle-safe).
- Update `play_wait` exit condition: terminal AND no awaiting
  descendants.
- Test: parent Success + grandchild AwaitingResume → play_wait blocks
  for the full timeout instead of returning immediately.
Adds an embedded Python AST parser invoked via `python3 -c` with the
source piped on stdin. Emits a JSON tree of recognised invoke/rpc/pause
nodes plus loop/if/try containers so the UI's flow view can render a
structural backdrop when no Play is overlaid (or anchor sub-Play nodes
to source lines when one is).

- `engine/source_parser.rs`: pure-Rust wrapper that spawns python3,
  bounded 5s timeout, returns a JSON string. Error cases produce a
  `{"error": "...", "entry":"", "body":[]}` envelope so the UI doesn't
  have to special-case anything.
- Schema: new `logic_parse_graph(version_sid: str) -> str` RPC.
- Handler in `rpc.rs`: fetches the LogicVersion and calls the parser.
- Tests: invoke/pause/rpc detection, loop with nested invoke, syntax
  error fallback.

Best-effort detection — chases `logic.invoke`, `ask_user.*`,
`logic.pause`, `logic.log`, `logic.record_stats`, and `Hero*Client(...)
` assignments + their method calls. Aliased imports / dynamic lookups
are missed; authors who want accurate rendering should stick to the
canonical surface.
Wholesale rewrite of hero_logic_admin to the issue-39 two-view surface.

Routes
- /                       new dashboard (Logic cards w/ latest-run + success rate)
- /logics/{sid}           the single Logic view (with optional ?play= overlay)
- /api/plays/{sid}/overlay   live-poll JSON: play + descendants
- /api/logics/{sid}/stats    derived stats over recent Plays of current version
- /workflows*  → 301 to /logics*    (legacy redirect window)
- /plays/{sid} → 301 to /logics/{logic_sid}?play={sid}
- /examples    → 301 to /

Templates (deleted: workflows.html, workflow_editor.html, plays.html,
play_detail.html, examples.html; new: logic_view.html; rewritten: index.html)
- logic_view.html has three regions + bottom three-column play bar exactly
  per the issue spec:
    LEFT  — breadcrumb, title, declared inputs, declared outputs, versions
    MIDDLE — Flow / Code / Split tabs. Flow tab renders the static graph
             from logic_parse_graph and anchors sub-Play nodes to the
             overlaid run's descendants (status + token/cost stats inline).
    RIGHT — stats card. Idle: benchmark over current version. Overlay:
            this Play's status + duration + subtree token/cost totals
            (sums Play.stats across descendants per the new well-known
            stats shape). Refresh + Cancel actions.
    PLAY BAR (left) — input widgets driven by LogicVersion.inputs, Examples
             dropdown, Recent Plays dropdown, ▶ Run button.
    PLAY BAR (middle) — live trace (descendants ordered by started_at) +
             pause-form banners for any awaiting_resume in the chain.
             Pause widgets handle text / number / choice / multi_choice /
             confirm per ResumeRequest.ui.
    PLAY BAR (right) — output_data rendered as one card per declared
             output field; falls back to raw JSON otherwise.

Sub-Logic navigation (issue #39 Phase 8)
- Clicking a sub-Play node in the flow view navigates to
  /logics/{child_logic_sid}?play={child_play_sid}. Breadcrumb at top of
  the left sidebar walks the parent_play_sid chain (server-side).

Stats (issue #39 Phase 9)
- /api/logics/{sid}/stats sums runs / success_pct / p50 duration /
  avg tokens / avg cost across the last 50 Plays of the current version.
- Per-Play overlay sums tokens/cost across the Play subtree by walking
  Play.stats on each descendant.

Cleanup (issue #39 Phase 10 partial)
- All five old templates deleted along with their handlers + DTO structs.
- 301 redirects from every legacy URL.
- Dashboard "+ New Logic" creates a Logic + empty LogicVersion with a
  starter stub and marks it current — operator workflow remains intact.

Build clean, 33/33 lib tests still passing.
Cosmetic + behavioural cleanup that completes the rename in the places
the foundation phases didn't touch:

- examples/pause_resume_demo.py + pause_resume_prefill.py:
  - Switch to logic.* / logicversion.* RPC names (workflow.* gone).
  - Inputs moved from the Logic body onto the LogicVersion body, per
    the issue-39 schema shape.
  - Drop the spans-based assertion (spans no longer exist); the demo
    now verifies replay via step_outputs survival and the prefill
    demo verifies via the entry's output_data.

- seed_flows/service_agent.py, service_code_gen.py, optimize_flow.py:
  - `from hero_tracing import flow` → `import logic`.
  - `@flow(...)` → `@logic(...)`.
  - `flow.X(...)` → `logic.X(...)` across `Failed`, `invoke`, `log`,
    `pause`, `step`, `span`, `current_span`.
  - Docstring/comment mentions left alone — they reference historical
    decorator names for context, not running code.

Not done (deferred to a follow-up, per the issue's handoff note):
  - Splitting seed_flows/service_agent.py into 8 separate Logic
    records. Currently still seeds as one file with 8 @logic-decorated
    helpers — works correctly under the new in-process invoke model,
    but the Play tree groups them under one root Logic.

33/33 lib tests still pass.
Removes the back-compat surface left over from the rename. After this
commit the SDK has one decorator (`@logic`) and one singleton (`logic`);
there is no `flow` / `@flow` / `flow.step` / `flow.span` /
`flow.current_span` / `instrument()` / `_Span` / `_NullSpan` /
`_open_span` / `_open_root_span` / `_bootstrap_run`.

Replaces the per-Span `_current_span` ContextVar with a `_current_frame`
ContextVar that carries only the deterministic call-tree state
(`_LogicFrame.path` / counters) — the per-invocation memoization and
pause-id derivation contract.

Drops `prefill_only` / `prefill_resumes_json` entirely (was a special
headless mode for benchmarks). Pauses always pause; callers — UI,
bake-off, E2E — drive them to completion via `play_resume` on the same
single path.

Env vars renamed `HERO_FLOW_*` → `HERO_LOGIC_*`:
- HERO_LOGIC_EVENT_SOCK / PLAY_SID / NAME / VERSION_SID / PARENT_PLAY_SID
- HERO_LOGIC_INPUT

Decorator marker renamed `__hero_flow__` → `__hero_logic__`. Schema
type `FlowField` renamed to `LogicField`. Boot stub + executor tests
updated to match.

Misc dead-code removal: the old workflow_editor.js / hero_logic_graph.js
static assets (the new logic_view UI inlines its JS). The
pause_resume_prefill.py example (its premise — headless pre-fill — no
longer exists). The bakeoff script ported to logic_* RPC names + a
sub_play_sids walk for the attempts counter (in place of the gone
span-based heuristic).

33/33 lib tests still pass.
Per the issue spec — `service_agent` is now a thin orchestrator that
calls each phase as a sub-Logic via `logic.invoke(...)`. The Play tree
gains one child Play per step, drillable from the UI flow view.

New seed_flows files (one Logic each):
- fetch_catalog.py        — healthy services via hero_router
- select_services.py      — pick 1-3 with model_call ladder
- compile_stubs.py        — stage clients + INTERFACES + health probe
- script_execution.py     — subprocess + silent-failure detection
- debug_feedback.py       — pattern-match errors → fix advice
- summarize.py            — stdout → user-facing reply

service_agent.py is now ~80 lines (was 607) — orchestrator only.

Drops `logic.step("attempt N", …)` (replaced with `logic.log(...)`) and
`logic.current_span.log(...)` (also `logic.log(...)`) since the span
machinery is gone. Each `logic.invoke(...)` call shows up as its own
child Play in the tree, so the per-attempt grouping the old span-step
provided is preserved by Play structure.

seed.rs gets 6 minimal BuiltInFlow entries (LogicVersion.inputs/outputs
metadata) for the new sub-Logics. Each is invokable directly from the
dashboard too.

33/33 lib tests still pass.
Suite covers the issue-39 two-view UI end-to-end against the running
dashboard. Each test is self-contained — sets up via RPC, asserts via
js_evaluate in the running page, cleans up its own data.

Coverage:
- Foundation (1-5): dashboard load + seeded Logic list + "+ New Logic"
  create-and-navigate + logic-view 3-region layout + brand-link back
  to dashboard.
- Left sidebar (6): declared inputs/outputs/versions render from the
  active LogicVersion.
- Middle pane (7-10): Flow tab renders parsed invoke/rpc/pause/loop
  nodes from logic_parse_graph; Code tab loads python_source; Split
  shows both; tab switching toggles .active correctly.
- Bottom play bar (11-14): typed input widgets per LogicField.field_type;
  examples dropdown loads + click populates inputs; Recent Plays list
  renders.
- Run + live polling (15-17): ▶ Run starts a Play and overlays it,
  overlay polls fire while the Play runs, outputs render as labeled
  cards per declared field.
- Stats (18-19): idle benchmark card from /api/logics/{sid}/stats,
  overlay card sums subtree tokens/cost across descendants.
- Drill navigation (20): clicking an invoke node lands on the child
  Logic with the right Play and a parent breadcrumb.

Master runner at testcases/run_all.md describes prerequisites, parallel
execution, retry policy, hero_browser MCP tool cheat sheet. Repo-local
skill .claude/skills/run_ui_tests/SKILL.md explains how to wire this
up against `make run`.

Also fixes the UI socket name mismatch: hero_logic_admin now binds
`hero_logic/ui.sock` (matching its `.well-known/heroservice.json`
declaration) instead of `admin.sock`. The router scanner picks it up
under `/hero_logic/`.
Aligns hero_logic with the rest of the Hero workspace: each binary
crate carries a `service.toml` at its root, `main.rs` uses
`service_base!()` + `validate_service_toml` + `handle_info_flag` +
`print_startup_banner` + `prepare_sockets` from `herolib_core::base`.
Lifecycle (build / install / start / stop / status) is driven by
`lab service hero_logic …` reading `service.toml` via `<bin> --info
--json`. Per hero_skills ADR-0001.

Removed
- `Makefile`, `buildenv.sh`, `crates/hero_logic/scripts/build_lib.sh` —
  superseded by `lab`.
- `crates/hero_logic/src/main.rs` (the old hero_logic selfstart CLI) —
  per `hero_service_test`, selfstart-only binaries provide no value;
  `lab service` covers it now. The CLI's deps (`hero_service`,
  `hero_proc_sdk`, `clap` in the package) were dropped.

Restructured
- `src/bin/hero_logic_server.rs` → `src/main.rs` so `service_base!()`
  can `include_str!("../service.toml")` correctly. `[[bin]]` updated.
- `hero_logic_admin/src/main.rs` rewritten on top of `herolib_core::base`
  (banner, prepare_sockets, validate_service_toml, handle_info_flag).
- Admin reverted from `ui.sock` → `admin.sock` (canonical convention
  per `hero_service_test`; `ui.sock` is the OLD admin name).
- `heroservice.json` discovery on admin updated: `protocol: "http"`,
  `socket: "admin"` (matches the path it actually binds).

Added
- `crates/hero_logic/service.toml` — server binary manifest.
- `crates/hero_logic_admin/service.toml` — admin manifest with
  dependency on hero_logic_server.

README rewritten
- Removes every `make` reference. Documents `lab --install`, `lab
  service hero_logic --start|--stop|--status`, the `--info` / `--info
  --json` introspection, the `service.toml`-driven discovery, the
  hero_router URL, and the development loop after UI changes.
- Updated to reflect the issue-39 model end-to-end: Logic / LogicVersion
  / LogicExample / Play tree as the unit of execution; no flow / no
  spans; cost via Play.stats; live-poll endpoints; sub-Play drill nav.

Dependency hygiene
- Path-patched 8 `hero_lib` crates to the local sibling checkout to
  side-step the upstream herolib_ai ↔ hero_aibroker_sdk skew (phase-9
  per-domain refactor dropped the streaming surface; herolib_ai
  hasn't been republished). The local hero_lib carries a small
  workaround that gates off `chat_stream` / `with_streaming_socket`
  until upstream realigns — documented in README.

33/33 lib tests still pass; clean build with no warnings.
hero_router doesn't proxy `web`-protocol services (only `openrpc`),
and hero_proxy has no listeners configured by default — so the admin
dashboard was only reachable via the UDS path. Browsers need a TCP
URL.

- `hero_logic_admin` now binds both the UDS (`hero_logic/admin.sock`)
  and `127.0.0.1:9820` via the same hyper-on-tokio serve loop the UDS
  uses. service.toml declares the TCP port. axum::serve was tried
  first but hung silently; the hyper http1 serve_connection pattern
  matches what UDS uses and works reliably.
- Drop the per-Logic "latest play + success rate" lookup from the
  dashboard index handler. With ~1100 historical plays in OSIS and no
  logic_sid index, `play_list_for_logic` scans every record per call;
  doing that N times per dashboard render made the page 60+ seconds.
  Latest-play info still surfaces on the Logic view (where it's worth
  the cost). Dashboard now renders in ~130 ms.

Open the dashboard at http://127.0.0.1:9820/ — direct, no router /
proxy involvement needed.
This pull request has changes conflicting with the target branch.
  • Cargo.lock
  • crates/hero_logic/service.toml
  • crates/hero_logic/src/bin/hero_logic_server.rs
  • crates/hero_logic/src/logic/server/osis_server_generated.rs
  • crates/hero_logic/src/main.rs
  • crates/hero_logic_admin/service.toml
  • crates/hero_logic_admin/src/main.rs
View command line instructions

Checkout

From your project repository, check out a new branch and test the changes.
git fetch -u origin feat/39-logic-model:feat/39-logic-model
git switch feat/39-logic-model

Merge

Merge the changes and update on Forgejo.

Warning: The "Autodetect manual merge" setting is not enabled for this repository, you will have to mark this pull request as manually merged afterwards.

git switch development
git merge --no-ff feat/39-logic-model
git switch feat/39-logic-model
git rebase development
git switch development
git merge --ff-only feat/39-logic-model
git switch feat/39-logic-model
git rebase development
git switch development
git merge --no-ff feat/39-logic-model
git switch development
git merge --squash feat/39-logic-model
git switch development
git merge --ff-only feat/39-logic-model
git switch development
git merge feat/39-logic-model
git push origin development
Sign in to join this conversation.
No reviewers
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!40
No description provided.