{
  "schemaVersion": "1.0",
  "item": {
    "slug": "openserv-agent-sdk",
    "name": "OpenServ Agent Sdk",
    "source": "tencent",
    "type": "skill",
    "category": "开发工具",
    "sourceUrl": "https://clawhub.ai/issa-me-sush/openserv-agent-sdk",
    "canonicalUrl": "https://clawhub.ai/issa-me-sush/openserv-agent-sdk",
    "targetPlatform": "OpenClaw"
  },
  "install": {
    "downloadMode": "redirect",
    "downloadUrl": "/downloads/openserv-agent-sdk",
    "sourceDownloadUrl": "https://wry-manatee-359.convex.site/api/v1/download?slug=openserv-agent-sdk",
    "sourcePlatform": "tencent",
    "targetPlatform": "OpenClaw",
    "installMethod": "Manual import",
    "extraction": "Extract archive",
    "prerequisites": [
      "OpenClaw"
    ],
    "packageFormat": "ZIP package",
    "includedAssets": [
      "reference.md",
      "troubleshooting.md",
      "SKILL.md",
      "examples/file-operations.ts",
      "examples/haiku-poet-agent.ts",
      "examples/capability-example.ts"
    ],
    "primaryDoc": "SKILL.md",
    "quickSetup": [
      "Download the package from Yavira.",
      "Extract the archive and review SKILL.md first.",
      "Import or place the package into your OpenClaw setup."
    ],
    "agentAssist": {
      "summary": "Hand the extracted package to your coding agent with a concrete install brief instead of figuring it out manually.",
      "steps": [
        "Download the package from Yavira.",
        "Extract it into a folder your agent can access.",
        "Paste one of the prompts below and point your agent at the extracted folder."
      ],
      "prompts": [
        {
          "label": "New install",
          "body": "I downloaded a skill package from Yavira. Read SKILL.md from the extracted folder and install it by following the included instructions. Tell me what you changed and call out any manual steps you could not complete."
        },
        {
          "label": "Upgrade existing",
          "body": "I downloaded an updated skill package from Yavira. Read SKILL.md from the extracted folder, compare it with my current installation, and upgrade it while preserving any custom configuration unless the package docs explicitly say otherwise. Summarize what changed and any follow-up checks I should run."
        }
      ]
    },
    "sourceHealth": {
      "source": "tencent",
      "status": "healthy",
      "reason": "direct_download_ok",
      "recommendedAction": "download",
      "checkedAt": "2026-04-30T16:55:25.780Z",
      "expiresAt": "2026-05-07T16:55:25.780Z",
      "httpStatus": 200,
      "finalUrl": "https://wry-manatee-359.convex.site/api/v1/download?slug=network",
      "contentType": "application/zip",
      "probeMethod": "head",
      "details": {
        "probeUrl": "https://wry-manatee-359.convex.site/api/v1/download?slug=network",
        "contentDisposition": "attachment; filename=\"network-1.0.0.zip\"",
        "redirectLocation": null,
        "bodySnippet": null
      },
      "scope": "source",
      "summary": "Source download looks usable.",
      "detail": "Yavira can redirect you to the upstream package for this source.",
      "primaryActionLabel": "Download for OpenClaw",
      "primaryActionHref": "/downloads/openserv-agent-sdk"
    },
    "validation": {
      "installChecklist": [
        "Use the Yavira download entry.",
        "Review SKILL.md after the package is downloaded.",
        "Confirm the extracted package contains the expected setup assets."
      ],
      "postInstallChecks": [
        "Confirm the extracted package includes the expected docs or setup files.",
        "Validate the skill or prompts are available in your target agent workspace.",
        "Capture any manual follow-up steps the agent could not complete."
      ]
    },
    "downloadPageUrl": "https://openagent3.xyz/downloads/openserv-agent-sdk",
    "agentPageUrl": "https://openagent3.xyz/skills/openserv-agent-sdk/agent",
    "manifestUrl": "https://openagent3.xyz/skills/openserv-agent-sdk/agent.json",
    "briefUrl": "https://openagent3.xyz/skills/openserv-agent-sdk/agent.md"
  },
  "agentAssist": {
    "summary": "Hand the extracted package to your coding agent with a concrete install brief instead of figuring it out manually.",
    "steps": [
      "Download the package from Yavira.",
      "Extract it into a folder your agent can access.",
      "Paste one of the prompts below and point your agent at the extracted folder."
    ],
    "prompts": [
      {
        "label": "New install",
        "body": "I downloaded a skill package from Yavira. Read SKILL.md from the extracted folder and install it by following the included instructions. Tell me what you changed and call out any manual steps you could not complete."
      },
      {
        "label": "Upgrade existing",
        "body": "I downloaded an updated skill package from Yavira. Read SKILL.md from the extracted folder, compare it with my current installation, and upgrade it while preserving any custom configuration unless the package docs explicitly say otherwise. Summarize what changed and any follow-up checks I should run."
      }
    ]
  },
  "documentation": {
    "source": "clawhub",
    "primaryDoc": "SKILL.md",
    "sections": [
      {
        "title": "OpenServ Agent SDK",
        "body": "Build and deploy custom AI agents for the OpenServ platform using TypeScript."
      },
      {
        "title": "Why build an agent?",
        "body": "An OpenServ agent is a service that runs your code and exposes it on the OpenServ platform—so it can be triggered by workflows, other agents, or paid calls (e.g. x402). The platform sends tasks to your agent; your agent runs your capabilities (APIs, tools, file handling) and returns results. You don't have to use an LLM—e.g. it could be a static API that just returns data. If you need LLM reasoning, you have two options: use runless capabilities (the platform handles the AI call for you—no API key needed) or use generate() (delegates the LLM call to the platform); alternatively, bring your own LLM (any provider you have access to)."
      },
      {
        "title": "How it works (the flow)",
        "body": "Define your agent — System prompt plus capabilities. Capabilities come in two flavors: runnable (with a Zod schema and a run handler) and runless (just a name and description—the platform handles the AI call automatically). You can also use generate() inside runnable capabilities to delegate LLM calls to the platform.\nRegister with the platform — You need an account on the platform; often the easiest way is to let provision() create one for you automatically by creating a wallet and signing up with it (that account is reused on later runs). Call provision() (from @openserv-labs/client): it creates or reuses a wallet, registers the agent, and writes API key and auth token into your env (or you pass agent.instance to bind them directly). In development you can skip setting an endpoint URL; the SDK can use a built-in tunnel to the platform.\nStart the agent — Call run(agent). The agent listens for tasks, runs your capabilities (and your LLM if you use one), and responds. Use reference.md and troubleshooting.md for details; examples/ has full runnable code."
      },
      {
        "title": "What your agent can do",
        "body": "Runless Capabilities — Just a name and description. The platform handles the AI call automatically—no API key, no run() function needed. Optionally define inputSchema and outputSchema for structured I/O.\nRunnable Capabilities — The tools your agent can run (e.g. search, transform data, call APIs). Each has a name, description, inputSchema, and run() function.\ngenerate() method — Delegate LLM calls to the platform from inside any runnable capability. No API key needed—the platform performs the call and records usage. Supports text and structured output.\nTask context — When running in a task, the agent can attach logs and uploads to that task via methods like addLogToTask() and uploadFile().\nMulti-agent workflows — Your agent can be part of workflows with other agents; see the openserv-client skill for the Platform API, workflows, and ERC-8004 on-chain identity.\n\nReference: reference.md (patterns) · troubleshooting.md (common issues) · examples/ (full examples)"
      },
      {
        "title": "Installation",
        "body": "npm install @openserv-labs/sdk @openserv-labs/client zod\n\nNote: openai is only needed if you use the process() method for direct OpenAI calls. Most agents don't need it—use runless capabilities or generate() instead."
      },
      {
        "title": "Minimal Agent",
        "body": "See examples/basic-agent.ts for a complete runnable example.\n\nThe pattern is simple:\n\nCreate an Agent with a system prompt\nAdd capabilities with agent.addCapability()\nCall provision() to register on the platform (pass agent.instance to bind credentials)\nCall run(agent) to start"
      },
      {
        "title": "File Structure",
        "body": "my-agent/\n├── src/agent.ts\n├── .env\n├── .gitignore\n├── package.json\n└── tsconfig.json"
      },
      {
        "title": "Dependencies",
        "body": "npm init -y && npm pkg set type=module\nnpm i @openserv-labs/sdk @openserv-labs/client dotenv zod\nnpm i -D @types/node tsx typescript\n\nNote: The project must use \"type\": \"module\" in package.json. Add a \"dev\": \"tsx src/agent.ts\" script for local development. Only install openai if you use the process() method for direct OpenAI calls."
      },
      {
        "title": ".env",
        "body": "Most agents don't need any LLM API key—use runless capabilities or generate() and the platform handles LLM calls for you. If you use process() for direct OpenAI calls, set OPENAI_API_KEY. The rest is filled by provision().\n\n# Only needed if you use process() for direct OpenAI calls:\n# OPENAI_API_KEY=your-openai-key\n# ANTHROPIC_API_KEY=your_anthropic_key  # If using Claude directly\n\n# Auto-populated by provision():\nWALLET_PRIVATE_KEY=\nOPENSERV_API_KEY=\nOPENSERV_AUTH_TOKEN=\nPORT=7378\n# Production: skip tunnel and run HTTP server only\n# DISABLE_TUNNEL=true\n# Force tunnel even when endpointUrl is set\n# FORCE_TUNNEL=true"
      },
      {
        "title": "Capabilities",
        "body": "Capabilities come in two flavors:"
      },
      {
        "title": "Runless Capabilities (recommended for most use cases)",
        "body": "Runless capabilities don't need a run function—the platform handles the AI call automatically. Just provide a name and description:\n\n// Simplest form — just name + description\nagent.addCapability({\n  name: 'generate_haiku',\n  description: 'Generate a haiku poem (5-7-5 syllables) about the given input.'\n})\n\n// With custom input schema\nagent.addCapability({\n  name: 'translate',\n  description: 'Translate text to the target language.',\n  inputSchema: z.object({\n    text: z.string(),\n    targetLanguage: z.string()\n  })\n})\n\n// With structured output\nagent.addCapability({\n  name: 'analyze_sentiment',\n  description: 'Analyze the sentiment of the given text.',\n  outputSchema: z.object({\n    sentiment: z.enum(['positive', 'negative', 'neutral']),\n    confidence: z.number().min(0).max(1)\n  })\n})\n\nNo run function — the platform performs the LLM call\nNo API key needed — the platform handles it\ninputSchema is optional — defaults to z.object({ input: z.string() }) if omitted\noutputSchema is optional — define it for structured output from the platform\n\nSee examples/haiku-poet-agent.ts for a complete runless example."
      },
      {
        "title": "Runnable Capabilities",
        "body": "Runnable capabilities have a run function for custom logic. Each requires:\n\nname - Unique identifier\ndescription - What it does (helps AI decide when to use it)\ninputSchema - Zod schema defining parameters\nrun - Function returning a string\n\nagent.addCapability({\n  name: 'greet',\n  description: 'Greet a user by name',\n  inputSchema: z.object({ name: z.string() }),\n  async run({ args }) {\n    return `Hello, ${args.name}!`\n  }\n})\n\nSee examples/capability-example.ts for basic capabilities.\n\nNote: The schema property still works as an alias for inputSchema but is deprecated. Use inputSchema for new code."
      },
      {
        "title": "Using Agent Methods",
        "body": "Access this in capabilities to use agent methods like addLogToTask(), uploadFile(), generate(), etc.\n\nSee examples/capability-with-agent-methods.ts for logging and file upload patterns."
      },
      {
        "title": "generate() — Platform-Delegated LLM Calls",
        "body": "The generate() method lets you make LLM calls without any API key. The platform performs the call and records usage to the workspace.\n\n// Text generation\nconst poem = await this.generate({\n  prompt: `Write a short poem about ${args.topic}`,\n  action\n})\n\n// Structured output (returns validated object matching the schema)\nconst metadata = await this.generate({\n  prompt: `Suggest a title and 3 tags for: ${poem}`,\n  outputSchema: z.object({\n    title: z.string(),\n    tags: z.array(z.string()).length(3)\n  }),\n  action\n})\n\n// With conversation history\nconst followUp = await this.generate({\n  prompt: 'Suggest a related topic.',\n  messages,  // conversation history from run function\n  action\n})\n\nParameters:\n\nprompt (string) — The prompt for the LLM\naction (ActionSchema) — The action context (passed into your run function)\noutputSchema (Zod schema, optional) — When provided, returns a validated structured output\nmessages (array, optional) — Conversation history for multi-turn generation\n\nThe action parameter is required because it identifies the workspace/task for billing. Use it inside runnable capabilities where action is available from the run function arguments."
      },
      {
        "title": "Task Management",
        "body": "await agent.createTask({ workspaceId, assignee, description, body, input, dependencies })\nawait agent.updateTaskStatus({ workspaceId, taskId, status: 'in-progress' })\nawait agent.addLogToTask({ workspaceId, taskId, severity: 'info', type: 'text', body: '...' })\nawait agent.markTaskAsErrored({ workspaceId, taskId, error: 'Something went wrong' })\nconst task = await agent.getTaskDetail({ workspaceId, taskId })\nconst tasks = await agent.getTasks({ workspaceId })"
      },
      {
        "title": "File Operations",
        "body": "const files = await agent.getFiles({ workspaceId })\nawait agent.uploadFile({ workspaceId, path: 'output.txt', file: 'content', taskIds: [taskId] })\nawait agent.deleteFile({ workspaceId, fileId })"
      },
      {
        "title": "Action Context",
        "body": "The action parameter in capabilities is a union type — task only exists on the 'do-task' variant. Always narrow with a type guard before accessing action.task:\n\nasync run({ args, action }) {\n  // action.task does NOT exist on all action types — you must narrow first\n  if (action?.type === 'do-task' && action.task) {\n    const { workspace, task } = action\n    workspace.id        // Workspace ID\n    workspace.goal      // Workspace goal\n    task.id             // Task ID\n    task.description    // Task description\n    task.input          // Task input\n    action.me.id        // Current agent ID\n  }\n}\n\nDo not extract action?.task?.id before the type guard — TypeScript will error with Property 'task' does not exist on type 'ActionSchema'."
      },
      {
        "title": "Workflow Name & Goal",
        "body": "The workflow object in provision() requires two important properties:\n\nname (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: 'Crypto Alpha Scanner', 'AI Video Studio', 'Instant Blog Machine'.\ngoal (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 workflow's purpose.\n\nworkflow: {\n  name: 'Haiku Poetry Generator',  // Polished display name — the ERC-8004 agent name users see\n  goal: 'Transform any theme or emotion into a beautiful traditional 5-7-5 haiku poem using AI',\n  trigger: triggers.x402({ ... }),\n  task: { description: 'Generate a haiku about the given topic' }\n}"
      },
      {
        "title": "Trigger Types",
        "body": "import { triggers } from '@openserv-labs/client'\n\ntriggers.webhook({ waitForCompletion: true, timeout: 600 })\ntriggers.x402({ name: '...', description: '...', price: '0.01', timeout: 600 })\ntriggers.cron({ schedule: '0 9 * * *' })\ntriggers.manual()\n\nImportant: Always set timeout to at least 600 seconds (10 minutes) for webhook and x402 triggers. Agents often take significant time to process requests — especially when performing research, content generation, or other complex tasks. A low timeout will cause premature failures. For multi-agent pipelines with many sequential steps, consider 900 seconds or more."
      },
      {
        "title": "API Keys: Agent vs User",
        "body": "provision() creates two types of credentials. They are not interchangeable:\n\nOPENSERV_API_KEY (Agent API key) — Used internally by the SDK to authenticate when receiving tasks. Set automatically by provision() when you pass agent.instance. Do not use this key with PlatformClient.\nWALLET_PRIVATE_KEY / OPENSERV_USER_API_KEY (User credentials) — Used with PlatformClient to make management calls (list tasks, debug workflows, etc.). Authenticate with client.authenticate(walletKey) or pass apiKey to the constructor.\n\nIf you need to debug tasks or inspect workflows, use wallet authentication:\n\nconst client = new PlatformClient()\nawait client.authenticate(process.env.WALLET_PRIVATE_KEY)\nconst tasks = await client.tasks.list({ workflowId: result.workflowId })\n\nSee troubleshooting.md for details on 401 errors."
      },
      {
        "title": "Local Development",
        "body": "npm run dev\n\nThe run() function automatically:\n\nStarts the agent HTTP server (port 7378, with automatic fallback)\nConnects via WebSocket to agents-proxy.openserv.ai\nRoutes platform requests to your local machine\n\nNo need for ngrok or other tunneling tools - run() handles this seamlessly. Just call run(agent) and your local agent is accessible to the platform."
      },
      {
        "title": "Production",
        "body": "When deploying to a hosting provider like Cloud Run, set DISABLE_TUNNEL=true as an environment variable. This makes run() start only the HTTP server without opening a WebSocket tunnel — the platform reaches your agent directly at its public URL.\n\nawait provision({\n  agent: {\n    name: 'my-agent',\n    description: '...',\n    endpointUrl: 'https://my-agent.example.com' // Required for production\n  },\n  workflow: {\n    name: 'Lightning Service Pro',\n    goal: 'Describe in detail what this workflow does — be thorough, vague goals cause failures',\n    trigger: triggers.webhook({ waitForCompletion: true, timeout: 600 }),\n    task: { description: 'Process incoming requests' }\n  }\n})\n\n// With DISABLE_TUNNEL=true, run() starts only the HTTP server (no tunnel)\nawait run(agent)"
      },
      {
        "title": "ERC-8004: On-Chain Agent Identity",
        "body": "After provisioning, register your agent on-chain for discoverability via the Identity Registry.\n\nRequires ETH on Base. Registration calls register() on the ERC-8004 contract on Base mainnet (chain 8453), which costs gas. The wallet created by provision() starts with a zero balance. Fund it with a small amount of ETH on Base before the first registration attempt. The wallet address is logged during provisioning (Created new wallet: 0x...).\n\nAlways wrap in try/catch so a registration failure (e.g. unfunded wallet) doesn't prevent run(agent) from starting.\n\nTwo important patterns:\n\nUse dotenv programmatically (not import 'dotenv/config') so you can reload .env after provision() writes WALLET_PRIVATE_KEY.\nCall dotenv.config({ override: true }) after provision() to pick up the freshly written key before ERC-8004 registration.\n\nimport dotenv from 'dotenv'\ndotenv.config()\n\nimport { Agent, run } from '@openserv-labs/sdk'\nimport { provision, triggers, PlatformClient } from '@openserv-labs/client'\n\n// ... define agent and capabilities ...\n\nconst result = await provision({\n  agent: { instance: agent, name: 'my-agent', description: '...' },\n  workflow: {\n    name: 'My Service',\n    goal: 'Detailed description of what the workflow does',\n    trigger: triggers.x402({ name: 'My Service', description: '...', price: '0.01', timeout: 600 }),\n    task: { description: 'Process requests' }\n  }\n})\n\n// Reload .env to pick up WALLET_PRIVATE_KEY written by provision()\ndotenv.config({ override: true })\n\n// Register on-chain (non-blocking — requires funded wallet on Base)\ntry {\n  const client = new PlatformClient()\n  await client.authenticate(process.env.WALLET_PRIVATE_KEY)\n\n  const erc8004 = await client.erc8004.registerOnChain({\n    workflowId: result.workflowId,\n    privateKey: process.env.WALLET_PRIVATE_KEY!,\n    name: 'My Service',\n    description: 'What this agent does'\n  })\n\n  console.log(`Agent ID: ${erc8004.agentId}`) // \"8453:42\"\n  console.log(`TX: ${erc8004.blockExplorerUrl}`)\n  console.log(`Scan: ${erc8004.scanUrl}`) // \"https://www.8004scan.io/agents/base/42\"\n} catch (error) {\n  console.warn('ERC-8004 registration skipped:', error instanceof Error ? error.message : error)\n}\n\nawait run(agent)\n\nFirst run mints a new identity NFT. Re-runs update the URI — agent ID stays the same.\nNever clear the wallet state unless you intentionally want a new agent ID. To update metadata, just re-run.\nDefault chain: Base mainnet (8453). Pass chainId / rpcUrl for others.\n\nSee openserv-client skill for the full ERC-8004 API reference and troubleshooting."
      },
      {
        "title": "DO NOT USE",
        "body": "this.process() inside capabilities — Legacy method requiring an OpenAI API key. Use this.generate() instead (platform-delegated, no key needed), or use runless capabilities\ndoTask override — The SDK handles task execution automatically\nthis.completeTask() — Task completion is handled by the Runtime API"
      },
      {
        "title": "Updating This Skill",
        "body": "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.\n\n# Check if updates are available\nnpx skills check\n\n# Update all installed skills to latest versions\nnpx skills update\n\nOr reinstall the OpenServ skills directly:\n\nnpx skills add openserv-labs/skills"
      },
      {
        "title": "Related Skills",
        "body": "openserv-client - Full Platform Client API reference\nopenserv-multi-agent-workflows - Multi-agent collaboration patterns\nopenserv-launch - Launch tokens on Base blockchain\nopenserv-ideaboard-api - Find ideas and ship agent services on the Ideaboard"
      }
    ],
    "body": "OpenServ Agent SDK\n\nBuild and deploy custom AI agents for the OpenServ platform using TypeScript.\n\nWhy build an agent?\n\nAn OpenServ agent is a service that runs your code and exposes it on the OpenServ platform—so it can be triggered by workflows, other agents, or paid calls (e.g. x402). The platform sends tasks to your agent; your agent runs your capabilities (APIs, tools, file handling) and returns results. You don't have to use an LLM—e.g. it could be a static API that just returns data. If you need LLM reasoning, you have two options: use runless capabilities (the platform handles the AI call for you—no API key needed) or use generate() (delegates the LLM call to the platform); alternatively, bring your own LLM (any provider you have access to).\n\nHow it works (the flow)\nDefine your agent — System prompt plus capabilities. Capabilities come in two flavors: runnable (with a Zod schema and a run handler) and runless (just a name and description—the platform handles the AI call automatically). You can also use generate() inside runnable capabilities to delegate LLM calls to the platform.\nRegister with the platform — You need an account on the platform; often the easiest way is to let provision() create one for you automatically by creating a wallet and signing up with it (that account is reused on later runs). Call provision() (from @openserv-labs/client): it creates or reuses a wallet, registers the agent, and writes API key and auth token into your env (or you pass agent.instance to bind them directly). In development you can skip setting an endpoint URL; the SDK can use a built-in tunnel to the platform.\nStart the agent — Call run(agent). The agent listens for tasks, runs your capabilities (and your LLM if you use one), and responds. Use reference.md and troubleshooting.md for details; examples/ has full runnable code.\nWhat your agent can do\nRunless Capabilities — Just a name and description. The platform handles the AI call automatically—no API key, no run() function needed. Optionally define inputSchema and outputSchema for structured I/O.\nRunnable Capabilities — The tools your agent can run (e.g. search, transform data, call APIs). Each has a name, description, inputSchema, and run() function.\ngenerate() method — Delegate LLM calls to the platform from inside any runnable capability. No API key needed—the platform performs the call and records usage. Supports text and structured output.\nTask context — When running in a task, the agent can attach logs and uploads to that task via methods like addLogToTask() and uploadFile().\nMulti-agent workflows — Your agent can be part of workflows with other agents; see the openserv-client skill for the Platform API, workflows, and ERC-8004 on-chain identity.\n\nReference: reference.md (patterns) · troubleshooting.md (common issues) · examples/ (full examples)\n\nQuick Start\nInstallation\nnpm install @openserv-labs/sdk @openserv-labs/client zod\n\n\nNote: openai is only needed if you use the process() method for direct OpenAI calls. Most agents don't need it—use runless capabilities or generate() instead.\n\nMinimal Agent\n\nSee examples/basic-agent.ts for a complete runnable example.\n\nThe pattern is simple:\n\nCreate an Agent with a system prompt\nAdd capabilities with agent.addCapability()\nCall provision() to register on the platform (pass agent.instance to bind credentials)\nCall run(agent) to start\nComplete Agent Template\nFile Structure\nmy-agent/\n├── src/agent.ts\n├── .env\n├── .gitignore\n├── package.json\n└── tsconfig.json\n\nDependencies\nnpm init -y && npm pkg set type=module\nnpm i @openserv-labs/sdk @openserv-labs/client dotenv zod\nnpm i -D @types/node tsx typescript\n\n\nNote: The project must use \"type\": \"module\" in package.json. Add a \"dev\": \"tsx src/agent.ts\" script for local development. Only install openai if you use the process() method for direct OpenAI calls.\n\n.env\n\nMost agents don't need any LLM API key—use runless capabilities or generate() and the platform handles LLM calls for you. If you use process() for direct OpenAI calls, set OPENAI_API_KEY. The rest is filled by provision().\n\n# Only needed if you use process() for direct OpenAI calls:\n# OPENAI_API_KEY=your-openai-key\n# ANTHROPIC_API_KEY=your_anthropic_key  # If using Claude directly\n\n# Auto-populated by provision():\nWALLET_PRIVATE_KEY=\nOPENSERV_API_KEY=\nOPENSERV_AUTH_TOKEN=\nPORT=7378\n# Production: skip tunnel and run HTTP server only\n# DISABLE_TUNNEL=true\n# Force tunnel even when endpointUrl is set\n# FORCE_TUNNEL=true\n\nCapabilities\n\nCapabilities come in two flavors:\n\nRunless Capabilities (recommended for most use cases)\n\nRunless capabilities don't need a run function—the platform handles the AI call automatically. Just provide a name and description:\n\n// Simplest form — just name + description\nagent.addCapability({\n  name: 'generate_haiku',\n  description: 'Generate a haiku poem (5-7-5 syllables) about the given input.'\n})\n\n// With custom input schema\nagent.addCapability({\n  name: 'translate',\n  description: 'Translate text to the target language.',\n  inputSchema: z.object({\n    text: z.string(),\n    targetLanguage: z.string()\n  })\n})\n\n// With structured output\nagent.addCapability({\n  name: 'analyze_sentiment',\n  description: 'Analyze the sentiment of the given text.',\n  outputSchema: z.object({\n    sentiment: z.enum(['positive', 'negative', 'neutral']),\n    confidence: z.number().min(0).max(1)\n  })\n})\n\nNo run function — the platform performs the LLM call\nNo API key needed — the platform handles it\ninputSchema is optional — defaults to z.object({ input: z.string() }) if omitted\noutputSchema is optional — define it for structured output from the platform\n\nSee examples/haiku-poet-agent.ts for a complete runless example.\n\nRunnable Capabilities\n\nRunnable capabilities have a run function for custom logic. Each requires:\n\nname - Unique identifier\ndescription - What it does (helps AI decide when to use it)\ninputSchema - Zod schema defining parameters\nrun - Function returning a string\nagent.addCapability({\n  name: 'greet',\n  description: 'Greet a user by name',\n  inputSchema: z.object({ name: z.string() }),\n  async run({ args }) {\n    return `Hello, ${args.name}!`\n  }\n})\n\n\nSee examples/capability-example.ts for basic capabilities.\n\nNote: The schema property still works as an alias for inputSchema but is deprecated. Use inputSchema for new code.\n\nUsing Agent Methods\n\nAccess this in capabilities to use agent methods like addLogToTask(), uploadFile(), generate(), etc.\n\nSee examples/capability-with-agent-methods.ts for logging and file upload patterns.\n\nAgent Methods\ngenerate() — Platform-Delegated LLM Calls\n\nThe generate() method lets you make LLM calls without any API key. The platform performs the call and records usage to the workspace.\n\n// Text generation\nconst poem = await this.generate({\n  prompt: `Write a short poem about ${args.topic}`,\n  action\n})\n\n// Structured output (returns validated object matching the schema)\nconst metadata = await this.generate({\n  prompt: `Suggest a title and 3 tags for: ${poem}`,\n  outputSchema: z.object({\n    title: z.string(),\n    tags: z.array(z.string()).length(3)\n  }),\n  action\n})\n\n// With conversation history\nconst followUp = await this.generate({\n  prompt: 'Suggest a related topic.',\n  messages,  // conversation history from run function\n  action\n})\n\n\nParameters:\n\nprompt (string) — The prompt for the LLM\naction (ActionSchema) — The action context (passed into your run function)\noutputSchema (Zod schema, optional) — When provided, returns a validated structured output\nmessages (array, optional) — Conversation history for multi-turn generation\n\nThe action parameter is required because it identifies the workspace/task for billing. Use it inside runnable capabilities where action is available from the run function arguments.\n\nTask Management\nawait agent.createTask({ workspaceId, assignee, description, body, input, dependencies })\nawait agent.updateTaskStatus({ workspaceId, taskId, status: 'in-progress' })\nawait agent.addLogToTask({ workspaceId, taskId, severity: 'info', type: 'text', body: '...' })\nawait agent.markTaskAsErrored({ workspaceId, taskId, error: 'Something went wrong' })\nconst task = await agent.getTaskDetail({ workspaceId, taskId })\nconst tasks = await agent.getTasks({ workspaceId })\n\nFile Operations\nconst files = await agent.getFiles({ workspaceId })\nawait agent.uploadFile({ workspaceId, path: 'output.txt', file: 'content', taskIds: [taskId] })\nawait agent.deleteFile({ workspaceId, fileId })\n\nAction Context\n\nThe action parameter in capabilities is a union type — task only exists on the 'do-task' variant. Always narrow with a type guard before accessing action.task:\n\nasync run({ args, action }) {\n  // action.task does NOT exist on all action types — you must narrow first\n  if (action?.type === 'do-task' && action.task) {\n    const { workspace, task } = action\n    workspace.id        // Workspace ID\n    workspace.goal      // Workspace goal\n    task.id             // Task ID\n    task.description    // Task description\n    task.input          // Task input\n    action.me.id        // Current agent ID\n  }\n}\n\n\nDo not extract action?.task?.id before the type guard — TypeScript will error with Property 'task' does not exist on type 'ActionSchema'.\n\nWorkflow Name & Goal\n\nThe workflow object in provision() requires two important properties:\n\nname (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: 'Crypto Alpha Scanner', 'AI Video Studio', 'Instant Blog Machine'.\ngoal (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 workflow's purpose.\nworkflow: {\n  name: 'Haiku Poetry Generator',  // Polished display name — the ERC-8004 agent name users see\n  goal: 'Transform any theme or emotion into a beautiful traditional 5-7-5 haiku poem using AI',\n  trigger: triggers.x402({ ... }),\n  task: { description: 'Generate a haiku about the given topic' }\n}\n\nTrigger Types\nimport { triggers } from '@openserv-labs/client'\n\ntriggers.webhook({ waitForCompletion: true, timeout: 600 })\ntriggers.x402({ name: '...', description: '...', price: '0.01', timeout: 600 })\ntriggers.cron({ schedule: '0 9 * * *' })\ntriggers.manual()\n\n\nImportant: Always set timeout to at least 600 seconds (10 minutes) for webhook and x402 triggers. Agents often take significant time to process requests — especially when performing research, content generation, or other complex tasks. A low timeout will cause premature failures. For multi-agent pipelines with many sequential steps, consider 900 seconds or more.\n\nAPI Keys: Agent vs User\n\nprovision() creates two types of credentials. They are not interchangeable:\n\nOPENSERV_API_KEY (Agent API key) — Used internally by the SDK to authenticate when receiving tasks. Set automatically by provision() when you pass agent.instance. Do not use this key with PlatformClient.\nWALLET_PRIVATE_KEY / OPENSERV_USER_API_KEY (User credentials) — Used with PlatformClient to make management calls (list tasks, debug workflows, etc.). Authenticate with client.authenticate(walletKey) or pass apiKey to the constructor.\n\nIf you need to debug tasks or inspect workflows, use wallet authentication:\n\nconst client = new PlatformClient()\nawait client.authenticate(process.env.WALLET_PRIVATE_KEY)\nconst tasks = await client.tasks.list({ workflowId: result.workflowId })\n\n\nSee troubleshooting.md for details on 401 errors.\n\nDeployment\nLocal Development\nnpm run dev\n\n\nThe run() function automatically:\n\nStarts the agent HTTP server (port 7378, with automatic fallback)\nConnects via WebSocket to agents-proxy.openserv.ai\nRoutes platform requests to your local machine\n\nNo need for ngrok or other tunneling tools - run() handles this seamlessly. Just call run(agent) and your local agent is accessible to the platform.\n\nProduction\n\nWhen deploying to a hosting provider like Cloud Run, set DISABLE_TUNNEL=true as an environment variable. This makes run() start only the HTTP server without opening a WebSocket tunnel — the platform reaches your agent directly at its public URL.\n\nawait provision({\n  agent: {\n    name: 'my-agent',\n    description: '...',\n    endpointUrl: 'https://my-agent.example.com' // Required for production\n  },\n  workflow: {\n    name: 'Lightning Service Pro',\n    goal: 'Describe in detail what this workflow does — be thorough, vague goals cause failures',\n    trigger: triggers.webhook({ waitForCompletion: true, timeout: 600 }),\n    task: { description: 'Process incoming requests' }\n  }\n})\n\n// With DISABLE_TUNNEL=true, run() starts only the HTTP server (no tunnel)\nawait run(agent)\n\nERC-8004: On-Chain Agent Identity\n\nAfter provisioning, register your agent on-chain for discoverability via the Identity Registry.\n\nRequires ETH on Base. Registration calls register() on the ERC-8004 contract on Base mainnet (chain 8453), which costs gas. The wallet created by provision() starts with a zero balance. Fund it with a small amount of ETH on Base before the first registration attempt. The wallet address is logged during provisioning (Created new wallet: 0x...).\n\nAlways wrap in try/catch so a registration failure (e.g. unfunded wallet) doesn't prevent run(agent) from starting.\n\nTwo important patterns:\n\nUse dotenv programmatically (not import 'dotenv/config') so you can reload .env after provision() writes WALLET_PRIVATE_KEY.\nCall dotenv.config({ override: true }) after provision() to pick up the freshly written key before ERC-8004 registration.\nimport dotenv from 'dotenv'\ndotenv.config()\n\nimport { Agent, run } from '@openserv-labs/sdk'\nimport { provision, triggers, PlatformClient } from '@openserv-labs/client'\n\n// ... define agent and capabilities ...\n\nconst result = await provision({\n  agent: { instance: agent, name: 'my-agent', description: '...' },\n  workflow: {\n    name: 'My Service',\n    goal: 'Detailed description of what the workflow does',\n    trigger: triggers.x402({ name: 'My Service', description: '...', price: '0.01', timeout: 600 }),\n    task: { description: 'Process requests' }\n  }\n})\n\n// Reload .env to pick up WALLET_PRIVATE_KEY written by provision()\ndotenv.config({ override: true })\n\n// Register on-chain (non-blocking — requires funded wallet on Base)\ntry {\n  const client = new PlatformClient()\n  await client.authenticate(process.env.WALLET_PRIVATE_KEY)\n\n  const erc8004 = await client.erc8004.registerOnChain({\n    workflowId: result.workflowId,\n    privateKey: process.env.WALLET_PRIVATE_KEY!,\n    name: 'My Service',\n    description: 'What this agent does'\n  })\n\n  console.log(`Agent ID: ${erc8004.agentId}`) // \"8453:42\"\n  console.log(`TX: ${erc8004.blockExplorerUrl}`)\n  console.log(`Scan: ${erc8004.scanUrl}`) // \"https://www.8004scan.io/agents/base/42\"\n} catch (error) {\n  console.warn('ERC-8004 registration skipped:', error instanceof Error ? error.message : error)\n}\n\nawait run(agent)\n\nFirst run mints a new identity NFT. Re-runs update the URI — agent ID stays the same.\nNever clear the wallet state unless you intentionally want a new agent ID. To update metadata, just re-run.\nDefault chain: Base mainnet (8453). Pass chainId / rpcUrl for others.\n\nSee openserv-client skill for the full ERC-8004 API reference and troubleshooting.\n\nDO NOT USE\nthis.process() inside capabilities — Legacy method requiring an OpenAI API key. Use this.generate() instead (platform-delegated, no key needed), or use runless capabilities\ndoTask override — The SDK handles task execution automatically\nthis.completeTask() — Task completion is handled by the Runtime API\nUpdating This Skill\n\nThis 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.\n\n# Check if updates are available\nnpx skills check\n\n# Update all installed skills to latest versions\nnpx skills update\n\n\nOr reinstall the OpenServ skills directly:\n\nnpx skills add openserv-labs/skills\n\nRelated Skills\nopenserv-client - Full Platform Client API reference\nopenserv-multi-agent-workflows - Multi-agent collaboration patterns\nopenserv-launch - Launch tokens on Base blockchain\nopenserv-ideaboard-api - Find ideas and ship agent services on the Ideaboard"
  },
  "trust": {
    "sourceLabel": "tencent",
    "provenanceUrl": "https://clawhub.ai/issa-me-sush/openserv-agent-sdk",
    "publisherUrl": "https://clawhub.ai/issa-me-sush/openserv-agent-sdk",
    "owner": "issa-me-sush",
    "version": "1.0.5",
    "license": null,
    "verificationStatus": "Indexed source record"
  },
  "links": {
    "detailUrl": "https://openagent3.xyz/skills/openserv-agent-sdk",
    "downloadUrl": "https://openagent3.xyz/downloads/openserv-agent-sdk",
    "agentUrl": "https://openagent3.xyz/skills/openserv-agent-sdk/agent",
    "manifestUrl": "https://openagent3.xyz/skills/openserv-agent-sdk/agent.json",
    "briefUrl": "https://openagent3.xyz/skills/openserv-agent-sdk/agent.md"
  }
}