Requirements
- Target platform
- OpenClaw
- Install method
- Manual import
- Extraction
- Extract archive
- Prerequisites
- OpenClaw
- Primary doc
- SKILL.md
Design and implement Oban background job workers for Elixir. Configure queues, retry strategies, uniqueness constraints, cron scheduling, and error handling. Generate Oban workers, queue config, and test setups. Use when adding background jobs, async processing, scheduled tasks, or recurring cron jobs to an Elixir project using Oban.
Design and implement Oban background job workers for Elixir. Configure queues, retry strategies, uniqueness constraints, cron scheduling, and error handling. Generate Oban workers, queue config, and test setups. Use when adding background jobs, async processing, scheduled tasks, or recurring cron jobs to an Elixir project using Oban.
Hand the extracted package to your coding agent with a concrete install brief instead of figuring it out manually.
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.
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.
# mix.exs {:oban, "~> 2.18"} # config/config.exs config :my_app, Oban, repo: MyApp.Repo, queues: [default: 10, mailers: 20, webhooks: 50, events: 5], plugins: [ Oban.Plugins.Pruner, {Oban.Plugins.Cron, crontab: [ {"0 2 * * *", MyApp.Workers.DailyCleanup}, {"*/5 * * * *", MyApp.Workers.MetricsCollector} ]} ] # In application.ex children: {Oban, Application.fetch_env!(:my_app, Oban)} Generate the Oban migrations: mix ecto.gen.migration add_oban_jobs_table defmodule MyApp.Repo.Migrations.AddObanJobsTable do use Ecto.Migration def up, do: Oban.Migration.up(version: 12) def down, do: Oban.Migration.down(version: 1) end
defmodule MyApp.Workers.SendEmail do use Oban.Worker, queue: :mailers, max_attempts: 5, priority: 1 @impl Oban.Worker def perform(%Oban.Job{args: %{"to" => to, "template" => template} = args}) do case MyApp.Mailer.deliver(to, template, args) do {:ok, _} -> :ok {:error, :temporary} -> {:error, "temporary failure"} # Will retry {:error, :permanent} -> {:cancel, "invalid address"} # Won't retry end end end
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+)
See references/worker-patterns.md for common worker patterns.
QueueConcurrencyUse Casedefault10General-purposemailers20Email delivery (I/O bound)webhooks50Webhook delivery (I/O bound, high volume)media5Image/video processing (CPU bound)events5Analytics, audit logscritical3Billing, payments
Jobs within a queue execute by priority (0 = highest). Use sparingly: %{user_id: user.id} |> MyApp.Workers.SendEmail.new(priority: 0) # Urgent |> Oban.insert()
Oban uses exponential backoff: attempt^4 + attempt seconds.
defmodule MyApp.Workers.WebhookDelivery do use Oban.Worker, queue: :webhooks, max_attempts: 10 @impl Oban.Worker def backoff(%Oban.Job{attempt: attempt}) do # Exponential with jitter: 2^attempt + random(0..30) trunc(:math.pow(2, attempt)) + :rand.uniform(30) end @impl Oban.Worker def perform(%Oban.Job{args: args}) do # ... end end
use Oban.Worker, queue: :media @impl Oban.Worker def timeout(%Oban.Job{args: %{"size" => "large"}}), do: :timer.minutes(10) def timeout(_job), do: :timer.minutes(2)
Prevent duplicate jobs: defmodule MyApp.Workers.SyncAccount do use Oban.Worker, queue: :default, unique: [ period: 300, # 5 minutes states: [:available, :scheduled, :executing, :retryable], keys: [:account_id] # Unique by this arg key ] end
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
%{account_id: id} |> MyApp.Workers.SyncAccount.new( replace: [:scheduled_at], # Update scheduled_at if duplicate schedule_in: 60 ) |> Oban.insert()
# config.exs plugins: [ {Oban.Plugins.Cron, crontab: [ {"0 */6 * * *", MyApp.Workers.DigestEmail}, {"0 2 * * *", MyApp.Workers.DailyCleanup}, {"0 0 1 * *", MyApp.Workers.MonthlyReport}, {"*/5 * * * *", MyApp.Workers.HealthCheck, args: %{service: "api"}}, ]} ] Cron expressions: minute hour day_of_month month day_of_week.
# Immediate %{user_id: user.id, template: "welcome"} |> MyApp.Workers.SendEmail.new() |> Oban.insert() # Scheduled %{report_id: id} |> MyApp.Workers.GenerateReport.new(schedule_in: 3600) |> Oban.insert() # Scheduled at specific time %{report_id: id} |> MyApp.Workers.GenerateReport.new(scheduled_at: ~U[2024-01-01 00:00:00Z]) |> Oban.insert() # Bulk insert changesets = Enum.map(users, fn user -> MyApp.Workers.SendEmail.new(%{user_id: user.id}) end) Oban.insert_all(changesets) # Inside Ecto.Multi Ecto.Multi.new() |> Ecto.Multi.insert(:user, changeset) |> Oban.insert(:welcome_email, fn %{user: user} -> MyApp.Workers.SendEmail.new(%{user_id: user.id}) end) |> Repo.transaction()
Available with Oban Pro license:
# Process items in batch, run callback when all complete batch = MyApp.Workers.ProcessItem.new_batch( items |> Enum.map(&%{item_id: &1.id}), callback: {MyApp.Workers.BatchComplete, %{batch_name: "import"}} ) Oban.insert_all(batch)
Oban.Pro.Workflow.new() |> Oban.Pro.Workflow.add(:extract, MyApp.Workers.Extract.new(%{file: path})) |> Oban.Pro.Workflow.add(:transform, MyApp.Workers.Transform.new(%{}), deps: [:extract]) |> Oban.Pro.Workflow.add(:load, MyApp.Workers.Load.new(%{}), deps: [:transform]) |> Oban.insert_all()
defmodule MyApp.Workers.BulkIndex do use Oban.Pro.Workers.Chunk, queue: :indexing, size: 100, # Process 100 at a time timeout: 30_000 # Or after 30s @impl true def process(jobs) do items = Enum.map(jobs, & &1.args) SearchIndex.bulk_upsert(items) :ok end end
See references/testing-oban.md for detailed testing patterns.
# config/test.exs config :my_app, Oban, testing: :manual # or :inline for synchronous execution # test_helper.exs (if using :manual) Oban.Testing.start()
use Oban.Testing, repo: MyApp.Repo test "enqueues welcome email on signup" do {:ok, user} = Accounts.register(%{email: "test@example.com"}) assert_enqueued worker: MyApp.Workers.SendEmail, args: %{user_id: user.id, template: "welcome"}, queue: :mailers end
test "processes email delivery" do {:ok, _} = perform_job(MyApp.Workers.SendEmail, %{ "to" => "user@example.com", "template" => "welcome" }) end
# Attach in application.ex :telemetry.attach_many("oban-logger", [ [:oban, :job, :start], [:oban, :job, :stop], [:oban, :job, :exception] ], &MyApp.ObanTelemetry.handle_event/4, %{})
Job execution duration (p50, p95, p99) Queue depth (available jobs per queue) Error rate per worker Retry rate per worker
Agent frameworks, memory systems, reasoning layers, and model-native orchestration.
Largest current source with strong distribution and engagement signals.