Skip to content

Agent API v1

This is the wire reference for trust-dev agent serve.

Use Agent API overview first when you are choosing capabilities rather than implementing the protocol.

Protocol

trust-dev agent serve is the first stable automation surface outside VS Code.

It exists to let agents, shell automation, and future hosted workers drive truST without depending on editor internals.

Transport and Security

  • Transport: JSON-RPC 2.0 over stdio
  • Framing: one JSON request per input line, one JSON response per output line
  • Network listeners: not supported in v1
  • Security rule for any future non-stdio mode:
  • default to loopback only
  • refuse non-loopback unless explicit auth is configured
  • unauthenticated remote execution is unsupported

Request Envelope

{"jsonrpc":"2.0","id":1,"method":"agent.describe","params":{}}

Each non-empty input line is one complete request. The top-level value MUST be an object with jsonrpc exactly equal to "2.0", a non-empty string method, and an explicit id whose value is a string or a JSON number. Notifications (requests with no id) and null, Boolean, object, or array identifiers are not supported by v1 and return -32600 with a null response ID. params, when present, MUST be an object or array. A scalar params value is an invalid request envelope rather than method-specific input.

Invalid JSON returns one parse-error response with code -32700 and a null ID. A syntactically valid but invalid envelope returns -32600; it MUST NOT be misreported as a parse error. Blank lines are ignored. Unknown top-level members are ignored for JSON-RPC interoperability.

Response Envelope

Success:

{"jsonrpc":"2.0","id":1,"result":{"transport":"stdio"}}

Failure:

{"jsonrpc":"2.0","id":1,"error":{"code":-32601,"message":"Method 'missing' is not available."}}

Every accepted request line produces exactly one response line with the same ID. A response contains exactly one of result or error, never both and never neither. Error responses preserve the original string or numeric ID whenever the envelope supplied a valid one.

Stable Error Codes

Code Meaning
-32700 Parse error
-32600 Invalid JSON-RPC envelope
-32601 Unknown method
-32602 Invalid params
-32001 Workspace path escapes the workspace root
-32002 I/O or runtime command failure
-32003 Harness not loaded
-32004 harness.run_until exceeded the requested cycle budget

Error data, when present, is machine-readable. Workspace containment errors include kind = "path_outside_workspace" and the submitted path. I/O errors include the normalized in-workspace path when one exists. Harness boundary errors preserve their stable boundary kind, path, and candidate list. Secrets, including runtime control tokens and source contents unrelated to the failed operation, MUST NOT be copied into an error message or error data.

Current Methods

Method Purpose
agent.describe Return transport info, workspace root, and supported method list
workspace.read Read a file inside the workspace root
workspace.write Write a file inside the workspace root
workspace.project_info Return project-root, source-root, dependency, config, and runtime-orientation metadata
lsp.diagnostics Return machine-readable diagnostics for one ST file or a whole project source tree
lsp.ast_canonicalize Return the canonical normalized AST token stream and 5-gram fingerprints for one source
lsp.ast_similarity Compare two canonical AST token streams and return their similarity evidence
lsp.format Return a formatting preview for one ST file without mutating disk
runtime.build Reuse the trust-runtime build --ci payload path
runtime.compile_reload Run the full diagnose -> build -> reload loop and return a single structured result
runtime.validate Reuse the trust-runtime validate --ci payload path
runtime.test Reuse the trust-dev test --output json payload path
runtime.reload Rebuild the program image program.tpi and send it with bytecode.reload to a running runtime control endpoint
harness.load Load inline/project sources into a deterministic in-process harness
harness.reload Reload harness sources while preserving retain semantics when supported
harness.execute Run one isolated deterministic fixture transaction and return structured step/assertion evidence
harness.cycle Advance cycles, optionally advancing virtual time first
harness.set_input Set a harness input/global/program variable
harness.get_output Read a harness output/global/program variable
harness.advance_time Advance virtual time without executing a cycle
harness.run_until Cycle until a named output matches an expected value

agent.describe.methods is the authoritative, duplicate-free list of methods accepted by this version. Every listed method MUST have a dispatch route, and no accepted public method may be omitted from the list. The response also reports the canonical workspace root, stdio transport, and jsonl framing.

Method Notes

workspace.read

Params:

{"path":"src/main.st"}

workspace.write

Params:

{"path":"src/main.st","text":"PROGRAM Main\nEND_PROGRAM\n","create_parents":true}

bytes_written is the UTF-8 byte length, not the Unicode scalar or display character count. When create_parents is false, a missing parent is an I/O error and no file or directory is created.

Workspace filesystem containment

All workspace, project, source-root, and explicit harness-file paths pass two independent gates before any read, write, source collection, build, validation, formatting, reload, or harness load:

  1. lexical normalization rejects absolute paths, platform drive/UNC roots, empty paths, and any parent traversal that escapes the submitted relative path; and
  2. filesystem resolution confirms that the target, or the nearest existing ancestor for a target that does not yet exist, remains below the canonical workspace root.

Symbolic links, junctions, reparse points, or other aliasing components MUST NOT permit access outside the canonical workspace root. For writes with parent creation enabled, containment is checked before creating any directory. A containment rejection uses -32001 and performs no filesystem mutation.

Project paths MUST resolve to an existing directory inside the workspace. Project-relative source roots and file paths are resolved relative to that canonical project directory and must remain inside both the project and the workspace. A symlinked project or source path that escapes either boundary is rejected rather than treated as a valid project.

Successful path-bearing results use normalized forward-slash relative paths for machine portability. An I/O failure does not silently return an empty file, an empty project, or a successful zero-byte write.

workspace.project_info

Optional params:

{"project":"runtime-a","sources_root":"src"}

Returns a stable orientation payload for agents:

  • canonical project
  • resolved sourcesRoot
  • sourceCount
  • sources
  • dependencyRoots
  • resolvedDependencies
  • file presence for runtime.toml, io.toml, simulation.toml, trust-lsp.toml, and the program image program.tpi
  • lsp.vendorProfile when trust-lsp.toml declares one
  • runtime summary (controlEndpoint, web/discovery/mesh settings, etc.) when runtime.toml parses cleanly
  • IO summary (drivers, driverCount, safeStateCount) when io.toml parses cleanly

If runtime or IO config files exist but fail to parse, the payload keeps the file path and reports parseError instead of failing the whole request.

runtime.build

Optional params:

{"project":"runtime-a","sources_root":"src","issues_limit":500}

issues_limit is optional; a value below 1 is an invalid-params error. When it is present and the payload carries more issues than the limit, issues[] is the first issues_limit entries of the same deterministic order and the response also carries issuesTruncated: true and totalIssues. When it is omitted, or when the limit is not exceeded, the response is unchanged and carries neither field. An applied limit also bounds a failed build's projected error text: the compiler dump keeps its first issues_limit lines plus one closing line naming the omitted remainder and the dump's total.

Returns the same machine-readable payload shape as trust-runtime build --ci.

lsp.diagnostics

Optional params:

{"project":"runtime-a","sources_root":"src","path":"src/main.st","content":"PROGRAM Main\nEND_PROGRAM\n","page":1,"page_size":50}

Rules:

  • project is relative to the agent workspace root when present
  • sources_root is relative to the selected project root when present, and narrows the listed issues of a whole-project request to the files under it. A folder that does not exist ("sources directory not found") or that holds none of the project's source files is an I/O error (-32002), never an empty list
  • path is relative to the selected project root when present, and names one of the project's source files; then only that file's issues are listed, and sources_root does not apply. A file outside the project's include paths is an I/O error (-32002) that says so
  • a sources_root or path that resolves outside the project or the workspace, through a symbolic link for example, is refused with -32001 before anything is analyzed
  • content is an in-memory override for path; it is invalid without path
  • page and page_size are optional paging parameters. They must be given together; either one alone, a page below 1, or a page_size below 1 is an invalid-params error (-32602). When they are present, issues[] is the requested window of the same deterministic sort, and the response also carries page, pageSize, totalIssues, and totalPages. There is always at least one page, and a page beyond totalPages returns an empty issues[] with the honest totals. When both are omitted, the response is the full unpaged list and carries none of the paging fields.

The diagnostics are the language server's: the same findings that trust-lsp check reports for the project. The project is loaded as the language server loads a workspace folder, with its trust-lsp.toml: include paths, library paths and libraries, vendor profile, [diagnostics] toggles and severity overrides. If path is omitted, every source file of the project and its trust-lsp.toml are diagnosed and listed in one aggregated list; otherwise only path is.

The analysis changes nothing on disk and fetches nothing: it neither reads nor writes the language server's index cache, and it never clones or fetches a dependency. A git dependency is used only when its checkout already exists, for example after the editor or trust-lsp check fetched it; otherwise its trust-lsp.toml finding (L007) says that it is not available.

Only files inside the agent workspace are listed. A library that the configuration names outside the workspace is analyzed, so that its symbols resolve, but its findings are not listed. An issue's path is relative to the project root, or the file's full path for a listed file outside it, such as a library elsewhere in the workspace.

Returns:

  • target
  • errors and warnings, counted over every listed issue, also on a page
  • when page and page_size are present: page, pageSize, totalIssues, and totalPages
  • issues[] (the page's, when a page was asked for) with path, absolute file, line, column, severity, message, optional code, and optional related entries

Every issue uses one-based line and column coordinates, the columns in UTF-16 code units as the editor counts them, while span.start and span.end remain zero-based UTF-8 byte offsets. Related entries use the same coordinate convention and name places in the issue's file. errors and warnings are the exact counts of listed issues whose severity is error and warning; information and hint issues remain in issues but do not inflate either counter. Issues are sorted deterministically by file, position, severity, code, and message before a page is cut. An inline content override is analyzed without writing it to disk.

lsp.ast_canonicalize

Optional params:

{"project":"runtime-a","path":"src/main.st","content":"PROGRAM Main\nEND_PROGRAM\n"}

Exactly one effective source is required. Inline content takes precedence over disk, including when the named file does not exist; otherwise path is read relative to the selected project. Supplying neither is an error. The operation never writes the source.

The result contains algorithm, gramSize, parseErrorCount, stream, and fiveGrams. Version 1 uses canonical_ast_jaccard_5gram_v1 with a gram size of five. Trivia, identifier spellings, and literal values do not enter the canonical token stream. fiveGrams is the sorted, duplicate-free set of canonical five-token windows, and parse errors remain visible through parseErrorCount rather than silently changing the algorithm.

lsp.ast_similarity

Optional params:

{
  "project":"runtime-a",
  "left_path":"src/before.st",
  "right_content":"PROGRAM After\nEND_PROGRAM\n"
}

The left and right inputs independently follow the canonicalization source selection rule: inline content wins, otherwise that side requires a project-relative path. The result reports the same algorithm and gram size, the Jaccard score, unique sharedGrams, leftGrams, and rightGrams, plus strict threshold070 and threshold095 decisions. A score exactly equal to a threshold does not clear it. Two inputs with no five-grams have score 1.0; the operation does not mutate either file.

lsp.format

Params:

{"project":"runtime-a","path":"src/main.st","content":"PROGRAM Main\nEND_PROGRAM\n"}

Rules:

  • path is required and is relative to the selected project root
  • content is optional; when omitted, the formatter reads the file from disk

Returns a formatting preview only:

  • path
  • absolute file
  • formatted content
  • changed

Inline content takes precedence over the file on disk and is never persisted. When content is omitted, the selected file must exist and be valid UTF-8. changed is true exactly when returned content differs byte-for-byte from the effective input. The returned path is normalized and project-relative; file identifies its absolute project location.

runtime.validate

Optional params:

{"project":"runtime-a","issues_limit":500}

issues_limit follows the runtime.build rule: optional, values below 1 are invalid params, and an applied limit adds issuesTruncated: true and totalIssues to the response.

Returns the same machine-readable payload shape as trust-runtime validate --ci.

runtime.test

Optional params:

{"project":"runtime-a","filter":"CI_","list":false,"timeout_seconds":5}

Returns the same JSON summary shape as trust-dev test --output json.

runtime.compile_reload

Optional params:

{"project":"runtime-a","sources_root":"src","endpoint":"tcp://127.0.0.1:9001","token":"secret","issues_limit":500}

issues_limit follows the runtime.build rule: optional, values below 1 are invalid params, and an applied limit truncates issues[] to the first issues_limit entries of the same deterministic order, bounds the build-failure runtimeMessage and the method's error text the same way, and adds issuesTruncated: true and totalIssues to the response.

Behavior:

  • collects the project's diagnostics as lsp.diagnostics does, the language server's, with the same sources_root rule
  • blocks build/reload when the listed diagnostics contain errors
  • reuses the normal trust-runtime build path when diagnostics are clean
  • reuses the same bytecode.reload control-endpoint path as runtime.reload

Returns a single machine-readable loop result with:

  • target
  • dirty (currently always false for agent-driven file writes)
  • errors
  • warnings
  • issues
  • runtimeStatus
  • runtimeMessage
  • optional build
  • optional reload
  • when issues_limit truncated the list: issuesTruncated (true) and totalIssues

This is the preferred agent method for iterative write -> diagnose -> build -> reload loops because it keeps diagnostics and reload state in one payload.

runtime.reload

Optional params:

{"project":"runtime-a","sources_root":"src","endpoint":"tcp://127.0.0.1:9001","token":"secret"}

Behavior:

  • rebuilds the program image program.tpi for the selected project
  • reads the rebuilt image from disk
  • sends it with bytecode.reload to the runtime control endpoint from the project bundle or the explicit endpoint override

Returns a combined payload with the build report plus the control reload result.

Current scope:

  • intended for same-project runtime iteration and agent repair loops
  • source-aware attached-session reload workflows are still future work

harness.load

Either load inline sources:

{
  "inline_sources": [
    {"text":"PROGRAM Main\nEND_PROGRAM\n"}
  ]
}

Or load a project from the current workspace:

{"project":"runtime-a"}

Or load explicit files relative to the workspace root:

{"files":["src/main.st","src/tests.st"]}

Source selection is unambiguous and fail-closed:

  • inline_sources, files, and project are mutually exclusive selectors;
  • an explicit inline or file list must be non-empty;
  • every inline source must contain non-empty source text;
  • explicit file paths must be unique after normalization and must pass the workspace containment rules; and
  • when no selector is supplied, the configured agent workspace is the project.

harness.load replaces the stateful harness session. harness.reload requires an already loaded stateful session and uses the selected replacement sources. Load or reload failure leaves no partially replaced session.

harness.execute

harness.execute is an isolated fixture transaction. It accepts the same source selector as harness.load, plus ordered steps, ordered assertions, and a final watch list:

{
  "inline_sources": [
    {"path":"main.st","text":"PROGRAM Main\nEND_PROGRAM\n"}
  ],
  "steps": [
    {"op":"set_input","name":"start","value":{"type":"BOOL","value":true}},
    {"op":"cycle","count":1,"dt_ms":10}
  ],
  "assertions": [
    {"kind":"output_equals","name":"running","equals":{"type":"BOOL","value":true}}
  ],
  "watch":["running"]
}

Every execute request creates a fresh harness. It neither reads nor mutates the stateful session owned by harness.load, harness.reload, and the individual harness methods. The harness performs its documented initial cycle during load; stepsRun counts only explicitly requested steps.

Supported steps are set_input, set_access, bind_direct, set_direct_input, advance_time, cycle, run_until, and restart. Steps execute in request order. cycle.count defaults to one. Restart mode defaults to cold; accepted spellings are case-insensitive cold and warm. An unknown operation, malformed typed value, negative duration, zero or otherwise invalid execution bound, or unsupported restart mode is invalid params (-32602) before any later step executes.

Supported assertions are output_equals, access_equals, and direct_output_equals. Assertions execute in request order after all steps. A value mismatch is an assertion_failed result entry with typed expected and actual values. A boundary-resolution error is a structured failure entry with the stable boundary kind, path, and candidates; it is not converted into a successful assertion and does not erase earlier assertion results.

The method returns a JSON-RPC success envelope whenever it produced a complete fixture result, even when that result has status = "fail". The result contains:

  • sourceCount, stepsRun, status, and passed;
  • assertion total, evaluated, passed, and failed, satisfying evaluated = passed + failed and never exceeding total;
  • a final typed watchSnapshot; and
  • ordered failure entries identifying compile, runtime-cycle, step, run-until-timeout, boundary, or assertion failures.

A compile failure occurs before any step and reports stepsRun = 0. A step failure stops later steps and assertions, retains the zero-based stepIndex and failing step, and returns the best available watch snapshot. A run-until timeout preserves the typed expected value and the last readable actual value. Assertion mismatches do not prevent later independent assertions from being evaluated. passed is true exactly when the failure list is empty; successful results omit failures.

Malformed request structure and invalid argument values use JSON-RPC -32602. Compile/runtime/boundary outcomes reached by a valid fixture request are represented inside the result so batch callers can distinguish a failed fixture from a broken protocol call.

harness.cycle

{"count":10,"dt_ms":10,"watch":["q","et"]}

dt_ms advances virtual time before each cycle. watch returns typed values for the named outputs after the final cycle.

harness.set_input

{"name":"start_pb","value":{"type":"BOOL","value":true}}

harness.get_output

{"name":"motor_run"}

harness.advance_time

{"duration_ms":25}

harness.run_until

{
  "name":"q",
  "equals":{"type":"BOOL","value":true},
  "dt_ms":10,
  "max_cycles":5,
  "watch":["q","et"]
}

If the cycle budget is exhausted, the response uses error code -32004 and returns the expected value in the error data.

Typed Values

Harness values use typed JSON payloads so agents do not need to guess IEC data shapes. Examples:

{"type":"BOOL","value":true}
{"type":"INT","value":7}
{"type":"TIME","nanos":30000000}
{"type":"STRING","value":"hello"}
{"type":"ARRAY","dimensions":[[0,1]],"elements":[{"type":"BOOL","value":true},{"type":"BOOL","value":false}]}

trust-harness uses the same value encoding. See docs/guides/TRUST_HARNESS_PROTOCOL.md.

Current Scope Boundary

The following are intentionally not in v1 yet:

  • lsp.code_actions
  • attached-session source-aware runtime.reload

Those stay pending until the code-action surface is extracted into a shared service instead of living only inside the LSP handler stack, and until attached-session reload semantics are lifted out of the VS Code-specific runtime panel flow.