Refactor service_*.nu modules to thin delegators over the selfstart CLI #100
Labels
No labels
prio_critical
prio_low
type_bug
type_contact
type_issue
type_lead
type_question
type_story
type_task
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
lhumina_code/hero_skills#100
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?
Motivation
Every lhumina_code Hero service (except
hero_officeandhero_osis, tracked as hero_office#2 and hero_osis#32) already ships a CLI with--start/--stopthat callshero_proc_sdk::lifecycledirectly. That CLI is the canonical source of truth for the action spec (retry policy, kill_other, env, health checks, stop timeouts).The current
tools/modules/services/service_*.numodules hand-transcribe the same spec in Nushell. Every upstream tune in a service'sbuild_service_definition()silently drifts against the nu copy. Concrete symptoms observed during #75 work:service_claudespec (#99) needed six deviations from the baseline; most were duplication-driven.service_agentprobe showed the Rust CLI propagates only a subset of the hero_zero TOML env, so the two paths already disagree.service_officeships ~400 lines of nu for what is effectively onehero_office --startcall (pending #2).Proposal
Refactor each
service_*.nuto a thin delegator:installkeeps everything it does today —svc_require_sudo,forge_ensure_local,svc_cargo_install,--reset/--update/svc_bins_okshort-circuit, any seeding helpers that are genuinely module-owned (e.g.svx_seed_models_configfor aibroker,svx_install_server_wrapperfor claude).startbecomes: ensure binaries present → run any non-fatal preflight warnings (API keys, soft deps) →^(svc_bin "hero_<name>" $root) --startwithsudo -E HOME=/rootdispatch on--root.stopbecomes:^(svc_bin "hero_<name>" $root) --stopwith the same sudo dispatch.statusstays as-is (proc service status $svc --root=$root).Drops roughly 60–70% of each module's LOC and makes drift impossible by construction.
New shared helpers (in
tools/modules/services/lib.nu)svc_cli_start [cli_bin: string, root: bool]— runs^$cli_bin --start, wraps insudo -E HOME=/rootwhensvc_need_sudo $root, surfaces exit code as a clean error.svc_cli_stop [cli_bin: string, root: bool]— same for--stop.svc_cli_run [cli_bin: string, args: list<string>, root: bool]— generic version for services withstatus/logssubcommands on the CLI (hero_bookshas these).Per-module checklist
Each cycle: branch
development_refactor_service_<name>, one commit, one PR, smoke-test on Hetzner against existing behaviour (install → start → status → probes → stop), close this issue line, merge.service_os.nu→ delegate tohero_osservice_books.nu→ delegate tohero_books(has extra--status/--logssubcommands, keep as-is or wire through)service_collab.nu→ delegate tohero_collabservice_whiteboard.nu→ delegate tohero_whiteboardservice_biz.nu→ delegate tohero_bizservice_db.nu→ delegate tohero_dbservice_indexer.nu→ delegate tohero_indexerservice_voice.nu→ delegate tohero_voiceservice_aibroker.nu→ delegate tohero_aibroker(keepsvx_seed_models_config+svx_check_api_keys)service_foundry.nu→ delegate tohero_foundryservice_compute.nu— still to be created; create it as a delegator from day oneservice_agent.nu— still to be created; create it as a delegator from day one (waits on no upstream dep)service_claude.nu— still to be created; create it as a delegator from day one (waits on no upstream dep)service_shrimp.nu— still to be created; create it as a delegator (uses thehero_shrimpRust wrapper inhero_shrimp_proc/)Blocked on upstream selfstart additions:
service_office.nu→ delegate tohero_office(blocked on hero_office#2)service_osis.nu→ delegate to the new osis CLI (blocked on hero_osis#32)Pilot
Suggest picking
service_whiteboard.nuas the first refactor — shortest, zero preflights, no env-seeding helpers, so the diff shows the canonical delegator shape with nothing else cluttering the review. Once approved, subsequent modules follow the same pattern.Scope guardrails
service_<name>.nu(and possiblylib.nuon the pilot to addsvc_cli_start/svc_cli_stop).--root/--reset/--updateflags; semantics stay operator-facing.Refs: #75
Implementation Spec for Issue #100 — Pilot (PR A)
Objective
Eliminate the duplicated hero_proc action spec that
service_whiteboard.nucurrently hand-transcribes, and move itsstart/stopverbs to thin delegators over thehero_whiteboardselfstart CLI — whosebuild_service_definition()inlhumina_code/hero_whiteboard/crates/hero_whiteboard/src/main.rsis the canonical source of truth for the registration. The pilot introduces two reusable helpers (svc_cli_start,svc_cli_stop) intools/modules/services/lib.nu, then refactors exactly one module —service_whiteboard.nu— so reviewers see the canonical delegator shape before PR B mass-applies the same transform to the remaining 11 non-blocked modules.Requirements
Helpers to add (to
tools/modules/services/lib.nu):svc_cli_start [cli_name: string, root: bool]— invokes<svc_bin cli_name root> --start, dispatching sudo when needed.svc_cli_stop [cli_name: string, root: bool]— invokes<svc_bin cli_name root> --stop, same dispatch.Dispatch rules (shared by both):
svc_bin $cli_name $root.svc_need_sudo-awaretest -x(same idiom as insvc_bins_ok).root == false→^($bin) --startwith invoker's env.root == trueAND invoker is root →with-env { HOME: "/root" } { ^$bin --start }.root == trueAND invoker is NOT root →^sudo -E HOME=/root $bin --start.-EpreservesHERO_SOCKET_DIRetc.; theHOME=/rootoverride redirects the CLI's hero_proc socket resolution to root's dir.svc_sudo_prefixfn).| complete, thenprint $r.stdoutandprint $r.stderr, branch on$r.exit_code, raiseerror makewith stderr attached on failure. Acceptable tradeoff: output appears in one chunk after CLI exits (~1s, negligible).Module to refactor:
tools/modules/services/service_whiteboard.nu.Verb-to-CLI mapping:
service_whiteboard installservice_whiteboard start [--root]if $root { svc_require_sudo }; install --root=$root --update=$update --reset=$reset; svc_cli_start "hero_whiteboard" $rootservice_whiteboard stop [--root]if $root { svc_require_sudo }; if not (svc_proc_healthy $root) { warn+return }; svc_cli_stop "hero_whiteboard" $rootservice_whiteboard status [--root]What stays:
SVX_SERVICE_NAME,SVX_FORGE_LOC,SVX_BINARIES,SVX_ACTIONS.install(unchanged — stillsvc_update+svc_bins_okshort-circuit +svc_cargo_install).status(unchanged).use ../clients/proc.nu *anduse ./lib.nu *imports.What goes:
svx_server_action,svx_ui_action,svx_service_config,svx_drop_registration— all four def blocks. The CLI'sbuild_service_definition()owns this spec.start(CLI'srestart_serviceis idempotent by design).start(CLI prints its own success line).svc_require_proccall insidestart— CLI does its own hero_proc reachability check.statuskeeps itssvc_require_proc.Files to Modify
Two files:
tools/modules/services/lib.nu— appendsvc_cli_startandsvc_cli_stopin a new section aftersvc_cargo_install, matching the existing section banner style.tools/modules/services/service_whiteboard.nu— trim from 317 lines to ~100.No other files are touched.
mod.nuno edit (module already exported). Otherservice_*.numodules no edit.Implementation Plan
Step 1: Design
svc_cli_start/svc_cli_stopBoth helpers have an identical body modulo the flag. Shape:
svc_cli_stopis identical with--stopin place of--startand the error message string adjusted.Step 2: Add helpers to
lib.nusvc_cargo_install(current end of file).svc_bin;is-admin→ bare withHOME=/root; non-root invoker with--root→sudo -E HOME=/root; errors raised with CLI stderr surfaced first.lib.nuto add aSelfstart CLI delegators — svc_cli_start, svc_cli_stopline.Step 3: Trim
service_whiteboard.nuheaderStep 4: Delete removed helpers
Remove in order (keeps diff readable):
def svx_server_action/def svx_ui_actionblocks.def svx_service_config.def svx_drop_registration.Keep the Constants banner and the four
const SVX_*lines.Step 5: Rewrite
startStep 6: Rewrite
stopStep 7: Leave
installandstatusaloneinstallbyte-identical.statusbyte-identical — we keepproc service statusbecause it returns a structured record that callers pipe intowhere state == "running", whichhero_whiteboard --statusdoesn't expose.Expected post-refactor LOC: ~100 lines (down from 317).
Smoke Test Plan (Hetzner,
--root)service_whiteboard install --root— verify binaries present, no behavioural change vs. pre-refactor.service_whiteboard start --root— CLI'shero_whiteboard started successfullyline prints;proc service statusshowsrunning; both sockets exist;curl --unix-socket ui.sock /health→ 200; server OpenRPC health OK.restart_servicehandles clean re-register; stillrunning.service_whiteboard status --root— record shape byte-identical to baseline.service_whiteboard stop --root— CLI'shero_whiteboard stopped successfullyprints; post-stop status returns not-found.⚠ hero_proc is not runningbranch fires; helper never called.sudo chmod -x hero_whiteboard CLI, runstart --root→ preflight raises with actionable message. Restore chmod.Exit criteria: all eight cases pass; LOC drop ~200 lines; user-visible output dominated by the CLI's own messages.
Acceptance Criteria (from #100, pilot portion)
lib.nuexportssvc_cli_startandsvc_cli_stopwith the contract in §Requirements.service_whiteboard.nuno longer containssvx_server_action,svx_ui_action,svx_service_config, orsvx_drop_registration.startbody issvc_require_sudo(conditional) →install→svc_cli_start.stopbody issvc_require_sudo(conditional) → hero_proc-down warning →svc_cli_stop.installbyte-identical to pre-refactor.statusbyte-identical to pre-refactor.--rootsmoke matrix passes (all eight cases).Notes
--resetsemantic shift: pre-refactor meant "re-register even if already running". CLI'srestart_serviceis idempotent, so--resetnow functionally means "force rebuild before restart". Documented in thestartdoc-comment.svc_cli_run [verb]yet: two specific helpers keep call sites (svc_cli_start "hero_whiteboard" $root) self-documenting and let us evolve--startvs.--stopdivergence without touching callers. Revisit after PR B if a second shape wants sharing (e.g.--status).statusstays unchanged:hero_whiteboard --statusdoesn't exist today, andproc service statusreturns a structured nu record that smoke tests and other skills pipe intowhere/get state. Replacing it with a CLI invocation would regress the data shape.svc_require_procfromstartbut keep it instatus:startnow delegates to the CLI, which does its ownhero_proc_factory().await?check insideself_startand returns a clear error. Two redundant preflights muddy the error output.statususes the nu client directly and benefits from the cleaner nu-side message.service_whiteboard.nu: canonical-shape review is the goal; spreading across 12 files would make review harder and delay rollback if we picked the wrong helper shape. PR B applies the same transform in bulk after the helpers are validated.Critical Files for Implementation
tools/modules/services/lib.nu(new helpers)tools/modules/services/service_whiteboard.nu(pilot refactor)lhumina_code/hero_whiteboard/crates/hero_whiteboard/src/main.rs(reference — canonical action spec)tools/modules/services/service_db.nu(reference —installflag shape with--reset)Pilot (PR A) implementation summary
Changes
tools/modules/services/lib.nu(+78 lines): addedsvc_cli_start/svc_cli_stopwith a privatesvc_cli_invokedispatcher that handles three--rootcases:--root=false→ bare exec, invoker's env--root=true+ invoker is root →with-env { HOME: "/root" } { ... }bare--root=true+ non-root invoker →sudo -E HOME=/rootCLI stdout/stderr surfaced via
| complete+ reprint; non-zero exit raiseserror makewith stderr attached.tools/modules/services/service_whiteboard.nu(317 → 85 lines, −232): removedsvx_server_action,svx_ui_action,svx_service_config,svx_drop_registration, the "already running → skip" idempotency precheck instart, the 13-line summary block, and the now-redundantsvc_require_proccall instart(the CLI surfaces its own).installandstatusbyte-identical to pre-refactor.Hetzner smoke test (
--root)installshort-circuits viasvc_bins_ok("binaries already in place — skipping build")startcold — CLI's output (hero_whiteboard started (pid: ...)+hero_whiteboard started successfully) flows throughcurl --unix-socket ui.sock /health→ HTTP 200statusreturns structured record (name,state: running,pid,current_run_id: 22,restarts: 0)startwarm (no flags) — CLI'srestart_servicere-registers idempotently (new pid)stop— CLI'shero_whiteboard stopped successfullysurfacesstatusreturnsstate: exited, pid: 0(see note below)stopwith hero_proc down —⚠ hero_proc is not running — nothing to stopbranch firesBehavioural note — post-stop
statusPre-refactor the module called
proc service deleteafter theproc service stopin itssvx_drop_registrationhelper; subsequentstatusreturned "service not found". The CLI'sstop_serviceonly stops — it leaves the service record inexitedstate. Net effect is positive: hero_proc retains the history row (useful for restart counts and audit logs), and any downstream skill that pipedstatusthrough| get statecontinues to work — they getexitedinstead of an RPC error. Callers that explicitly test for "not found" would need to update; a codebase search shows none inhero_skills.Path to PR B
Once PR A is approved and merged, the 11 remaining non-blocked modules can be mass-applied in a single follow-up PR using the same transform:
service_os,service_books,service_collab,service_biz,service_db,service_indexer,service_voice,service_aibroker,service_foundryservice_agent,service_claude,service_compute,service_shrimp— the four yet-to-be-created modules land directly as delegators (no pre-refactor state).service_officeandservice_osisremain blocked on hero_office#2 and hero_osis#32 respectively.Acceptance criteria (#100, pilot portion)
lib.nuexportssvc_cli_startandsvc_cli_stopwith the documented dispatch contract.service_whiteboard.nuno longer containssvx_server_action,svx_ui_action,svx_service_config, orsvx_drop_registration.startbody reduced tosvc_require_sudo→install→svc_cli_start.stopbody reduced tosvc_require_sudo→ hero_proc-down guard →svc_cli_stop.installbyte-identical to pre-refactor.statusbyte-identical to pre-refactor.--rootsmoke matrix passes.Pilot PR A opened: #101
PR B (mass apply to the 11 remaining non-blocked modules) will follow once this is approved and merged.
Closing — direction reversed, superseded by a new issue capturing the actual target architecture.
Original proposal: refactor nu modules to delegators over
hero_<svc> --start/--stopCLI flags. Rust CLI as source of truth.Actual direction (per @despiegk): the opposite.
--start/--stopshould be removed from everyhero_*binary — they add no value beyond what the nu layer does. Nushell (specificallyservice_*.nu+lib.nuhelpers) owns full lifecycle and will replace the per-repo Makefiles.The
5df145f Refactor service lifecycle management into shared lib.nu helperscommit ondevelopmentalready moved in this direction (~1,700lines of duplication removed;lib.nuhelpers dispatch directly viahero_proc_sdk/proc service set+proc service start).Closing in favour of # which scopes the removal of
--start/--stopfrom the binaries themselves. Pilot PR #101 also closed.Successor: #102 —
refactor: remove --start/--stop from hero_* binaries; nushell owns full lifecycle.