Provider protocol#

Orchard and its providers communicate using a line-oriented JSON protocol over stdio. This page defines the wire format. If you’re writing a provider, these are the exact bytes you need to read and write.

Framing: JSON Lines#

Every message is a single JSON object on its own line, terminated by \n. Readers split on \n, parse the line as one JSON value, and match it to a request by id.

{"id":1,"method":"describe","params":{}}\n
{"id":1,"result":{"schema":{...}}}\n

Implementation notes:

  • Do not pretty-print. Each frame is a single line.
  • Do not emit framing bytes (length prefixes, headers). Just JSON + newline.
  • Write frames atomically — if you share stdout across goroutines, serialize writes or you’ll interleave messages.
  • Flush stdout after each frame. Reader on the other end is waiting for \n.
  • Write logs to stderr, never stdout. stdout is reserved for the protocol.

Envelope#

Orchard sends requests; the provider sends responses keyed by the same id. Responses can arrive in any order; providers that process sequentially will always respond in order.

Request#

{
  "id": 42,
  "method": "describe",
  "params": { }
}
FieldTypeDescription
idint64Unique id for this request. Provider echoes it back in the response.
methodstringOne of describe, configure, execute, shutdown.
paramsobjectMethod-specific payload. May be omitted if empty.

Response — success#

{
  "id": 42,
  "result": { "schema": { "name": "echo", ... } }
}

Response — error#

{
  "id": 42,
  "error": { "code": "unknown_action", "message": "unknown action: foo" }
}

A response must contain exactly one of result or error.

Error object#

FieldTypeDescription
codestringOne of the well-known codes below, or a provider-specific code.
messagestringShort human-readable message.
detailstringOptional longer detail string.

Well-known error codes:

CodeMeaning
invalid_requestThe request is malformed or cannot be decoded.
unknown_methodThe provider does not recognize method.
unknown_actionexecute called with an action not in the provider’s schema.
validation_failedAction attributes fail the provider’s runtime validation.
configure_failedconfigure failed (bad credentials, connection refused, etc.).
execute_failedexecute failed (a runtime error while running the action).
internal_errorProvider-side bug or unexpected condition.

Providers are free to define additional codes. Orchard reasons about the above codes directly; others are treated as generic failures.

Methods#

See execution model for the full lifecycle. In brief:

MethodParamsResultCalled
describe{}{ "schema": ProviderSchema }Once, at startup, before anything else.
configure{ "config": { ... } }{}Once, in run mode only, before any execute.
execute{ "action": "name", "attrs": { ... } }{ "outputs": { ... } }Once per action in the scenario.
shutdown{}{}Once, at the end of the scenario.

Protocol versioning#

Every provider schema must declare "protocol": "1". Orchard checks this at handshake time and refuses providers that advertise a mismatched version. There is no negotiation; the version is either right or wrong.

ProtocolVersion is defined in pkg/protocol/schema.go. When it changes, Orchard and providers must be upgraded together.

The schema subcommand#

For orchard plan, Orchard does not start the full RPC loop. Instead it runs:

<binary> schema

The binary must print the ProviderSchema as JSON to stdout and exit 0. Any stderr output is surfaced to the user on failure. Implement this as a separate code path from the main loop:

func main() {
    if len(os.Args) > 1 && os.Args[1] == "schema" {
        writeSchemaJSON(os.Stdout)
        return
    }
    runRPCLoop()
}

Without a schema subcommand, orchard plan cannot validate your provider’s scenarios.

Reference implementation#

pkg/protocol/ in the Orchard repo defines the envelope, methods, and schema types. Go providers can import it directly. Providers in other languages implement the protocol from scratch against the types documented here.