{
  "schemaVersion": "1.0",
  "item": {
    "slug": "oban-designer",
    "name": "Oban Designer",
    "source": "tencent",
    "type": "skill",
    "category": "AI 智能",
    "sourceUrl": "https://clawhub.ai/gchapim/oban-designer",
    "canonicalUrl": "https://clawhub.ai/gchapim/oban-designer",
    "targetPlatform": "OpenClaw"
  },
  "install": {
    "downloadMode": "redirect",
    "downloadUrl": "/downloads/oban-designer",
    "sourceDownloadUrl": "https://wry-manatee-359.convex.site/api/v1/download?slug=oban-designer",
    "sourcePlatform": "tencent",
    "targetPlatform": "OpenClaw",
    "installMethod": "Manual import",
    "extraction": "Extract archive",
    "prerequisites": [
      "OpenClaw"
    ],
    "packageFormat": "ZIP package",
    "includedAssets": [
      "references/worker-patterns.md",
      "references/testing-oban.md",
      "SKILL.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",
      "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/oban-designer"
    },
    "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/oban-designer",
    "agentPageUrl": "https://openagent3.xyz/skills/oban-designer/agent",
    "manifestUrl": "https://openagent3.xyz/skills/oban-designer/agent.json",
    "briefUrl": "https://openagent3.xyz/skills/oban-designer/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": "Installation",
        "body": "# mix.exs\n{:oban, \"~> 2.18\"}\n\n# config/config.exs\nconfig :my_app, Oban,\n  repo: MyApp.Repo,\n  queues: [default: 10, mailers: 20, webhooks: 50, events: 5],\n  plugins: [\n    Oban.Plugins.Pruner,\n    {Oban.Plugins.Cron, crontab: [\n      {\"0 2 * * *\", MyApp.Workers.DailyCleanup},\n      {\"*/5 * * * *\", MyApp.Workers.MetricsCollector}\n    ]}\n  ]\n\n# In application.ex children:\n{Oban, Application.fetch_env!(:my_app, Oban)}\n\nGenerate the Oban migrations:\n\nmix ecto.gen.migration add_oban_jobs_table\n\ndefmodule MyApp.Repo.Migrations.AddObanJobsTable do\n  use Ecto.Migration\n  def up, do: Oban.Migration.up(version: 12)\n  def down, do: Oban.Migration.down(version: 1)\nend"
      },
      {
        "title": "Basic Worker",
        "body": "defmodule MyApp.Workers.SendEmail do\n  use Oban.Worker,\n    queue: :mailers,\n    max_attempts: 5,\n    priority: 1\n\n  @impl Oban.Worker\n  def perform(%Oban.Job{args: %{\"to\" => to, \"template\" => template} = args}) do\n    case MyApp.Mailer.deliver(to, template, args) do\n      {:ok, _} -> :ok\n      {:error, :temporary} -> {:error, \"temporary failure\"}  # Will retry\n      {:error, :permanent} -> {:cancel, \"invalid address\"}   # Won't retry\n    end\n  end\nend"
      },
      {
        "title": "Return Values",
        "body": "ReturnEffect:okJob marked complete{:ok, result}Job marked complete{:error, reason}Job retried (counts as attempt){:cancel, reason}Job cancelled, no more retries{:snooze, seconds}Re-scheduled, doesn't count as attempt{:discard, reason}Job discarded (Oban 2.17+)"
      },
      {
        "title": "Queue Configuration",
        "body": "See references/worker-patterns.md for common worker patterns."
      },
      {
        "title": "Sizing Guidelines",
        "body": "QueueConcurrencyUse Casedefault10General-purposemailers20Email delivery (I/O bound)webhooks50Webhook delivery (I/O bound, high volume)media5Image/video processing (CPU bound)events5Analytics, audit logscritical3Billing, payments"
      },
      {
        "title": "Queue Priority",
        "body": "Jobs within a queue execute by priority (0 = highest). Use sparingly:\n\n%{user_id: user.id}\n|> MyApp.Workers.SendEmail.new(priority: 0)  # Urgent\n|> Oban.insert()"
      },
      {
        "title": "Default Backoff",
        "body": "Oban uses exponential backoff: attempt^4 + attempt seconds."
      },
      {
        "title": "Custom Backoff",
        "body": "defmodule MyApp.Workers.WebhookDelivery do\n  use Oban.Worker,\n    queue: :webhooks,\n    max_attempts: 10\n\n  @impl Oban.Worker\n  def backoff(%Oban.Job{attempt: attempt}) do\n    # Exponential with jitter: 2^attempt + random(0..30)\n    trunc(:math.pow(2, attempt)) + :rand.uniform(30)\n  end\n\n  @impl Oban.Worker\n  def perform(%Oban.Job{args: args}) do\n    # ...\n  end\nend"
      },
      {
        "title": "Timeout",
        "body": "use Oban.Worker, queue: :media\n\n@impl Oban.Worker\ndef timeout(%Oban.Job{args: %{\"size\" => \"large\"}}), do: :timer.minutes(10)\ndef timeout(_job), do: :timer.minutes(2)"
      },
      {
        "title": "Uniqueness",
        "body": "Prevent duplicate jobs:\n\ndefmodule MyApp.Workers.SyncAccount do\n  use Oban.Worker,\n    queue: :default,\n    unique: [\n      period: 300,               # 5 minutes\n      states: [:available, :scheduled, :executing, :retryable],\n      keys: [:account_id]        # Unique by this arg key\n    ]\nend"
      },
      {
        "title": "Unique Options",
        "body": "OptionDefaultDescriptionperiod60Seconds to enforce uniqueness (:infinity for forever)statesall activeWhich job states to checkkeysall argsSpecific arg keys to comparetimestamp:inserted_atUse :scheduled_at for scheduled uniqueness"
      },
      {
        "title": "Replace Existing",
        "body": "%{account_id: id}\n|> MyApp.Workers.SyncAccount.new(\n  replace: [:scheduled_at],    # Update scheduled_at if duplicate\n  schedule_in: 60\n)\n|> Oban.insert()"
      },
      {
        "title": "Cron Scheduling",
        "body": "# config.exs\nplugins: [\n  {Oban.Plugins.Cron, crontab: [\n    {\"0 */6 * * *\", MyApp.Workers.DigestEmail},\n    {\"0 2 * * *\", MyApp.Workers.DailyCleanup},\n    {\"0 0 1 * *\", MyApp.Workers.MonthlyReport},\n    {\"*/5 * * * *\", MyApp.Workers.HealthCheck, args: %{service: \"api\"}},\n  ]}\n]\n\nCron expressions: minute hour day_of_month month day_of_week."
      },
      {
        "title": "Inserting Jobs",
        "body": "# Immediate\n%{user_id: user.id, template: \"welcome\"}\n|> MyApp.Workers.SendEmail.new()\n|> Oban.insert()\n\n# Scheduled\n%{report_id: id}\n|> MyApp.Workers.GenerateReport.new(schedule_in: 3600)\n|> Oban.insert()\n\n# Scheduled at specific time\n%{report_id: id}\n|> MyApp.Workers.GenerateReport.new(scheduled_at: ~U[2024-01-01 00:00:00Z])\n|> Oban.insert()\n\n# Bulk insert\nchangesets = Enum.map(users, fn user ->\n  MyApp.Workers.SendEmail.new(%{user_id: user.id})\nend)\nOban.insert_all(changesets)\n\n# Inside Ecto.Multi\nEcto.Multi.new()\n|> Ecto.Multi.insert(:user, changeset)\n|> Oban.insert(:welcome_email, fn %{user: user} ->\n  MyApp.Workers.SendEmail.new(%{user_id: user.id})\nend)\n|> Repo.transaction()"
      },
      {
        "title": "Oban Pro Features",
        "body": "Available with Oban Pro license:"
      },
      {
        "title": "Batch (group of jobs)",
        "body": "# Process items in batch, run callback when all complete\nbatch = MyApp.Workers.ProcessItem.new_batch(\n  items |> Enum.map(&%{item_id: &1.id}),\n  callback: {MyApp.Workers.BatchComplete, %{batch_name: \"import\"}}\n)\nOban.insert_all(batch)"
      },
      {
        "title": "Workflow (job dependencies)",
        "body": "Oban.Pro.Workflow.new()\n|> Oban.Pro.Workflow.add(:extract, MyApp.Workers.Extract.new(%{file: path}))\n|> Oban.Pro.Workflow.add(:transform, MyApp.Workers.Transform.new(%{}), deps: [:extract])\n|> Oban.Pro.Workflow.add(:load, MyApp.Workers.Load.new(%{}), deps: [:transform])\n|> Oban.insert_all()"
      },
      {
        "title": "Chunk (aggregate multiple jobs)",
        "body": "defmodule MyApp.Workers.BulkIndex do\n  use Oban.Pro.Workers.Chunk,\n    queue: :indexing,\n    size: 100,            # Process 100 at a time\n    timeout: 30_000       # Or after 30s\n\n  @impl true\n  def process(jobs) do\n    items = Enum.map(jobs, & &1.args)\n    SearchIndex.bulk_upsert(items)\n    :ok\n  end\nend"
      },
      {
        "title": "Testing",
        "body": "See references/testing-oban.md for detailed testing patterns."
      },
      {
        "title": "Setup",
        "body": "# config/test.exs\nconfig :my_app, Oban,\n  testing: :manual  # or :inline for synchronous execution\n\n# test_helper.exs (if using :manual)\nOban.Testing.start()"
      },
      {
        "title": "Asserting Job Enqueued",
        "body": "use Oban.Testing, repo: MyApp.Repo\n\ntest \"enqueues welcome email on signup\" do\n  {:ok, user} = Accounts.register(%{email: \"test@example.com\"})\n\n  assert_enqueued worker: MyApp.Workers.SendEmail,\n    args: %{user_id: user.id, template: \"welcome\"},\n    queue: :mailers\nend"
      },
      {
        "title": "Executing Jobs in Tests",
        "body": "test \"processes email delivery\" do\n  {:ok, _} =\n    perform_job(MyApp.Workers.SendEmail, %{\n      \"to\" => \"user@example.com\",\n      \"template\" => \"welcome\"\n    })\nend"
      },
      {
        "title": "Telemetry Events",
        "body": "# Attach in application.ex\n:telemetry.attach_many(\"oban-logger\", [\n  [:oban, :job, :start],\n  [:oban, :job, :stop],\n  [:oban, :job, :exception]\n], &MyApp.ObanTelemetry.handle_event/4, %{})"
      },
      {
        "title": "Key Metrics to Track",
        "body": "Job execution duration (p50, p95, p99)\nQueue depth (available jobs per queue)\nError rate per worker\nRetry rate per worker"
      }
    ],
    "body": "Oban Designer\nInstallation\n# mix.exs\n{:oban, \"~> 2.18\"}\n\n# config/config.exs\nconfig :my_app, Oban,\n  repo: MyApp.Repo,\n  queues: [default: 10, mailers: 20, webhooks: 50, events: 5],\n  plugins: [\n    Oban.Plugins.Pruner,\n    {Oban.Plugins.Cron, crontab: [\n      {\"0 2 * * *\", MyApp.Workers.DailyCleanup},\n      {\"*/5 * * * *\", MyApp.Workers.MetricsCollector}\n    ]}\n  ]\n\n# In application.ex children:\n{Oban, Application.fetch_env!(:my_app, Oban)}\n\n\nGenerate the Oban migrations:\n\nmix ecto.gen.migration add_oban_jobs_table\n\ndefmodule MyApp.Repo.Migrations.AddObanJobsTable do\n  use Ecto.Migration\n  def up, do: Oban.Migration.up(version: 12)\n  def down, do: Oban.Migration.down(version: 1)\nend\n\nWorker Implementation\nBasic Worker\ndefmodule MyApp.Workers.SendEmail do\n  use Oban.Worker,\n    queue: :mailers,\n    max_attempts: 5,\n    priority: 1\n\n  @impl Oban.Worker\n  def perform(%Oban.Job{args: %{\"to\" => to, \"template\" => template} = args}) do\n    case MyApp.Mailer.deliver(to, template, args) do\n      {:ok, _} -> :ok\n      {:error, :temporary} -> {:error, \"temporary failure\"}  # Will retry\n      {:error, :permanent} -> {:cancel, \"invalid address\"}   # Won't retry\n    end\n  end\nend\n\nReturn Values\nReturn\tEffect\n:ok\tJob marked complete\n{:ok, result}\tJob marked complete\n{:error, reason}\tJob retried (counts as attempt)\n{:cancel, reason}\tJob cancelled, no more retries\n{:snooze, seconds}\tRe-scheduled, doesn't count as attempt\n{:discard, reason}\tJob discarded (Oban 2.17+)\nQueue Configuration\n\nSee references/worker-patterns.md for common worker patterns.\n\nSizing Guidelines\nQueue\tConcurrency\tUse Case\ndefault\t10\tGeneral-purpose\nmailers\t20\tEmail delivery (I/O bound)\nwebhooks\t50\tWebhook delivery (I/O bound, high volume)\nmedia\t5\tImage/video processing (CPU bound)\nevents\t5\tAnalytics, audit logs\ncritical\t3\tBilling, payments\nQueue Priority\n\nJobs within a queue execute by priority (0 = highest). Use sparingly:\n\n%{user_id: user.id}\n|> MyApp.Workers.SendEmail.new(priority: 0)  # Urgent\n|> Oban.insert()\n\nRetry Strategies\nDefault Backoff\n\nOban uses exponential backoff: attempt^4 + attempt seconds.\n\nCustom Backoff\ndefmodule MyApp.Workers.WebhookDelivery do\n  use Oban.Worker,\n    queue: :webhooks,\n    max_attempts: 10\n\n  @impl Oban.Worker\n  def backoff(%Oban.Job{attempt: attempt}) do\n    # Exponential with jitter: 2^attempt + random(0..30)\n    trunc(:math.pow(2, attempt)) + :rand.uniform(30)\n  end\n\n  @impl Oban.Worker\n  def perform(%Oban.Job{args: args}) do\n    # ...\n  end\nend\n\nTimeout\nuse Oban.Worker, queue: :media\n\n@impl Oban.Worker\ndef timeout(%Oban.Job{args: %{\"size\" => \"large\"}}), do: :timer.minutes(10)\ndef timeout(_job), do: :timer.minutes(2)\n\nUniqueness\n\nPrevent duplicate jobs:\n\ndefmodule MyApp.Workers.SyncAccount do\n  use Oban.Worker,\n    queue: :default,\n    unique: [\n      period: 300,               # 5 minutes\n      states: [:available, :scheduled, :executing, :retryable],\n      keys: [:account_id]        # Unique by this arg key\n    ]\nend\n\nUnique Options\nOption\tDefault\tDescription\nperiod\t60\tSeconds to enforce uniqueness (:infinity for forever)\nstates\tall active\tWhich job states to check\nkeys\tall args\tSpecific arg keys to compare\ntimestamp\t:inserted_at\tUse :scheduled_at for scheduled uniqueness\nReplace Existing\n%{account_id: id}\n|> MyApp.Workers.SyncAccount.new(\n  replace: [:scheduled_at],    # Update scheduled_at if duplicate\n  schedule_in: 60\n)\n|> Oban.insert()\n\nCron Scheduling\n# config.exs\nplugins: [\n  {Oban.Plugins.Cron, crontab: [\n    {\"0 */6 * * *\", MyApp.Workers.DigestEmail},\n    {\"0 2 * * *\", MyApp.Workers.DailyCleanup},\n    {\"0 0 1 * *\", MyApp.Workers.MonthlyReport},\n    {\"*/5 * * * *\", MyApp.Workers.HealthCheck, args: %{service: \"api\"}},\n  ]}\n]\n\n\nCron expressions: minute hour day_of_month month day_of_week.\n\nInserting Jobs\n# Immediate\n%{user_id: user.id, template: \"welcome\"}\n|> MyApp.Workers.SendEmail.new()\n|> Oban.insert()\n\n# Scheduled\n%{report_id: id}\n|> MyApp.Workers.GenerateReport.new(schedule_in: 3600)\n|> Oban.insert()\n\n# Scheduled at specific time\n%{report_id: id}\n|> MyApp.Workers.GenerateReport.new(scheduled_at: ~U[2024-01-01 00:00:00Z])\n|> Oban.insert()\n\n# Bulk insert\nchangesets = Enum.map(users, fn user ->\n  MyApp.Workers.SendEmail.new(%{user_id: user.id})\nend)\nOban.insert_all(changesets)\n\n# Inside Ecto.Multi\nEcto.Multi.new()\n|> Ecto.Multi.insert(:user, changeset)\n|> Oban.insert(:welcome_email, fn %{user: user} ->\n  MyApp.Workers.SendEmail.new(%{user_id: user.id})\nend)\n|> Repo.transaction()\n\nOban Pro Features\n\nAvailable with Oban Pro license:\n\nBatch (group of jobs)\n# Process items in batch, run callback when all complete\nbatch = MyApp.Workers.ProcessItem.new_batch(\n  items |> Enum.map(&%{item_id: &1.id}),\n  callback: {MyApp.Workers.BatchComplete, %{batch_name: \"import\"}}\n)\nOban.insert_all(batch)\n\nWorkflow (job dependencies)\nOban.Pro.Workflow.new()\n|> Oban.Pro.Workflow.add(:extract, MyApp.Workers.Extract.new(%{file: path}))\n|> Oban.Pro.Workflow.add(:transform, MyApp.Workers.Transform.new(%{}), deps: [:extract])\n|> Oban.Pro.Workflow.add(:load, MyApp.Workers.Load.new(%{}), deps: [:transform])\n|> Oban.insert_all()\n\nChunk (aggregate multiple jobs)\ndefmodule MyApp.Workers.BulkIndex do\n  use Oban.Pro.Workers.Chunk,\n    queue: :indexing,\n    size: 100,            # Process 100 at a time\n    timeout: 30_000       # Or after 30s\n\n  @impl true\n  def process(jobs) do\n    items = Enum.map(jobs, & &1.args)\n    SearchIndex.bulk_upsert(items)\n    :ok\n  end\nend\n\nTesting\n\nSee references/testing-oban.md for detailed testing patterns.\n\nSetup\n# config/test.exs\nconfig :my_app, Oban,\n  testing: :manual  # or :inline for synchronous execution\n\n# test_helper.exs (if using :manual)\nOban.Testing.start()\n\nAsserting Job Enqueued\nuse Oban.Testing, repo: MyApp.Repo\n\ntest \"enqueues welcome email on signup\" do\n  {:ok, user} = Accounts.register(%{email: \"test@example.com\"})\n\n  assert_enqueued worker: MyApp.Workers.SendEmail,\n    args: %{user_id: user.id, template: \"welcome\"},\n    queue: :mailers\nend\n\nExecuting Jobs in Tests\ntest \"processes email delivery\" do\n  {:ok, _} =\n    perform_job(MyApp.Workers.SendEmail, %{\n      \"to\" => \"user@example.com\",\n      \"template\" => \"welcome\"\n    })\nend\n\nMonitoring\nTelemetry Events\n# Attach in application.ex\n:telemetry.attach_many(\"oban-logger\", [\n  [:oban, :job, :start],\n  [:oban, :job, :stop],\n  [:oban, :job, :exception]\n], &MyApp.ObanTelemetry.handle_event/4, %{})\n\nKey Metrics to Track\nJob execution duration (p50, p95, p99)\nQueue depth (available jobs per queue)\nError rate per worker\nRetry rate per worker"
  },
  "trust": {
    "sourceLabel": "tencent",
    "provenanceUrl": "https://clawhub.ai/gchapim/oban-designer",
    "publisherUrl": "https://clawhub.ai/gchapim/oban-designer",
    "owner": "gchapim",
    "version": "1.0.1",
    "license": null,
    "verificationStatus": "Indexed source record"
  },
  "links": {
    "detailUrl": "https://openagent3.xyz/skills/oban-designer",
    "downloadUrl": "https://openagent3.xyz/downloads/oban-designer",
    "agentUrl": "https://openagent3.xyz/skills/oban-designer/agent",
    "manifestUrl": "https://openagent3.xyz/skills/oban-designer/agent.json",
    "briefUrl": "https://openagent3.xyz/skills/oban-designer/agent.md"
  }
}