Skip to content

Golden Path: Your First Resolver

This is the fastest route to understanding Resolve end to end. By the end you’ll have a custom resolver registered with a running backend, and you’ll drive an instance through it while watching its event stream — touching the four contracts every other page assumes you know: the manifest.json schema, the event vocabulary, the registration mechanism, and the instance lifecycle.

  1. A running Resolve backend. Either a local stack or the hosted platform. You’ll need its base URL and, if auth is enabled, a Bearer token.

  2. An LLM provider credential in the environment (for example ANTHROPIC_API_KEY or OPENAI_API_KEY), and GH_TOKEN for repo access.

  3. A container runtime. Locally, Resolve uses Incus for worker containers — incusd must be running. (Docker is only used for a future remote-deploy path, not local development.)

A resolver is a separate process, not an in-process Python object. The platform launches it per the command in your manifest.json and drives it over JSON-RPC on stdio. You write your resolver against the amplifier-resolver-sdk base class; the SDK speaks the protocol for you.

That means a resolver has exactly two things you must get right:

  1. A manifest.json that tells the platform how to launch and register it.
  2. A run() implementation that does the work and emits the required events — ending with resolver:completed.

Create manifest.json. This is the registration contract — the platform validates it against a JSON Schema when you add the resolver.

{
"name": "hello-world",
"version": "0.1.0",
"description": "A minimal resolver that greets and completes.",
"command": ["python", "-m", "hello_world_resolver"]
}

Implement run(). The single most important rule in all of Resolve:

hello_world_resolver/__main__.py
from amplifier_resolver_sdk import Resolver, ResolverResult
class HelloWorldResolver(Resolver):
async def run(self, instance) -> ResolverResult:
# 1. Announce a phase of work.
await self.emit("resolver:phase", {"name": "greeting"})
# 2. Do the work and record an artifact.
message = f"Hello from {instance.spec.get('name', 'Resolve')}!"
await self.emit("resolver:artifact", {"kind": "summary", "value": message})
# 3. Complete. NEVER skip this.
return ResolverResult(status="completed", summary=message)
if __name__ == "__main__":
HelloWorldResolver().serve()

Resolvers are not discovered via Python entry points. You register them explicitly with the CLI, which clones (or reads a local path), validates the manifest, optionally fetches the viewport bundle, and writes the resolver into the backend’s resolvers.json catalog. The backend live-reloads — no restart.

Terminal window
# From a local path during development:
amplifier-resolve resolver add ./hello-world
# ...or from a git URL:
amplifier-resolve resolver add 'git+https://github.com/you/hello-world@main'
# Confirm it registered:
amplifier-resolve resolver list

You can also verify over the API:

Terminal window
curl -s "$RESOLVE_URL/api/resolvers" -H "Authorization: Bearer $TOKEN" | jq '.[].name'

Submit an instance that targets your resolver, then watch it work:

Terminal window
# Create the instance
curl -s -X POST "$RESOLVE_URL/api/instances" \
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d '{"resolver": "hello-world", "input": {"name": "world"}}'
# -> { "instance_id": "abc123...", ... }

Then stream its events (Server-Sent Events) until it completes:

Terminal window
curl -N "$RESOLVE_URL/api/instances/abc123/events" \
-H "Authorization: Bearer $TOKEN"
# resolver:created ... resolver:phase ... resolver:artifact ... resolver:completed

When you see resolver:completed, the instance transitions to the terminal completed state. You’ve just exercised the full contract.

The four contracts

  • manifest.json — how the platform launches and registers your resolver
  • Event vocabulary — the created → phase → artifact → completed chain
  • Registrationresolver add writing to resolvers.json, live-reloaded
  • Instance lifecyclecreated → running → completed