feat(#39): Logic/LogicVersion/LogicExample/Play foundation (phases 1–3 + cleanup) #40
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "feat/39-logic-model"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
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→LogicExampleinputs/outputsmoved fromLogicontoLogicVersionso different versions can evolve their signatures independentlySpan,SpanKind,SpanStatusvalue types;Benchmark+PerRunResultrootobjects;pick_version,span_push,benchmark_*RPCsPlay.spans/Play.parent_span_idreplaced withPlay.parent_play_sid+Play.sub_play_sids— the Play tree now mirrors the call treeflow_library_search→logic_library_search; fullworkflow_*→logic_*RPC renamespan_socket.rsslimmed to a minimal per-Play event listener (log / output / step_output / pause / cost events). The Span concept is gone.python_executor.rsfield renames:parent_span_id→parent_play_sid,workflow_name→logic_name,workflow_version_sid→logic_version_sid. NewHERO_LOGIC_*env vars (HERO_FLOW_*kept as aliases for one release).seed.rsemits the new RPC method names;inputs/outputsgo onto LogicVersionPhase 2 — SDK rename (
736c7fa)logicnamespace (alias offlowfor one release so existing source keeps loading)logic.log(text)— emits a{"type":"log",...}event the per-Play listener routes toplay.logsflow.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)logic.invoke(name, **kwargs)now registers a child Play row whoseparent_play_sidpoints back to the caller and whose sid is appended to the caller'ssub_play_sids. Same subprocess — no fork per sub-Play._current_play_sidcontextvar tracks the currently-executing Play; nested invokes correctly attribute parent._invoke_sid_cachemirrors the existing entry-function cache so we don't re-do the library-search lookup on every call._create_child_play/_append_sub_play_sid/_finalize_child_playuse the auto-generatedplay.set/play.getRPCs (no new server methods needed).(parent_path, name, args).Phase 10 (partial) — cleanups (
0f3ccd7,d10ac9b)build_node_preview,fetch_referenced_actions,build_graph_and_panel,play_detail_handler+PlayDetailTemplate,pretty_json_or_string,rpc_call) — net −416 LOCpython_executor::integration_testsmodule and standalonetests/e2e_create_event.rs(span-based, referenced aservice_agent_v3.pythat doesn't exist)Deferred to follow-up PRs
play_waitreturns when any descendant Play entersawaiting_resume)/)/logics/{sid}?play={play_sid}with breadcrumbs)workflows.html/plays.html/play_detail.html/examples.htmltemplates + their routes, dashboard creation, 301 redirectsThe UI templates still reference the old names (
workflow_sidetc.) 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 aboutbackend_onlinetemplate field, will clear in Phase 7)cargo test --lib -p hero_logic— 29 passed / 0 failedimport hero_tracing; logic = hero_tracing.logic; @logic(...) def f(...): ...— works standalone (no executor)play_startruns to completion🤖 Generated with Claude Code
- 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>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.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.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.View command line instructions
Checkout
From your project repository, check out a new branch and test the changes.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.