<!-- zibby-template-version: 4 -->
# /zibby-compose — compose marketplace agents into one wrapper workflow

You are helping the user combine the capabilities of MULTIPLE existing
Zibby agents (marketplace or custom) behind a single workflow. The
result is a small project-private **wrapper** graph that dispatches the
deployed agents as sub-workflows.

Two hard rules before anything else:

1. **Never rebuild what a marketplace agent already does.** If the
   user's need spans capabilities of existing agents, compose them.
2. **NEVER modify a marketplace template's source.** Bricks are shared
   LEGO pieces maintained upstream — the wrapper is the only thing you
   author.

## Steps

1. **Map the need to bricks.**
   ```
   zibby marketplace                 # live marketplace (cloud or self-host)
   zibby marketplace docs <slug>     # each candidate's inputs + pipeline
   zibby agents list                 # what's already deployed in the project
   ```
   Sketch which brick covers which part, and what the wrapper must map
   between them (brick A's output fields → brick B's input fields).

2. **Deploy missing bricks — and apply the reuse policy.**
   - Brick NOT deployed yet: deploy it by slug.
     - Cloud: MCP tool `zibby_deploy_marketplace_workflow`
       (`{ projectId, marketplaceSlug, displayName? }`; install the MCP
       via `/zibby-mcp-install`) or the Studio marketplace UI. There is
       no `zibby agents deploy` CLI command.
     - Self-host: `zibby self-host add <slug> --deploy` (default
       project, bare slug) or the dashboard marketplace (custom name).
   - Brick ALREADY deployed: **ASK the user — never silently choose:**
     - *Reuse it* — the composition's runs fold into that instance;
       env/custom-prompt/store config is shared with standalone use.
     - *Deploy a dedicated named instance* — custom
       `displayName`/`workflowName`, config isolated.

3. **Scaffold the wrapper** — a normal custom workflow:
   ```
   zibby agent new <wrapper-name>
   ```

4. **Write the wrapper graph.** A sub-workflow node = `addNode` with a
   `workflow:` field naming the DEPLOYED slug (the `workflowType` from
   `zibby agent list`). The child runs in-process where possible and
   its final state lands at `state[nodeName]`:
   ```js
   graph.addNode('review', {
     workflow: 'gitlab-code-review',
     // Input mapping is the wrapper's job — use the brick's CANONICAL
     // structured fields (zibby marketplace docs <slug>). In-process children
     // skip the brick's run()-normalization (e.g. mrUrl parsing).
     input: (state) => ({ projectId: state.projectId, mrIid: state.mrIid }),
     timeoutMs: 15 * 60 * 1000,
   });
   // Bricks are full multi-exit graphs — branch on the returned state.
   // Child node outputs are NESTED: state.review.review.posted is the
   // child's own `review` node output.
   graph.addConditionalEdges('review', (state) =>
     state.review?.review?.posted === true ? 'meter' : 'END');
   ```
   Options per sub-workflow node: `input` (object or fn of state),
   `output` (dot-path or fn extracting from the child final state),
   `async: true` (fire-and-forget), `timeoutMs`, `retries`. Parallel
   fan-out: `dispatchSubgraph(slug, { input })` from
   `@zibby/agent-workflow` inside one `execute` node with
   `Promise.allSettled`. See §10 of `.claude/CLAUDE.md` for a complete
   compiling example.

5. **Env.** In-process children inherit the WRAPPER's run env — a
   brick's own Env tab applies only to its standalone runs. Set every
   credential the bricks need on the wrapper:
   `zibby agent env set <wrapper-uuid> CLAUDE_CODE_OAUTH_TOKEN=...`
   (missing creds show up as `authentication_failed` inside the brick).

6. **Triggers — INHERIT the entry brick's events (agent-driven, no
   hardcoding).** Webhook-driven composition: READ the ENTRY brick's
   declared events — `GET $ZIBBY_API_URL/projects/<projectId>/
   workflows/<entry-slug>` → `.triggers.events` (or the brick
   template's `agent.json`) — and copy that exact array into the
   WRAPPER's `agent.json`
   (`{ "triggers": { "events": [<entry brick's events, verbatim>] } }`).
   Never invent or hand-pick events. The platform suppresses the
   wrapped members' own subscriptions (derived from the wrapper row's
   `composedOf`) so nothing double-fires. Cron/manual/chat
   compositions need nothing special.

7. **Validate, deploy, trigger** — the standard loop:
   ```
   zibby agent validate <wrapper-name>
   zibby agent deploy <wrapper-name>
   zibby agent trigger <uuid> -p key=value
   zibby agent logs <uuid> -t
   ```
   Confirm each brick ran as a child of the wrapper's execution (the
   run log shows the sub-workflow dispatches; child executions appear
   in the project's execution list).

## Self-host

Identical flow. Point the CLI at the control plane first:
```
export ZIBBY_API_URL=<control-plane URL>    # e.g. https://zibby.internal.example.com
export ZIBBY_API_KEY=zby_pat_xxx            # USER pat — `zibby self-host token` mints one
```
Verified self-host specifics:
- Use a USER PAT (`zby_pat_…`) — a project-scoped token gets 403 on
  marketplace routes and can't presign the wrapper's source upload.
- In-process child dispatch needs a one-time per-brick bundle step:
  `node backend/selfhosted/build-child-bundle.mjs --project <id>
  --workflow <child-slug>` (ships with the stack; re-run after
  re-deploying that brick). Without it, dispatch still works but each
  child cold-starts a second run container.
- Deploy from the stack host (or its docker network) if the presigned
  upload URL from the stack's own object store (MinIO) uses an
  internal hostname like `minio:9000`.
- Self-signed HTTPS: no `--insecure` flag exists — use
  `export NODE_EXTRA_CA_CERTS=/path/to/ca.pem` (Node's standard escape
  hatch). Plain-HTTP self-hosts need nothing.

## Common failure modes

- **`Sub-graph dispatch requires PROJECT_API_TOKEN`** — you ran the
  wrapper somewhere without the project run-env (e.g. a bare local
  script). Deployed runs get it automatically.
- **Child input rejected (400)** — the brick's stateSchema didn't like
  the mapped input. `zibby marketplace docs <slug>` for the expected fields;
  fix the wrapper's `input:` mapping, not the brick.
- **Dispatch works but the branch never fires** — you branched on the
  wrong path. Child node outputs are nested under the child's node
  names inside `state[wrapperNodeName]`. Log the returned state once.
- **Brick failure kills the whole run** — wrap parallel dispatches in
  `Promise.allSettled`, or give the sub-workflow node `retries`.
