Skill: issa-me-sush/openserv-multi-agent-workflows (version 1.0.2)

--- name: openserv-multi-agent-workflows description: Multi-agent workflow examples to work together on the OpenServ Platform. Covers agent discovery, multi-agent workspaces, task dependencies, and workflow orchestration using the Platform Client. Read reference.md for the full API reference. Read openserv-agent-sdk and openserv-client for building and running agents. --- # Multi-Agent Workflows on OpenServ Build workflows where multiple AI agents collaborate to complete complex tasks. **Reference files:** - `reference.md` - Workflow patterns, declarative sync, triggers, monitoring - `troubleshooting.md` - Common issues and solutions - `examples/` - Complete pipeline examples (blog, youtube-to-blog, etc.) --- ## Quick Start See `examples/` for complete runnable examples: - `blog-pipeline.md` - Simple 2-agent workflow (research → write) - `content-creation-pipeline.md` - 3-agent workflow (research → write → image) - `life-coaching-pipeline.md` - Complex 6-agent workflow with comprehensive input schema **Recommended pattern using `workflows.sync()`:** 1. Authenticate with `client.authenticate()` 2. Find agents with `client.agents.listMarketplace()` 3. Create workflow with `client.workflows.create()` including: - Triggers - Tasks - **Edges** (⚠️ CRITICAL - connects triggers and tasks together) **⚠️ CRITICAL:** Always define edges when creating workflows. Setting task `dependencies` is NOT enough - you must create workflow edges to actually connect triggers to tasks and tasks to each other. --- ## Workflow Name & Goal When creating workflows (via `workflows.create()` or `provision()`), two properties are critical: - **`name`** (string) - This becomes the **agent name in ERC-8004**. Make it polished, punchy, and memorable — this is the public-facing brand name users see. Think product launch, not variable name. Examples: `'Instant Blog Machine'`, `'AI Video Studio'`, `'Polymarket Intelligence'`. - **`goal`** (string, required) - A detailed description of what the workflow accomplishes. Must be descriptive and thorough — short or vague goals will cause API calls to fail. Write at least a full sentence explaining the end-to-end purpose of the workflow. --- ## Core Concepts ### Workflows A workflow (workspace) is a container that holds multiple agents and their tasks. ### Task Dependencies - Each task is assigned to a specific agent - Tasks can depend on other tasks: `dependencies: [taskId1, taskId2]` - A task only starts when all dependencies are `done` - Output from dependencies is passed to dependent tasks ### Workflow Graph - **Nodes**: Triggers and tasks - **Edges**: Connections between nodes - When Task A completes, its output flows to dependent tasks via edges ### Agent Discovery ```typescript // Search marketplace for agents by name/capability (semantic search) const result = await client.agents.listMarketplace({ search: 'research' }) const agents = result.items // Array of marketplace agents // Get agent details const agent = await client.agents.get({ id: 123 }) console.log(agent.capabilities_description) // Note: client.agents.searchOwned() only searches YOUR OWN agents // Use listMarketplace() to find public agents for multi-agent workflows ``` Common agent types: Research (Grok, Perplexity), Content writers, Data analysis, Social media (Nano Banana Pro), Video/audio creators. --- ## Edge Design Best Practices **CRITICAL: Carefully design your workflow edges to avoid creating tangled "spaghetti" graphs.** A well-designed workflow has clear, intentional data flow. Common mistakes lead to unmaintainable workflows. ### Bad Pattern - Everything Connected to Everything ``` ┌──────────────────────────────────┐ │ ┌─────────┐ │ │ ┌─────┤ Agent A ├─────┐ │ │ │ └────┬────┘ │ │ │ │ │ │ │ Trigger ─┼─────┼──────────┼──────────┼──────┤ │ │ │ │ │ │ │ ┌────┴────┐ │ │ │ └─────┤ Agent B ├─────┘ │ │ └─────────┘ │ └──────────────────────────────────┘ (Spaghetti - avoid this!) ``` This creates: - Unclear execution order - Difficult debugging - Agents receiving redundant/conflicting inputs - Hard to understand what depends on what ### Good Patterns **Sequential Pipeline:** ``` Trigger → Research → Content → Enhancement → Output ``` **Staged Fan-Out:** ``` ┌─ Task A ─┐ Trigger → Research ─┼─ Task B ─┼─→ Combiner → Output └─ Task C ─┘ ``` **Conditional Branching (v1.1.3+):** ``` ┌─[approved]─→ Process Trigger → Review ──┤ └─[rejected]─→ Reject Handler ``` Use `outputOptions` on tasks and `sourcePort` on edges for branching. ### Guidelines for Clean Workflows 1. **Linear is usually best**: Start with a simple chain, only add complexity when truly needed 2. **Each task should have a clear purpose**: If you can't explain why Task A connects to Task B, remove the edge 3. **Minimize cross-connections**: Avoid connecting every agent to every other agent 4. **Use fan-out only for parallel work**: Multiple tasks from one source is fine; connecting everything to everything is not 5. **One combiner at the end**: If you need to merge outputs, have ONE final task that depends on all parallel branches ### Before Adding an Edge, Ask: - Does Task B actually need the output of Task A? - Would Task B work without this connection? - Am I adding this edge "just in case"? (Don't!) --- ## Updating This Skill This skill may be outdated. The OpenServ team ships updates regularly—new features, API changes, and bug fixes won't be reflected here until you update. **If something isn't working as expected, or you want to make sure you have the latest information, update the skill before proceeding.** ```bash # Check if updates are available npx skills check # Update all installed skills to latest versions npx skills update ``` Or reinstall the OpenServ skills directly: ```bash npx skills add openserv-labs/skills ``` --- ## Related Skills - **openserv-agent-sdk** - Building individual agent capabilities - **openserv-client** - Full Platform Client API reference - **openserv-launch** - Launch tokens on Base blockchain - **openserv-ideaboard-api** - Find ideas and ship agent services on the Ideaboard

path: reference.md
content: # Multi-Agent Workflows Reference

API reference for workflow patterns, triggers, and operations.

For complete pipeline examples, see `examples/` folder.

---

## Workflow Patterns

### Sequential Chain

```
Trigger → Task 1 → Task 2 → Task 3
```

Link tasks with `dependencies`:

```typescript
const task2 = await client.tasks.create({
  workflowId,
  agentId: agent2Id,
  description: 'Step 2',
  dependencies: [task1.id] // waits for task1
})
```

### Parallel Fan-Out

```
         → Task A →
Trigger  → Task B  → Final Task
         → Task C →
```

```typescript
// Parallel: no dependencies
const taskA = await client.tasks.create({ workflowId, agentId: a, dependencies: [] })
const taskB = await client.tasks.create({ workflowId, agentId: b, dependencies: [] })

// Combiner: depends on all parallel tasks
const final = await client.tasks.create({
  workflowId,
  agentId: finalAgent,
  dependencies: [taskA.id, taskB.id]
})
```

See `examples/blog-pipeline.md` for a complete sequential example.

---

## Declarative Workflow Sync

**Recommended approach** for creating workflows. Define triggers, tasks, and edges declaratively with `workflows.sync()`:

```typescript
await client.workflows.sync({
  id: workflow.id,
  triggers: [triggers.webhook({ name: 'api' })],
  tasks: [
    { name: 'research', agentId: researcherId, description: 'Research topic' },
    { name: 'write', agentId: writerId, description: 'Write article' }
  ],
  // ⚠️ CRITICAL: Always define edges to connect your workflow nodes
  edges: [
    { from: 'trigger:api', to: 'task:research' },
    { from: 'task:research', to: 'task:write' }
  ]
})
```

**Why edges matter:** Setting `dependencies` on tasks only tells the backend about execution order. **Edges define the actual graph structure** that connects triggers to tasks and tasks to each other. Without edges, your workflow won't execute.

**Adding agents:** `sync()` automatically adds any agents referenced in tasks that aren't already in the workspace. You can also add agents explicitly with `workflow.addAgent(agentId)` or `client.workflows.addAgent({ id, agentId })`.

### Branching Workflows with Output Options

Tasks can define multiple output options for conditional branching:

```typescript
await client.workflows.sync({
  id: workflow.id,
  triggers: [triggers.webhook({ name: 'webhook' })],
  tasks: [
    {
      name: 'review',
      agentId: reviewerAgent,
      description: 'Review submission and decide',
      outputOptions: {
        approved: { name: 'Approved', type: 'text', instructions: 'Approve with notes' },
        rejected: { name: 'Rejected', type: 'text', instructions: 'Reject with reason' }
      }
    },
    { name: 'process', agentId: processorAgent, description: 'Process approved' },
    { name: 'reject', agentId: rejectAgent, description: 'Handle rejection' }
  ],
  edges: [
    { from: 'trigger:webhook', to: 'task:review' },
    { from: 'task:review', to: 'task:process', sourcePort: 'approved' },
    { from: 'task:review', to: 'task:reject', sourcePort: 'rejected' }
  ]
})
```

**Output Options:**

- Each key (e.g., `approved`, `rejected`) becomes an output port
- The agent selects which output to use when completing the task
- Only the connected branch executes based on the agent's choice

**Edge Ports:**

- `sourcePort` - connects from a specific output (default: `'default'`)
- `targetPort` - connects to a specific input (default: `'input'`)

### Auto-Generated Edges

Without explicit edges, sync connects each trigger to the first task automatically.

### Integration Connection Resolution

The client auto-resolves `type: 'webhook'` → integration connection ID. No need to call `getOrCreateConnection()` when using `workflows.sync()`.

---

## Triggers

### Webhook

```typescript
// By workflow ID (recommended)
const result = await client.triggers.fireWebhook({
  workflowId: 123,
  input: { topic: 'AI trends' }
})

// By trigger name (for multi-trigger workflows)
const result = await client.triggers.fireWebhook({
  workflowId: 123,
  triggerName: 'My Webhook',
  input: { topic: 'AI trends' }
})

// Or by direct URL
const result = await client.triggers.fireWebhook({
  triggerUrl: 'https://api.openserv.ai/webhooks/trigger/TOKEN',
  input: { topic: 'AI trends' }
})
```

### x402 (Paid)

When using x402 triggers with `workflows.create()` or `workflow.sync()`, the wallet address for payouts is injected automatically via a fallback chain:

1. **`x402WalletAddress` on the trigger config** (highest priority)
2. **`client.walletAddress`** (set by `client.authenticate()`)
3. **`process.env.WALLET_PRIVATE_KEY`** (derived, lowest priority)

If using `provision()`, the wallet is handled entirely automatically.

```typescript
// Option 1: Auto-injection (wallet resolved from client or env)
triggers: [
  triggers.x402({ name: 'Pipeline', price: '0.10', input: { topic: { type: 'string', title: 'Topic' } } })
]

// Option 2: Explicit per-trigger wallet override
triggers: [
  triggers.x402({ name: 'Pipeline', price: '0.10', walletAddress: '0x...custom-payout-address' })
]
```

Paywall URL: `https://platform.openserv.ai/workspace/paywall/${trigger.token}`

### Calling x402 Programmatically

```typescript
// By workflow ID (recommended)
const result = await client.payments.payWorkflow({
  workflowId: 123,
  input: { topic: 'AI trends' }
})

// By trigger name (for multi-trigger workflows)
const result = await client.payments.payWorkflow({
  workflowId: 123,
  triggerName: 'Premium Pipeline',
  input: { topic: 'AI trends' }
})

// Or by direct URL (e.g. from discoverServices)
const services = await client.payments.discoverServices()
const result = await client.payments.payWorkflow({
  triggerUrl: services[0].webhookUrl,
  input: { topic: 'AI trends' }
})
```

---

## Managing Workspaces

```typescript
// Check status
const workflow = await client.workflows.get({ id: workflowId })
const tasks = await client.tasks.list({ workflowId })

// Add an agent to an existing workspace
await workflow.addAgent(456)
// or
await client.workflows.addAgent({ id: workflowId, agentId: 456 })

// Delete
await client.workflows.delete({ id: workflowId })
```

---

## Example Pipelines

Complete examples in `examples/`:

- `blog-pipeline.md` - 2-agent research → write
- `content-creation-pipeline.md` - 3-agent research → write → image
- `life-coaching-pipeline.md` - 6-agent comprehensive coaching report
- `youtube-to-blog-pipeline.md` - 3-agent video → blog → image
- `social-media-pipeline.md` - Research → write → post
- `video-content-factory.md` - Research → script → video
- `crypto-alpha-pipeline.md` - Market analysis workflow
- `polymarket-intelligence.md` - Prediction market analysis
- `podcast-production-line.md` - Research → essay → podcast
- `tokenomics-design-pipeline.md` - Token utility → model → whitepaper
- `paid-image-pipeline.md` - x402 paid 2-agent pipeline with `provision()`


path: troubleshooting.md
content: # Multi-Agent Workflows Troubleshooting

Common issues and solutions for multi-agent workflows.

---

## Workflow graph looks like spaghetti

If your workflow diagram shows a web of interconnected edges:

1. **Stop and redesign**: Don't try to fix it by adding more edges
2. **Map out the actual data flow**: Write down what each agent needs as input
3. **Rebuild as stages**:
   - Stage 1: Initial processing (parallel if independent)
   - Stage 2: Tasks that need Stage 1 outputs
   - Stage 3: Final combination/output
4. **Delete unnecessary edges**: If an agent doesn't truly need another's output, remove that edge
5. **Use the declarative sync**: Define edges explicitly rather than letting them accumulate

---

## Workflow creation fails with INVALID_PROJECT_GOAL

The backend validates workflow goals and rejects vague or placeholder-like descriptions.

**Error messages:**

- `"The description is too vague and lacks a clear objective."`
- `"The description seems like a placeholder or technical jargon without a clear objective."`

**Rejected examples:**

- `"Test something"`
- `"Process data"`
- `"Handle requests"`
- Technical jargon without clear business purpose

**Working examples:**

- `"Research topics and produce engaging blog posts"`
- `"Process incoming requests and generate automated responses"`
- `"Analyze customer feedback and create actionable reports"`

Goals should describe a clear business objective, not test names or implementation details.

---

## Tasks not executing in order

- Verify `dependencies` array contains correct task IDs
- Each task should only depend on the previous one (not all previous tasks)

---

## Workflow stuck

- Check all dependencies are satisfied (all `done`)
- Verify workflow is in `running` state
- Check agent endpoints are reachable

---

## Output not flowing between tasks

- Ensure workflow edges connect the tasks correctly
- Verify tasks have proper input/output configuration

---

## Triggers not firing tasks

**Most common cause:** Missing workflow edges.

1. **Check edges exist**: Triggers must be connected to tasks via edges in the workflow graph

```typescript
const workflow = await client.workflows.get({ id })
console.log('Edges:', workflow.edges)
// Should show connections like: trigger-xxx → task-xxx
```

If edges are missing, tasks will never execute even if dependencies are set correctly.

2. **Verify trigger is activated**: `await client.triggers.activate({ workflowId, id: triggerId })`

3. **Check workflow is running**: `await workflow.setRunning()`

---

## "Assignee agent not found in workspace"

This error means a task references an agent that isn't a member of the workspace.

**With `workflows.create()`:** This should not happen -- `agentIds` are automatically derived from `tasks[].agentId`. Every agent referenced in a task is included in the workspace. You don't need to specify `agentIds` manually.

```typescript
// This just works -- agents 123 and 456 are auto-included
const workflow = await client.workflows.create({
  name: 'Hyper Agent Relay',
  goal: 'Route incoming requests through a multi-step agent pipeline for processing and response generation',
  triggers: [triggers.webhook({ name: 'api' })],
  tasks: [
    { name: 'step-1', agentId: 123, description: 'First step' },
    { name: 'step-2', agentId: 456, description: 'Second step' }
  ],
  edges: [
    { from: 'trigger:api', to: 'task:step-1' },
    { from: 'task:step-1', to: 'task:step-2' }
  ]
})
```

**With `workflow.sync()`:** `sync()` automatically adds any agents referenced in tasks that aren't already in the workspace. If you sync a task assigned to a new agent, that agent is added to the workspace before the sync happens. You can also add agents explicitly:

```typescript
await workflow.addAgent(789)  // Add agent 789 to the workspace
// or
await client.workflows.addAgent({ id: workflowId, agentId: 789 })
```

**With `provision()`:** Agents are derived from `tasks[].agentId` at creation time, and on re-provision, `sync()` automatically adds any new agents. This is fully idempotent -- you can re-provision with additional agents without recreating the workspace.

---

## "Workspace payout wallet address not found" (x402 triggers)

The x402 trigger needs an `x402WalletAddress` in its props to know where to send payments.

**If using `provision()`:** This is handled automatically via `client.resolveWalletAddress()`.

**If using `workflows.create()` or `workflow.sync()` directly:** Ensure the client was authenticated with a wallet (`client.authenticate(privateKey)`) or that `WALLET_PRIVATE_KEY` is in the environment. The wallet address is auto-injected for x402 triggers. You can also set it explicitly per-trigger:

```typescript
triggers: [
  triggers.x402({ price: '0.01', walletAddress: '0x...explicit-payout-address' })
]
```

---

## Integration connection errors

The `workflows.sync()` and `workflows.create()` methods automatically handle integration connection IDs when you provide a trigger `type`. If you're creating triggers manually using the triggers API directly, you need to resolve connection IDs first:

```typescript
// Always resolve to actual connection ID first
const connId = await client.integrations.getOrCreateConnection('webhook-trigger')

await client.triggers.create({
  workflowId,
  name: 'My Trigger',
  integrationConnectionId: connId, // UUID required
  props: { ... }
})
```


path: examples/youtube-to-blog-pipeline.md
content: # YouTube to Blog Pipeline

A 3-agent sequential workflow that analyzes a YouTube video, writes a blog post, and generates a header image.

## Pipeline

```mermaid
flowchart LR
    T[Trigger] --> A[Youtube Video Understanding]
    A --> B[Copywriter]
    B --> C[Nano Banana Pro Agent]
```

## Task Dependencies (Sequential)

```mermaid
flowchart TD
    A["Task 1: Youtube Understanding<br/>dependencies: []"] --> B["Task 2: Copywriter<br/>dependencies: [task1Id]"]
    B --> C["Task 3: Nano Banana Pro<br/>dependencies: [task2Id]"]
```

Each task waits for the previous one to complete before starting.

---

## Complete Setup Script

### Project Structure

```
youtube-to-blog-pipeline/
├── src/
│   └── setup.ts
├── .env
├── package.json
└── tsconfig.json
```

### .env

```env
WALLET_PRIVATE_KEY=0x...
```

### Dependencies

```bash
npm init -y && npm pkg set type=module
npm i @openserv-labs/client dotenv
npm i -D @types/node tsx typescript
```

> **Note:** The project must use `"type": "module"` in `package.json`. Add a `"setup": "tsx src/setup.ts"` script for local development.

### src/setup.ts

**Recommended Approach: Using `workflows.sync()`**

```typescript
import 'dotenv/config'
import { PlatformClient, triggers } from '@openserv-labs/client'

async function setup() {
  const client = new PlatformClient()

  if (!process.env.WALLET_PRIVATE_KEY) {
    console.error('Missing WALLET_PRIVATE_KEY in .env')
    process.exit(1)
  }

  console.log('1. Authenticating with wallet...')
  await client.authenticate(process.env.WALLET_PRIVATE_KEY)

  console.log('2. Finding agents from marketplace...')
  const youtubeResult = await client.agents.listMarketplace({ search: 'youtube' })
  const copywriterResult = await client.agents.listMarketplace({ search: 'copywriter' })
  const nanoBananaResult = await client.agents.listMarketplace({ search: 'nano banana' })
  const youtubeUnderstanding = youtubeResult.items[0]
  const copywriter = copywriterResult.items[0]
  const nanoBanana = nanoBananaResult.items[0]

  if (!youtubeUnderstanding || !copywriter || !nanoBanana) {
    console.error('   Could not find required agents')
    const all = await client.agents.listMarketplace({})
    all.items.slice(0, 15).forEach(a => console.log(`   ID: ${a.id} | ${a.name}`))
    process.exit(1)
  }

  console.log(`   Youtube Understanding: ${youtubeUnderstanding.name} (ID: ${youtubeUnderstanding.id})`)
  console.log(`   Copywriter: ${copywriter.name} (ID: ${copywriter.id})`)
  console.log(`   Nano Banana Pro: ${nanoBanana.name} (ID: ${nanoBanana.id})`)

  console.log('3. Creating workflow...')
  const workflow = await client.workflows.create({
    name: 'YouTube to Blog Converter',
    goal: 'Analyze YouTube videos to extract key insights, transform them into comprehensive blog posts, and generate eye-catching header images',
    agentIds: [youtubeUnderstanding.id, copywriter.id, nanoBanana.id],
    triggers: [
      triggers.webhook({
        name: 'webhook',
        waitForCompletion: true,
        timeout: 900,
        input: {
          youtube_url: { type: 'string', title: 'YouTube URL', description: 'YouTube video URL to convert' }
        }
      })
    ],
    tasks: [
      {
        name: 'analyze',
        agentId: youtubeUnderstanding.id,
        description: 'Analyze YouTube video',
        body: 'Analyze the YouTube video at the given URL. Provide a comprehensive summary including: main topics covered, key points and arguments, notable quotes, and overall message.',
        input: '{{trigger.youtube_url}}'
      },
      {
        name: 'write',
        agentId: copywriter.id,
        description: 'Write blog post',
        body: 'Based on the video analysis, write a comprehensive blog post. Structure it with: 1) Engaging headline, 2) Introduction that hooks the reader, 3) Main content sections with subheadings, 4) Key takeaways, 5) Conclusion.'
      },
      {
        name: 'image',
        agentId: nanoBanana.id,
        description: 'Generate blog header image',
        body: 'Generate an eye-catching header image for the blog post. The image should be visually appealing, professional, and relevant to the blog topic.'
      }
    ],
    // ⚠️ CRITICAL: Edges define the data flow
    edges: [
      { from: 'trigger:webhook', to: 'task:analyze' },
      { from: 'task:analyze', to: 'task:write' },
      { from: 'task:write', to: 'task:image' }
    ]
  })
  console.log(`   Workflow ID: ${workflow.id}`)

  console.log('4. Activating workflow...')
  const trigger = workflow.triggers[0]
  await client.triggers.activate({ workflowId: workflow.id, id: trigger.id })
  await workflow.setRunning()

  console.log('\n========================================')
  console.log('YouTube to Blog Pipeline Setup Complete!')
  console.log('========================================')
  console.log(`\nWorkflow ID: ${workflow.id}`)
  console.log(`\nWorkflow: Trigger → Youtube Understanding → Copywriter → Nano Banana Pro`)
  console.log(`\nWebhook URL:`)
  console.log(`  POST https://api.openserv.ai/webhooks/trigger/${trigger.token}`)
  console.log(`\nExample:`)
  console.log(`  curl -X POST https://api.openserv.ai/webhooks/trigger/${trigger.token} \\`)
  console.log(`    -H "Content-Type: application/json" \\`)
  console.log(`    -d '{"youtube_url": "https://www.youtube.com/watch?v=..."}'`)
  console.log('========================================')
}

setup().catch(err => {
  console.error('Setup failed:', err.message)
  process.exit(1)
})
```

---

## How It Works

1. **Trigger fires** with `{ "youtube_url": "https://www.youtube.com/watch?v=..." }`
2. **Youtube Video Understanding Agent** analyzes the video content
3. **Youtube Understanding completes** → Copywriter task becomes ready
4. **Copywriter** writes a blog post
5. **Copywriter completes** → Nano Banana Pro task becomes ready
6. **Nano Banana Pro** generates a header image for the blog
7. **Nano Banana Pro completes** → Blog post and image returned via webhook response

## Workflow Graph

```mermaid
flowchart TD
    T[Trigger] -->|youtube_url| A[Task 1: Youtube Understanding]
    A -->|video analysis| B[Task 2: Copywriter]
    B -->|blog post| C[Task 3: Nano Banana Pro]
    C -->|header image| R[Result]
```

## Usage

```bash
# Run setup (creates workflow, tasks, trigger)
npm run setup

# Trigger the workflow
curl -X POST https://api.openserv.ai/webhooks/trigger/{token} \
  -H "Content-Type: application/json" \
  -d '{"youtube_url": "https://www.youtube.com/watch?v=..."}'
```


path: examples/paid-image-pipeline.md
content: # Paid Image Pipeline Example

A 2-agent workflow using `provision()` with x402 monetization: a custom agent acknowledges a message, then a marketplace image agent generates a visual.

## Pipeline

```mermaid
flowchart LR
    T["x402 Trigger (paid)"] --> A[Custom Agent]
    A --> B[Image Agent]
```

## Complete Agent Code

### Project Structure

```
paid-image-pipeline/
├── src/
│   └── agent.ts
├── .env
├── package.json
└── tsconfig.json
```

### .env

```env
OPENAI_API_KEY=your-openai-key
# Auto-populated by provision():
WALLET_PRIVATE_KEY=
```

### Dependencies

```bash
npm init -y && npm pkg set type=module
npm i @openserv-labs/sdk @openserv-labs/client dotenv openai zod
npm i -D @types/node tsx typescript
```

> **Note:** The project must use `"type": "module"` in `package.json`. Add a `"dev": "tsx src/agent.ts"` script for local development.

### src/agent.ts

```typescript
import 'dotenv/config'
import { Agent, run } from '@openserv-labs/sdk'
import { provision, triggers } from '@openserv-labs/client'
import { z } from 'zod'

const IMAGE_AGENT_ID = 1044 // Nano Banana Pro Agent (from marketplace)

const agent = new Agent({
  systemPrompt:
    'You are a concise agent. When a user sends you any message, respond with "Roger that".'
})

agent.addCapability({
  name: 'roger-that',
  description: 'Acknowledge any message with "Roger that"',
  schema: z.object({
    message: z.string().describe('The incoming message')
  }),
  async run({ args }) {
    console.log(`Received: ${args.message}`)
    return 'Roger that'
  }
})

async function main() {
  const result = await provision({
    agent: {
      instance: agent,
      name: 'roger-that',
      description: 'Acknowledges messages with "Roger that"'
    },
    workflow: {
      name: 'Roger That Image',
      goal: 'Acknowledge user messages and generate a bold, eye-catching "Roger that" image in response',
      trigger: triggers.x402({
        name: 'Roger That Image',
        description: 'Send any message and get a "Roger that" image',
        price: '0.01',
        timeout: 600,
        input: {
          message: {
            type: 'string',
            title: 'Your Message',
            description: 'Say anything'
          }
        }
      }),
      tasks: [
        {
          name: 'acknowledge',
          description: 'Read the user message and respond with "Roger that" using the roger-that capability',
          input: '{{trigger.message}}'
          // No agentId -- defaults to the provisioned agent
        },
        {
          name: 'generate-image',
          description: 'Generate a bold image that says "Roger that" in a confident, eye-catching style',
          agentId: IMAGE_AGENT_ID // Marketplace agent
        }
      ]
      // No edges needed -- sequential edges auto-generated:
      //   trigger -> acknowledge -> generate-image
      // No agentIds needed -- derived from tasks automatically
    }
  })

  console.log(`Agent ID: ${result.agentId}`)
  console.log(`Paywall: ${result.paywallUrl}`)

  await run(agent)
}

main().catch(console.error)
```

## How It Works

1. `provision()` registers the custom agent on the platform
2. It creates a multi-agent workflow with:
   - **x402 trigger** -- paid paywall at $0.01 per request
   - **Task 1** (custom agent) -- acknowledges the message with "Roger that"
   - **Task 2** (Nano Banana Pro) -- generates an image based on the acknowledgment
3. `x402WalletAddress` is auto-injected from the provisioned wallet (no manual setup)
4. `agentIds` are derived from tasks (no need to specify `[agentId, 1044]` manually)
5. Sequential edges are auto-generated: trigger -> acknowledge -> generate-image
6. `run(agent)` starts the local agent with built-in tunnel

## Adding ERC-8004 On-Chain Registration

Add on-chain identity after provisioning so the agent is discoverable via the Identity Registry. **Requires ETH on Base** for gas. Always wrap in try/catch so failures don't prevent `run(agent)` from starting. Reload `.env` after `provision()` to pick up the freshly written `WALLET_PRIVATE_KEY`.

```typescript
import { PlatformClient } from '@openserv-labs/client'

// ... after provision(), before run():

// Reload .env to pick up WALLET_PRIVATE_KEY written by provision()
dotenv.config({ override: true })

try {
  const client = new PlatformClient()
  await client.authenticate(process.env.WALLET_PRIVATE_KEY)

  const erc8004 = await client.erc8004.registerOnChain({
    workflowId: result.workflowId,
    privateKey: process.env.WALLET_PRIVATE_KEY!,
    name: 'Roger That Image',
    description: 'Send any message and get a "Roger that" image',
  })

  console.log(`ERC-8004 Agent ID: ${erc8004.agentId}`)
  console.log(`Block Explorer: ${erc8004.blockExplorerUrl}`)
  console.log(`Scan: ${erc8004.scanUrl}`)
} catch (error) {
  console.warn('ERC-8004 registration skipped:', error instanceof Error ? error.message : error)
}

await run(agent)
```

**Re-runs update the agent card URI** — the agent ID stays the same. Never clear the wallet state unless you intentionally want a new on-chain identity.

## Key Points

1. **No manual wallet injection**: `provision()` handles `x402WalletAddress` automatically via `client.resolveWalletAddress()`
2. **No manual agentIds**: Derived from `tasks[].agentId` automatically
3. **No manual edges**: Sequential edges are auto-generated when no explicit edges are provided
4. **Backward compatible**: The `task` shorthand (single task) still works for simple agents
5. **Idempotent**: Re-running `npm run dev` updates the existing workflow via `sync()` rather than creating duplicates
6. **ERC-8004 re-deploy**: `registerOnChain` detects existing agent and calls `setAgentURI` instead of minting new


Security scan verdicts — ClawScan: suspicious | static analysis: suspicious | VirusTotal: clean | SkillSpector: suspicious (CRITICAL)