resonatehq/resonate/integration
Claude Code and agent skill by resonatehq/resonate
- community
Checking that the SKILL.md file is still in the repository.
Build a Resonate integration — a custom ResonateWorker that makes one durable promise stand for one run in an external system (Apache Airflow, dbt Cloud, Databricks, SageMaker, a batch queue, a partner API). Use when asked to integrate Resonate with a downstream system, add a new address scheme, write a worker/transport that is not application code, or design the create/monitor/settle lifecycle and the promise param/value schemas for such a bridge. Scoped to idempotent downstream systems monitored by polling.
Install
npx vskill@latest install resonatehq/resonate/integrationSKILL.md
Building a Resonate Integration
What an integration is
An integration is an implementation of the ResonateWorker port that does not run the work itself. It makes one durable promise stand for one run in an external system:
It lives in the server process, registered under its own URL scheme:
To the rest of the application it is indistinguishable from any other remote function: someone creates a promise tagged resonate:target: airflow://prod, blocks on it, and gets back a value.
An integration is not a Resonate SDK application, and not a place for business logic. It maps a request to a run, and a run's state to a promise's state.
Why integrations look alike
There will be a lot of these, and they all work the same way: claim a task, start one run, watch two clocks, settle one promise. Because the mechanism is the same, the code should be recognisable — someone who has read one integration should open the next and find where it starts the run, where it decides, where it settles in the same place, under the same name.
They are not the same integration with the names changed. Addresses, config, params and what "done" means all differ, in whatever way makes sense for the system being wrapped — forcing those to match would make the integration lie about the system.
references/structure.md splits the two: the skeleton that is fixed everywhere, and the parts you decide for your system.
The two in-tree references
| File | What it shows | |
|---|---|---|
src/transport/transport_exec_bash.rs | A worker that runs the work itself, in process | |
src/transport/transport_airflow.rs | An integration: create / monitor / settle against a foreign API |
Read the Airflow worker before writing a new one. It is the complete worked example — address schema, idempotency key, error classification, poll loop, settlement — compiled and unit-tested in this repo.
One difference to expect: this skill writes protocol calls in their typed form, server.process(Request::…). Follow the skill for those; the surrounding lifecycle, classification and monitoring are what the in-tree worker is there to show.
The ports
Three traits in src/core/, which depends on nothing else in the crate. Verbatim:
Request and Response are the protocol's own unions — one variant per operation, the same ones the canonical types-raw.ts defines. A worker names the operation it wants and matches the answer: no envelope, no kind string, no JSON on this path.
Message is what a server emits toward a worker, and Unavailable is the only error any port returns:
The shape an integration implements
One trait, one method. Everything else is a free choice.
The work lives in run, and it starts by claiming the task through the server handle — server.process(Request::TaskAcquire(..)), the same operation a remote worker would put on the wire. That claim is the gate: before it the only way to report a failure is Err(Unavailable), which the dispatch loop logs and drops; after it, every failure can settle the promise. So validation, config lookup and the downstream call all belong on the far side of it. Full stub in references/registration.md.
Registering it is one line, and nothing in core changes:
Four consequences that shape every integration:
- `send` means accepted for delivery, not executed. A DAG run takes hours; the
dispatch loop must not block on it. Spawn and return Ok(()).
- The address arrives unparsed, and the worker owns everything past the scheme.
core::address::is_valid_address only checks that the string is a URI — deliberately, so admission is a pure function of the string and identical on every deployment. Syntax errors past the scheme surface at delivery, not at promise.create.
- A new scheme is a registration, never a change to `core`. That is the whole cost of
adding an integration.
- `Unavailable` is the only error. Everything the peer can say — not found, conflict,
forbidden — comes back as an Ok response. Unavailable means the exchange did not complete, and the request may already have been applied. Retries must be idempotent.
An in-process worker holds Arc<dyn ResonateServer> and calls process directly: no HTTP hop, no auth, but the same validation and settlement path a remote worker's calls take.
The fact that determines the design
`execute` is at-least-once and is re-sent until the promise settles. The server re-enqueues it every tasks.retry_timeout (default 30 s) while a task is pending, and again whenever a lease expires. The message carries {task: {id, version}} and nothing else — no delivery counter, no redelivery flag. A worker cannot tell a first delivery from a redelivery, and task.version counts acquisitions, not downstream side effects.
So the integration must not ask "have I already done this?". Build it so asking is unnecessary:
**Fire the request again, every time, under a key derived from the promise id, and letthe downstream system reject the duplicate.**
Airflow's 409 on a duplicate dag_run_id is then the recovery path: an earlier attempt already triggered the run, so re-attach and monitor it. If the downstream system offers no such key, stop and discuss with the user — see references/idempotency.md. There is no safe default.
Scope of this skill
| Dimension | In scope | Out of scope | |
|---|---|---|---|
| Downstream create | Idempotent (client-supplied run id or dedupe key) | Non-idempotent create | |
| Completion detection | Polling the downstream for status | Push / callback from downstream |
For push, see references/push-vs-poll.md — the questions to settle before building it.
The five things every integration defines
- Address schema — the scheme it owns and the syntax past it.
references/schemas.md
- Param schema — the request, in
promise.param.data. The protocol carries opaque
bytes, so the encoding is yours to pick and yours to document; validate against it before the first side effect and reject on violation.
- Value schema — the outcome, in
promise.value.data, for both branches. - Create — idempotently start or re-attach to the downstream run.
- Check — map the downstream state to pending / succeeded / failed.
All five go in the integration's README, which is the contract callers write against — references/readme-template.md.
The three phases
Phase 0 is task.acquire; a 409 means another attempt owns the task, so drop the message and never touch the downstream system.
- Create — runs on every delivery. Issue the create with a deterministic key derived
from promise.id. Treat "already exists" as success and fall through to monitoring. Never a UUID, a clock read, or task.version.
- Monitor — two independent clocks. The lease clock heartbeats
task.heartbeat
at a third of the lease TTL. The downstream clock asks the external system for the run's state on a backing-off interval sized for its cost and latency. They answer to different authorities and run at different frequencies; keep the heartbeat in its own task so the poll interval can back off past the lease TTL without the lease lapsing. Both stop at promise.timeoutAt — the server settles rejected_timedout there regardless.
- Settle.
task.fulfill, which completes task and promise in one transaction and is
what finally stops redelivery.
Every downstream error is permanent (reject the promise), transient (do not settle; let redelivery re-run the idempotent create), or ambiguous — and ambiguous is transient. references/lifecycle.md has the full table.
Workflow for building one
This is the start of the file. Read the full SKILL.md on GitHub.
Security
- Security scanPassed, 95 of 100Pattern checks on the skill's files.
- Model reviewPassed
Quality evaluation
- Task evaluationNot runNo evaluation yet. Run one in Skill Studio.
Source
- SKILL.md in the repositoryCheckingimpl/server/core/.claude/skills/integration/SKILL.md
Popularity
- Repository stars
- 674
- Repository forks
- 56
- 7-day trend score
- 33
Stars and forks are for the whole GitHub repository, not this skill alone. Not refreshed yet.
Works with
Any agent vskill installs to: 56 targets in all. These 39 have a page on this site.
- Claude Code
- Cursor
- GitHub Copilot
- Windsurf
- Codex
- Gemini CLI
- Cline
- Amp
- Roo Code
- Goose
- Continue
- Kilo Code
- Junie
- Trae
- OpenHands
- Qwen Code
- AI Agent
- Tabnine Chat
- Sourcegraph Cody
- Amazon Q
- Firebase Genkit
- Sweep
- Devin
- Phind
- Mentat
- Claude Desktop
- Aider
- GPT Pilot
- Plandex
- bolt.new
- v0
- Replit Agent
- Anse
- TypingMind
- BetterChatGPT
- LibreChat
- Warp AI
- Pieces
- Fig AI
Embed badge
[](https://verified-skill.com/skills/resonatehq/resonate/integration)