Agent API v1¶
Machine-facing JSON-RPC contract for trust-dev agent serve.
This specification owns the JSON-RPC contract exposed by
trust-dev agent serve.
1. Scope¶
The agent API is the machine-facing orchestration contract for workspace, LSP, runtime, harness, and agent operations. It complements the standard LSP and DAP surfaces; it does not replace them.
2. Canonical Contract¶
Agent Contract v1¶
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.0overstdio - 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:
- lexical normalization rejects absolute paths, platform drive/UNC roots, empty paths, and any parent traversal that escapes the submitted relative path; and
- 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 sourceCountsourcesdependencyRootsresolvedDependencies- file presence for
runtime.toml,io.toml,simulation.toml,trust-lsp.toml, and the program imageprogram.tpi lsp.vendorProfilewhentrust-lsp.tomldeclares one- runtime summary (
controlEndpoint, web/discovery/mesh settings, etc.) whenruntime.tomlparses cleanly - IO summary (
drivers,driverCount,safeStateCount) whenio.tomlparses 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:
projectis relative to the agent workspace root when presentsources_rootis 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 listpathis 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, andsources_rootdoes not apply. A file outside the project's include paths is an I/O error (-32002) that says so- a
sources_rootorpaththat resolves outside the project or the workspace, through a symbolic link for example, is refused with-32001before anything is analyzed contentis an in-memory override forpath; it is invalid withoutpathpageandpage_sizeare optional paging parameters. They must be given together; either one alone, apagebelow 1, or apage_sizebelow 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 carriespage,pageSize,totalIssues, andtotalPages. There is always at least one page, and apagebeyondtotalPagesreturns an emptyissues[]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:
targeterrorsandwarnings, counted over every listed issue, also on a page- when
pageandpage_sizeare present:page,pageSize,totalIssues, andtotalPages issues[](the page's, when a page was asked for) withpath, absolutefile,line,column,severity,message, optionalcode, 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:
pathis required and is relative to the selected project rootcontentis 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.diagnosticsdoes, the language server's, with the samesources_rootrule - blocks build/reload when the listed diagnostics contain errors
- reuses the normal
trust-runtime buildpath when diagnostics are clean - reuses the same
bytecode.reloadcontrol-endpoint path asruntime.reload
Returns a single machine-readable loop result with:
targetdirty(currently alwaysfalsefor agent-driven file writes)errorswarningsissuesruntimeStatusruntimeMessage- optional
build - optional
reload - when
issues_limittruncated the list:issuesTruncated(true) andtotalIssues
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.tpifor the selected project - reads the rebuilt image from disk
- sends it with
bytecode.reloadto the runtime control endpoint from the project bundle or the explicitendpointoverride
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, andprojectare 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, andpassed;- assertion
total,evaluated,passed, andfailed, satisfyingevaluated = passed + failedand never exceedingtotal; - 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.
3. Lexical workspace-relative paths¶
Workspace path parameters are normalized lexically before they are joined to
the configured workspace root. A current-directory component (.) is removed,
and an in-scope parent component (..) removes the preceding ordinary path
component. For example, ./src/../src/main.st normalizes to
src/main.st.
A parent component that would move above the lexical workspace-relative root
is rejected with Agent API error code -32001. For example,
../../secret.st is rejected rather than normalized to an escaping path.
Lexical normalization is the first containment gate, not the complete security
boundary. Before a filesystem operation, the canonical workspace root and the
resolved target (or nearest existing ancestor for a new target) are compared as
specified by the canonical contract. Symbolic links, junctions, and other path
aliases MUST NOT permit a read, write, project selection, source-root
selection, or explicit harness-file load outside the workspace. Containment
failure uses -32001 and occurs before filesystem mutation.
4. JSON-RPC envelope and dispatch authority¶
The request, response, stable-error-code, current-method, and method-note sections included by the canonical contract are normative for JSON parsing, envelope validation, request-ID correlation, parameter boundaries, method discovery, dispatch, and error projection. A valid fixture or workflow result is distinct from a JSON-RPC protocol failure; one must not be reclassified as the other merely because the result reports unsuccessful product behavior.
5. Workspace and project filesystem authority¶
The canonical contract's workspace filesystem containment and path-bearing method notes are normative for normalized result paths, UTF-8 byte accounting, parent creation, project selection, source-root resolution, I/O error data, and symbolic-link containment. Lexical acceptance never substitutes for the canonical filesystem check, and containment is checked before mutation.
6. Harness request and result transaction authority¶
The canonical contract's harness.load, harness.reload,
harness.execute, individual harness-method, typed-value, and stable-error
sections are normative for source selection, validation timing, fresh versus
stateful sessions, ordered steps and assertions, accounting, failure ordering,
watch snapshots, restart modes, and run-until timeout data.
7. Agent workflow projection authority¶
The canonical contract's workspace.project_info, lsp.diagnostics,
lsp.ast_canonicalize, lsp.ast_similarity, and lsp.format notes are
normative for project orientation, source discovery, public diagnostic
coordinates, canonical token and five-gram results, similarity decisions, and
non-mutating formatting previews. Optional project and source-root parameters
retain their documented resolution bases; an inline override affects only the
request result and never changes the selected file.
lsp.diagnostics and the diagnostics of runtime.compile_reload are the
language server's: the same findings that trust-lsp check reports for the
selected project, which is loaded with its trust-lsp.toml (include paths,
libraries, vendor profile, [diagnostics] toggles and severity overrides,
specification 14), so that the agent and the editor never disagree on a
project (issue #236, 4 November 2026). Only files inside the workspace are
listed (§3): a library that the configuration names outside it is analyzed but
not listed. The analysis writes nothing and fetches nothing: no index cache,
and a git dependency only once its checkout exists. A sources_root or path
that resolves outside the project or the workspace is -32001, and one that
names no source file of the project is an error, never an empty list.
lsp.diagnostics takes page and page_size together to bound
the listed issues, while errors, warnings and, on a page, totalIssues
count all of them. The issues_limit cap on
runtime.build, runtime.validate, and runtime.compile_reload is opt-in
and deterministic: a limit selects the first entries of the same sorted lists
and bounds the projected build-failure text, and an omitted limit preserves
the established whole-list responses.
8. MCP server binding¶
trust-dev agent serve-mcp exposes the Agent API as MCP tools over the
platform's one MCP connection layer (trust_lsp::mcp, specification 43 §3.1).
The canonical contract remains the single authority on behaviour: the binding
is a tool provider that forwards every call, with its raw JSON arguments, to
the same dispatch table that serves trust-dev agent serve, and projects the
same results and stable error codes onto MCP tool results. It adds no method,
no validation rule, and no configuration beyond the shared
--project/--bundle flag.
The binding's normative rules — the connection layer's protocol revision
negotiation, the 1:1 tool mapping with dot-to-underscore name mangling, the
advisory status of generated input schemas, the success and error result
mappings, the tools-only capability set, and the one-call-at-a-time,
shared-harness, non-preemptible concurrency model with the refused-call and
panicked-session answers — are specified in
docs/guides/AGENT_MCP_BINDING_V1.md, which this section incorporates by
reference. A call whose tool name does not map to an Agent API method is
refused by the connection layer's parameter check (-32602), never a tool
result.