Services & contracts

Publish a reusable capability and call it from another Curio through a versioned contract.

Verified September 11, 2026

On this page

Define the provider#

A public service is a versioned capability implemented by a Curio Backend. This example uses provider ID dev.example.text-tools and service ID dev.example.text-tools.transformer. The names are illustrative; no such provider is automatically installed.

Add this fragment to the provider’s manifest, alongside its real Backend configuration:

json
{
  "services": {
    "provides": [{
      "id": "dev.example.text-tools.transformer",
      "version": "1.0.0",
      "contract": "contracts/transformer.json"
    }]
  }
}

The service ID must be under the provider’s Curio namespace. Include the contract and executable in package.files. See Backend integration for process and protocol setup.

Write the contract#

Create contracts/transformer.json:

json
{
  "schemaVersion": 1,
  "service": "dev.example.text-tools.transformer",
  "version": "1.0.0",
  "methods": {
    "uppercase": {
      "params": {
        "type": "object",
        "properties": { "text": { "type": "string" } },
        "required": ["text"],
        "additionalProperties": false
      },
      "result": {
        "type": "object",
        "properties": { "text": { "type": "string" } },
        "required": ["text"],
        "additionalProperties": false
      },
      "timeoutMs": 5000,
      "idempotent": true
    }
  },
  "events": {}
}

Host validates public method inputs and outputs against the contract. An interface declaration does not implement the method: your Backend must handle uppercase, return { text }, and report structured errors. idempotent: true describes the operation’s semantics; it does not automatically retry it.

The Service Contract schema is available for tooling. Service ID and version must match the manifest exactly. Evolve the service version when changing its public contract, independently of the Curio package version.

Declare the consumer dependency#

Add the following to the calling Curio’s manifest:

json
{
  "services": {
    "requires": [{
      "id": "dev.example.text-tools.transformer",
      "version": "^1.0.0",
      "optional": false
    }]
  }
}

Install or run a compatible provider, then call it from the consumer:

ts
import { trove } from "@trove/plugin-sdk";

await trove.ready();
const result = await trove.call<{ text: string }>(
  "dev.example.text-tools.transformer",
  "uppercase",
  { text: "hello" },
  { timeoutMs: 5_000 },
);
console.log(result.text); // HELLO

The dependency declaration does not download a provider. optional: true means your tool can work without that capability; handle absence in your UI and disable only the dependent feature. Use host.services.resolve with the required version range to diagnose availability.

Generate TypeScript#

sh
trove contract generate contracts/transformer.json --out src/generated/transformer.ts

Without an explicit file, the CLI selects from the current manifest’s provided contracts and generates into src/generated/. Multiple contracts require selection; existing output requires confirmation or --overwrite. Only TypeScript generation is currently supported.

The generator emits a ServiceContract with methods and events, not the function-shaped interface accepted by service<TContract>. Use the generated parameter/result types with call (this example is in src/):

ts
import { trove } from "@trove/plugin-sdk";
import type { ServiceContract } from "./generated/transformer";

type Uppercase = ServiceContract["methods"]["uppercase"];
const result = await trove.call<Uppercase["result"], Uppercase["params"]>(
  "dev.example.text-tools.transformer", "uppercase", { text: "hello" },
);

Keep the JSON contract as the shared source of truth and regenerate after changing it. Generated types do not replace Host validation or Backend tests.

Add events deliberately#

An event contract declares params for the subscription, payload for emitted data, and coalesce (none or latest). Implement subscribe, unsubscribe, event sequence numbers, and cleanup in the provider before exposing the topic. A consumer uses SDK subscriptions and closes them when the owning task ends.

Test both an available provider and a missing or incompatible provider. Also test a provider disconnect during an active call, invalid input, and a response that violates its contract. See testing.