Mycel's Client Surface — and how it evolves using itself
The Exchange serves the same host-agnostic Shell every habitat serves (@umwelten/substrate/serve), with components that are strictly read-only over endpoints that already exist. https://mycel.thefocus.ai/ lands on /shell/; the built-in roster is health (store reachability), models (the catalogue, priced at the cheapest eligible Offer), and catalogue-stats (the at-a-glance summary — itself grown through the loop below, then promoted).
Two decisions bound everything here (ADR 0026, #409):
- Read-only. Nothing on this surface moves money or changes configuration. Admin stays on the operator CLI (
mycel …), deliberately — seepackages/mycel/src/command.tsfor why there is no HTTP admin API. The constraint holds by construction: the mycel manifest declares no provider entries, so noshell:tools(or any mutating service) exists for a component to inject. All a component can do isfetchthe Exchange's public reads (/health,/v1/models). - Zero deps on the exchange paths.
@umwelten/substrate(itself dependency-free) is imported only byclient-surface/serve.ts; dispatch and metering code never touch it.
The self-assembly loop (#410)
Mycel front-end development is a conversation with the agent that owns the mycel surface. The moving parts:
A dev Exchange with a components directory. The
--components-dirflag (orMYCEL_COMPONENTS_DIR) points a running Exchange at a directory of agent-authored components, served live under the standard contract — scanned per manifest request, versioned by mtime, hot-reloaded by the shell's 2-second poll:bashdotenvx run -- pnpm run cli mycel serve --ephemeral \ --components-dir ~/mycel-agent/components # → http://localhost:7438/shell/The mycel-owning agent. Any habitat whose work directory carries the mycel surface. Its
create_componenttool writes plain-ESM modules toworkDir/components/— the same directory the dev Exchange serves:bashdotenvx run -- pnpm run cli habitat local --work-dir ~/mycel-agentThe conversation. Ask for a view in chat — "add a card showing how many models are on offer and the cheapest completion price". The agent writes
components/catalogue-stats.js; within a poll the card is on the open shell page, no rebuild, no reload. Ask for a change; the edit hot-replaces the card. A broken edit rolls back visibly: the previous version keeps rendering while the shell status line names the failure (custom:catalogue-stats: SyntaxError …); the next good edit recovers.
The loop is exercised end-to-end (minus the LLM choosing to call the tool) by packages/mycel/src/client-surface/self-assembly.integration.test.ts — a real Exchange, a real Chromium, all four legs: grow, hot-replace, broken edit, recover.
Promotion: dev instance → deployed surface
Agent-authored components are a dev-instance affordance. The deployed Exchange (its own VM, ADR 0030) serves the repo, so promotion is a normal change:
- Copy the module from the dev directory into
packages/mycel/src/client-surface/components/<name>.js— verbatim; the contract is identical, so no edits are needed beyond a header comment. - Add its row to
ENTRIESinpackages/mycel/src/client-surface/serve.ts(this also changes its id fromcustom:<name>to<name>). - Update the manifest expectation in
serve.test.ts, run the client-surface tests, open a PR. - Deploy mycel as usual (
deploy/mycel/README.md).
catalogue-stats is this path exercised once: grown as custom:catalogue-stats on a dev Exchange, promoted with steps 1–3, its header recording the provenance.
What to check after touching this surface
npx vitest run packages/mycel/src/client-surface/serve.test.ts
PLAYWRIGHT_CHROMIUM=/opt/pw-browsers/chromium \
npx vitest run --config vitest.integration.config.ts packages/mycel/src/client-surface/And the read-only rule when reviewing an evolved component: it may fetch GET endpoints and render; anything that would POST, hold a credential, or reveal a Supplier does not get promoted.