feat: Logic / LogicVersion / Play model + single logic view (supersedes #38) #39
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
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?
feat: Logic / LogicVersion / Play model + single logic view (supersedes #38)
Final shape after the discussion in #38. This issue captures the full target state — schema, SDK, execution, UI — and is the implementation reference.
TL;DR
The system collapses to three rootobjects (Logic, LogicVersion, Play), one navigation rule (click a sub-Logic → open its view), and one execution rule (every invocation = a child Play).
1. Data model
Rootobjects
Value types
Deleted from today's schema
Workflow,WorkflowVersionrootobjects → renamed toLogic,LogicVersion.Examplerootobject → renamed toLogicExamplerootobject; gainslogic_version_sidfor per-version scoping (""= applies to all versions).Benchmarkrootobject +PerRunResultvalue type → benchmark stats are derived on the fly from queries over Plays.Spanvalue type,SpanKindandSpanStatusenums → gone entirely. No span concept.Play.spans,Play.parent_span_id→ gone (replaced byparent_play_sid+sub_play_sids).Stored-data migration
Workflowbecomes aLogic(same SID). Add#[serde(alias = "Workflow")]if the type tag is persisted; otherwise straight rename.WorkflowVersionbecomes aLogicVersion(same SID).Workflow.inputs/Workflow.outputsmigrate to the currentLogicVersion(since that's what the latest source matches). Older versions migrate with emptyinputs/outputs; the editor can re-populate from a static parse or the user fills them.python_sourcecontains multiple@flow-decorated functions: walk the AST, extract each@flowdefinto a fresh standaloneLogicrecord, rewrite the parent's source to call them by name (e.g.logic.invoke("model_call", …)). The migration script seeds the new Logic records and updates the parent's source. One-shot at upgrade.Examplerecords: rename in place toLogicExample. Existingworkflow_sid/workflow_version_sidfields becomelogic_sid/logic_version_sid(serde aliases preserve back-compat). No data loss.Benchmarkrecords: drop. Stats re-derive from Plays.Playrecords:workflow_sid→logic_sidandworkflow_version_sid→logic_version_sid(with serde aliases for back-compat)spansinto nothing (info we keep — status/timing/cost — already lives on Play directly; status ofparent_span_id→parent_play_sidset to""if it was a span on the same play, or to the parent play's sid if the play was launched withplay_run_async(parent_span_id=…))sub_play_sidspopulated are inspectable as terminal records but won't have the new tree-drill-in; new plays after the migration have it.2. SDK surface (Python)
Decorator
Every call to a
@logic-decorated function creates a child Play of the currently-executing Play. The first decorated call (the entry) is the top-level Play. Recursion is the only composition primitive.Invocation
Resolves by name against the Logic library. The child Play's
logic_sid= the resolved Logic;logic_version_siddefaults to the resolved Logic'scurrent_version_sid. The parent'ssub_play_sidsgets the child's sid appended in invocation order.from <logic_name> import <logic_name>works too (sugar; meta-path resolver), but the canonical surface islogic.invoke(...).Pause / resume
logic.pauseputs THIS Play intoawaiting_resume(only the current play; its parent stays inrunningand its ownplay_waiton the child blocks). Resumption posts toplay_resume(child_play_sid, …).Logging
Appends to the current Play's
logsfield with a timestamp. Replaces today'sflow.current_span.log(…).Errors
Marks the Play
failedwith the given message (no traceback). Any other exception is captured with full traceback inerror_message.Gone from the SDK
flow.step,flow.span— make a function instead.flow.current_span.tag— write tologsor uselogic.log.instrument(client)— replaced by transport-level auto-trace.The
flowname is retained as an alias oflogicfor one release so stored python_source keeps working without immediate rewrite. (from hero_tracing import flow as logicis effectively how migrated sources read.)3. Execution model
Spawn
Every
logic.invoke(...)call:Logic+LogicVersion.parent_play_sid= current play's sid,logic_sid+logic_version_sidset,input_data= the JSON of kwargs,status=running.sub_play_sids.python_sourcein the SAME subprocess (in-process; no per-call subprocess fork). The child's body runs to completion.output_data,status=success,completed_at,duration_ms. Returns the value to the caller.logic.Failedor any exception: setsstatus=failed,error_message. Re-raises so the parent sees it (unless caught).The top-level Play (the one started by
LogicService.play_start) runs in its own subprocess as today. Sub-Plays do NOT fork subprocesses by default — they're records of in-process invocations.Transport-level auto-trace
The JSON-RPC transport used by every generated Hero client wraps each call:
usage.prompt_tokens/usage.completion_tokens(the aibroker shape), looks up the model's price and increments the current Play'stotal_tokens_*andtotal_cost_usd.No per-call record is persisted. The aggregates on the Play are the only runtime trace. Per-call detail (params, result, exact timing of one of N similar calls) is reconstructable from source + logs if needed for debugging.
Pause / resume
Each Play has its own resume state. When
logic.pause(...)runs:pauseevent over the per-Play UDS socket with a deterministicresume_id(logic name + call sequence within this Play).pending_resumes, setsstatus=awaiting_resume.play_wait(blocking call) detects the child enteredawaiting_resume, stays parked, and propagatesawaiting_resumeup the chain.play_resume(play_sid, resume_id, payload)writes the answer to the matching Play'sreceived_resumes, flips its status back torunning, respawns its subprocess. The chain rewakens naturally.Each Play has its own
step_outputscache (memoization of child Play results keyed by(version_sid, parent_path, child_logic_name, child_input_args)). On replay, child invocations whose key is in the cache short-circuit to the cached output without re-executing the child. Side-effects don't double-fire.Wall-clock + sandbox
Same as today's Tier 0 (
hero_logic#14): per-subprocess wall-clock budget, address-space cap, fd cap, scrubbed env. Applies to the top-level Play's subprocess. Sub-Plays inherit by virtue of being in the same subprocess.4. UI
Routes (final)
//logics/{sid}/logics/{sid}?play={play_sid}/api/plays/{sid}/overlay/rpcEverything else gets removed:
/workflows/*,/examples,/plays,/plays/{sid}, the top toolbar on the editor.Dashboard
A simple table or card grid:
Plus a
+ New Logicbutton.Logic view layout
Left sidebar — info
Logic.name), description.Middle — flow / code
/logics/{child_logic_sid}?play={child_play_sid}.service.methodlabel — click opens a side popover (shows runtime cost if it accumulated tokens; otherwise just the static source line).successbut populated via cached output).LogicVersion.python_source. Save → creates a new LogicVersion.Right sidebar — stats
Two modes:
current_version_sid. Stats derived on the fly from a query over recent Plays of this version. Refresh button.Bottom play bar — three columns
Left column
Logic.examples. Click → populate inputs. "Save current as example" button.play_start, overlays the new Play.Middle column
awaiting_resume: pause-form banner at the TOP of the column, accent border, persistent until answered. Forms render perResumeRequest.ui.kind: text / number / choice / multi_choice / confirm. Submit postsplay_resumewith the matchingplay_sid(could be the overlaid play or a child along the chain).Right column
output_datarendered as it accumulates. IfLogic.outputsdeclares fields, render one labeled card per output. Else raw JSON.Sub-logic mid-Play navigation
Click a sub-Play node in the flow → navigate to
/logics/{child_logic_sid}?play={child_play_sid}. The whole logic view rerenders for the child:service_agent / select_services).input_data, plus the child's examples and plays. ▶ Run launches a fresh standalone top-level Play of the child Logic with those inputs (different from the overlaid child Play).Breadcrumb click → navigate up. Browser back works. URLs share.
5. Walked example: service_agent
The existing
service_agentworkflow (8@flowfunctions in one file) migrates to 8 separate Logic records:Initial state — dashboard
Dashboard lists all 8 Logics. User clicks
service_agent.Logic view — idle
service_agent, description, inputs (prompt, code_gen_model), outputs (summary), versions (v3 current).fetch_catalog → select_services → for attempt in range(3): {service_code_gen → script_execution → debug_feedback} → summarize. Each is a clickable sub-Logic node.User picks "Calendar event" example, hits ▶ Run
Inputs auto-fill (
prompt="Create a calendar event …",model=""). Run →play_start→ new top-level Play02j7created → subprocess spawns → Play overlays the view.Right sidebar switches to "Play 02j7 — running 0.4s — 0 tokens".
Middle flow view starts updating live:
Then more arrive —
select_servicesPlay starts; child Plays accumulate.Play bar middle column logs each event chronologically. Right column starts filling as
output_dataaccumulates.A pause fires inside
select_servicesThe Play for
select_services(say02j9) emits a pause event. Its subprocess exits 75. Itspending_resumesgets the new ResumeRequest. Its status →awaiting_resume. The parentservice_agentPlay sees its child enteredawaiting_resume(viaplay_waitreturning non-terminal) and propagates: parent's status →awaiting_resumeas well.Play bar middle column shifts to show:
User clicks "Event", submits. The form posts
play_resume(02j9, "select_services#0@ask_user.choice#0", "Event"). Server respawnsselect_services's subprocess with the cached answer; it returns through ai chain → parent unblocks → execution continues.User clicks
service_code_gennode mid-PlayPage navigates to
/logics/{service_code_gen_sid}?play=02jb(the child Play sid).service_code_genmetadata + breadcrumbservice_agent / service_code_gen.model_callPlay 02jc — running) and the source's primitivere.search(...)andast.parse(...)calls as ⚡ markers.running, 4s, 1284 prompt + 320 completion, $0.012.prompt="…",services=[…]); examples = service_code_gen's saved examples; ▶ Run starts a fresh standalone play of service_code_gen with those inputs.Click
service_agentin the breadcrumb → back to parent.Play completes
Top-level Play 02j7 reaches
status=success. Output column fills in:{"summary": "Calendar event 'Standup' created for tomorrow 10am."}. Right sidebar shows final stats: duration 11.4s, total cost $0.024. Plays list in the play bar prepends02j7 success just now.6. Implementation phases
@flow→@logic,flow.*→logic.*. Dropflow.step,instrument(), span events. Addlogic.log(). Eachlogic.invoke()creates a child Play.parent_play_sidandsub_play_sids.play_waitreturns when a descendant Play entersawaiting_resume.play_resumeworks on any Play in the chain.play.list_by_versionfiltered queries.play_detail.html,examples.html,plays.html,workflows.html, and their handlers. 301 redirects from old URLs.7. Acceptance
@logicfunction call creates a child Play row whoseparent_play_sidmatches and whose sid appears in the parent'ssub_play_sids.awaiting_resumeup to the top-level Play; answering it viaplay_resume(child_play_sid, …)resumes the chain.step_outputsis populated short-circuits sub-Play invocations to their cached outputs.total_tokens_*andtotal_cost_usdon that Play (not the parent)./is the dashboard,/logics/{sid}is the only other view./workflows/*,/examples,/plays,/plays/{sid}all 301 or 404.flowis exported as an alias oflogic).8. Out of scope (future)
Supersedes #38.
Handoff — phases 1–3 + partial 10 landed in PR #40
PR: #40
Branch:
feat/39-logic-model(4 commits ahead ofdevelopment). Do not branch off again — continue on this branch.PR will stay open (not merged) until the UI rebuild lands so the operator can smoke-test end-to-end before any of this hits
development.What is done
5b5a36bWorkflow→Logic,WorkflowVersion→LogicVersion,Example→LogicExample.inputs/outputsmoved from Logic onto LogicVersion (per-version signatures).Span/SpanKind/SpanStatus/Benchmark/PerRunResultdeleted.Play.spans/parent_span_idreplaced withparent_play_sid+sub_play_sids. RPC handlers ported (workflow_*→logic_*,benchmark_*/pick_version/span_pushdropped).span_socket.rsslimmed to a per-Play event listener forlog/output/step_output/pause/costevents only.python_executor.rsfield renames + newHERO_LOGIC_*env vars.seed.rsemits new RPC names; inputs/outputs go on LogicVersion. Wipe-and-reseed migration (no AST splitter).736c7falogicnamespace as alias offlow(one-release back-compat).logic.log(text)emits a Play-level log event. Module docstring rewritten for the issue #39 event protocol.eb06676logic.invoke(name, **kwargs)now creates a child Play row viaplay.set(no new RPC needed), pushes a_current_play_sidcontextvar, executes in-process, then finalizes the row._invoke_sid_cacheremembers the resolved(logic_sid, version_sid). Memoization short-circuits via_STEP_CACHEon replays.0f3ccd7,d10ac9btests/e2e_create_event.rs(referenced aservice_agent_v3.pythat doesn't exist).Test state:
cargo test --lib -p hero_logic→ 29 passed / 0 failed.cargo build→ 2 harmless warnings (backend_onlinefield, will clear in Phase 7).What is broken right now (expected)
workflow_sid/workflow_version_sid/workflow.set/workflowversion.set/example.*//workflows/*//plays/*routes that no longer exist server-side. The templates compile (they're typed against their own DTO structs, not the Rust schema types), but every RPC call from the JS will 404 / return method-not-found. Do not try to fix piecemeal — the UI rebuild (Phase 7) replaces these wholesale per the issue spec.seed_flows/service_agent.pyhas 8@flow-decorated functions in one file. Per the issue spec, these should be split into 8 separate Logic records (one file each). Currently they still seed as one big Logic. Theflow=logicalias means the file still loads, but the Play tree will only show one root Play instead of 8 child Plays per top-level call.flow.step/flow.span/flow.current_span/instrument()— kept as a back-compat surface but their events are now silently dropped by the per-Play event listener.flow.current_span.log(...)calls in existing seed_flows are no-ops.Play.total_tokens_*,total_cost_usd) are wired on the listener side (thecostevent handler exists) but nothing emits them yet — needs Phase 4.Critical files to read first
crates/hero_logic/schemas/logic/logic.oschema— source of truth for types + RPC. Edit this and regen viacargo buildbefore changing any handler.PRD.md— full design spec (the version still in the repo is pre-#39, but most of it is still accurate; #39's issue body is the canonical override).crates/hero_logic/src/logic/server/rpc.rs— handwritten RPC handlers. New methods that need adding: see Phase 5 below.crates/hero_logic/src/engine/span_socket.rs— minimal per-Play event listener. New event types are easy to add: add a variant toPlayEvent+ a handler arm inapply_event_to_play.crates/hero_logic/src/engine/python_executor.rs— subprocess spawn + sandbox. Theintegration_testsmodule is gone; new e2e tests should go here or intests/.crates/hero_logic/sdk/python/hero_tracing.py— the Python SDK._Flow.invoke(line ~1100) is where the new child-Play creation happens._create_child_play,_append_sub_play_sid,_finalize_child_playhelpers live just above.Remaining work (the punch list)
Per the issue body, in dependency order:
Phase 4 — transport-level auto-trace
_logic_rpc(or whatever RPC transport_resolve_flow_entryends up using for aibroker calls; checkrouter_python_dirgenerated clients) so that on every RPC response: if the response shape matches the aibrokerusage.{prompt_tokens, completion_tokens}shape, look up the model's price (modelsconfig.yml from hero_aibroker) and emit a{"type":"cost", "prompt_tokens":..., "completion_tokens":..., "cost_usd":...}event to the per-Play socket. The listener already handles this event and accumulates ontoPlay.total_*.hero_tracing.py(transport wrapper or post-call hook).Phase 5 — pause-chain propagation
play_waiton a parent must return when any descendant Play entersawaiting_resume(not just when the parent itself does). Currentlyplay_waitonly polls the named Play's status. New behavior: walksub_play_sidsrecursively each poll and treat any descendant'sawaiting_resumeas a non-terminal signal that propagates.play_resume(play_sid, ...)already accepts any Play sid; verify it correctly respawns the child's subprocess (not the parent's). Look atspawn_python_flowcall inplay_resume— it usesplay.logic_sidso this should already work, but worth a test.pending_resumes/received_resumes/step_outputs. The schema already supports this.crates/hero_logic/src/logic/server/rpc.rs(play_waitbody).Phase 6 — static Python source parser → flow graph
LogicVersion.python_sourceAST and emits a JSON graph of:logic.invoketarget names, recognized RPC client method calls (look forHero*Client(...)instances + their method calls), loop/conditional structure.logic_parse_graph(version_sid) -> str(returns JSON). Could be pre-computed atlogicversion_settime and cached on the record, but a new field would require a schema bump.Phase 7 — Logic view + play bar UI (THE BIG ONE)
The issue body has a detailed layout spec. Rough sketch:
/logics/{sid}and/logics/{sid}?play={play_sid}logic_view.html— three regions:python_source), Split view. Each sub-Play in the flow view is a drillable node.logic_view.htmlbottom play bar — three columns:play_list_for_logicrecent), ▶ Run buttonawaiting_resumeis pending (renders perResumeRequest.ui.kind)output_datarendered as labeled cards per declared output field/workflows/*,/plays/*,/examplesroutes + their templates (workflows.html,workflow_editor.html,plays.html,play_detail.html,examples.html). Theworkflow_editor.htmlis the closest model for the newlogic_view.html— start by copy-renaming and reshaping rather than from scratch.Phase 8 — sub-Logic navigation
/logics/{child_logic_sid}?play={child_play_sid}. Breadcrumb shows ancestor logic names (walkparent_play_sidchain). Browser back works.Phase 9 — stats from Play queries
play_list_for_version(already added in Phase 1) + a fewplay_statuscalls to compute success rate / p50 duration / avg cost over the last 50 Plays. Pure read-side aggregation; no new RPC needed beyond what exists.Phase 10 remainder
workflows.html,plays.html,play_detail.html,examples.html) + theirTemplate-derive structs + route registrations incrates/hero_logic_admin/src/main.rs/routes.rs/: list of Logics with latest-run + success-rate columns. Replaces the current index./workflows/{sid}→/logics/{sid},/plays/{sid}→/logics/{logic_sid}?play={play_sid}).examples/driver scripts (pause_resume_demo.py,pause_resume_prefill.py) to use the newlogic_*RPC method names.seed_flows/service_agent.pyinto 8 per-logic files + updateseed.rsto register all 8.flow.*tologic.*(cosmetic but matches the spec;flowalias keeps them working either way for one release).Useful commands
Gotchas
_create_child_play/_append_sub_play_sidhelpers in the SDK do read-modify-write on the parent Play to updatesub_play_sids— if you see lost field updates after a save, suspect OTOML serialization. Theencode_python_source_b64workaround inpython_executor.rsis a precedent.OsisLogic— when you add a new rootobject to the oschema, the build will fail with "no function found" for{rootobject}_trigger_*until you add no-op trigger stubs to the bottom ofrpc.rs(look for the existingimpl OsisLogic { ... }block).flowandlogicare the same singleton —logic is flowevaluates True in Python. Don't break this; the alias is the one-release back-compat contract.*_generated.rs/openrpc.json— they're rewritten bybuild.rson everycargo build. Edit the oschema and regen.Original issue spec stands as the canonical target. PR #40 covers ~30% of it (the foundational reshape); the UI rebuild is the bulk of what's left.
Handoff — session 2 (Phases 4-10 + lifecycle migration; UI regressed visually)
PR #40, branch
feat/39-logic-model(10 new commits stacked on top of thesession-1 foundation — phases 1-3 + partial 10). 33/33 lib tests green,
clean build, pushed.
Commits this session
3d3418cPlay.stats(JSON field with documentedtokens_prompt / tokens_completion / cost_usd / duration_ms / callsshape) + astatsevent type on the per-Play socket +logic.record_stats(...)SDK helper.model_callnow looks up aibroker pricing once per subprocess (viamodels.configRPC) and reports its own consumption. UI aggregators sum across the Play subtree on read.294d6bbplay_waitwalkssub_play_sidsrecursively and stays parked while any descendant is inawaiting_resume. Bounded walk, cycle-safe.0c06097engine/source_parser.rsspawnspython3with an embedded AST parser piped on stdin → JSON flow graph (invoke / rpc / pause / loop / if / try nodes). Newlogic_parse_graph(version_sid) -> strRPC.93ce00flogic_view.htmltemplate + new dashboard. All five old templates deleted (workflows.html,workflow_editor.html,plays.html,play_detail.html,examples.html) plusworkflow_editor.js+hero_logic_graph.js. 301 redirects from legacy URLs. The new UI is visually much worse than the one it replaced — see "Open issue" below.bf055d2examples/pause_resume_*.py+seed_flows/*.pyrenamedflow.*→logic.*.b4e6d67flowalias,_Span/_NullSpan/_current_span/flow.step/flow.span/flow.current_span/instrument()/_ClientProxy/_bootstrap_run. Replaced_current_span(span-based) with_current_frame(_LogicFrame, just carries the deterministic call-tree state). Droppedprefill_only/prefill_resumes_json+HERO_LOGIC_PREFILL_ONLY(per user's "plays should be plays, no special mode" feedback — headless callers now loop onplay_resumelike everyone else). Env vars renamedHERO_FLOW_*→HERO_LOGIC_*. Marker attr__hero_flow__→__hero_logic__. Schema typeFlowField→LogicField.ed4d6felogic.invoke(...). New seed_flows files:fetch_catalog.py,select_services.py,compile_stubs.py,script_execution.py,debug_feedback.py,summarize.py(service_code_gen + model_call already separate).seed.rsgets 6 new compactBuiltInFlowentries with their inputs/outputs metadata.72bae09testcases/, a masterrun_all.mdrunner, and a repo-local.claude/skills/run_ui_tests/SKILL.md. Also fixed the admin socket name mismatch (admin.sock→ui.sock— though this was reverted in the next phase, see below).311c3dalab+service.toml. Each crate has aservice.tomlat its root;main.rsusesservice_base!()+validate_service_toml+handle_info_flag+print_startup_banner+prepare_socketsfromherolib_core::base. DeletedMakefile,buildenv.sh,crates/hero_logic/scripts/build_lib.sh. Dropped thehero_logicselfstart CLI binary (per thehero_service_testskill — selfstart-only binaries provide no value;lab servicecovers it). Movedsrc/bin/hero_logic_server.rs→src/main.rssoinclude_str!("../service.toml")resolves correctly. Reverted admin socket name to canonicaladmin.sock. README rewritten end-to-end: nomakereferences, alllabcommands. Cargo.toml has a[patch."https://forge.ourworld.tf/lhumina_code/hero_lib.git"]block pointing at the local sibling checkout — needed because the upstreamherolib_aireferences streaming types (ChatChunkStream,StreamingClient,StreamError) thathero_aibroker_sdk's phase-9 per-domain refactor dropped. The local hero_lib checkout has a small workaround patch (crates/ai/src/client.rs+error.rs) that gates those refs off. Drop both the patch and the local edits once upstream realigns.de952a3hero_routerdoesn't proxyweb-protocol services (onlyopenrpc) so the dashboard wasn't reachable via the router URL. Added a TCP bind at127.0.0.1:9820alongside the UDS so a browser can hithttp://127.0.0.1:9820/directly. Used the same hyper http1serve_connectionpattern as the UDS (axum 0.8'saxum::servehung silently — possibly a peer-shutdown handling quirk). Also dropped the per-Logic "latest play + success rate" lookup from the dashboardindex_handler— with ~1100 plays in OSIS and nologic_sidindex on theplayrootobject,play_list_for_logicscans every record per call, making the dashboard ~60s; latest-play info lives on the per-Logic view where it's worth the cost. Dashboard now renders in ~130 ms.Open issue — UI visual regression
Phase 7's UI rewrite (
93ce00f) is the problem. I followed the issuespec's "three regions + bottom play bar" diagram literally and the
result is visually much worse than the old
workflow_editor.htmlitreplaced — see the side-by-side the user posted in the session
transcript. Specifically the new UI lost:
ms timings + indentation (e.g.
attempt 1 → Service Code Gen → Model Call). The new "flow tab" relies onlogic_parse_graphforthe static backdrop and on
descendants_jsonfor overlay anchoring,but the visual hierarchy is missing.
delete) — replaced with a read-only declared-fields list in the
left sidebar plus a separate "play bar" inputs column.
with the canvas — replaced with a small "Output" section in the
bottom-right of the play bar.
The new template/JS also has runtime errors visible in the
screenshots:
BASE_PATH is not defined(the stats card init runs before the<script>block that defines BASE_PATH).Source parse unavailable: syntax error at line 1: invalid decimal literal(Python 3.14's stricter literal parsing rejects somethingin
service_code_gen.py's source the parser is being handed —needs a closer look in
engine/source_parser.rs).bottom play bar is overflowing the viewport or being CSS-clipped).
Recommended path forward (for the next agent)
The session-1 instruction was "this is a single canonical issue, don't
fight it"; the session-2 read of the spec produced a worse UI. The
old
workflow_editor.htmldesign is the better starting point —single canvas with top toolbar (Start + Plays + stats), three columns
(INPUTS / FLOW+SOURCE / OUTPUT), all the right interaction surfaces
on one screen. To recover:
git show 4c9168e:crates/hero_logic_admin/templates/workflow_editor.html > crates/hero_logic_admin/templates/logic_view.html(likewise
workflow_editor.js→ keep as-is,hero_logic_graph.js→keep as-is, the four list templates —
workflows.html,plays.html,play_detail.html,examples.html— restore if you still want thelist pages, else skip).
wf_sid/wf_name/wf_description→logic_*inthe template, rewrite
/workflows/→/logics/. Done partiallyalready in the dead branch I just reverted.
workflow.set→logic.set,workflowversion.set→
logicversion.set,LogicService.workflow_*→LogicService.logic_*,example.*→logicexample.*,LogicService.example_*→LogicService.logic_example_*,flow_library_search→logic_library_search. DropLogicService.workflow_clone(noequivalent — use
logic_create_versiondifferently or wire fromthe dashboard).
hero_logic_graph.jsrenders the playtree from
Play.spans(gone in #39). The renderer needs to beadapted to walk
Play.sub_play_sids(with descendants fetchedclient-side or batched server-side via the existing
/api/plays/{sid}/overlayendpoint, which already returns{play, descendants}). The static fallback can stay aslogic_parse_graph's output (the parser is new in #0c06097andworks).
Play.stats(a JSON string —parse and sum across descendants).
Files & state at the time of handoff:
crates/hero_logic_admin/templates/logic_view.html— the BAD new template (Phase 7). Replace with the restored editor.crates/hero_logic_admin/templates/index.html— also new and usesLogicCardstruct fromroutes.rs. The old version listed both workflows + plays; restore if desired.crates/hero_logic_admin/static/js/— onlybootstrap.bundle.min.jsnow. Restoreworkflow_editor.js+hero_logic_graph.jsfrom4c9168e.crates/hero_logic_admin/src/routes.rs— speaks the new schema correctly vialogic.*RPCs but only renders the bad layout. To swap UIs: restore the oldroutes.rsfrom4c9168e, then apply the same mechanical RPC-name renames listed in (3) above. I had a partial-go at this (reverted before committing) so the sed script in the session transcript is a starting point.crates/hero_logic_admin/src/main.rs— routes for the new layout. Reset the route table to point at the old handlers (workflows_handler,workflow_editor_*_handler,plays_handler,examples_handler,play_detail_handler) once routes.rs is restored.Cargo.toml— the workspace patcheshero_libto a local sibling checkout. Do not drop this until upstreamherolib_airepublishes against the phase-9hero_aibroker_sdkshape. The local hero_lib'scrates/ai/src/client.rsanderror.rscarry a small workaround patch that gateschat_stream/with_streaming_socketoff; tracking it in the README note. There's also aCargo.toml.hero_builder_backupleft in the working tree (not committed) from the policy enforcement — safe to delete.Everything in commits
3d3418c..bf055d2andb4e6d67..311c3da(i.e.schema, SDK, runtime, lifecycle, service split, e2e tests, dependency
work) is solid and matches the issue spec. Only the UI in
93ce00fneeds to be redone from the better starting point.
Smoke-test commands: