Lelemon
  • Get started
  • Use cases
  • Guides
  • API basics
  • Webhooks
  • SDK, CLI and MCP
  • API reference
  • Changelog
  • v1 (legacy)
WhatsApp sales agentMeeting schedulingSupport with approvalsInternal copilot with a data connectorPlatform with subaccountsBackground runs
powered by Zudoku
Use cases

Give an agent a background job

You will

Run a reconciliation job on its own, follow it by webhook, answer the question the agent asks and cancel another run.

You need

  • An API key with agents:write, pipelines:write, actions:write, runs:write, runs:read and webhooks:write
  • An https accounting backend with three endpoints
  • Node.js 20 or later

Some work is longer than a conversation: matching a bank statement with invoices, filling a batch, preparing a report. A run is that job. You give the agent a goal and limits and the answer comes back at once with the run running. The agent works outside your request, for as many rounds as it needs. If it hits a case it cannot decide, it pauses and asks; you answer and it continues. When it is done it declares a summary and the things to check. You hear about every change by webhook and can cancel at any time.

Who it is for. Back offices and operations teams that delegate unattended work. Scopes. Listed above. runs:write decides which person the agent acts as, so give it only to systems you already run.

Code
Your system Agent (outside your request) Your backend | POST /v2/agents/{id}/runs -> round 1: list movements, list invoices ---> signed reads | goal, actor, limits round 2: record_match (clear matches) ---> signed write | <- 201 run "running" round 3: ambiguity -> pauses | <- webhook run.waiting_for_answer | POST /v2/runs/{id}/answer -> resumes with your answer | round 5: finish(summary, checks) | <- webhook run.completed status completed_unverified: read the summary and checks | POST /v2/runs/{id}/cancel -> a second job ends "canceled"

The script is in examples/use-cases/background-runs.

0. Set up

Code
import { Lelemon } from '@lelemon-ai/sdk'; const lelemon = new Lelemon({ apiKey: process.env.LELEMON_API_KEY });

1. Give the agent the tools of the job

An unattended agent uses any action it is granted, including the ones that write, with nobody looking. Grant only what the job needs: two reads and one write. Actions behind an approval policy do not run inside a run either: they wait for a person.

Code
actions ["list_bank_movements act_2IZsCjqianNzh86dA1W5M6","list_open_invoices act_5JKa1UZzrQaPMIuyqCML5i","record_match act_00CQSZFvd6SIgH1TfAxOll"]

2. Write the agent

The prompt of a worker is a procedure: what to read first, when two things match, what to do when they are ambiguous (ask, never guess), what never to do and how to finish. The agent must be active, created through the API and have the api channel.

Code
agent {"id":"agt_5A9zNFWs5LUPCAgj2G6Fbh","status":"active","ready":true}

3. Follow runs by webhook

Do not poll. Subscribe to the run events: run.started, run.waiting_for_answer, run.completed, run.stopped, run.failed and run.canceled. Each carries the run in data.run, exactly as GET /v2/runs/{id} would answer at that moment. They are sent at least once.

4. Give the agent a job

goal is what must be achieved, read by the agent as text from a user. actor is the person in your system the agent works for: your key vouches for them, Lelemon records that your key declared them and the run carries them to every action as the signed actor. limits can only lower the defaults (30 rounds, US$ 2, 30 minutes) up to the server maximums.

Code
run {"id":"run_1qcjwUbMC3T7JWVfYFPApU","status":"running","limits":{"maxRounds":12,"maxMinutes":10},"actor":{"id":"user_8841","role":"staff","name":"Matías Rivas","declaredBy":"key_1LCjyzH1d3yu8glA7Yy9b1"}}

The same Idempotency-Key returns the run already created, not a second one. ?dryRun=true runs the checks and queues nothing.

5. The agent asks, you answer

Two movements of the same customer with the same amount could belong to two invoices. The agent does not guess: the run pauses with status: "waiting_for_answer", question has the text and run.waiting_for_answer fires. The clear match was already recorded, signed with the actor you declared.

Code
webhook {"type":"run.waiting_for_answer"} run {"status":"waiting_for_answer","rounds":3,"question":"El movimiento mov_2 (transferencia de Café Central, 150.000 CLP, 2026-10-07) podría corresponder a la factura F-1207 o a la F-1209, ambas de Café Central por 150.000 CLP. ¿A cuál de las dos corresponde?"} matches so far [{"movementId":"mov_1","invoiceId":"F-1201","by":"user_8841"}]

A question is answered once: a run that is not paused answers 409 run_not_waiting. Someone from your team can also answer in the dashboard, under the agent, in Activity.

6. Read the result

The agent ends a run by calling its finish action with a summary and a list of things to check. The status is completed_unverified: nobody has verified it, so read result.summary and result.checks before you trust it.

Code
webhook {"type":"run.completed"} run {"status":"completed_unverified","rounds":5,"result":{"summary":"Conciliación de octubre. Matches registrados: (1) mov_1, transferencia Panadería Doña Rosa 238.000 CLP (2026-10-05) con factura F-1201 (mismo cliente, mismo monto). (2) mov_2, transferencia Café Central 150.000 CLP (2026-10-07) con factura F-1207, según confirmación de la persona que encargó la tarea. Quedó sin conciliar: factura F-1209 (Café Central, 150.000 CLP), pendiente de pago según la respuesta recibida. Excluido: mov_3, cargo bancario \"Bank fee\" por -4.500 CLP (2026-10-12), que no corresponde a ninguna factura.","checks":["mov_1 (238.000 CLP) registrado con F-1201 (Panadería Doña Rosa).","mov_2 (150.000 CLP) registrado con F-1207 (Café Central), decisión confirmada por la persona encargada.","F-1209 (Café Central, 150.000 CLP) sigue abierta, sin movimiento bancario asociado.","mov_3 (Bank fee, -4.500 CLP) no conciliado a propósito: es un cargo bancario sin factura."]}} matches [{"movementId":"mov_1","invoiceId":"F-1201","by":"user_8841"},{"movementId":"mov_2","invoiceId":"F-1207","by":"user_8841"}]

If a run ends stopped (rounds, cost, time, no_conclusion or loop) or failed, result.partial keeps the last thing the agent wrote: read it before you start the job again. See how a run ends.

7. Cancel a job

POST /v2/runs/{id}/cancel ends a run nobody is running at once. If a worker is running it, cancelRequested turns true and it ends when its next round starts. A run that already ended answers 409 run_finished.

Code
run {"id":"run_4na1UUjRyyACloSF8pdLLS","status":"running","cancelRequested":true} runs [{"id":"run_4na1UUjRyyACloSF8pdLLS","status":"canceled"},{"id":"run_1qcjwUbMC3T7JWVfYFPApU","status":"completed_unverified"}]

Notes

  • Your backend gets real signed calls.

What you see in the dashboard

  • Agent, Activity: every run with its status, rounds and actor, the question when it waits and the result when it ends. You can answer the question from there.
  • Webhooks, Deliveries: each run.* event with its delivery attempts.

What's next

  • Give an agent a job that runs on its own: limits, stop reasons and what keeps a run safe.
  • Approve agent actions for the actions that must wait for a person.
  • Track usage and cap conversations.
Platform with subaccounts
On this page
  • 0. Set up
  • 1. Give the agent the tools of the job
  • 2. Write the agent
  • 3. Follow runs by webhook
  • 4. Give the agent a job
  • 5. The agent asks, you answer
  • 6. Read the result
  • 7. Cancel a job
  • Notes
  • What you see in the dashboard
  • What's next
TypeScript
curl https://api.lelemon.cl/v2/actions \ -H "Authorization: Bearer $LELEMON_API_KEY" \ -H "Idempotency-Key: action-match-1" \ --json '{ "name": "record_match", "description": "Match one bank movement with one invoice. Only call it when you are sure; a wrong match changes the books.", "audience": "all", "request": { "method": "POST", "url": "https://books.example.com/matches" }, "input": { "type": "object", "required": ["movementId", "invoiceId"], "properties": { "movementId": { "type": "string" }, "invoiceId": { "type": "string" } } } }'
curl https://api.lelemon.cl/v2/agents \ -H "Authorization: Bearer $LELEMON_API_KEY" \ -H "Idempotency-Key: lucia-agent-1" \ --json '{ "name": "Lucía", "instructions": "$(cat instructions.md)", "llmConfig": { "model": "anthropic/claude-sonnet-5.5", "temperature": 0 }, "channels": ["api"] }' curl -X POST https://api.lelemon.cl/v2/agents/agt_.../activate \ -H "Authorization: Bearer $LELEMON_API_KEY"
curl https://api.lelemon.cl/v2/webhook-endpoints \ -H "Authorization: Bearer $LELEMON_API_KEY" \ -H "Idempotency-Key: webhook-runs-1" \ --json '{ "url": "https://example.com/lelemon", "enabledEvents": ["run.started", "run.waiting_for_answer", "run.completed", "run.stopped", "run.failed", "run.canceled"] }'
curl https://api.lelemon.cl/v2/agents/agt_.../runs \ -H "Authorization: Bearer $LELEMON_API_KEY" \ -H "Idempotency-Key: reconcile-2026-10" \ --json '{ "goal": "Reconcile the October bank statement with the open invoices. Record the matches you are sure about, ask me about the ones you are not, and finish with a summary.", "actor": { "id": "user_8841", "role": "staff", "name": "Matías Rivas" }, "limits": { "maxRounds": 12, "maxMinutes": 10 } }' curl "https://api.lelemon.cl/v2/agents/agt_.../runs?dryRun=true" \ -H "Authorization: Bearer $LELEMON_API_KEY" \ --json '{ "goal": "Reconcile the October bank statement." }'
curl https://api.lelemon.cl/v2/runs/run_... \ -H "Authorization: Bearer $LELEMON_API_KEY" curl https://api.lelemon.cl/v2/runs/run_.../answer \ -H "Authorization: Bearer $LELEMON_API_KEY" \ -H "Idempotency-Key: answer-1" \ --json '{ "answer": "The Café Central transfer pays F-1207. F-1209 is still pending.", "actor": { "id": "user_9020", "role": "staff", "name": "Camila Soto" } }'
curl https://api.lelemon.cl/v2/runs/run_... \ -H "Authorization: Bearer $LELEMON_API_KEY"
curl -X POST https://api.lelemon.cl/v2/runs/run_.../cancel \ -H "Authorization: Bearer $LELEMON_API_KEY" curl "https://api.lelemon.cl/v2/runs?agentId=agt_..." \ -H "Authorization: Bearer $LELEMON_API_KEY"