Skip to content

docs: add polling and idempotent-create patterns to extension model skill#651

Merged
stack72 merged 1 commit intomainfrom
docs/extension-model-polling-idempotency
Mar 8, 2026
Merged

docs: add polling and idempotent-create patterns to extension model skill#651
stack72 merged 1 commit intomainfrom
docs/extension-model-polling-idempotency

Conversation

@stack72
Copy link
Contributor

@stack72 stack72 commented Mar 8, 2026

Summary

Adds two opt-in patterns to the swamp-extension-model skill for extension models that manage real cloud resources:

  • Polling to completion — When a provider's API is async, poll until the resource is fully provisioned before returning from create/update/delete. Ensures writeResource() stores complete data so downstream CEL expressions resolve to real values (IPs, ARNs, endpoints), not placeholders like "pending".

  • Idempotent creates — Check context.dataRepository.getContent() for existing state before calling the provider's create API, then verify the resource still exists at the provider. Prevents duplicate resources when workflows are re-run after partial failures.

Why documentation, not framework changes

Both issues proposed framework-level solutions (waitFor config, createOrGet helpers, workflow-level wait steps). After working through the AWS CloudControl provider implementation, it's clear these are best handled at the model level:

  • Every provider has different readiness semantics — AWS uses request tokens and operation status polling, Hetzner has action IDs, DigitalOcean has action endpoints with status fields. A generic waitFor in swamp core would either be too simplistic or end up reimplementing what each model needs anyway.

  • Idempotency checks are inherently provider-specific — The model knows what field contains the resource identifier (id, VpcId, Arn, slug), how to verify existence (a provider-specific GET call), and whether the underlying API is already idempotent. A framework-level skip-if-data-exists would be fragile — stale data from a deleted resource would silently skip creation.

  • The primitives already existcontext.dataRepository.getContent() is available in every model method. No new framework code needed.

Both patterns are framed as opt-in — the skill tells Claude to ask the user whether they want polling/idempotency when creating a new extension model, rather than mandating them for every model.

What changed

File Change
.claude/skills/swamp-extension-model/SKILL.md Added "Optional Patterns for Cloud/API Models" subsection under CRUD Lifecycle Models with links to examples
.claude/skills/swamp-extension-model/references/examples.md Added "Polling to Completion" section with withRetry/pollUntilReady helpers and load balancer example; added "Idempotent Creates" section with droplet example using dataRepository.getContent()

SKILL.md stays at 470 lines (under the 500-line skill-creator guideline). No content duplication — SKILL.md has decision guidance, examples.md has the full patterns and code.

Both files are already in BUNDLED_SKILLS in skill_assets.ts, so updates ship automatically via swamp repo init and swamp repo upgrade.

Fixes #618
Fixes #625

🤖 Generated with Claude Code

…ension model skill

Add opt-in patterns for cloud/API extension models that manage real
resources with async provisioning or non-idempotent create APIs.

Fixes #618
Fixes #625

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@stack72 stack72 force-pushed the docs/extension-model-polling-idempotency branch from d166694 to 53bc28d Compare March 8, 2026 02:11
Copy link

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review: Approved

This documentation PR adds two well-designed optional patterns to the swamp-extension-model skill for handling cloud resource management.

What was reviewed

Files changed:

  • .claude/skills/swamp-extension-model/SKILL.md (+18 lines)
  • .claude/skills/swamp-extension-model/references/examples.md (+388 lines)

Findings

No blocking issues found.

Code Quality:

  • TypeScript examples use proper types with no any - uses Record<string, unknown> appropriately
  • Named exports used correctly (export const model, export async function)
  • Error handling is thorough (NOT_FOUND for stale data, retryable errors with exponential backoff)

DDD Alignment:

  • Patterns properly treat cloud resources as external integrations handled through domain services
  • Good use of dataRepository as a Repository abstraction
  • Infrastructure concerns (retry, polling) are cleanly separated from business logic
  • The decision to document model-level patterns rather than add framework abstractions is sound - each provider has different readiness semantics and idempotency characteristics

Documentation Quality:

  • Clear explanation of when to use vs skip each pattern
  • Good cross-linking between SKILL.md (decision guidance) and examples.md (implementation details)
  • Consistent style with existing examples
  • Properly explains the rationale for model-level patterns vs framework changes

The PR correctly fixes #618 and #625 by providing clear, opt-in patterns that extension model authors can adopt when appropriate.

Copy link

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adversarial Review

I examined the documentation changes and code examples in this PR with a focus on finding logic errors, edge cases, and patterns that could mislead developers.

Critical / High

None found.

Medium

None found.

Low

  1. Unreachable code in withRetry (examples.md:359)

    The final throw statement after the for loop is unreachable. The loop either returns on success or throws on non-retryable/final-attempt errors. The continue statement ensures we never fall through the loop.

    Impact: None - harmless dead code that serves as a safety net.

    Example: With any input, the function either succeeds (returns) or fails (throws in catch block). Line 359 never executes.

  2. Empty buffer edge case in idempotent creates (examples.md:611)

    If getContent() returns an empty Uint8Array (data corruption), then new TextDecoder().decode(existing) returns "", and JSON.parse("") throws SyntaxError: Unexpected end of JSON input.

    Impact: Low - this would surface as an exception rather than silent corruption. Throwing on corrupted data is reasonable behavior for example code.

Analysis Notes

I verified the following are not bugs:

  • getContent parameter consistency: The VPC example uses "vpc" as the third parameter (which happens to be both spec name and instance name), while the droplet example uses instanceName ("main"). Both are correct - they match the instance name passed to writeResource. The VPC example just coincidentally uses the same string for both.

  • state.id being undefined: If stored data lacks an id field, the code calls GET /droplets/undefined, gets a 404, catches it as NOT_FOUND, and falls through to create. This is graceful degradation, not a bug.

  • Missing 404 handling in load_balancer apiCall: This is intentional - the polling example doesn't check for existing resources, so it doesn't need 404 handling. The droplet example adds it specifically for the idempotency check.

  • Exponential backoff math: With baseDelay=1000 and maxDelay=90000, delays reach the cap after ~7 attempts. With 60 polls, total timeout is ~90 minutes. This is appropriate for cloud resources.

Verdict

PASS - This is a solid documentation PR. The patterns are correct, the code examples work as intended, and the guidance appropriately frames both patterns as opt-in. No issues that would mislead developers or cause bugs if the patterns are followed.

@stack72 stack72 merged commit ac37883 into main Mar 8, 2026
5 checks passed
@stack72 stack72 deleted the docs/extension-model-polling-idempotency branch March 8, 2026 02:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Support wait-until-ready semantics for async resource provisioning Extension models need built-in support for idempotent create operations

1 participant