Skip to content

Machine protocol

This page is for adapter and integration authors. Regular users should invoke a generated host capability and use the command reference.

The current JSON contract version is 2:

Use the machine protocol v2 tutorial for a runnable start-to-end host exchange.

The schemas define serialization shape. Runtime validation also enforces rules that JSON Schema cannot express completely: embedded manifest syntax, ordering, hashes, cross-field identity, idempotency, workspace state, and filesystem safety. Validate against the schema and preserve CLI errors rather than reimplementing the Go validator from prose.

The host calls the model, reads the repository, runs tools, and—when requested—uses GitHub credentials. Within this protocol exchange, the Run/source path is local and deterministic: it validates messages, records the Run, observes Git, and returns the next operation without calling a model or accessing GitHub. The separate public doctor command may invoke the user’s local gh for read-only diagnostics.

A host normally uses JSON on every step:

slipway run --budget N --json --root ROOT [--no-review] --goal-file GOAL_FILE [--source-file SOURCE_FILE]
slipway protocol submit --run RUN --action ACTION --root ROOT (--outcome-file FILE | --outcome-stdin)
slipway protocol answer --run RUN --action ACTION --root ROOT --text-file TEXT_FILE
slipway protocol answer --run RUN --action ACTION --root ROOT --confirm-destructive --scope-sha256 DIGEST [--text-file TEXT_FILE]
slipway protocol skip --run RUN --action ACTION --root ROOT
slipway protocol resume RUN --root ROOT [--budget N]
slipway protocol resume RUN --root ROOT (--source-file FILE | --use-pinned-source | --source-choice pinned|adopt --candidate CANDIDATE) [--budget N]
slipway protocol material --run RUN --action ACTION --root ROOT --section KEY

The protocol operations are versioned host interfaces. They are documented and visible in help, but they are not an alternative end-user command sequence: drive them from the structured next variants the CLI returns.

Canonical host invocation keeps the exact goal out of argv. Write it to a private temporary regular non-symlink file and resolve the returned start variant’s required goal_file path:

Terminal window
slipway run --budget 8 --json --root /absolute/worktree \
--goal-file /private/temp/goal.txt

An ad-hoc Run omits source fields. An issue-backed Run supplies a separate private temporary --source-file. Remove both temporary files after the CLI consumes them; the Run does not depend on either file or GitHub afterward. Human callers may still use one positional goal or --goal-stdin, but generated adapters use goal_file to avoid process-list/command-trace disclosure and platform command-line limits.

The start response contains the Run state, the initial orient Action, and a structured next operation.

An active Run contains one non-null Action:

{
"contract_version": 2,
"run_id": "...",
"action_id": "...",
"kind": "orient",
"goal": "...",
"brief": "...",
"context": "...",
"remaining_budget": 7
}

kind is one of orient, clarify, implement, review, or summarize.

Issue-backed Actions additionally contain:

  • source, manifest, and requirements revisions;
  • an ordered, bounded section catalog;
  • a structured protocol material reader;
  • the section keys required for the current Action.

They do not copy requirements Markdown into context. The material reader is valid only for the current non-void Action and verifies digest, byte count, and section revision before returning content.

The versioned field for those keys is requirements.required_for_action. In protocol v2 it is the ordered list of every key in requirements.sections; hosts must preserve that exact equality rather than infer a smaller subset.

The reader’s base_argv is the authoritative invocation and is pinned to an exact nine-element sequence that begins slipway protocol material --root ROOT --run RUN --action ACTION. Append the section flag as separate argv elements; do not reorder it to match the synopsis above, which is written in human flag order.

An Implement Action re-issued after a granted destructive confirmation carries a third variant field, destructive_authorization. It is absent from every other Action, so treat the two shapes above as non-exhaustive rather than modelling kind alone:

Field Meaning
request_id UUID of the destructive_request this grant answers.
originating_action_id The Implement Action that raised the request.
scope_version Always 1 in contract v2.
scope_sha256 sha256:<64 hex> over the canonical scope.
targets The exact non-empty target list; each is a path, git_ref, external_resource, or data_domain.
impact The stated consequence text.
confirmed_at RFC 3339 UTC instant of confirmation.

Slipway recomputes scope_sha256 from request_id, targets, and impact on every load and rejects the Action when it does not match. The grant authorizes that scope and nothing wider.

context is a bounded projection of active answers and prior Outcome summaries. It is not the full journal, source, conversation, or hidden model reasoning.

Submit an Outcome from exactly one input:

slipway protocol submit --run RUN --action ACTION --root ROOT --outcome-file FILE
slipway protocol submit --run RUN --action ACTION --root ROOT --outcome-stdin

Every public Outcome field is present. Arrays remain arrays when empty; inapplicable object branches are JSON null:

{
"contract_version": 2,
"action_id": "...",
"action_kind": "orient",
"status": "completed",
"summary": "observed facts",
"observations": [],
"known_issues": [],
"suggested_actions": [],
"pause": null,
"implementation": null,
"review": null
}

action_kind must match the outstanding Action. Host status is completed, needs_input, partial, or error; skip is a CLI operation, not an Outcome status.

  • Orient or Clarify may suggest at most one immediate clarify, implement, or summarize Action. A suggestion is advisory, not a reservation: when review is enabled and Slipway has already observed code changes, it interposes Review before honoring the suggestion.
  • A non-paused Implement uses the implementation branch and reports actual files, attempts, uncertainty, and test/type-check/build/lint activities with exit codes.
  • A non-paused Review uses the review branch and reports findings; it does not suggest repair work.
  • Summary and every needs_input Outcome have no suggested Action.
Action Host status Required result branches Allowed pause Allowed suggestions
Orient completed / partial / error implementation=null, review=null none zero or one Clarify, Implement, or Summarize
Orient needs_input implementation=null, review=null decision or environment none
Clarify completed / error implementation=null, review=null none zero or one Clarify, Implement, or Summarize
Clarify needs_input implementation=null, review=null decision or environment none
Implement completed implementation.result=applied|not_needed, review=null none none
Implement partial implementation.result=partial, review=null none none
Implement error implementation.result=unable, review=null none none
Implement needs_input implementation=null, review=null decision, destructive, or environment none
Review completed review.result=no_findings_reported|findings_reported, implementation=null none none
Review partial review.result=inconclusive, implementation=null none none
Review error review.result=error, implementation=null none none
Summarize completed / error implementation=null, review=null none none

Clarify intentionally has no legal partial combination: it carries one decision at a time. Review cannot use needs_input and cannot suggest any Action at all, and not_run is a CLI-owned review-skip projection.

A needs_input Outcome has one pause reason: decision_required, destructive_confirmation_required, or environment_unavailable. budget_exhausted is produced only by the CLI.

A pause may also carry the optional supersedes_answer_action_id, naming the earlier Clarify Action whose answer this pause replaces. It is legal only together with reason: "decision_required".

Destructive confirmation is valid only for an exact current Implement request and scope digest. Natural-language text such as “yes” is feedback, not authorization. Any Action change, resume, scope expansion, or mismatch invalidates the grant.

Outcome input is limited to 1 MiB, valid UTF-8, with no BOM, duplicate fields, unknown fields, or trailing data.

Every success or error that can continue contains a typed next object. It has:

  • operation: action, answer, resume, start, command, or none;
  • the original workspace_identity;
  • zero or more variants with id, base_argv, and typed inputs.

Input types are string, path, enum, and digest. A consumer selects one variant and inserts supplied values as separate argv elements in schema order. It must not parse or concatenate a display command.

Start variants carry the exact goal as a required goal_file path input rather than embedding goal text in base_argv. Decision and feedback variants likewise use required text_file paths; the destructive-confirmation note uses an optional text_file. Hosts create private temporary regular files, resolve the typed variants, and remove the files after consumption. Direct --text and stdin modes remain human-facing conveniences, not the generated machine canonical path.

Only a fully resolved, inputless variant may be rendered for a human shell. POSIX, cmd.exe, and PowerShell rendering is presentation only; structured argv is the machine value.

When a Windows display command contains expansion-sensitive % or ! values that cmd.exe cannot preserve safely, the renderer uses a PowerShell UTF-16LE EncodedCommand trampoline. This changes only the copyable display form; the decoded process argv must remain byte-for-byte equivalent to the structured variant.

Ended Runs use operation: "none" with an empty variant list.

A source envelope is at most 16 MiB and identifies one github.com Issue by repository and Issue node IDs. For a valid Change:

  • the first nonempty body line is <!-- slipway-level: change/v2 -->;
  • the next nonempty block is one strict slipway-manifest JSON fence;
  • the ordered manifest has 5–64 section entries and includes outcome, requirements, acceptance examples, constraints, and non-goals roles;
  • the envelope contains exactly the referenced comments;
  • each comment starts with its exact section marker and matches its declared digest.

A normalized section is at most 256 KiB; the complete section payload is at most 4 MiB; the manifest is at most 256 KiB. Missing, extra, duplicate, minimized, edited, oversized, or hash-mismatched references are rejected.

The top-level source schema intentionally permits empty or whitespace-only Issue/comment bodies and an empty comments array for an invalid refreshed head. This lets the CLI classify missing markers, empty referenced sections, and digest mismatches without the host rejecting the envelope or collecting unrelated discussion. The embedded manifest string and all semantic digest checks are validated by the runtime, not by the top-level schema alone.

The CLI persists stable identity, provenance, byte counts, revisions, and content-addressed accepted material. It does not journal the raw envelope, title labels as requirements, source-file path, or unreferenced comments.

An issue-backed resume explicitly does one of the following:

  • imports and compares a fresh envelope;
  • continues from the pinned snapshot;
  • resolves the exact current candidate by keeping pinned content or adopting a valid candidate.

No source option means neither “unchanged” nor implicit network access. A different Issue identity and an amendment based on another parent requirements revision are rejected without changing the Run. Candidate IDs and choices are stale-safe and idempotent.

The versioned schema is the serialization authority for the closed source_candidate.classification_code and resume_operation enums and for the valid/invalid candidate field combinations. For an invalid candidate, observation_sha256 is a domain-separated digest of the exact rejected source observation used for deterministic comparison; it is provenance, not accepted Requirements or proof that the host fetched from GitHub.

A successful resume voids stale outstanding work as required, revalidates the workspace, and normally returns a fresh Orient. If --budget is omitted, a positive remaining budget is preserved and zero is replenished to max(initial_budget, 3); an explicit --budget N replaces it with N. A replacement is applied only on the mutation that actually resumes the Run.

resume is accepted only while the current typed next.operation is resume. Calling it during an active Action or an answerable decision/destructive pause is rejected with that current typed recovery, so a lost response cannot be retried by silently voiding newer work. User take-over or reordering uses public slipway stop first, followed by a separately explicit resume.

Workspace identity includes the canonical worktree root, per-worktree Git directory, and Git common directory. Every load or mutation rediscovers and compares those paths before changing a journal.

Repository-wide status is the filesystem-read-only exception: it creates no namespace or lock, changes no permissions, and repairs no journal bytes. Runs from another linked worktree appear as FirstEvent header stubs with workspace_foreign and are not fully replayed outside their owning worktree. Unreadable local Run directories remain identified as unavailable_runs in the JSON of status without a Run ID; targeted corruption and absence are distinct errors. That listing is not slipway list, which reports host adapters and carries no Run data.

Git observations record hashes and bounded metadata for the index, porcelain status, and dirty paths. They never retain file content. A difference is evidence of change since Run start, not proof that the current host caused it.

  • Outcome idempotency hashes the exact accepted input bytes; differently serialized JSON conflicts even if semantically similar.
  • Answer, skip, resume, and candidate operations bind to current IDs and reject stale or conflicting retries.
  • Each Run has one writer at a time, enforced by the platform lock implementation.
  • Journal order is the recovery record; run.json may be rebuilt.

Machine errors include contract_version, a stable code, human message, exit_code, and structured recovery where available. Preserve all fields and branch on code/version, not message text.

If a mutation commits but deriving, rendering, or writing its response then fails, mutation_committed_output_failed reports committed: true, the exact Run ID, and a targeted slipway status RUN --root ROOT recovery. Do not blindly replay the mutation.

exit_code mirrors the process exit status and is one of two values:

Value Meaning
2 Usage error: the invocation itself was wrong.
3 Runtime error: the invocation was well-formed but the operation failed.

A process that fails without producing a structured CLI error exits 1 and emits no JSON body. Treat a 1 as a defect or an environment failure, not as a protocol outcome.

journal_record_too_large carries the strict detail fields context, size, and limit, plus a read-only status recovery variant for the affected Run when its ID is known. Rejecting an oversized record does not end or invalidate the persistent Run.

Unknown contract versions and fields are rejected. Version 2 does not promise compatibility with unreleased development formats that preceded it. A future incompatible contract must use a new explicit version rather than a silent alias.