Adding a Language SDK¶
This guide defines the expected shape for adding another Heddle language SDK. Use it before starting JVM, Go, Rust, TypeScript, or any other runtime.
The goal is language-native worker authoring without a new protocol. Every SDK
must speak the same Heddle wire contract exported from getheddle/heddle.
Required package shape¶
Each language should start with a small core package:
- wire models for
TaskMessage,TaskResult,OrchestratorGoal, andCheckpointState - subject helpers for Heddle task/result/control subjects
- shallow schema validation for worker input and output payloads
- worker base or runner that decodes, validates, processes, publishes, and resets
- transport interface with publish/subscribe semantics
- in-memory transport for examples and tests
Broker-specific packages should live beside the core package, not inside it:
- NATS adapter
- future broker adapters, if Heddle supports them upstream
- platform-specific transports such as Android-specific bindings, if needed
Contract source¶
Never hand-author a different contract.
- Start from
schemas/v1/*.schema.json. - Verify the manifest:
- If upstream changed, sync first:
- Follow Contract Evolution for migration rules.
New SDKs must preserve _trace_context when present and include it on
TaskResult. See
Contract Evolution → Trace context
for the full rule (it's a documented envelope extension, not yet in the
exported schemas).
Model rules¶
- Preserve snake_case wire keys exactly.
- Use idiomatic property names in the host language when serializers can map them cleanly.
- Encode timestamps as ISO 8601 strings compatible with upstream Heddle.
- Keep payload, output, metadata, and context as JSON-object-shaped values.
- Preserve unknown fields when practical. If the ecosystem makes that awkward, document the limitation.
- Decide enum behavior before publication. Strict enums are simple, but upstream enum additions become breaking changes unless the SDK has an unknown-value strategy.
Worker lifecycle¶
A processor worker must:
- Subscribe to
heddle.tasks.{worker_type}.{tier}. - Use queue group
processors-{worker_type}for broker-backed transports. - Decode incoming bytes as
TaskMessage. - Skip malformed messages and continue the subscription loop.
- Validate input with Heddle's shallow schema behavior.
- Run native processing code.
- Validate output with the same shallow behavior.
- Publish
TaskResulttoheddle.results.{parent_task_id or "default"}. - Reset per-task state before reading the next message.
The worker base should make the safe lifecycle the default path.
Validation scope¶
Heddle's runtime intentionally uses shallow JSON Schema checks:
- required top-level fields
- top-level property type checks
- object-shaped worker payloads and outputs
Do not make full JSON Schema validation the default unless the stricter behavior is explicitly documented for that SDK.
Transport interface¶
The core package should depend on a minimal transport boundary:
- publish subject + bytes
- subscribe subject + optional queue group
- async cancellation or disposal according to language norms
The in-memory transport should exercise the same worker loop as broker-backed transports. Examples should use in-memory by default so they run from a fresh checkout without infrastructure.
NATS adapter¶
Add NATS only after the core package is usable.
The adapter must preserve:
- Heddle subject names
- queue groups for processor workers
- at-most-once Core NATS assumptions
- subscribe-before-publish expectations for callers waiting on results
- cancellation/disposal semantics that do not strand subscriptions
If the official NATS client has platform gaps, document them in the language guide and keep the core package buildable on unsupported platforms.
Required tests¶
Every SDK should include tests for:
- model encode/decode using representative JSON envelopes
- subject helper output
- shallow schema success and failure cases
- worker success path
- worker failure path
- malformed-message skip behavior
- Middleware Lane durability (verify unknown
_-prefixed keys propagate) - in-memory transport publish/subscribe behavior
As the repository matures, these should converge on shared golden JSON fixtures so every language proves the same wire behavior.
Required docs and examples¶
Each new language needs:
- language guide under
docs/ - runnable echo worker example
- NATS usage section when the adapter exists
- Workshop compatibility notes
- package publication notes
- status row in the docs home page and roadmap
Examples should compile without a live broker. Live interop examples may be documented as optional snippets.
JVM recommendation¶
For JVM, prefer a Kotlin-authored core with Java-friendly APIs:
- Kotlin data classes or equivalent model types
- Java callers should not need Kotlin-only conventions for basic worker code
- coroutines are reasonable for Kotlin, but Java needs a clear
CompletableFutureor blocking bridge - Scala should work naturally through the Java/Kotlin public API
- Android should start as compatibility testing against the same core surface; split an Android-specific transport only if the NATS/client stack requires it
Keep the first JVM milestone small: core package, in-memory transport, Kotlin echo example, Java echo example, then NATS. tack requires it
Keep the first JVM milestone small: core package, in-memory transport, Kotlin echo example, Java echo example, then NATS.