Zedge Consumer Map: wiring the upstream editor to the guarded subagent swarm
Status: analysis / proposal only. No zedge code is changed by this document.
Zedge (open-source/zedge) is the UPSTREAM CONSUMER and is treated read-only.
Section 5 documents the new first-class forkjoin passthrough tier (in-progress,
implemented by a sibling); the contract there is agreed but the source is not yet
verified by this document -- items are flagged "to confirm in reconciliation".
This maps how zedge launches local inference today to how it should consume
the new producer-side primitives in open-source/gnosis:
monster-swarm— UCAN-gated, cost-admitted CRUD over a leased subagent fleet.monster-resident— the MCP server that surfaces that CRUD as thesubagent_*/swarm_*tools (plusorchestrate).monster-guard— the runtime sandbox + lease + revoke enforcer.moonshine— the default node (Qwen-knot fat-station warmer).fat-station— the dense/rknot inference station the Paris quality gate runs.
0. Producer-side facts established by reading the source
Verified against the producer tree (not assumed):
- Default node when none is given is
"moonshine", overridable per-call (nodearg) or globally viaMONSTER_SWARM_DEFAULT_NODE.monster-swarm/src/ops.rs:28-32. - Spawn is double-gated: UCAN
agent/spawn(oragent/*) onagent:fleetrooted at the resident root (ops.rs:61-90), then AntColony FRF admission (ops.rs:149-154), thenmonster-guard delegate+monster-guard run(guard.rs:98-163). Nodes must resolve inside the allowlist (MONSTER_SWARM_NODES/DI_BIN_DIR/ DI cargo build dirs) —guard.rs:35-95. - MCP tool contract is exact:
subagent_createtakes{ ucan, node?, id?, caste?, caps?, lease_secs?, max_secs?, grant_ttl_secs?, node_args? }, onlyucanrequired;subagent_list/subagent_reconcile/swarm_vibeneed no UCAN;subagent_update/subagent_reap/swarm_autotickrequire UCAN.monster-resident/src/main.rs:331-437, 570-681. - Per-subagent identity: spawn mints a DID +
0600keyfile and hands the nodeMONSTER_AGENT_ID,MONSTER_AGENT_DID,MONSTER_AGENT_KEYFILE(ops.rs:160-168). Shared state lives under$MONSTER_GUARD_HOME(default~/.moonshine.d). - The verified Qwen Paris quality gate spawns
fat-station(NOT moonshine) as a leased Scout withcaps=["net"],node_args = --knot <dense> --port 8779 --role both --layers 0..28, drives/decode-nextover the wire, asserts next token12095(" Paris"), then reaps.monster-swarm/tests/paris_subagent.rs. The knot it uses is~/.edgework/models/qwen-coder-7b.knot(a coder model); the test deliberately asserts the token-level completion (token 12095) of "The capital of France is", because a coder model deflects chat questions ("I'm a coding assistant…"). This is the chat-template deflection risk (see §4). - Built binaries present locally:
monster-resident(release + debug),fat-station(release),monster-guard. NOT present:fat-station-memorelease, and anyqwen2.5-0.5b-instruct-q4_k_m.knot(see §2 gap).
1. How zedge SHOULD spawn its inference as a UCAN-leased subagent
1a. What zedge does today (the unguarded path)
zedge/companion/src/moonshine-docker.ts is the entire launch path:
ensureMoonshineRunning()(:974) probes:8080(OpenAI shim) and:8000(fat-station), and if not healthy, callsstartLocalMoonshine()(:733).startLocalMoonshine()directlyspawnDetacheds the fat-station binary (FAT_STATION_BIN, preferringfat-station-memo, falling back tofat-station) with args built bybuildFatStationSourceArgs()(:667):- rknot mode:
--rknot <rknot> --dense <knot> - dense mode:
--knot <knot>plus--port 8000 --role both --layers <range>and theGNOSIS_*env knobs (:773-804).
- rknot mode:
- Then it
spawnDetacheds the TypeScript OpenAI shim (distributed-inference-host/src/bin/openai-server.ts) on:8080withFAT_STATION_URL,MODEL_NAME,AGENTIC=0, memo flags, optionalTOKENIZER_GGUF_PATH(:884-908). - A watchdog re-runs
ensureMoonshineRunning()on degrade (:1034).
There is no UCAN, no lease, no sandbox, no revoke, no population ceiling.
The fat-station runs as a bare detached child of the companion. This is exactly
the gap monster-swarm/monster-resident close.
1b. The target call sequence (guarded)
Replace the bare spawnDetached(FAT_STATION_BIN, …) with a guarded spawn that
goes through monster-resident. Two integration shapes; recommend B for the
companion sidecar.
Shape A — direct over MCP stdio (mirrors run-mcp-with-supervisor.sh).
The companion already speaks MCP stdio (mcp-stdio.ts). Add a second outbound
MCP client that launches monster-resident and calls its tools:
Provision once (out of band, by the operator):
export MONSTER_GUARD_HOME=~/.moonshine.d monster-resident whoami # the root DID; must root every cap # mint the fleet capability for the companion's station DID: monster-resident grant \ --audience <companion-station-did> \ --with agent:fleet --can agent/* --caps net --ttl 86400 > fleet.ucanNote:
grant --with agent:fleet --can agent/*is the swarm capability thesubagent_*tools verify (ops.rs:78-89). (The README examplegrant --with plugin:<name>is fororchestrate, a different surface.)Launch the resident with the node allowlist + guard wired:
MONSTER_GUARD_HOME=~/.moonshine.d \ MONSTER_GUARD_BIN=<…>/monster-guard/target/release/monster-guard \ MONSTER_SWARM_NODES=<…>/distributed-inference/target/release \ DI_BIN_DIR=<…>/distributed-inference/target/release \ monster-resident serve # stdio JSON-RPCFrom the companion, MCP
initialize, thentools/call subagent_create:{ "name": "subagent_create", "arguments": { "ucan": "<fleet.ucan>", "node": "fat-station", // the runnable dense station "id": "zedge-inference", "caste": "breeder", // persistent: it's the editor's model host "caps": ["net"], "lease_secs": 60, "grant_ttl_secs": 86400, "node_args": [ "--knot", "<~/.edgework/models/qwen-coder-7b.knot>", "--port", "8000", "--role", "both", "--layers", "0..28" ] }}This is the Paris-gate node line, parameterized. The node serves on
:8000exactly asmoonshine-docker.tsexpects, so the existing OpenAI shim step is unchanged and still bindsFAT_STATION_URL=http://127.0.0.1:8000.Health-wait on
:8000/health(zedge already haswaitUrlReady), then start the shim on:8080as today.On shutdown / model switch:
tools/call subagent_reap { ucan, id }— the node dies at the next lease tick. The companion watchdog callssubagent_reconcile(no UCAN) to prune zombie rows, andsubagent_listto read fleet state instead of port-probing.
Shape B — wrap monster-swarm spawn inside the existing
startLocalMoonshine() (recommended). Keep moonshine-docker.ts as the
orchestrator, but swap the fat-station spawnDetached for an MCP
subagent_create call (or, simplest, shell out to the monster-swarm CLI which
shares the same ops core). The OpenAI-shim spawn, health probes, model
fingerprint checks, and watchdog all stay. This is the minimum-blast-radius
change: only buildFatStationSourceArgs + the two spawnDetached(FAT_STATION_BIN…)
sites move behind the guard.
1c. Concrete env/knot/bin mapping
| zedge concept (moonshine-docker.ts) | guarded equivalent |
|---|---|
FAT_STATION_BIN (fat-station-memo/fat-station) |
node: "fat-station" resolved by MONSTER_SWARM_NODES/DI_BIN_DIR allowlist |
buildFatStationSourceArgs() → --knot/--rknot --dense |
node_args: [...] (same flags, byte-for-byte) |
--port 8000 --role both --layers <range> |
identical, passed through node_args |
spawnDetached(... detached:true) |
monster-guard run (lease + sandbox + revoke) via subagent_create |
watchdog ensureMoonshineRunning repair |
subagent_list + subagent_reconcile, respawn on dead row |
| (none) — no auth today | UCAN agent/* on agent:fleet, rooted at resident root |
| (none) — no kill switch | subagent_reap (revoke) |
ZEDGE_FAT_STATION_BIN env override |
still works for choosing the binary name passed as node |
GNOSIS_NUM_THREADS, GNOSIS_FFN_*, RUST_BACKTRACE |
inherited by the node (env inheritance — see §4 risk), set on the resident process |
The OpenAI shim (:8080) and Zed's openai_compatible provider pointing at
127.0.0.1:7331/v1 are unchanged. Only the fat-station birth is moved behind
the guard.
2. Knot / model config reconciliation (the central gap)
The gap
| What | Path | Exists? |
|---|---|---|
| zedge's expected dense knot | open-source/bitwise/datasets/qwen2.5-0.5b-instruct-q4_k_m.knot (moonshine-docker.ts:31-34) |
NO — bitwise/datasets/ has no .knot files |
| zedge's expected tokenizer GGUF | …/gguf/qwen2.5-0.5b-instruct-q4_k_m.gguf (:35-38) |
NO |
| zedge model id (catalog + Zed picker) | qwen2.5-0.5b-instruct (model-catalog.ts:30-34, moonshine-docker.ts:55) |
maps to the missing knot |
| the ONLY runnable dense knot | ~/.edgework/models/qwen-coder-7b.knot (4.7 GB, cached) |
YES |
| moonshine node default | falls back to ~/.edgework/models/qwen-coder-7b.knot, else the same missing qwen2.5-0.5b-instruct-q4_k_m.knot path (moonshine/src/main.rs:112-126) |
runnable only via the cached coder knot |
So zedge defaults to a 0.5B-instruct knot that is neither on disk nor in R2,
while the producer (moonshine + the Paris gate) has already standardized on the
7B coder dense knot that is cached. The 0.5B path's R2 fallback also does
not exist — only gemma4-31b-it.knot has a real R2 URL in zedge
(moonshine-docker.ts:43-45); there is no qwen…0.5b R2 URL at all.
Recommended config
Align zedge's runnable Qwen model with the cached coder knot the producer already verifies:
- Point the Qwen spec at
~/.edgework/models/qwen-coder-7b.knot(matchingmoonshine/src/main.rsprecedence and the Paris gate), OR, cleaner, add a new catalog entryqwen-coder-7bwhoseknotPathis the cached file and make it the local default. - Until a real
qwen2.5-0.5b-instruct-q4_k_m.knotis produced and uploaded to R2, do not advertiseqwen2.5-0.5b-instructas a local model (it will fail thesourceExists()check atmoonshine-docker.ts:222-230and silently fall back). Either gate it behind R2 availability or relabel. - The coder-7B is a base/coder model: keep
AGENTIC=0and prefer completions (/v1/completions, already wired for edit-predictions ingenerate-settings.ts:192-196) over chat for FIM/tab use, where token-level completion (the Paris-gate behavior) is exactly what you want. - The runtime fingerprint guard in
moonshine-docker.ts(hidden_dim,vocab_size,layers) means the layer range must match what the node reports. The Paris gate uses--layers 0..28for the 7B; zedge's Qwen spec carries nodefaultLayers(so it derives0..<num_layers>from knot metadata, or falls back to0..22—DEFAULT_KNOT_LAYER_COUNT). Set the Qwen-coder spec'sdefaultLayersto the coder knot's true depth (28 per the gate) to avoid the fingerprint mismatch restart loop.
3. PROPOSED minimal edits to zedge (checklist — DO NOT APPLY)
All edits are in open-source/zedge/companion/. Marked PROPOSED; for review.
- PROPOSED
companion/src/moonshine-docker.ts:31-38— repoint the Qwen spec to the runnable knot. Either changeQWEN_KNOT_PATHto~/.edgework/models/qwen-coder-7b.knot(withexistsSyncprecedence kept) or add aQWEN_CODER_KNOT_PATHconstant pointing there. - PROPOSED
companion/src/moonshine-docker.ts:157-178(LOCAL_MOONSHINE_MODELS) — add aqwen-coder-7bspec{ modelName, knotPath: cached, defaultLayers: '0..28' }; optionally make it the local default instead ofgemma4-31b-itat:284. - PROPOSED
companion/src/model-catalog.ts:16-41— add aqwen-coder-7bentry toKNOWN_ZEDGE_MODELS(and consider demotingqwen2.5-0.5b-instructuntil its knot exists in R2). - PROPOSED
companion/src/moonshine-docker.ts:733-911(startLocalMoonshine) — replace the twospawnDetached(FAT_STATION_BIN, [...buildFatStationSourceArgs(config), …])blocks (:773and:845) with a guarded spawn: an MCPsubagent_createto amonster-residentclient (Shape A) or amonster-swarm spawnshell-out (Shape B). Keep the OpenAI-shim spawn (:883-908) and all health probes. - PROPOSED new file
companion/src/monster-resident-client.ts— a small MCP stdio client (initialize → tools/call) that launches/connects tomonster-resident serveand exposessubagentCreate/list/reap/reconcile. Reuse the existing stdio framing frommcp-stdio.ts. - PROPOSED new env in
moonshine-docker.tsresolution block (near:57-120):ZEDGE_MONSTER_RESIDENT_BIN,ZEDGE_MONSTER_GUARD_BIN,ZEDGE_MONSTER_SWARM_NODES(→MONSTER_SWARM_NODES/DI_BIN_DIR),ZEDGE_FLEET_UCAN(or a path tofleet.ucan),MONSTER_GUARD_HOME(default~/.moonshine.d). - PROPOSED
companion/src/moonshine-docker.ts:1034-1066(startMoonshineRuntimeWatchdog) — on degrade, prefersubagent_reconcile+subagent_listover raw port probing, and respawn via the guarded path. - PROPOSED
scripts/companion-launch-agent.sh:249-265(plistEnvironmentVariables) — addMONSTER_GUARD_HOME,MONSTER_GUARD_BIN,MONSTER_SWARM_NODES/DI_BIN_DIRso the launchd-managed companion can reach the guard + node allowlist (launchd has no shell profile). - PROPOSED docs only:
scripts/generate-settings.ts— no functional change needed; the127.0.0.1:7331/v1provider and/v1/completionsedit-prediction wiring stay. Optionally update the model list comment to mentionqwen-coder-7b. - PROPOSED (operator runbook, not code) — document the one-time
monster-resident grant --with agent:fleet --can agent/*provisioning and wherefleet.ucanis stored (suggest~/.edgework/fleet.ucan,0600).
Smallest honest first step: do the §2 knot repoint (catalog + spec) so local inference works at all with the cached knot; then layer in the guarded spawn (Shape B) so it works safely.
4. Mismatches / risks
- Auth model mismatch. zedge's local auth is an OpenAI-compatible bearer key
(
ZEDGE_API_KEY/zedge-local,companion-launch-agent.sh:49,257-262) that protects the:7331/:8080HTTP surface. monster-swarm auth is a UCAN (agent/*onagent:fleet) that protects the spawn surface. These are orthogonal and both needed: the API key gates who can ask for inference; the UCAN gates who can create/kill inference nodes. Do not conflate them. zedge has aUcanBridge(companion/src/ucan-bridge.ts) but it is rooted at zedge's own identity for collab/file caps — the fleet capability must be rooted at the monster-resident root DID (monster-resident whoami), a different root. - Knot does not exist (highest-impact). The default Qwen knot zedge points at
(
qwen2.5-0.5b-instruct-q4_k_m.knot) is absent on disk and in R2; only the 7B coder knot is runnable. Without the §2 repoint, the guarded path will spawn a node that failssourceExists()and the companion silently degrades. - Coder-vs-instruct behavior + chat-template deflection. The runnable knot
is a coder model. On
/v1/chat/completionsit will deflect chat-style questions ("I'm a coding assistant…"); the producer's own quality gate works around this by asserting the raw next token, not chat output (paris_subagent.rs:5-9, 30-33). Implication for zedge: route the local coder-7B to completions/FIM (tab predictions, already wired) and treat the Agent-panel chat experience as second-class until a true instruct knot exists. KeepAGENTIC=0for the shim (already the default atmoonshine-docker.ts:890). - Ports. No conflict introduced: fat-station stays
:8000, OpenAI shim:8080, companion:7331, Paris-gate uses:8779(test-only). The guarded node must still be told--port 8000vianode_argsso the shim'sFAT_STATION_URLresolves. The resident itself uses stdio, no port. - Layer-range fingerprint loop. zedge restarts the listener when the runtime
fingerprint (hidden_dim/vocab/layers) mismatches the expected spec
(
moonshine-docker.ts:452-470, 820-837). If the Qwen-coder spec'sdefaultLayersis left unset (derives0..22) but the node serves0..28, the companion will loop restarting. SetdefaultLayers: '0..28'for the coder spec (per §2/§3). - fat-station-memo absent. zedge prefers
fat-station-memoand falls back tofat-station(moonshine-docker.ts:60-78); onlyfat-stationis built in release. The allowlist (MONSTER_SWARM_NODES) must contain the directory of whichever binary is chosen; if zedge passesnode: "fat-station-memo"it must exist in the allowlist orresolve_noderejects it (guard.rs:57-95). - Node allowlist is secure-by-default (fails closed). If neither
MONSTER_SWARM_NODESnorDI_BIN_DIRis set, no node resolves and every spawn errorsno node allowlist configured(guard.rs:65-69). The launchd plist and any sidecar env MUST set it explicitly — launchd does not inherit the shell profile. - Environment inheritance. Spawned nodes inherit the launcher's env
(monster-swarm SECURITY.md "Environment inheritance"). The companion holds
ZEDGE_API_KEY/OPENAI_API_KEYin its plist env; the leased fat-station doesn't need them. Run the resident with a minimal env, or accept that the node sees them (the keys are local placeholders, low severity, but worth noting). - Single-writer ledger. monster-swarm assumes one writer to
fleet.json(atomic but not lock-protected). If both the companion and an operator drivemonster-swarmconcurrently, an update can be lost. Keep the companion the sole driver in the running session. - macOS sandbox only enforces signed binaries. Unsigned allowlisted nodes
run without the seatbelt profile (lease + revoke still apply). The local
fat-stationis likely unsigned, so the sandbox dimension is advisory until the node binaries are signed; the lease/revoke kill switch is real regardless.
5. First-class forkjoin passthrough tier (new, in-progress)
Status: contract agreed; sibling (ZW1) is implementing the source. The points below describe the agreed contract, not yet-verified code. Items marked "to confirm in reconciliation" depend on the sibling's final implementation.
What it is
A new first-class inference tier named 'forkjoin' added to
companion/src/inference-bridge.ts. It forwards OpenAI-shaped chat-completion
requests straight THROUGH to the Forkjoin OWN distributed-inference mesh
(fat-station / knots / WASM worker, surfaced via
@a0n/distributed-inference-host). This is the project's own runtime: there is
NO third-party and NO paid inference involved. It is the same mesh the rest of
this document maps; the forkjoin tier is simply the direct OpenAI-compatible
passthrough into it from zedge's hotpath.
Where it sits in the hotpath
infer() in inference-bridge.ts is the tier chain. Today (baseline) the chain
is:
Tier 1: moonshine -> Tier 2: echo (guaranteed fallback)With the forkjoin tier wired in, the order becomes:
forkjoin (PRIMARY) -> moonshine (graceful fallback) -> echo (guaranteed last)forkjoinis tried first. On HTTP error, timeout, or any failure it does NOT throw the whole request: it falls through to Moonshine exactly as Moonshine today falls through to echo.moonshinekeeps its current role as the local-container fallback.echoremains the final belt-and-suspenders response so the editor always gets a reply.
The InferenceTier union (currently
'mesh' | 'edge' | 'cloudrun' | 'wasm' | 'echo' | 'moonshine') gains
'forkjoin'. The X-Zedge-Tier response header and the attempts[] chain
(TierAttempt / TierResult) report forkjoin like any other tier, so the
existing telemetry and SSE-proxy chain logging cover it without change.
Config (env-gated)
| Env var | Default | Meaning |
|---|---|---|
ZEDGE_FORKJOIN_ENABLED |
on (default) | Master switch for the forkjoin tier. When off, the chain reverts to moonshine -> echo. |
ZEDGE_FORKJOIN_URL |
own-mesh base | The OWN-runtime mesh OpenAI-compatible base URL the passthrough targets (local default is a loopback mesh endpoint). |
| forkjoin timeout | to confirm | Per-attempt timeout; on expiry the tier records timeout and falls through to moonshine. Exact env name/default: to confirm in reconciliation. |
When ZEDGE_FORKJOIN_ENABLED is off OR ZEDGE_FORKJOIN_URL is unset/empty, the
forkjoin tier is skipped and the hotpath behaves exactly as the moonshine -> echo
baseline (no behavior change for existing setups).
Request / response shape
OpenAI /v1/chat/completions passthrough. The same ChatCompletionRequest the
companion already builds (post engram-injection and system-prompt budgeting) is
forwarded to ${ZEDGE_FORKJOIN_URL}/v1/chat/completions. Both streaming
(SSE) and non-streaming responses are proxied back unchanged, matching the
moonshine path's response handling. No request reshaping beyond what the
companion already does for the existing chat path is required, because the mesh
endpoint is OpenAI-compatible (it is the same shim contract documented in
sections 1-4: fat-station behind the distributed-inference-host OpenAI server).
Auth: local loopback vs guarded production
- Local / development path: loopback, no-auth.
ZEDGE_FORKJOIN_URLpoints at a loopback OWN-mesh endpoint on the same host; no UCAN and no bearer key are required for the passthrough itself (the same posture as the local Moonshine container today). - Guarded production path: requests go through UCAN / monster-resident, i.e. the
leased, revocable, cost-admitted mesh access this document describes in
sections 1-4. The passthrough target is then the guarded mesh surface rather
than a bare loopback port. The exact wiring of the production UCAN root and
whether the forkjoin tier mints/attaches the capability itself or relies on an
already-provisioned
fleet.ucan: to confirm in reconciliation.
Note (consistent with section 4): the OpenAI-key surface that protects zedge's
:7331/:8080 HTTP listener and the UCAN that gates mesh node lifecycle are
orthogonal; the forkjoin passthrough is a request-time data-plane call into the
mesh and does not change the spawn-time UCAN model.
Relationship to the rest of this map
Sections 1-4 cover the BIRTH AND DEATH of the mesh node (guarded spawn / lease /
reap of the fat-station). The forkjoin tier covers the REQUEST PATH into that
already-running mesh. They compose: monster-swarm / monster-resident lease the
node; the forkjoin passthrough is how zedge's infer() sends OpenAI traffic to
it as the primary tier, with moonshine and echo as the graceful and guaranteed
fallbacks.
Summary of recommended consumer wiring
- Fix the model first (§2). Repoint zedge's Qwen spec from the missing
qwen2.5-0.5b-instruct-q4_k_m.knotto the cached, runnable~/.edgework/models/qwen-coder-7b.knot, add aqwen-coder-7bcatalog + spec entry withdefaultLayers: '0..28', and stop advertising the 0.5B knot as a local model until it exists in R2. - Guard the spawn (§1, Shape B). Inside
startLocalMoonshine, replace the barespawnDetached(FAT_STATION_BIN, …)of the fat-station with a guarded spawn throughmonster-resident'ssubagent_create(or amonster-swarmshell-out):node: "fat-station",caste: "breeder",caps: ["net"],node_args= the existing--knot/--rknotflags +--port 8000 --role both --layers 0..28. Keep the OpenAI shim, health probes, and watchdog; route teardown throughsubagent_reapand liveness throughsubagent_list/subagent_reconcile. - Provision the capability (§1b, operator).
monster-resident grant --with agent:fleet --can agent/* --caps net, rooted at the resident root, stored as~/.edgework/fleet.ucan(0600); wireMONSTER_GUARD_HOME,MONSTER_GUARD_BIN, andMONSTER_SWARM_NODES/DI_BIN_DIRinto the companion env (incl. the launchd plist). - Mind the model character (§4). The runnable knot is a coder model — route
it to completions/FIM, keep
AGENTIC=0, and expect chat deflection until a true instruct knot is produced.
Net: the API-key surface is unchanged; only the birth and death of the fat-station moves behind the UCAN + lease + allowlist, turning zedge's bare detached child into a leased, revocable, cost-admitted subagent.