Skip to content

feat(cli)(#48): sphere trader spawn/stop wrapper + sphere host local-* primitives#49

Merged
vrogojin merged 1 commit into
mainfrom
feat/issue-48-trader-spawn
Jun 10, 2026
Merged

feat(cli)(#48): sphere trader spawn/stop wrapper + sphere host local-* primitives#49
vrogojin merged 1 commit into
mainfrom
feat/issue-48-trader-spawn

Conversation

@vrogojin

Copy link
Copy Markdown
Contributor

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:

sphere wallet use alice
sphere trader spawn
# → prints { instance_name, tenant_direct_address, hm_container, ... }
sphere trader create-intent --tenant <addr> ...
sphere trader stop  # tears down trader + HM together

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 text.

Implementation notes

  • Two-shot bootstrap. The agentic-hosting drift guard (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 corrected MANAGER_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.
  • Per-controller scoping. 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. Two developers on the same host don't collide.
  • Canonical launch pattern. buildTraderEnv() mirrors trader-service/test/e2e-live/helpers/tenant-fixture.ts:300-365UNICITY_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 attempting a duplicate-name spawn (which the HM rejects with INVALID_PARAMS).
  • Ref-count-aware tear-down. sphere trader stop checks via hm.list whether any other tenants remain CREATED / BOOTING / RUNNING on the local HM. Tears the HM down only when no live tenants remain. --keep-hm overrides.
  • No new dependencies. Shells out to the docker CLI via child_process.execFile rather than adding dockerode. Keeps the surface narrow and audit-friendly.
  • Mainnet guard. --test-fund is refused on --network mainnet — self-mint funding only makes sense on testnet/dev.

Bundled templates.json

Ships DEFAULT_TEMPLATES covering trader-agent (ghcr.io/vrogojin/agentic-hosting/trader:v0.1) and escrow-service (ghcr.io/vrogojin/agentic-hosting/escrow:v0.3) so a fresh sphere trader spawn doesn'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 — clean
  • npx eslint <new files> — zero warnings, zero errors
  • npm 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 errors
  • node bin/sphere.mjs trader spawn --help, … trader stop --help, … host local-spawn --help render correct usage
  • Out of scope: docker-touching end-to-end (covered by sphere-sdk#477 — soak reshape — once trader-agent:v0.2 from agentic_hosting#26 is published)

Related

…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 vrogojin merged commit 2a6cf68 into main Jun 10, 2026
2 checks passed
@vrogojin vrogojin deleted the feat/issue-48-trader-spawn branch June 10, 2026 14:16
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).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

sphere trader local-spawn / local-stop: ergonomic launch of per-user local HM + trader tenant

1 participant