feat(cli)(#48): sphere trader spawn/stop wrapper + sphere host local-* primitives#49
Merged
Merged
Conversation
…p/local-status
Wraps the per-user local host-manager + trader-agent bring-up into a
single command. Without this, each developer who wants to run an
autonomous trader has to hand-orchestrate two docker containers: an
agentic-hosting HM with `AUTHORIZED_CONTROLLERS=<wallet-pubkey>`, then a
trader-agent tenant spawned by that HM with synthesized HM env vars,
then log-scrape the tenant's reachable address from
`{event: 'sphere_initialized', details: {agent_address: '...'}}`.
What lands:
- `sphere host local-spawn / local-stop / local-status` — primitive HM
container lifecycle, scoped to the current wallet. Idempotent.
- `sphere trader spawn / stop` — ergonomic wrapper that chains
ensureLocalHM + HMCP `hm.spawn` against the local HM, then prints
the tenant's reachable address as JSON or human-readable.
Local-HM bootstrap is two-shot: the agentic-hosting drift guard fails
the very first boot (intentional — protects against pointing the manager
at the wrong data dir post-misdeploy), embedding the real wallet pubkey
in the error message. The wrapper scrapes that, restarts the container
with corrected `MANAGER_PUBKEY`/`MANAGER_DIRECT_ADDRESS`, and waits for
`sphere_initialized`. The wallet persists in the bind-mounted volume so
subsequent invocations skip the bootstrap entirely.
Per-controller scoping prevents two developers on the same host from
colliding: container names use `sphere-hm-<wallet-prefix>`, data dirs
live under `./.sphere-cli/local-hm/<wallet-prefix>/`, and the diagnostics
health port is derived deterministically from the wallet prefix.
Canonical launch pattern mirrored from
trader-service/test/e2e-live/helpers/tenant-fixture.ts:300-365 —
`buildTraderEnv()` carries `UNICITY_CONTROLLER_PUBKEY`,
`UNICITY_TRUSTED_ESCROWS`, `TRADER_SCAN_INTERVAL_MS`, and the
`TRADER_TEST_FUND` + `TRADER_FAULT_INJECTION_ALLOWED=1` pair for testnet
self-mint. The ACP boot envelope (UNICITY_MANAGER_PUBKEY,
UNICITY_BOOT_TOKEN, UNICITY_INSTANCE_ID, UNICITY_INSTANCE_NAME,
UNICITY_TEMPLATE_ID) is injected by the HM — the wrapper deliberately
does NOT shadow those.
Idempotent re-spawn: on a second `sphere trader spawn` call with the
same wallet+name, the wrapper does `hm.list` first and returns the
existing tenant's info instead of trying a duplicate-name spawn (which
the HM rejects with INVALID_PARAMS).
`sphere trader stop` is reference-count-aware: when no other tenants
remain `CREATED`/`BOOTING`/`RUNNING` on the local HM, it tears the HM
down. `--keep-hm` overrides.
Test coverage: 45 new unit tests across local-hm.test.ts,
spawn.test.ts, and trader-commands.test.ts. Docker-touching paths and
the e2e roundtrip are out of scope here — covered by the
trader-roundtrip soak in sphere-sdk#474 once
this wrapper is consumed by it (sphere-sdk#477).
Closes #48
Related: unicity-sphere/sphere-sdk#477 (soak reshape), vrogojin/agentic_hosting#26 (trader image rebuild)
vrogojin
added a commit
to unicity-sphere/sphere-sdk
that referenced
this pull request
Jun 10, 2026
…rn (sphere trader spawn/stop)
The previous revision of `manual-test-trader-roundtrip.sh` assumed a
single shared Host Manager that both alice and bob spawned trader
tenants against, addressed via `HOST_MANAGER` / `SPHERE_HOST_MANAGER`
env vars and `sphere host spawn --manager $HOST_MANAGER`. That model
has two structural problems:
1. Auth-model collision. The public HM whitelists exactly one
controller pubkey; the soak creates two fresh wallets per run,
neither of which is on the whitelist, so spawn would fail at the
ACP-authorization step.
2. Per project owner guidance (memory: per-user local-HM design),
each developer runs their OWN local HM scoped to their controller
pubkey. The public HM is reserved for shared services (escrow,
faucet). The shared-HM pattern was never the intended model.
This commit reshapes the soak around the new `sphere trader spawn` /
`sphere trader stop` wrapper that landed in
unicity-sphere/sphere-cli#49. The wrapper brings up a per-user local
HM container scoped to the active wallet's controller pubkey + spawns
the trader tenant in one command. Each peer (alice, bob) gets its
own HM; no shared infra dependency at the HM layer.
Changes:
- §3 now calls `sphere trader spawn --name <slug> --trusted-escrows
$ESCROW --json` per peer instead of `sphere host spawn ... --env
UNICITY_CONTROLLER_PUBKEY=$ALICE_PUBKEY ...`.
- §4 simplifies to a single ACP smoke probe (`sphere trader
portfolio`); the wrapper's own `--ready-timeout-ms` covers the
container-RUNNING wait that `sphere host inspect` previously did.
- §11 cleanup switches to `sphere trader stop --name <slug>
[--keep-hm]`. KEEP_TENANTS=1 forwards `--keep-hm` so the per-user
HMs stay running for inspection; the wrapper auto-tears down the
HM when the last tenant attached to it stops.
- TRADER_DEAL_DEADLINE_S default bumped 600 -> 900s to cover
per-user HM bootstrap (two-shot drift-guard restart) on top of
the trader scan interval.
- Env contract: HOST_MANAGER / SPHERE_HOST_MANAGER / TRADER_TEMPLATE_ID
/ TRADER_IMAGE_OVERRIDE / UNICITY_CONTROLLER_PUBKEY all removed.
New env contract documents the sphere-cli#49 prerequisite + docker
requirement.
- KNOWN LIMITATIONS: dropped controller-auth caveat and the host-
CLI-no-image-flag caveat. Image-staleness caveat now points at
vrogojin/agentic_hosting#26 (trader-agent:v0.2 rebuild).
DEMO-PLAYBOOK-TRADER-ROUNDTRIP.md §3 narrative rewritten as "each peer
spins up its own local trader tenant" (stronger demo, and accurate).
§7.5 sidebar switches from `sphere host cmd TAIL_LOGS` to direct
`docker logs -f sphere-trader-<wallet>-<name>` since the per-user HM
exposes the container locally. Pre-flight checklist swaps
`sphere host list` for `docker info`. Cheat sheet + command quick
reference + exit-codes table all updated.
End-to-end 3-of-3 verification is out of scope for this commit:
gated on sphere-cli#49 wrapper merging into sphere-cli main and on
the trader-agent:v0.2 image landing (vrogojin/agentic_hosting#26).
vrogojin
added a commit
to unicity-sphere/sphere-sdk
that referenced
this pull request
Jun 10, 2026
…rift audit + #473 investigation (#475) * feat(trader)(sphere-sdk#474): roundtrip soak + demo playbook + spec drift audit + #473 investigation Closes the deliverables side of #474 -- the autonomous trader-agent roundtrip soak and presenter playbook. Implementation pieces (trader service, sphere-cli, agentic-hosting templates, escrow service) already exist; this commit wires them into a soak the operator can run and a demo a presenter can deliver. Deliverables: - manual-test-trader-roundtrip.sh -- 12-section soak that creates alice + bob controller wallets, faucets them asymmetrically, spawns two trader tenants via the host manager, posts matching SELL/BUY intents on the (UCT, ETH) pair with rate band [0.08, 0.12] ETH/UCT, waits for autonomous negotiation and settlement, and asserts net deltas inside the band. Uses human-friendly floats as the CLI surface (per project owner UX guidance) with an inline bigint-shim fallback for the current trader-cli surface. HOST_MANAGER (or SPHERE_HOST_MANAGER) is mandatory. - docs/DEMO-PLAYBOOK-TRADER-ROUNDTRIP.md -- ~25 minute presenter narrative mirroring the swap playbook skeleton. Highlights the "controllers go quiet; agents negotiate" beat in sections 6 and 7. Includes a pre-flight CLI form check + bigint fallback table for pre-#474-CLI builds. - docs/uxf/PROTOCOL-SPEC-DRIFT-474.md -- audit of trader-service/docs/protocol-spec.md vs trader-service implementation. 21 findings, 5 follow-up sub-issues drafted inline. Load-bearing findings: D1 (rate/volume types: float-in-spec vs bigint-in-code), D2 (NP envelope signature input formula), D3/D4 (deal_id derivation and DealTerms omit 4 fields including proposer_direction -- enables flip attacks), D-NEW (CLI should accept human-friendly floats per owner UX guidance). - docs/uxf/ISSUE-473-INVESTIGATION.md -- root cause + smallest-fix proposal for sphere-sdk#473. M1 is BLOCKING: the Mux advances the chat-side since cursor with wall-clock-now BEFORE the async handler chain (SwapModule.handleIncomingDM) completes. CLI exits during the soak's 3 s poll loop kill the handler mid-flight; the cursor advance persists; alice's gift-wrap is then permanently filtered out by the relay's since filter on subsequent boots. Smallest fix: defer updateLastDmEventTimestamp until after dispatch resolves, and double the look-back buffer at subscription time. No source change applied here; #473 fix ships separately on branch fix/473-since-cursor. Known limitations baked into the soak header: 1. #473 cross-process DM flakiness -- mitigated via with_retry + tenant cursor priming; real fix lands in sibling PR. 2. CLI float-vs-bigint UX -- soak writes float form (post-fix UX) with a bigint shim fallback. CLI fix is a sphere-cli follow-up. 3. trader-agent:v0.1 image staleness -- predates #456 / #457 / #464 / #465. Image rebuild is a follow-up. No SDK source files were modified. Related: - Closes sphere-sdk#474 (deliverables side; G1 testnet walkthrough is operator-only and tracked in the audit's recommendations) - sphere-sdk#437 (swap-roundtrip sibling, closed) - sphere-sdk#456 (escrow nametag rotation, closed) - sphere-sdk#473 (cross-process DM flakiness, sibling PR) * feat(trader)(sphere-sdk#477): reshape soak to per-user local-HM pattern (sphere trader spawn/stop) The previous revision of `manual-test-trader-roundtrip.sh` assumed a single shared Host Manager that both alice and bob spawned trader tenants against, addressed via `HOST_MANAGER` / `SPHERE_HOST_MANAGER` env vars and `sphere host spawn --manager $HOST_MANAGER`. That model has two structural problems: 1. Auth-model collision. The public HM whitelists exactly one controller pubkey; the soak creates two fresh wallets per run, neither of which is on the whitelist, so spawn would fail at the ACP-authorization step. 2. Per project owner guidance (memory: per-user local-HM design), each developer runs their OWN local HM scoped to their controller pubkey. The public HM is reserved for shared services (escrow, faucet). The shared-HM pattern was never the intended model. This commit reshapes the soak around the new `sphere trader spawn` / `sphere trader stop` wrapper that landed in unicity-sphere/sphere-cli#49. The wrapper brings up a per-user local HM container scoped to the active wallet's controller pubkey + spawns the trader tenant in one command. Each peer (alice, bob) gets its own HM; no shared infra dependency at the HM layer. Changes: - §3 now calls `sphere trader spawn --name <slug> --trusted-escrows $ESCROW --json` per peer instead of `sphere host spawn ... --env UNICITY_CONTROLLER_PUBKEY=$ALICE_PUBKEY ...`. - §4 simplifies to a single ACP smoke probe (`sphere trader portfolio`); the wrapper's own `--ready-timeout-ms` covers the container-RUNNING wait that `sphere host inspect` previously did. - §11 cleanup switches to `sphere trader stop --name <slug> [--keep-hm]`. KEEP_TENANTS=1 forwards `--keep-hm` so the per-user HMs stay running for inspection; the wrapper auto-tears down the HM when the last tenant attached to it stops. - TRADER_DEAL_DEADLINE_S default bumped 600 -> 900s to cover per-user HM bootstrap (two-shot drift-guard restart) on top of the trader scan interval. - Env contract: HOST_MANAGER / SPHERE_HOST_MANAGER / TRADER_TEMPLATE_ID / TRADER_IMAGE_OVERRIDE / UNICITY_CONTROLLER_PUBKEY all removed. New env contract documents the sphere-cli#49 prerequisite + docker requirement. - KNOWN LIMITATIONS: dropped controller-auth caveat and the host- CLI-no-image-flag caveat. Image-staleness caveat now points at vrogojin/agentic_hosting#26 (trader-agent:v0.2 rebuild). DEMO-PLAYBOOK-TRADER-ROUNDTRIP.md §3 narrative rewritten as "each peer spins up its own local trader tenant" (stronger demo, and accurate). §7.5 sidebar switches from `sphere host cmd TAIL_LOGS` to direct `docker logs -f sphere-trader-<wallet>-<name>` since the per-user HM exposes the container locally. Pre-flight checklist swaps `sphere host list` for `docker info`. Cheat sheet + command quick reference + exit-codes table all updated. End-to-end 3-of-3 verification is out of scope for this commit: gated on sphere-cli#49 wrapper merging into sphere-cli main and on the trader-agent:v0.2 image landing (vrogojin/agentic_hosting#26).
4 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Wraps the per-user local host-manager + trader-agent bring-up into a single command. Closes unicity-sphere/sphere-cli#48.
Before this PR, each developer who wants to run an autonomous trader had to hand-orchestrate two docker containers (an agentic-hosting HM with their wallet whitelisted, then a trader-agent tenant on that HM with synthesized HM env vars) and scrape the tenant address from
{event: 'sphere_initialized'}log lines. After this PR:What lands
sphere host local-spawn / local-stop / local-status— primitive HM container lifecycle, scoped to the current wallet. Idempotent.sphere trader spawn / stop— ergonomic wrapper that chainsensureLocalHM+ HMCPhm.spawnagainst the local HM, then prints the tenant's reachable address as JSON or human-readable text.Implementation notes
host-manager/main.ts:502) fails the very first boot — intentional protection against pointing the manager at the wrong data dir post-misdeploy — embedding the real wallet pubkey in the error message. The wrapper scrapes that, restarts the container with correctedMANAGER_PUBKEY/MANAGER_DIRECT_ADDRESS, and waits for the success marker (sphere_initialized). The wallet persists in the bind-mounted volume so subsequent invocations skip the bootstrap entirely.sphere-hm-<wallet-prefix>, data dirs live under./.sphere-cli/local-hm/<wallet-prefix>/, and the diagnostics health port is derived deterministically from the wallet prefix. Two developers on the same host don't collide.buildTraderEnv()mirrorstrader-service/test/e2e-live/helpers/tenant-fixture.ts:300-365—UNICITY_CONTROLLER_PUBKEY,UNICITY_TRUSTED_ESCROWS,TRADER_SCAN_INTERVAL_MS, and theTRADER_TEST_FUND+TRADER_FAULT_INJECTION_ALLOWED=1pair for testnet self-mint. The ACP boot envelope (UNICITY_MANAGER_PUBKEY,UNICITY_BOOT_TOKEN,UNICITY_INSTANCE_ID,UNICITY_INSTANCE_NAME,UNICITY_TEMPLATE_ID) is injected by the HM — the wrapper deliberately does NOT shadow those.sphere trader spawncall with the same wallet + name, the wrapper doeshm.listfirst and returns the existing tenant's info instead of attempting a duplicate-name spawn (which the HM rejects withINVALID_PARAMS).sphere trader stopchecks viahm.listwhether any other tenants remainCREATED/BOOTING/RUNNINGon the local HM. Tears the HM down only when no live tenants remain.--keep-hmoverrides.dockerCLI viachild_process.execFilerather than addingdockerode. Keeps the surface narrow and audit-friendly.--test-fundis refused on--network mainnet— self-mint funding only makes sense on testnet/dev.Bundled templates.json
Ships
DEFAULT_TEMPLATEScoveringtrader-agent(ghcr.io/vrogojin/agentic-hosting/trader:v0.1) andescrow-service(ghcr.io/vrogojin/agentic-hosting/escrow:v0.3) so a freshsphere trader spawndoesn't need the operator to write a templates.json by hand. Overridable with--templates-file. Bumps to the trader image follow vrogojin/agentic_hosting#26 → CLI follow-up PR.Test plan
npx tsc --noEmit— cleannpx eslint <new files>— zero warnings, zero errorsnpm test— 187 / 187 pass (45 new tests across 3 files:local-hm.test.ts,spawn.test.ts,trader-commands.test.ts)npm run build— tsup produces 341 KB ESM + 344 KB CJS, no errorsnode bin/sphere.mjs trader spawn --help,… trader stop --help,… host local-spawn --helprender correct usageRelated