{
  "schemaVersion": "1.0",
  "item": {
    "slug": "durable-agents",
    "name": "Durable Agents",
    "source": "tencent",
    "type": "skill",
    "category": "AI 智能",
    "sourceUrl": "https://clawhub.ai/ainakwalamonk/durable-agents",
    "canonicalUrl": "https://clawhub.ai/ainakwalamonk/durable-agents",
    "targetPlatform": "OpenClaw"
  },
  "install": {
    "downloadMode": "redirect",
    "downloadUrl": "/downloads/durable-agents",
    "sourceDownloadUrl": "https://wry-manatee-359.convex.site/api/v1/download?slug=durable-agents",
    "sourcePlatform": "tencent",
    "targetPlatform": "OpenClaw",
    "installMethod": "Manual import",
    "extraction": "Extract archive",
    "prerequisites": [
      "OpenClaw"
    ],
    "packageFormat": "ZIP package",
    "includedAssets": [
      "SKILL.md",
      "setupSkill.md"
    ],
    "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",
      "slug": "durable-agents",
      "status": "healthy",
      "reason": "direct_download_ok",
      "recommendedAction": "download",
      "checkedAt": "2026-05-01T06:08:18.627Z",
      "expiresAt": "2026-05-08T06:08:18.627Z",
      "httpStatus": 200,
      "finalUrl": "https://wry-manatee-359.convex.site/api/v1/download?slug=durable-agents",
      "contentType": "application/zip",
      "probeMethod": "head",
      "details": {
        "probeUrl": "https://wry-manatee-359.convex.site/api/v1/download?slug=durable-agents",
        "contentDisposition": "attachment; filename=\"durable-agents-1.0.0.zip\"",
        "redirectLocation": null,
        "bodySnippet": null,
        "slug": "durable-agents"
      },
      "scope": "item",
      "summary": "Item download looks usable.",
      "detail": "Yavira can redirect you to the upstream package for this item.",
      "primaryActionLabel": "Download for OpenClaw",
      "primaryActionHref": "/downloads/durable-agents"
    },
    "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/durable-agents",
    "agentPageUrl": "https://openagent3.xyz/skills/durable-agents/agent",
    "manifestUrl": "https://openagent3.xyz/skills/durable-agents/agent.json",
    "briefUrl": "https://openagent3.xyz/skills/durable-agents/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": "Durable Agents — Multi-Agent Pipeline Development Guide",
        "body": "Mastra (AI agent framework) + Trigger.dev (durable task execution with retries, timeouts, fan-out). Build autonomous multi-agent pipelines where each agent owns a single stage, hands off structured output to the next stage through Trigger.dev, and never holds the full context of a pipeline it doesn't own."
      },
      {
        "title": "Core Principles",
        "body": "All intelligence lives in AGENT.md, not in code. The .ts file is boilerplate wiring. Writing logic in the agent's TypeScript file is wrong.\nOne agent, one job. Each agent has a single clear responsibility. If an agent does two unrelated things, split it into two agents in a pipeline.\nTasks handle durability, agents handle reasoning. Trigger.dev tasks wrap agent calls with retries and timeouts. The agent receives input and produces output.\nTools return errors, never throw. Every tool returns { success, errorMessage? } on failure. Throwing inside a tool crashes the task. Returning an error lets the agent reason about it.\nType everything. Input schemas, output schemas, tool schemas — all Zod. If it crosses a boundary (tool input, task payload, pipeline stage), it has a schema.\nAgents are autonomous, not scripted. Give agents an outcome and a quality bar. Don't wire their steps in code.\nPipelines break context, not logic. Split a pipeline at the point where a different capability is needed — not to artificially divide one agent's work.\nAll agentic I/O persists to the database. Agent inputs, outputs, and intermediate results are stored as records. The database is the source of truth, not in-memory state.\nEvery tool that touches a real system is permission-gated. If a tool can post, publish, delete, charge, or trigger anything external, it must confirm intent before executing."
      },
      {
        "title": "1. Create the directory",
        "body": "src/agents/{name}/\n  AGENT.md\n  {name}.ts"
      },
      {
        "title": "2. Write the AGENT.md",
        "body": "# AGENT: {Name}\n\n## Role\nWho this agent is. One sentence.\n\n## Tools\nWhat tools it has and when to use each one. Be explicit — \"Use `sqlQuery` to\ncheck if a table exists before referencing it\" not just \"Has sqlQuery tool.\"\n\n## Inputs\nWhat payload it receives. Describe the shape and what each field means.\n\n## Goal\nWhat it must achieve. Describe the outcome, not the steps. The agent decides\nhow to get there. \"Produce a deployment plan for the given architecture\" not\n\"First read the architecture, then list the services, then...\"\n\n## Output Contract\nExact shape it must return. If structured output is needed, specify the JSON\nschema here. Example:\n  { \"plan\": string, \"steps\": string[], \"risks\": string[] }\n\n## Quality Standards\nWhat makes output good vs bad. Be specific. \"Each step must be independently\nexecutable\" not \"Steps should be good.\"\n\n## Guardrails\nWhat it must NOT do. \"Never modify database schema directly.\" \"Never assume\nthe API is authenticated unless payload says so.\"\n\n## Self-Validation\nChecklist the agent must verify before returning:\n- Does output match the Output Contract?\n- Are all required fields present?\n- Does it satisfy the Quality Standards?"
      },
      {
        "title": "3. Create the agent .ts file",
        "body": "Pure boilerplate. No logic here.\n\nimport fs from \"fs\";\nimport path from \"path\";\nimport { fileURLToPath } from \"url\";\nimport { Agent } from \"@mastra/core/agent\";\nimport { model } from \"../../config/model.js\";\n\nconst __dirname = path.dirname(fileURLToPath(import.meta.url));\nconst instructions = fs.readFileSync(path.join(__dirname, \"AGENT.md\"), \"utf8\");\n\nexport const myAgent = new Agent({\n    id: \"my-agent\",\n    name: \"My Agent\",\n    instructions,\n    model,\n});\n\nTo give the agent tools:\n\nimport { myTool } from \"../../tools/myTool.js\";\n\nexport const myAgent = new Agent({\n    id: \"my-agent\",\n    name: \"My Agent\",\n    instructions,\n    model,\n    tools: { myTool },\n});"
      },
      {
        "title": "4. Register the agent",
        "body": "In src/mastra/index.ts:\n\nimport { myAgent } from \"../agents/my-agent/my-agent.js\";\n\nexport const mastra = new Mastra({\n    agents: { plannerAgent, reviewerAgent, myAgent },\n});"
      },
      {
        "title": "Structure",
        "body": "import { createTool } from \"@mastra/core/tools\";\nimport { z } from \"zod\";\n\nexport const myTool = createTool({\n    id: \"my-tool\",\n    description: \"What it does and WHEN to use it\",\n    inputSchema: z.object({\n        query: z.string().describe(\"The search query\"),\n    }),\n    outputSchema: z.object({\n        success: z.boolean(),\n        data: z.any().optional(),\n        errorMessage: z.string().optional(),\n    }),\n    execute: async ({ query }) => {\n        try {\n            const result = await doSomething(query);\n            return { success: true, data: result };\n        } catch (error: any) {\n            return { success: false, errorMessage: error.message };\n        }\n    },\n});"
      },
      {
        "title": "Tool Rules",
        "body": "Always define outputSchema. The agent uses it to understand what the tool returns.\nNever throw from execute. Return { success: false, errorMessage } instead. Throwing crashes the Trigger.dev task.\nDescription is for the agent. Write it as instructions: \"Use this to check if a database table exists. Pass the table name. Returns true/false.\"\nOne tool does one thing. \"Query the database\" not \"Query the database and format the results and send an email.\"\nUse .describe() on Zod fields to tell the agent what to pass.\nNo side effects unless necessary. If a tool writes, document it clearly in the description and in the agent's AGENT.md guardrails."
      },
      {
        "title": "Where to put tools",
        "body": "Shared tools: src/tools/{name}.ts\nAgent-specific tools: src/agents/{agentName}/tools/{name}.ts\n\nRegister shared tools in src/mastra/index.ts. Agent-specific tools import directly in the agent file."
      },
      {
        "title": "Permissioned Tools for Destructive or External Actions",
        "body": "Any tool that touches a real system — posting to an API, publishing content, sending a message, charging a user, deleting data, triggering a webhook — must be permission-gated. Agents must not be able to fire these actions without explicit intent confirmation.\n\nBefore building a tool that has real-world side effects, ask the user:\n\nWhat exact action does this tool take?\nShould the agent be able to trigger this autonomously, or does a human need to approve it first?\nWhat are the consequences of it misfiring?\nShould this be rate-limited or scoped to specific records?\n\nBuild the answer into the tool's permission layer, not just the agent's AGENT.md guardrails. Guardrails are instructions; permission layers are enforcement."
      },
      {
        "title": "Pattern: Confirm Before Execute",
        "body": "For any action that can't be undone or that has cost/visibility consequences, the tool must receive an explicit confirmed: true in its input before it proceeds. The agent must call a read/preview tool first, then call the action tool only when it has verified the result and received confirmed: true from the calling context.\n\nexport const publishPostTool = createTool({\n    id: \"publish-post\",\n    description: \"Publishes a post to the platform. Only call this after previewing with `previewPostTool` and receiving confirmed: true from the task payload.\",\n    inputSchema: z.object({\n        postId: z.string().describe(\"ID of the post record to publish\"),\n        confirmed: z.boolean().describe(\"Must be true. Do not set this yourself — it must come from the task payload.\"),\n    }),\n    outputSchema: z.object({\n        success: z.boolean(),\n        publishedUrl: z.string().optional(),\n        errorMessage: z.string().optional(),\n    }),\n    execute: async ({ postId, confirmed }) => {\n        if (!confirmed) {\n            return { success: false, errorMessage: \"Publish requires confirmed: true in payload.\" };\n        }\n        try {\n            const url = await publishPost(postId);\n            return { success: true, publishedUrl: url };\n        } catch (error: any) {\n            return { success: false, errorMessage: error.message };\n        }\n    },\n});"
      },
      {
        "title": "Pattern: Scope to Records",
        "body": "Destructive or write tools must operate on a specific record ID — never on a query, a filter, or an implicit \"current item.\" The agent must always pass the exact ID of the record it's acting on. This prevents the tool from accidentally operating on the wrong item.\n\ninputSchema: z.object({\n    recordId: z.string().describe(\"Exact DB ID of the record to act on. Do not pass a search query.\"),\n})"
      },
      {
        "title": "What belongs in AGENT.md Guardrails vs in the tool",
        "body": "ConcernWhere it lives\"Don't publish unless quality score > 0.8\"AGENT.md Guardrails\"Don't call this without confirmed: true\"Tool input schema + execute guard\"Only act on records in status: draft\"Tool execute guard (check DB before acting)\"Never delete more than one record per run\"Tool execute guard (enforce the count)"
      },
      {
        "title": "How to Create a Pipeline",
        "body": "Pipelines chain Trigger.dev tasks. Each task calls one agent and passes its output to the next. No single agent holds the full pipeline context — each stage receives only what it needs."
      },
      {
        "title": "1. Create task files in src/pipelines/tasks/",
        "body": "import { task, logger } from \"@trigger.dev/sdk/v3\";\nimport { mastra } from \"../../mastra/index.js\";\n\nexport const planTask = task({\n    id: \"plan-task\",\n    retry: { maxAttempts: 3, minTimeoutInMs: 1000, factor: 2 },\n    run: async (payload: { prompt: string }) => {\n        logger.info(\"Running planner\", { promptLength: payload.prompt.length });\n        const agent = mastra.getAgent(\"plannerAgent\");\n        const response = await agent.generate(JSON.stringify(payload));\n        return response.text;\n    },\n});"
      },
      {
        "title": "2. Create the pipeline orchestrator",
        "body": "In src/pipelines/{name}.ts, chain tasks using triggerAndWait:\n\nimport { planTask } from \"./tasks/plan-task.js\";\nimport { reviewTask } from \"./tasks/review-task.js\";\n\nexport async function runMyPipeline(input: string) {\n    const planResult = await planTask.triggerAndWait({ prompt: input });\n    if (!planResult.ok) throw new Error(\"Plan task failed\");\n\n    const reviewResult = await reviewTask.triggerAndWait({ plan: planResult.output });\n    if (!reviewResult.ok) throw new Error(\"Review task failed\");\n\n    return { plan: planResult.output, review: reviewResult.output };\n}"
      },
      {
        "title": "3. Export tasks for the worker",
        "body": "In src/trigger/index.ts:\n\nexport * from \"../pipelines/tasks/plan-task.js\";\nexport * from \"../pipelines/tasks/review-task.js\";\n\nEvery task must be exported here or the Trigger.dev worker won't discover it."
      },
      {
        "title": "4. Add an API endpoint",
        "body": "In src/app/index.ts:\n\napp.post(\"/my-pipeline\", async (req, res) => {\n    const { input } = req.body;\n    const result = await runMyPipeline(input);\n    res.json({ success: true, ...result });\n});"
      },
      {
        "title": "Pipeline Design: Agents vs Scripts",
        "body": "Not every pipeline stage needs an agent. Use agents where judgment is required. Use scripts (plain TypeScript functions or Trigger.dev tasks with no agent) where the action is deterministic."
      },
      {
        "title": "Example: Content Production Pipeline",
        "body": "[Director Agent]         — generates ideas, writes scripts, validates against criteria\n        ↓\n[Media Selector Agent]   — selects or processes media assets based on the script\n        ↓\n[Overlay Task]           — no agent; deterministic script that composites text onto video and stores result\n\nThe overlay stage has no reasoning to do. It receives exact inputs, executes a fixed operation, and stores the output. Putting an agent here adds latency and cost for no benefit."
      },
      {
        "title": "When to use an agent in a pipeline stage",
        "body": "Use an agent when the stage requires:\n\nJudgment or evaluation (does this meet a quality bar?)\nSelection from ambiguous options (which asset fits this script best?)\nGeneration from a goal (write a script for this topic)\nIterative refinement based on feedback\n\nUse a plain task (no agent) when the stage is:\n\nA deterministic transformation (resize, encode, composite)\nA storage write (save output to DB or file system)\nA notification or webhook trigger\nA lookup with no interpretation needed"
      },
      {
        "title": "Splitting pipeline stages",
        "body": "Split at the boundary where a different capability is needed — not to artificially divide one agent's work. A director agent that generates ideas, writes a script, and validates it against criteria is doing one coherent job. That's one agent, one task. The media selection is a different capability — that's the split."
      },
      {
        "title": "Fan-Out (Parallel Sub-tasks)",
        "body": "import { tasks } from \"@trigger.dev/sdk/v3\";\n\nconst handles = await tasks.batchTrigger(\"process-item\",\n    items.map(item => ({ payload: { item } }))\n);\n\nEach sub-task runs independently with its own retries."
      },
      {
        "title": "Review Checkpoint",
        "body": "Insert a review stage between pipeline steps. Three modes:\n\nModeBehavior\"none\"Auto-approve. Trigger next stage immediately.\"agent\"Call a reviewer agent. If approved, continue. If rejected, feed feedback back to the previous stage for revision.\"human\"Set a status in the DB to pending. Return. A human reviews externally. Resume the pipeline via an API callback."
      },
      {
        "title": "Retry Configuration",
        "body": "Every task must have explicit retry config. LLM calls are flaky — the default (no retries) means one transient API error kills the pipeline.\n\nretry: {\n    maxAttempts: 3,\n    minTimeoutInMs: 1000,\n    factor: 2,\n}"
      },
      {
        "title": "Database as the Agentic Record Layer",
        "body": "Every agent input, output, and intermediate result must be persisted to the database before the next stage runs. This is not optional. Agents operate on DB records — they do not pass raw data through in-memory pipelines."
      },
      {
        "title": "Why",
        "body": "Deduplication. Check if an equivalent job has already run before triggering a new one. Compare by content hash, source ID, or a natural key.\nVerification. The next stage reads from the DB, not from the previous task's return value. If a record isn't in the DB, the stage doesn't proceed.\nRecord keeping. Every generated asset, decision, and status transition is a row. You can audit, replay, and debug any run.\nResume on failure. If a task retries, it checks the DB first. If the output already exists, it skips regeneration and continues."
      },
      {
        "title": "Pattern: Write Before Passing",
        "body": "Every task writes its output to the DB and returns the record ID. The next task receives the ID, reads from the DB, and operates on the record.\n\n// Stage 1: director agent writes its output\nexport const scriptTask = task({\n    id: \"script-task\",\n    retry: { maxAttempts: 3, minTimeoutInMs: 1000, factor: 2 },\n    run: async (payload: { projectId: string }) => {\n        const existing = await db.script.findFirst({ where: { projectId: payload.projectId } });\n        if (existing) return { scriptId: existing.id }; // already done, skip\n\n        const agent = mastra.getAgent(\"directorAgent\");\n        const response = await agent.generate(JSON.stringify(payload));\n        const output = ScriptOutputSchema.parse(JSON.parse(response.text));\n\n        const record = await db.script.create({\n            data: { projectId: payload.projectId, content: output.script, status: \"draft\" },\n        });\n        return { scriptId: record.id };\n    },\n});\n\n// Stage 2: next agent reads by ID\nexport const mediaTask = task({\n    id: \"media-task\",\n    retry: { maxAttempts: 3, minTimeoutInMs: 1000, factor: 2 },\n    run: async (payload: { scriptId: string }) => {\n        const script = await db.script.findUniqueOrThrow({ where: { id: payload.scriptId } });\n        const agent = mastra.getAgent(\"mediaSelectorAgent\");\n        const response = await agent.generate(JSON.stringify({ script: script.content }));\n        const output = MediaOutputSchema.parse(JSON.parse(response.text));\n\n        const record = await db.mediaSelection.create({\n            data: { scriptId: payload.scriptId, assetIds: output.assetIds, status: \"selected\" },\n        });\n        return { mediaSelectionId: record.id };\n    },\n});"
      },
      {
        "title": "Pattern: Status Transitions as Pipeline Control",
        "body": "Store a status field on every record. Use it to gate pipeline stages and drive human review checkpoints.\n\nStatusMeaningpendingCreated, not yet processedprocessingTask is runningdraftAgent output produced, not reviewedapprovedPassed review (agent or human)rejectedFailed review, needs revisionpublishedFinal action takenfailedUnrecoverable error\n\nawait db.script.update({\n    where: { id: scriptId },\n    data: { status: \"processing\" },\n});\n// ... agent call ...\nawait db.script.update({\n    where: { id: scriptId },\n    data: { status: \"draft\", content: output.script },\n});"
      },
      {
        "title": "Keeping Agents Autonomous",
        "body": "Define the destination and the quality bar. Don't specify how to get there.\n\nWrong — micromanaging the agent:\n\n1. Read the input\n2. Extract the requirements\n3. For each requirement, write a task\n4. Format the tasks as a numbered list\n5. Return the list\n\nRight — defining the outcome:\n\n## Goal\nProduce a technical implementation plan for the given objective.\n\n## Output Contract\n{ \"tasks\": [{ \"title\": string, \"description\": string, \"dependencies\": string[] }] }\n\n## Quality Standards\n- Each task must be independently executable by a developer\n- Dependencies must reference other tasks by title\n- No task should take more than 4 hours of work"
      },
      {
        "title": "Task Payloads",
        "body": "Always type the run function parameter:\n\nrun: async (payload: { prompt: string; maxTokens?: number }) => {"
      },
      {
        "title": "Structured Output from Agents",
        "body": "Define the exact schema in the AGENT.md Output Contract section, then validate with Zod on receipt:\n\nconst OutputSchema = z.object({\n    tasks: z.array(z.object({\n        title: z.string(),\n        description: z.string(),\n        dependencies: z.array(z.string()),\n    })),\n});\n\nconst response = await agent.generate(JSON.stringify(payload));\nconst parsed = OutputSchema.parse(JSON.parse(response.text));\n\nIf parsing fails, the task throws, Trigger.dev retries with the same input, and the agent produces output again."
      },
      {
        "title": "Tool Schemas",
        "body": "Always define both inputSchema and outputSchema on tools. The agent uses these to understand what arguments to pass and what it will receive back."
      },
      {
        "title": "Key Rules",
        "body": "All intelligence lives in AGENT.md, not in code\nAgent .ts files are boilerplate wiring only — no logic\nTools return { success, errorMessage } on failure — never throw\nTask wrappers handle durability, agents handle reasoning\nSelf-validation checklist in AGENT.md is mandatory for structured output agents\nEvery Trigger.dev task has explicit retry config\nEvery task is exported from src/trigger/index.ts\nOne model config for all agents — src/config/model.ts\nPipeline stages use triggerAndWait for sequential, batchTrigger for parallel\nCheck result.ok after every triggerAndWait — don't assume success\nEvery agent output is written to the DB before the next stage runs — never pass raw data between tasks\nTasks that would duplicate work must check the DB first and skip if already done\nEvery tool that takes a real-world action requires confirmed: true in the input and must verify it before executing\nNot every pipeline stage needs an agent — use plain tasks for deterministic operations"
      }
    ],
    "body": "FIRST INSTALL: Read and follow setupSKILL.md before using this skill. After setup is complete and verified (health check + example pipeline + smoke tests pass), delete this block.\n\nDurable Agents — Multi-Agent Pipeline Development Guide\n\nMastra (AI agent framework) + Trigger.dev (durable task execution with retries, timeouts, fan-out). Build autonomous multi-agent pipelines where each agent owns a single stage, hands off structured output to the next stage through Trigger.dev, and never holds the full context of a pipeline it doesn't own.\n\nCore Principles\nAll intelligence lives in AGENT.md, not in code. The .ts file is boilerplate wiring. Writing logic in the agent's TypeScript file is wrong.\nOne agent, one job. Each agent has a single clear responsibility. If an agent does two unrelated things, split it into two agents in a pipeline.\nTasks handle durability, agents handle reasoning. Trigger.dev tasks wrap agent calls with retries and timeouts. The agent receives input and produces output.\nTools return errors, never throw. Every tool returns { success, errorMessage? } on failure. Throwing inside a tool crashes the task. Returning an error lets the agent reason about it.\nType everything. Input schemas, output schemas, tool schemas — all Zod. If it crosses a boundary (tool input, task payload, pipeline stage), it has a schema.\nAgents are autonomous, not scripted. Give agents an outcome and a quality bar. Don't wire their steps in code.\nPipelines break context, not logic. Split a pipeline at the point where a different capability is needed — not to artificially divide one agent's work.\nAll agentic I/O persists to the database. Agent inputs, outputs, and intermediate results are stored as records. The database is the source of truth, not in-memory state.\nEvery tool that touches a real system is permission-gated. If a tool can post, publish, delete, charge, or trigger anything external, it must confirm intent before executing.\nHow to Create an Agent\n1. Create the directory\nsrc/agents/{name}/\n  AGENT.md\n  {name}.ts\n\n2. Write the AGENT.md\n# AGENT: {Name}\n\n## Role\nWho this agent is. One sentence.\n\n## Tools\nWhat tools it has and when to use each one. Be explicit — \"Use `sqlQuery` to\ncheck if a table exists before referencing it\" not just \"Has sqlQuery tool.\"\n\n## Inputs\nWhat payload it receives. Describe the shape and what each field means.\n\n## Goal\nWhat it must achieve. Describe the outcome, not the steps. The agent decides\nhow to get there. \"Produce a deployment plan for the given architecture\" not\n\"First read the architecture, then list the services, then...\"\n\n## Output Contract\nExact shape it must return. If structured output is needed, specify the JSON\nschema here. Example:\n  { \"plan\": string, \"steps\": string[], \"risks\": string[] }\n\n## Quality Standards\nWhat makes output good vs bad. Be specific. \"Each step must be independently\nexecutable\" not \"Steps should be good.\"\n\n## Guardrails\nWhat it must NOT do. \"Never modify database schema directly.\" \"Never assume\nthe API is authenticated unless payload says so.\"\n\n## Self-Validation\nChecklist the agent must verify before returning:\n- Does output match the Output Contract?\n- Are all required fields present?\n- Does it satisfy the Quality Standards?\n\n3. Create the agent .ts file\n\nPure boilerplate. No logic here.\n\nimport fs from \"fs\";\nimport path from \"path\";\nimport { fileURLToPath } from \"url\";\nimport { Agent } from \"@mastra/core/agent\";\nimport { model } from \"../../config/model.js\";\n\nconst __dirname = path.dirname(fileURLToPath(import.meta.url));\nconst instructions = fs.readFileSync(path.join(__dirname, \"AGENT.md\"), \"utf8\");\n\nexport const myAgent = new Agent({\n    id: \"my-agent\",\n    name: \"My Agent\",\n    instructions,\n    model,\n});\n\n\nTo give the agent tools:\n\nimport { myTool } from \"../../tools/myTool.js\";\n\nexport const myAgent = new Agent({\n    id: \"my-agent\",\n    name: \"My Agent\",\n    instructions,\n    model,\n    tools: { myTool },\n});\n\n4. Register the agent\n\nIn src/mastra/index.ts:\n\nimport { myAgent } from \"../agents/my-agent/my-agent.js\";\n\nexport const mastra = new Mastra({\n    agents: { plannerAgent, reviewerAgent, myAgent },\n});\n\nHow to Create a Tool\nStructure\nimport { createTool } from \"@mastra/core/tools\";\nimport { z } from \"zod\";\n\nexport const myTool = createTool({\n    id: \"my-tool\",\n    description: \"What it does and WHEN to use it\",\n    inputSchema: z.object({\n        query: z.string().describe(\"The search query\"),\n    }),\n    outputSchema: z.object({\n        success: z.boolean(),\n        data: z.any().optional(),\n        errorMessage: z.string().optional(),\n    }),\n    execute: async ({ query }) => {\n        try {\n            const result = await doSomething(query);\n            return { success: true, data: result };\n        } catch (error: any) {\n            return { success: false, errorMessage: error.message };\n        }\n    },\n});\n\nTool Rules\nAlways define outputSchema. The agent uses it to understand what the tool returns.\nNever throw from execute. Return { success: false, errorMessage } instead. Throwing crashes the Trigger.dev task.\nDescription is for the agent. Write it as instructions: \"Use this to check if a database table exists. Pass the table name. Returns true/false.\"\nOne tool does one thing. \"Query the database\" not \"Query the database and format the results and send an email.\"\nUse .describe() on Zod fields to tell the agent what to pass.\nNo side effects unless necessary. If a tool writes, document it clearly in the description and in the agent's AGENT.md guardrails.\nWhere to put tools\nShared tools: src/tools/{name}.ts\nAgent-specific tools: src/agents/{agentName}/tools/{name}.ts\n\nRegister shared tools in src/mastra/index.ts. Agent-specific tools import directly in the agent file.\n\nPermissioned Tools for Destructive or External Actions\n\nAny tool that touches a real system — posting to an API, publishing content, sending a message, charging a user, deleting data, triggering a webhook — must be permission-gated. Agents must not be able to fire these actions without explicit intent confirmation.\n\nBefore building a tool that has real-world side effects, ask the user:\n\nWhat exact action does this tool take?\nShould the agent be able to trigger this autonomously, or does a human need to approve it first?\nWhat are the consequences of it misfiring?\nShould this be rate-limited or scoped to specific records?\n\nBuild the answer into the tool's permission layer, not just the agent's AGENT.md guardrails. Guardrails are instructions; permission layers are enforcement.\n\nPattern: Confirm Before Execute\n\nFor any action that can't be undone or that has cost/visibility consequences, the tool must receive an explicit confirmed: true in its input before it proceeds. The agent must call a read/preview tool first, then call the action tool only when it has verified the result and received confirmed: true from the calling context.\n\nexport const publishPostTool = createTool({\n    id: \"publish-post\",\n    description: \"Publishes a post to the platform. Only call this after previewing with `previewPostTool` and receiving confirmed: true from the task payload.\",\n    inputSchema: z.object({\n        postId: z.string().describe(\"ID of the post record to publish\"),\n        confirmed: z.boolean().describe(\"Must be true. Do not set this yourself — it must come from the task payload.\"),\n    }),\n    outputSchema: z.object({\n        success: z.boolean(),\n        publishedUrl: z.string().optional(),\n        errorMessage: z.string().optional(),\n    }),\n    execute: async ({ postId, confirmed }) => {\n        if (!confirmed) {\n            return { success: false, errorMessage: \"Publish requires confirmed: true in payload.\" };\n        }\n        try {\n            const url = await publishPost(postId);\n            return { success: true, publishedUrl: url };\n        } catch (error: any) {\n            return { success: false, errorMessage: error.message };\n        }\n    },\n});\n\nPattern: Scope to Records\n\nDestructive or write tools must operate on a specific record ID — never on a query, a filter, or an implicit \"current item.\" The agent must always pass the exact ID of the record it's acting on. This prevents the tool from accidentally operating on the wrong item.\n\ninputSchema: z.object({\n    recordId: z.string().describe(\"Exact DB ID of the record to act on. Do not pass a search query.\"),\n})\n\nWhat belongs in AGENT.md Guardrails vs in the tool\nConcern\tWhere it lives\n\"Don't publish unless quality score > 0.8\"\tAGENT.md Guardrails\n\"Don't call this without confirmed: true\"\tTool input schema + execute guard\n\"Only act on records in status: draft\"\tTool execute guard (check DB before acting)\n\"Never delete more than one record per run\"\tTool execute guard (enforce the count)\nHow to Create a Pipeline\n\nPipelines chain Trigger.dev tasks. Each task calls one agent and passes its output to the next. No single agent holds the full pipeline context — each stage receives only what it needs.\n\n1. Create task files in src/pipelines/tasks/\nimport { task, logger } from \"@trigger.dev/sdk/v3\";\nimport { mastra } from \"../../mastra/index.js\";\n\nexport const planTask = task({\n    id: \"plan-task\",\n    retry: { maxAttempts: 3, minTimeoutInMs: 1000, factor: 2 },\n    run: async (payload: { prompt: string }) => {\n        logger.info(\"Running planner\", { promptLength: payload.prompt.length });\n        const agent = mastra.getAgent(\"plannerAgent\");\n        const response = await agent.generate(JSON.stringify(payload));\n        return response.text;\n    },\n});\n\n2. Create the pipeline orchestrator\n\nIn src/pipelines/{name}.ts, chain tasks using triggerAndWait:\n\nimport { planTask } from \"./tasks/plan-task.js\";\nimport { reviewTask } from \"./tasks/review-task.js\";\n\nexport async function runMyPipeline(input: string) {\n    const planResult = await planTask.triggerAndWait({ prompt: input });\n    if (!planResult.ok) throw new Error(\"Plan task failed\");\n\n    const reviewResult = await reviewTask.triggerAndWait({ plan: planResult.output });\n    if (!reviewResult.ok) throw new Error(\"Review task failed\");\n\n    return { plan: planResult.output, review: reviewResult.output };\n}\n\n3. Export tasks for the worker\n\nIn src/trigger/index.ts:\n\nexport * from \"../pipelines/tasks/plan-task.js\";\nexport * from \"../pipelines/tasks/review-task.js\";\n\n\nEvery task must be exported here or the Trigger.dev worker won't discover it.\n\n4. Add an API endpoint\n\nIn src/app/index.ts:\n\napp.post(\"/my-pipeline\", async (req, res) => {\n    const { input } = req.body;\n    const result = await runMyPipeline(input);\n    res.json({ success: true, ...result });\n});\n\nPipeline Design: Agents vs Scripts\n\nNot every pipeline stage needs an agent. Use agents where judgment is required. Use scripts (plain TypeScript functions or Trigger.dev tasks with no agent) where the action is deterministic.\n\nExample: Content Production Pipeline\n[Director Agent]         — generates ideas, writes scripts, validates against criteria\n        ↓\n[Media Selector Agent]   — selects or processes media assets based on the script\n        ↓\n[Overlay Task]           — no agent; deterministic script that composites text onto video and stores result\n\n\nThe overlay stage has no reasoning to do. It receives exact inputs, executes a fixed operation, and stores the output. Putting an agent here adds latency and cost for no benefit.\n\nWhen to use an agent in a pipeline stage\n\nUse an agent when the stage requires:\n\nJudgment or evaluation (does this meet a quality bar?)\nSelection from ambiguous options (which asset fits this script best?)\nGeneration from a goal (write a script for this topic)\nIterative refinement based on feedback\n\nUse a plain task (no agent) when the stage is:\n\nA deterministic transformation (resize, encode, composite)\nA storage write (save output to DB or file system)\nA notification or webhook trigger\nA lookup with no interpretation needed\nSplitting pipeline stages\n\nSplit at the boundary where a different capability is needed — not to artificially divide one agent's work. A director agent that generates ideas, writes a script, and validates it against criteria is doing one coherent job. That's one agent, one task. The media selection is a different capability — that's the split.\n\nPipeline Patterns\nFan-Out (Parallel Sub-tasks)\nimport { tasks } from \"@trigger.dev/sdk/v3\";\n\nconst handles = await tasks.batchTrigger(\"process-item\",\n    items.map(item => ({ payload: { item } }))\n);\n\n\nEach sub-task runs independently with its own retries.\n\nReview Checkpoint\n\nInsert a review stage between pipeline steps. Three modes:\n\nMode\tBehavior\n\"none\"\tAuto-approve. Trigger next stage immediately.\n\"agent\"\tCall a reviewer agent. If approved, continue. If rejected, feed feedback back to the previous stage for revision.\n\"human\"\tSet a status in the DB to pending. Return. A human reviews externally. Resume the pipeline via an API callback.\nRetry Configuration\n\nEvery task must have explicit retry config. LLM calls are flaky — the default (no retries) means one transient API error kills the pipeline.\n\nretry: {\n    maxAttempts: 3,\n    minTimeoutInMs: 1000,\n    factor: 2,\n}\n\nDatabase as the Agentic Record Layer\n\nEvery agent input, output, and intermediate result must be persisted to the database before the next stage runs. This is not optional. Agents operate on DB records — they do not pass raw data through in-memory pipelines.\n\nWhy\nDeduplication. Check if an equivalent job has already run before triggering a new one. Compare by content hash, source ID, or a natural key.\nVerification. The next stage reads from the DB, not from the previous task's return value. If a record isn't in the DB, the stage doesn't proceed.\nRecord keeping. Every generated asset, decision, and status transition is a row. You can audit, replay, and debug any run.\nResume on failure. If a task retries, it checks the DB first. If the output already exists, it skips regeneration and continues.\nPattern: Write Before Passing\n\nEvery task writes its output to the DB and returns the record ID. The next task receives the ID, reads from the DB, and operates on the record.\n\n// Stage 1: director agent writes its output\nexport const scriptTask = task({\n    id: \"script-task\",\n    retry: { maxAttempts: 3, minTimeoutInMs: 1000, factor: 2 },\n    run: async (payload: { projectId: string }) => {\n        const existing = await db.script.findFirst({ where: { projectId: payload.projectId } });\n        if (existing) return { scriptId: existing.id }; // already done, skip\n\n        const agent = mastra.getAgent(\"directorAgent\");\n        const response = await agent.generate(JSON.stringify(payload));\n        const output = ScriptOutputSchema.parse(JSON.parse(response.text));\n\n        const record = await db.script.create({\n            data: { projectId: payload.projectId, content: output.script, status: \"draft\" },\n        });\n        return { scriptId: record.id };\n    },\n});\n\n// Stage 2: next agent reads by ID\nexport const mediaTask = task({\n    id: \"media-task\",\n    retry: { maxAttempts: 3, minTimeoutInMs: 1000, factor: 2 },\n    run: async (payload: { scriptId: string }) => {\n        const script = await db.script.findUniqueOrThrow({ where: { id: payload.scriptId } });\n        const agent = mastra.getAgent(\"mediaSelectorAgent\");\n        const response = await agent.generate(JSON.stringify({ script: script.content }));\n        const output = MediaOutputSchema.parse(JSON.parse(response.text));\n\n        const record = await db.mediaSelection.create({\n            data: { scriptId: payload.scriptId, assetIds: output.assetIds, status: \"selected\" },\n        });\n        return { mediaSelectionId: record.id };\n    },\n});\n\nPattern: Status Transitions as Pipeline Control\n\nStore a status field on every record. Use it to gate pipeline stages and drive human review checkpoints.\n\nStatus\tMeaning\npending\tCreated, not yet processed\nprocessing\tTask is running\ndraft\tAgent output produced, not reviewed\napproved\tPassed review (agent or human)\nrejected\tFailed review, needs revision\npublished\tFinal action taken\nfailed\tUnrecoverable error\nawait db.script.update({\n    where: { id: scriptId },\n    data: { status: \"processing\" },\n});\n// ... agent call ...\nawait db.script.update({\n    where: { id: scriptId },\n    data: { status: \"draft\", content: output.script },\n});\n\nKeeping Agents Autonomous\n\nDefine the destination and the quality bar. Don't specify how to get there.\n\nWrong — micromanaging the agent:\n\n1. Read the input\n2. Extract the requirements\n3. For each requirement, write a task\n4. Format the tasks as a numbered list\n5. Return the list\n\n\nRight — defining the outcome:\n\n## Goal\nProduce a technical implementation plan for the given objective.\n\n## Output Contract\n{ \"tasks\": [{ \"title\": string, \"description\": string, \"dependencies\": string[] }] }\n\n## Quality Standards\n- Each task must be independently executable by a developer\n- Dependencies must reference other tasks by title\n- No task should take more than 4 hours of work\n\nType Enforcement\nTask Payloads\n\nAlways type the run function parameter:\n\nrun: async (payload: { prompt: string; maxTokens?: number }) => {\n\nStructured Output from Agents\n\nDefine the exact schema in the AGENT.md Output Contract section, then validate with Zod on receipt:\n\nconst OutputSchema = z.object({\n    tasks: z.array(z.object({\n        title: z.string(),\n        description: z.string(),\n        dependencies: z.array(z.string()),\n    })),\n});\n\nconst response = await agent.generate(JSON.stringify(payload));\nconst parsed = OutputSchema.parse(JSON.parse(response.text));\n\n\nIf parsing fails, the task throws, Trigger.dev retries with the same input, and the agent produces output again.\n\nTool Schemas\n\nAlways define both inputSchema and outputSchema on tools. The agent uses these to understand what arguments to pass and what it will receive back.\n\nKey Rules\nAll intelligence lives in AGENT.md, not in code\nAgent .ts files are boilerplate wiring only — no logic\nTools return { success, errorMessage } on failure — never throw\nTask wrappers handle durability, agents handle reasoning\nSelf-validation checklist in AGENT.md is mandatory for structured output agents\nEvery Trigger.dev task has explicit retry config\nEvery task is exported from src/trigger/index.ts\nOne model config for all agents — src/config/model.ts\nPipeline stages use triggerAndWait for sequential, batchTrigger for parallel\nCheck result.ok after every triggerAndWait — don't assume success\nEvery agent output is written to the DB before the next stage runs — never pass raw data between tasks\nTasks that would duplicate work must check the DB first and skip if already done\nEvery tool that takes a real-world action requires confirmed: true in the input and must verify it before executing\nNot every pipeline stage needs an agent — use plain tasks for deterministic operations"
  },
  "trust": {
    "sourceLabel": "tencent",
    "provenanceUrl": "https://clawhub.ai/ainakwalamonk/durable-agents",
    "publisherUrl": "https://clawhub.ai/ainakwalamonk/durable-agents",
    "owner": "ainakwalamonk",
    "version": "1.0.0",
    "license": null,
    "verificationStatus": "Indexed source record"
  },
  "links": {
    "detailUrl": "https://openagent3.xyz/skills/durable-agents",
    "downloadUrl": "https://openagent3.xyz/downloads/durable-agents",
    "agentUrl": "https://openagent3.xyz/skills/durable-agents/agent",
    "manifestUrl": "https://openagent3.xyz/skills/durable-agents/agent.json",
    "briefUrl": "https://openagent3.xyz/skills/durable-agents/agent.md"
  }
}