The contract every frontend (core API, MCP server, Jupyter kernel, VSCode extension) must obey.
This document defines the shape of every value returned by stata_code.run(), regardless of which Stata backend produced it (pystata or console fallback) and which frontend the user is using. The schema is the project's load-bearing artifact: if it is right, frontends are thin; if it is wrong, every frontend grows hacks.
This Markdown document is normative. Generated artifacts (Pydantic models, JSON Schema, TypeScript types) are derived; when they disagree with this document, the generator is the bug.
These principles drive every field choice below. When in doubt, return to them.
-
Agent-native, not human-native. The primary consumer is an LLM with a token budget, not a human reading a terminal. Optimize for parseability and token economy before prettiness.
-
Deterministic over conversational. Errors are typed (
kind,rc), not English sentences. Status is a boolean (ok), not a string to grep. Common error remediations are surfaced as structuredsuggestions, not buried in prose. -
Token-efficient by default, full-fidelity on demand. The default response carries summaries and references; full payloads (long logs, large matrices, graph bytes) are fetched by a follow-up call only when the agent actually wants them.
-
Native types, not stringified. Stata scalars are JSON numbers, not strings. Matrices are 2-D arrays with named axes. The agent should never need to
parseFloat. -
Multi-session is first-class. Every result names the session that produced it. Single-session use just defaults
session_idto"main". -
Stable across backends. A
pystataresult and a console-fallback result for the same code are structurally identical. Backend differences live behind the schema, not in front of it. -
No per-frontend special cases. The Jupyter kernel and the MCP server transform the same result for their respective transports. Neither produces or consumes a different shape.
Every successful or failed Stata execution returns one result object:
{
"ok": true,
"rc": 0,
"session_id": "main",
"request_id": "01HXJ2K4Q9V8P3F7N6M5R2T1B0",
"started_at": "2026-04-30T14:22:08.123Z",
"elapsed_ms": 234,
"stata_elapsed_ms": 198,
"stata": {
"version": "18.0",
"edition": "MP",
"backend": "pystata"
},
"log": {
"head": "(1 variable, 74 observations)\n...",
"tail": " _cons 6.165698 0.5497 ...\n",
"lines_total": 42,
"bytes_total": 2380,
"truncated": true,
"complete": true,
"error_window": null,
"ref": "log://run-7f3a9b"
},
"results": {
"r": {
"scalars": {"mean": 21.297, "N": 74, "Var": 33.472},
"macros": {},
"matrices": {}
},
"e": {
"scalars": {"N": 74, "df_m": 1, "r2": 0.219},
"macros": {"cmd": "regress", "depvar": "mpg"},
"matrices": {
"b": {
"rows": [],
"cols": [],
"values": null,
"ref": "matrix://01HXJ2K4Q9V8P3F7N6M5R2T1B0/e/b",
"n_rows": 1,
"n_cols": 2
}
}
},
"last_estimation_cmd": "regress",
"estimation": {
"command": "regress",
"command_family": "ols",
"depvar": "mpg",
"n_obs": 74,
"df_model": 1,
"df_resid": 72,
"statistic_kind": "t",
"source": "r_table",
"ci_level": 95.0,
"coefficients": [
{
"term": "weight",
"b": -0.006,
"se": 0.00051,
"statistic": -11.6,
"p_value": 0.0,
"ci_low": -0.00702,
"ci_high": -0.00498
},
{
"term": "_cons",
"b": 39.44,
"se": 1.614,
"statistic": 24.44,
"p_value": 0.0,
"ci_low": 36.22,
"ci_high": 42.66
}
],
"n_coefficients": 2,
"coefficients_truncated": false,
"model_stats": {"N": 74, "df_m": 1, "r2": 0.219},
"diagnostics": {}
}
},
"dataset": {
"frame": "default",
"n_obs": 74,
"n_vars": 12,
"changed": false,
"filename": "auto.dta",
"variables": [
{"name": "make", "type": "str18", "label": "Make and Model"},
{"name": "price", "type": "int", "label": "Price"},
{"name": "mpg", "type": "int", "label": "Mileage (mpg)"}
]
},
"graphs": [
{
"ref": "graph://7f3a9b/0",
"name": "Graph",
"format": "png",
"width": 800,
"height": 600,
"source_command": "scatter price mpg",
"source_line": 5,
"inline": null
}
],
"outputs": [
{"path": "/work/tables/table1.tex", "bytes": 4552, "created": true}
],
"warnings": [],
"error": null,
"schema_version": "1.0",
"capabilities": ["log_truncation", "graph_ref", "matrix_ref", "multi_session",
"result_budget", "output_tracking", "log_hygiene"]
}A failed execution sets ok: false, rc != 0, and populates error:
{
"ok": false,
"rc": 111,
"session_id": "main",
"request_id": "01HXJ2K4Q9V8P3F7N6M5R2T1B1",
"started_at": "2026-04-30T14:22:09.456Z",
"elapsed_ms": 12,
"stata_elapsed_ms": 8,
"stata": { "version": "18.0", "edition": "MP", "backend": "pystata" },
"log": {
"head": "use auto, clear\nsummarize mpgg\nvariable mpgg not found\nr(111);",
"tail": "",
"lines_total": 4,
"bytes_total": 60,
"truncated": false,
"complete": true,
"error_window": "summarize mpgg\nvariable mpgg not found\nr(111);",
"ref": null
},
"results": { "r": {"scalars": {}, "macros": {}, "matrices": {}},
"e": {"scalars": {}, "macros": {}, "matrices": {}},
"last_estimation_cmd": null,
"estimation": null },
"dataset": { "frame": "default", "n_obs": 74, "n_vars": 12, "changed": false,
"filename": "auto.dta", "variables": null },
"graphs": [],
"warnings": [],
"error": {
"kind": "varname_not_found",
"rc": 111,
"rc_label": "variable not found",
"message": "variable mpgg not found",
"command": "summarize mpgg",
"line": 2,
"source_file": null,
"context": {
"before": ["use auto, clear"],
"failing": "summarize mpgg",
"after": []
},
"commands_executed": 1,
"varname": "mpgg",
"path": null,
"name": null,
"suggestions": [
{"action": "Check the variable name. Did you mean `mpg`?",
"command": "describe"}
],
"recovery": {
"category": "user_code",
"retriable": false,
"needs_code_change": true,
"needs_user_input": false
}
},
"schema_version": "1.0",
"capabilities": ["log_truncation", "graph_ref", "matrix_ref", "multi_session"]
}| Field | Type | Required | Notes |
|---|---|---|---|
ok |
bool |
yes | The authoritative success bit. Producers MUST keep ok, rc, and error-presence consistent. Consumers branch on ok first. |
rc |
int |
yes | Stata's _rc after the last user-submitted command (after any capture masking). 0 on success. Synthetic codes are negative: -1 adapter crash, -2 timeout, -3 cancellation. |
session_id |
string |
yes | Defaults to "main". MUST match [A-Za-z0-9_-]+. The character : is reserved for future remote-prefixing (e.g., host-7:main), so v1 producers MUST NOT emit colons. Producers may map ids that are not legal Stata frame names (for example model-a or 9abc) to private frame names internally, but MUST echo the caller's session_id in the result. |
request_id |
string |
yes | Producer-generated, unique per call. Recommended format: ULID or UUIDv7 (sortable). Consumers use this for log correlation and ref lookup. |
started_at |
string (ISO 8601 UTC) |
yes | Timestamp at which the producer began handling the call, e.g. "2026-04-30T14:22:08.123Z". Always UTC, always with millisecond precision. |
elapsed_ms |
int |
yes | Wall-clock duration of the call, in milliseconds. Minimum reported value is 1; sub-millisecond calls round up. |
stata_elapsed_ms |
int | null |
yes | Time spent in Stata only (excluding adapter/IPC overhead), when measurable. null when the backend cannot separate it. |
stata |
object |
yes | Backend identity; see §3.2. |
log |
object |
yes | Log envelope; see §3.3. |
results |
object |
yes | Stata r() and e() returns; see §3.4. Always present, may be empty. |
dataset |
object |
yes | Snapshot of the active frame; see §3.5. |
graphs |
array |
yes | Captured graphs; see §3.6. May be empty. |
outputs |
array<OutputFile> |
no | Files the run created or modified in its working directory; see §3.6a. Empty when nothing was written or track_output_files: false. |
warnings |
array<Warning> |
yes | Non-fatal advisories. See §3.8. De-duplicated by (kind, message). |
error |
object | null |
yes | null iff ok: true. See §3.7. |
origin |
object | null |
no | Echo of the editor-side origin metadata supplied with the request (origin_path, origin_kind, origin_label, origin_cell_id). null when the caller provided none. See §3.9. |
schema_version |
string |
yes | Semver-major + minor. v1.0 producers emit "1.0". See §6. |
capabilities |
array<string> |
yes | Optional features the producer supports beyond v1.0 baseline. See §6 for the registry. |
Producer consistency. When ok: true, the producer MUST set error: null and rc: 0. When ok: false, the producer MUST set error to a non-null object whose rc equals the top-level rc. If a consumer encounters inconsistency, it MUST treat the result as failed.
Synthetic rcs and error.rc. When rc < 0 (adapter crash, timeout, cancellation, policy block, session contention), error.rc mirrors that synthetic code. The corresponding error.kind is adapter_crash (-1), timeout (-2), cancelled (-3), policy_blocked (-4), or session_busy (-5).
Numeric encoding. All JSON numbers in this schema are IEEE-754 doubles. Producers MUST emit them with sufficient precision to roundtrip (typically 17 significant digits for doubles). Consumers MUST treat them as doubles. Stata's system missing (.) is encoded as JSON null, in scalars and in every matrix cell alike. Stata's extended missing values (.a–.z) also become null, so which extended missing it was is lost — agents needing that must request it with ad-hoc Stata commands. Note that Stata represents missings internally as doubles at or above 2^1023 (8.988e+307); producers MUST convert them rather than passing that number through, or a consumer will format a missing standard error as a real one. Stata does not emit Inf/NaN in normal operation; if encountered, producers encode them as null and emit a warning of kind non_finite.
| Field | Type | Notes |
|---|---|---|
version |
string | null |
E.g. "18.0", "17.5". null when the producer cannot determine it. |
edition |
"MP" | "SE" | "IC" | "BE" | "unknown" |
Stata 17+ shipped BE in place of IC; both values may be observed depending on which Stata is installed. Agents reasoning about edition limits (e.g., BE = 2,048 vars) MUST also check version. |
backend |
"pystata" | "console" |
Which adapter executed the code. Open enum: future backends may add values. |
Edition casing in
stata_info. The MCPstata_infotool returns the normalized form ("MP","SE","IC","BE","unknown") inside the nestedstata.editionfield. For backward compatibility it also exposes a flat top-leveleditionfield that mirrors the raw runtime value (lower-case, e.g."mp"). New clients should preferstata.edition; the flat alias is kept so older clients keep working.
The single biggest token-economy decision in the schema. Default response carries head + tail + error_window + a ref; the full log is fetched separately if the agent asks.
| Field | Type | Notes |
|---|---|---|
head |
string |
First N lines of the log, where N defaults to 20. ANSI escapes stripped. When truncated: false, this contains the entire log. |
tail |
string |
Last N lines. Empty string when truncated: false (avoiding overlap ambiguity). |
lines_total |
int |
Line count of the full log, after \r\n → \n normalization. A trailing empty line is not counted. |
bytes_total |
int |
UTF-8 byte count of the full log after ANSI stripping (i.e., the bytes a get_log(ref) call would return). |
truncated |
bool |
true iff head does not contain the entire log. When true, the producer MUST also set ref and MUST implement get_log. |
complete |
bool |
Reserved for v2 streaming. Always true in v1. v2 may emit interim results with complete: false. |
error_window |
string | null |
When error is non-null, the ~10 log lines immediately surrounding the failing emission (regardless of head/tail window). Cheap for the producer to compute; saves agents from bumping log_lines or fetching the full log just to see "what did Stata say right when it broke." null on success or when not computable. |
ref |
string | null |
Opaque reference for get_log. Required when truncated: true; may be set when truncated: false for caller convenience; null is allowed when full log is in head. |
files |
object | null |
Persistent .log / .smcl artifacts written for file-backed runs when requested. null when no files were written. See "Persistent log files" below. |
ANSI handling. All log views (head, tail, error_window, the payload returned by get_log(ref)) are ANSI-escape-stripped, consistently.
Output streams. In v1, the log object captures all backend output text — Stata stdout plus any output from embedded python: or mata: blocks — concatenated in emission order. v2 may surface streams separately under log.streams; producers MUST NOT use that name for any v1 field.
Ref lifetime. Refs are valid only within the same client/server connection (or process, for in-process backends). Producers MUST invalidate refs on reset_session, process exit, or after a documented TTL. Consumers MUST NOT persist refs across sessions.
Defaults. head=20, tail=20. Configurable per call via log_lines_head / log_lines_tail (see §4). If lines_total ≤ head+tail, the producer MUST set truncated: false, place the full log in head, set tail: "", and set ref: null.
Persistent log files. When a frontend passes a source .do path and requests persist_log_files, producers write immutable run artifacts under:
<do-file-dir>/log-files/<do-stem>__<UTC timestamp>__<session_id>__<request_id>/
log.files then has:
{
"directory": "/abs/path/log-files/test1__20260508T012233123Z__main__abc123",
"log_path": "/abs/path/.../test1__20260508T012233123Z__main__abc123.log",
"smcl_path": "/abs/path/.../test1__20260508T012233123Z__main__abc123.smcl",
"manifest_path": "/abs/path/.../manifest.json",
"code_path": "/abs/path/.../submitted.do",
"working_dir": "/abs/path",
"graphs_dir": "/abs/path/.../graphs",
"outputs_dir": "/abs/path/.../outputs",
"graph_paths": ["/abs/path/.../graphs/01-Graph.png"],
"output_paths": ["/abs/path/.../outputs/table.xlsx"],
"policy": "per_run_directory",
"append": false
}The stable folder name is log-files; timestamps belong on child run directories, not on the root. Producers SHOULD NOT append different executions into one log file, because parallel sessions, reruns after a pause, and selection/cell executions become ambiguous. Each run directory SHOULD include a manifest and submitted-code snapshot so the log is attributable without relying on editor history.
When origin_path is supplied, producers SHOULD default Stata's working directory to the source .do file's directory before running. This mirrors how users organize project-relative graph export, putexcel, esttab using, collect export, and similar output commands. Frontends may disable this with use_origin_workdir: false or override it with working_dir.
When persist_generated_files is true, producers SHOULD copy newly created or modified common output files from the run working directory into outputs/, preserving relative paths where practical. Captured graph refs SHOULD also be materialized into graphs/, with the corresponding GraphInfo.file_path set.
Stata's r() and e() return dictionaries, structurally separated. Each follows the same shape:
{
"scalars": { "<name>": <number | null>, ... },
"macros": { "<name>": "<string>", ... },
"matrices": { "<name>": Matrix, ... }
}| Sub-field | Type | Notes |
|---|---|---|
scalars |
dict<str, number | null> |
Native floats / ints. Stata's system missing (.) → JSON null. Extended missings (.a–.z) → null with information loss. |
macros |
dict<str, string> |
Stata macro values verbatim, except that a value longer than 256 characters is truncated and suffixed with … (N more chars elided). The cap exists for macros like e(rngstate), which is ~2 KB of hex on every bootstrap / permute / simulate run and carries nothing a consumer can act on. The name is always kept, and results.estimation is derived from the uncapped values. |
matrices |
dict<str, Matrix> |
See Matrix below. |
Matrix shape:
{
"rows": ["<label>", ...],
"cols": ["<label>", ...],
"values": [[<number | null>, ...], ...],
"ref": "matrix://..." | null,
"n_rows": <int | null>,
"n_cols": <int | null>
}valuesis row-major:values.length == rows.length, every inner array hascols.length. Producers MUST NOT flatten.- Every numeric cell follows the same missing-value rule as
scalars: Stata's system missing (.) and extended missings (.a–.z) become JSONnull. Producers MUST NOT emit Stata's internal8.988e+307representation as a number. - For
e(b):colsare coefficient names,rowsare equation names. Single-equation models populaterowswith the depvar name (or Stata's placeholder"y1"); multi-equation models (mlogit,sureg,gsem) populate them with real equation names. - For large matrices, the producer MAY emit
values: nullandref: "matrix://..."to be fetched viaget_matrix(ref). Producers SHOULD do this when a matrix would inline more than ~10,000 cells.values: nullandref: nulltogether are forbidden. n_rows/n_colsreport the matrix's true shape and are populated even whenrows/colsare elided, so a consumer can judge whether fetching the values is worth a round-trip.- Under
include_results: "scalars"(the default; see §4) every matrix is emitted as a stub:values: null, aref, emptyrows/cols, and populatedn_rows/n_cols.get_matrix(ref)returns the labels along with the values. This is a wire-representation choice only —results.estimationis always derived from the complete values, so inference is never degraded by the budget.
Top-level convenience field:
| Field | Type | Notes |
|---|---|---|
last_estimation_cmd |
string | null |
Mirrors e(cmd) for callers who don't want to dig into e.macros. After multi-command code, this reflects the last command that wrote to e(). null if no estimation has been performed. |
estimation |
EstimationResult | null |
Typed coefficient table derived from r(table) or e(b) / e(V). null when no inline e(b) is available. |
EstimationResult shape:
| Field | Type | Notes |
|---|---|---|
command |
string | null |
Mirrors e(cmd) when available; falls back to last_estimation_cmd. |
command_family |
string | null |
Coarse estimator family derived from the command name (ols / iv / gmm / panel / count / did / …); null when the command is unrecognized. |
depvar |
string | null |
Mirrors e(depvar). |
n_obs |
int | null |
Integer form of e(N) when available. |
df_model |
number | null |
Mirrors e(df_m). |
df_resid |
number | null |
Mirrors e(df_r). |
statistic_kind |
"t" | "z" |
Which statistic fills each coefficient's statistic field, and which distribution produced p_value / ci_low / ci_high. On the e_b_v path this follows Stata's own rule — t on df_resid degrees of freedom when e(df_r) is set, z otherwise — so a rebuilt table agrees with the printed log rather than reporting a normal-approximation interval next to a P>|t| column. |
source |
"r_table" | "e_b_v" |
r_table means values were copied from Stata's displayed r(table) after verifying its columns and b row match e(b); e_b_v means point estimates come from e(b) and inference, when present, is computed from e(V). A matrix returned by ref is resolved before use, so a deferred e(V) still yields standard errors. |
ci_level |
number |
Confidence level used for ci_low / ci_high. Mirrors e(level) when the command stored it, so regress, level(90) reports 90.0; defaults to 95.0 otherwise. |
coefficients |
array<Coefficient> |
One row per term in e(b), subject to the caller's include_estimation / max_coefficients budget. |
n_coefficients |
int |
The model's true term count. Equals coefficients.length unless the caller trimmed the table, so 12 rows out of n_coefficients: 141 is never mistaken for a 12-term model. |
coefficients_truncated |
bool |
true when rows were dropped to satisfy the budget. |
model_stats |
dict<str, number | null> |
High-signal subset of e() scalars such as N, df_m, df_r, r2, F, chi2, ll, and rmse. Full scalars remain under results.e.scalars. |
diagnostics |
dict<str, number | null> |
Command-aware identification/specification statistics surfaced from e() (e.g. weak-ID F and Hansen J for ivreg2/ivreghdfe, AR(2)/Hansen for xtabond2, within-R² for reghdfe, rho for xtreg). Only scalars actually present in e() appear — never fabricated. |
Coefficient shape:
| Field | Type | Notes |
|---|---|---|
term |
string |
Term / coefficient column name. |
b |
number | null |
Point estimate. |
se |
number | null |
Standard error when available. |
statistic |
number | null |
t or z, per EstimationResult.statistic_kind. |
p_value |
number | null |
Two-sided p-value when available. |
ci_low |
number | null |
Lower confidence interval bound when available. |
ci_high |
number | null |
Upper confidence interval bound when available. |
Empty is empty. Sub-dicts are {} when Stata returned nothing — never absent, never null.
e(sample) and s() are intentionally not surfaced in v1. e(sample) is a potentially huge indicator vector, and s() is rarely used outside of parser-internal commands. Agents needing them must run explicit Stata commands.
A summary of the active Stata frame after the command ran. Always populated.
| Field | Type | Notes |
|---|---|---|
frame |
string |
Active Stata frame name. Stata's master frame is named "default". For session ids that are not legal Stata frame names, this may be a private generated frame name. ⚠ Note this is unrelated to session_id == "main". |
n_obs |
int |
_N. |
n_vars |
int |
c(k). |
changed |
bool |
c(changed). ⚠ Stata sets this on any dataset-touching command, including no-op replaces — treat as a "may be dirty" hint, not a guarantee. |
filename |
string | null |
c(filename). null if no file backs the frame (e.g., after clear or for in-memory frames). |
variables |
array<VariableInfo> | null |
Variable list with types and labels. May be null if include_dataset_variables: false was requested or if the frame is empty. |
VariableInfo:
{ "name": "mpg", "type": "int", "label": "Mileage (mpg)" }type is Stata's storage type (byte, int, long, float, double, str#, strL). label is the variable label string, or "" if none.
When n_vars is large (default cap: 200), the producer truncates variables to the first 200 entries and emits a warning of kind dataset_variables_truncated. Agents wanting all variables should call describe directly.
Each entry describes one captured graph. By default the bytes are not inlined; the agent fetches them via ref.
| Field | Type | Notes |
|---|---|---|
ref |
string |
The canonical handle. Resolvable via get_graph(ref). Unique within the result. Use array index to refer to "the second graph"; use ref for cross-call references. |
name |
string |
Stata's graph name (graph display Graph etc.) when known. Not unique within the result — Stata's default name is just "Graph". |
format |
"png" | "svg" | "pdf" |
The format actually produced. Producers MUST convert Stata-native .gph to one of these at capture time. Default is "png". |
width |
int | null |
CSS pixels (96 dpi convention). For raster: actual pixel width. For vector (svg/pdf): width at 1× / nominal. |
height |
int | null |
CSS pixels, same convention. |
source_command |
string | null |
The user-submitted command line that produced this graph, when isolatable. |
source_line |
int | null |
1-indexed line within the submitted code that produced this graph. |
inline |
string | null |
Base64-encoded bytes when the caller explicitly asked for inline (include_graphs: "inline"); else null. Transports with a native image type SHOULD deliver the bytes in that form and set inline: null — see below. |
inline_delivered |
bool | null |
Set by such a transport: true when the bytes were delivered natively, false when they were not (with inline_skipped_reason explaining why). Absent on transports that inline into the JSON. |
file_path |
string | null |
Persistent graph file path when the run bundle materialized captured graphs under log.files.graphs_dir; else null. |
Inline graphs and native image transports. A base64 string sitting in a JSON field is not viewable by a vision-capable consumer — it costs tokens and conveys nothing. A transport with a first-class image type (MCP's ImageContent, a Jupyter display_data bundle) MUST use it: emit the bytes as an image part, set inline: null in the structured body, and set inline_delivered: true. Producers SHOULD cap how many images one response carries (the MCP server's cap is 4) and report the overflow as a inline_graphs_truncated warning; the remaining graphs stay reachable through get_graph(ref). Formats a consumer cannot render as an image (pdf) are not delivered inline at all.
Files the run created or modified inside its working directory — the esttab tables, graph export images, and saved datasets a script produces. Detected by diffing a size/mtime snapshot taken around the run and filtering to common export extensions.
| Field | Type | Notes |
|---|---|---|
path |
string |
Absolute path of the written file. |
bytes |
int | null |
Size after the run; null if it could not be stat'ed. |
created |
bool |
true when the file did not exist before the run, false when it was overwritten. |
This is deliberately independent of persist_log_files: knowing what a run wrote is useful on every call, whereas copying those files into an immutable run bundle is an explicit archival choice. When the working directory holds more than the producer's scan cap (5,000 files), detection is skipped and an output_tracking_skipped warning is emitted rather than reporting a partial answer as complete.
Populated iff ok: false. The schema's most important contribution to agent UX: a typed error with structured remediation hints.
| Field | Type | Notes |
|---|---|---|
kind |
string |
Semantic class. Drawn from the closed enum below. The taxonomy, not the rc, is what an agent should branch on. Consumers MUST treat unrecognized values as unknown. |
rc |
int |
The numeric _rc (mirrors top-level rc). For synthetic codes (-1, -2, -3), set to that value. |
rc_label |
string |
Stata's official short label for that rc when known; else a producer-supplied descriptor. |
message |
string |
Human-readable, single line. Truncated to 4,096 characters; truncation indicator … appended if cut. |
command |
string | null |
The specific command line that failed, if isolatable. Truncated to 1,024 characters. |
line |
int | null |
1-indexed line within the file named by source_file, or within the top-level submitted code when source_file is null. |
source_file |
string | null |
Absolute path of the do / run script line indexes into, when the failure happened inside a script the submitted code invoked. null means line refers to the submitted code. Producers SHOULD resolve this: a bare do "analysis.do" otherwise yields no line number at all, which is the most expensive failure mode for an agent to debug. |
context |
object |
Surrounding-code window; see below. |
commands_executed |
int | null |
Number of commands that ran before the failure, if isolatable. The state in results and dataset reflects this post-failure state, not a pre-failure rollback. |
path |
string | null |
For file_* kinds, the file path at issue. |
varname |
string | null |
For varname_not_found and related, the variable name at issue. |
name |
string | null |
For name_conflict and invalid_name, the conflicting/invalid name. |
suggestions |
array<Suggestion> |
Producer-supplied remediation hints. Empty when none apply. See below. |
recovery |
Recovery | null |
Machine-readable recovery contract for agents. Present on current producers; old or third-party producers may omit it, so consumers should handle null. |
context shape:
{
"before": ["<line>", ...], // up to 3 lines before the failing command
"failing": "<line>", // the failing command itself
"after": ["<line>", ...] // up to 1 line after
}Suggestion shape:
{
"action": "Check the variable name. Did you mean `mpg`?",
"command": "describe" // optional concrete command to run, or null
}Suggestions are best-effort; agents should treat them as hints, not directives. A suggestion is not consent to mutate source files or silently retry changed code; consumers should apply fixes automatically only in workflows where the user requested repair or approved iteration. The kind enum below documents what suggestions are typically populated.
Recovery shape:
| Field | Type | Notes |
|---|---|---|
category |
"user_code" | "data" | "model" | "resource" | "environment" | "internal" | "unknown" |
Broad failure domain for routing. |
retriable |
bool |
Whether re-running the exact same code may succeed. True mainly for transient environment or producer-side failures. |
needs_code_change |
bool |
Whether the submitted Stata code must change to succeed. |
needs_user_input |
bool |
Whether resolution likely requires a human or out-of-band action such as permissions, license/edition limits, or re-acquiring a corrupt file. |
kind enum (v1.0):
rc(s) below cite StataCorp [P] error (Stata 19, 2025). The code is authoritative; this table is a readable mirror.
kind |
Typical rc(s) | Notes / suggestion seed |
|---|---|---|
syntax |
100, 101, 102, 103, 121–127, 130, 132, 197, 198 | Generic parser failure (incl. numlist errors 121–127). No automatic suggestion. |
command_not_found |
199 | Often resolved by ssc install or net install; suggestions populated when Stata reports a likely package name. |
varname_not_found |
111 | varname populated. Suggestions may include similar varnames from dataset.variables. |
invalid_name |
(no dedicated rc) | Stata folds "invalid name" into r(198). name populated when constructed by a producer. |
type_mismatch |
109, 408 | Suggestion: destring/tostring. |
name_conflict |
110 | name populated. Suggestion typically: replace. |
not_sorted |
5 | Suggestion: sort <varlist>. |
convergence |
430 | |
infeasible |
480, 491 | Distinct from convergence: starting values not feasible (e.g. nl, ml). |
estimation_sample_empty |
(no dedicated rc) | Empty estimation samples surface as r(2000); producer-set otherwise. |
estimation_failure |
322, 1400, 1401, 1402 | Postestimation/prefix saw an unexpected result, or numerical overflow. |
no_estimation_results |
301 | Common when calling predict/margins without prior estimation. |
no_observations |
2000, 2001 | |
data_in_memory |
4 | Suggestion: clear. |
matrix_singular |
506, 508 | Matrix not positive definite / not invertible. |
matrix_conformability |
503, 507 | Dimension mismatch; 507 is a matrix post row/col name conflict kept in the matrix bucket. |
matrix_missing |
504 | Matrix has missing values. |
file_not_found |
601 | path populated. |
file_exists |
602 | path populated. Suggestion: pass replace option. |
file_corrupt |
610, 688 | path populated. "Not a Stata file" (610) or genuinely corrupt (688). |
file_io |
603, 691, 692, 693 | path populated. Catch-all for open/read/write failures (691–693 are local filesystem I/O). |
log_state |
604, 606 | The session's log handles are in the wrong state: a log is already open (604) or none is open (606). Almost always the residue of an earlier run that aborted between log using and log close. recovery.retriable is true — the fix is capture log close _all, not a code change. |
network |
2, 631, 672, 677 | Connection timed out / host not found / server refused / remote connection failed. |
permission |
608 | path populated. File is read-only / not writable. |
encoding |
(no dedicated rc) | Unicode / encoding-conversion failures; producer-set. |
stata_limit |
901, 902, 903, 907 | Edition / maxvar / width caps. Distinct from OS OOM. Suggestion: set maxvar or upgrade edition. |
out_of_memory |
909, 950 | OS-level memory exhaustion. Suggestion: compress. |
interrupt |
1 | User Break / Ctrl-C from a frontend. |
cancelled |
(synthetic rc: -3) |
Cancellation was requested. Subprocess-backed producers may terminate an in-flight worker; the direct in-process runner only short-circuits before Stata receives code. |
timeout |
(synthetic rc: -2) |
Adapter-imposed time limit exceeded. |
session_busy |
(synthetic rc: -5) |
The session's Stata process was still running an earlier request when this call's timeout_ms elapsed. Nothing was submitted to Stata, the worker is healthy and is not killed. Retriable as-is; alternatives are a longer timeout_ms, run_in_background, or a different session_id. |
adapter_crash |
(synthetic rc: -1) |
Producer-side failure (pystata exception, IPC death). |
policy_blocked |
(synthetic rc: -4) |
The command-safety policy rejected the code before Stata ran (an OS-escape / file-deletion command such as shell, erase, rmdir, or !). error.recovery.needs_code_change is true. Configurable via STATA_CODE_COMMAND_POLICY / STATA_CODE_POLICY_ALLOW / STATA_CODE_POLICY_BLOCK. |
unknown |
any unmapped rc | Catch-all. Agents fall back to message. We aim to shrink this over time. |
The rc-to-kind table is approximate and lives in code (stata_code.core.errors), not in this document. Discrepancies between the enum and a specific rc are bugs in the table, not in the schema. New rcs default to unknown.
{ "kind": "convergence", "message": "convergence not achieved at iter 100" }| Field | Type | Notes |
|---|---|---|
kind |
string |
Open enum. Common values: convergence, singular, boundary, omitted_collinear, non_finite, dataset_variables_truncated, log_closed, output_tracking_skipped, estimation_from_e_b_v, unknown. |
message |
string |
Human-readable, single line. Truncated to 1,024 characters. |
Warnings are de-duplicated by (kind, message).
estimation_from_e_b_v reports that this run performed an estimation whose
r(table) was already gone by the time results were read — a later command in
the same submission cleared it — so results.estimation was rebuilt from
e(b) / e(V). The rebuilt numbers still match the printed log (see
statistic_kind in §3.5), so this is provenance rather than a correctness
alarm; putting the estimation last in the block restores the r_table path.
It is emitted only when the estimation was produced by this run: e() is
session-global, so a later summarize in the same session keeps reporting the
inherited table through the same fallback without re-warning.
{
"path": "/abs/path/to/notebook.ipynb",
"kind": "cell",
"label": "demo/analysis.ipynb:cell-3",
"cell_id": "8f2c1a40-1f3d-4b7e-9a1b-bd3a17a90c33"
}Pure round-trip echo of the editor-side origin metadata supplied with the request. The runner does not interpret these fields beyond forwarding them to the on-disk run-bundle manifest. Consumers MAY use them to correlate stata_run calls with editor surfaces (file, selection, notebook cell) without the protocol itself becoming notebook-aware.
| Field | Type | Notes |
|---|---|---|
path |
string | null |
Absolute path of the source surface (.do, .ipynb, …). |
kind |
string | null |
Open enum. Common values: file, selection, line, cell, section, code, unknown. |
label |
string | null |
Human-readable label (e.g. demo/test1.do:1). |
cell_id |
string | null |
Stable nbformat 4.5+ cell id when the code is one cell of a .ipynb. Producers do not assign or validate this — it round-trips whatever the caller supplied. |
The whole origin object is null iff the caller supplied none of origin_path, origin_kind, origin_label, origin_cell_id. Older producers that don't populate origin MAY emit null here; consumers MUST tolerate both the null and the fully-populated cases.
The schema also dictates what callers may ask for. Every frontend exposes the same options under the same names:
| Option | Type | Default | Effect |
|---|---|---|---|
code |
string |
— | The Stata code to run. |
session_id |
string |
"main" |
Routes to a named persistent session. Pattern: [A-Za-z0-9_-]+ (no colons in v1). The public id is stable even when the backend maps it to a private Stata frame name. |
log_lines_head |
int |
20 |
Lines to retain at the start of log.head. 0 disables. |
log_lines_tail |
int |
20 |
Lines to retain at the end of log.tail. 0 disables. |
include_full_log |
bool |
false |
If true, the full log is placed inline in log.head regardless of size; truncated: false, ref: null. Use when token budget is generous and follow-up calls are expensive. |
include_graphs |
"ref" | "inline" | "none" |
"ref" |
"none" skips graph capture entirely (cheapest); "ref" captures and returns refs; "inline" base64-encodes bytes into inline. |
graph_format |
"png" | "svg" | "pdf" |
"png" |
Render format. |
include_dataset_variables |
bool |
true |
Set false to omit dataset.variables. |
include_results |
"none" | "scalars" | "full" |
"scalars" |
Payload budget for results.r / results.e. "scalars" inlines scalars and macros and emits every matrix as a stub (§3.4); "full" inlines matrix values up to the ~10,000-cell cap; "none" omits r() / e() entirely. Never affects results.estimation: the model-level fields (n_obs, df_model, df_resid, model_stats, depvar) are read from e() regardless of this setting and merely withheld from the wire, so "none" does not hollow out the estimation contract. Use include_estimation to trim that block. |
include_estimation |
"none" | "summary" | "full" |
"full" |
Payload budget for results.estimation. "summary" keeps the model-level block and drops per-term rows. |
max_coefficients |
int | null |
null |
Cap on estimation.coefficients rows. n_coefficients still reports the true count. |
timeout_ms |
int | null |
600000 (10 min) |
Hard timeout. null disables. On expiry, returns ok: false, error.kind: "timeout", rc: -2. The budget covers queueing: a call waiting on a session whose Stata process is mid-run returns rc: -5, error.kind: "session_busy" rather than blocking past its deadline. Frontends MAY override the default if their use case demands. |
run_in_background |
bool |
false |
Return a job_id immediately instead of the Result; poll with stata_run_status. Producers that do not implement background execution MUST ignore it and run synchronously. |
track_output_files |
bool |
true |
Populate outputs by diffing the working directory around the run. Independent of persist_log_files. |
auto_close_logs |
bool |
true |
On a failed run, close log handles that this run opened. Handles opened by earlier runs are left alone. |
persist_log_files |
bool |
false |
With origin_path, writes immutable .log / .smcl / manifest files under the source .do file's log-files/ directory. |
persist_generated_files |
bool |
true |
When log files are persisted (i.e. persist_log_files: true and origin_path set), also copies newly created or modified table/export files into the bundle's outputs/ and captured graphs into graphs/. To merely learn what a run wrote, use track_output_files — it needs no bundle. |
origin_path |
string | null |
null |
Absolute source .do (or .ipynb) path used for working-directory defaults and run-bundle placement. |
origin_kind |
string | null |
null |
Editor surface that produced the code ("file", "selection", "line", "cell", "section", "code", "unknown"). Echoed in result.origin and the run-bundle manifest. |
origin_label |
string | null |
null |
Human-readable source label, e.g. demo/test1.do:1. Echoed in result.origin and the run-bundle manifest. |
origin_cell_id |
string | null |
null |
Stable nbformat 4.5+ cell id when the code is one cell of a .ipynb. Pure metadata: not interpreted by the runner; echoed in result.origin and recorded in the run-bundle manifest so notebook-aware agents can correlate runs with cells without the protocol becoming notebook-aware. |
use_origin_workdir |
bool |
true |
With origin_path, cd Stata to the source directory before running. |
working_dir |
string | null |
null |
Explicit Stata working directory; overrides the source directory. |
Frontends translate their native idiom (MCP inputSchema, Jupyter kernel options, VSCode commands) into these names without renaming.
The schema implies a small set of follow-up calls. Frontends expose them under consistent names:
| Tool / method | Purpose | Returns |
|---|---|---|
get_log(ref) |
Fetch the full log behind a log.ref. Mandatory when any run() may emit truncated: true. |
{text: string, lines_total: int, bytes_total: int} |
get_graph(ref, format?) |
Fetch graph bytes (default returns the captured format; can request a re-render to png/svg/pdf). | {format: string, bytes_b64: string, width: int, height: int} |
get_matrix(ref) |
Fetch a matrix's values when the producer omitted them inline. Mandatory when any run() may emit matrices[*].ref != null. |
{rows: [...], cols: [...], values: [[...]]} |
stata_run_status(job_id, wait_ms?) |
Poll a run submitted with run_in_background: true. Mandatory for producers that advertise background_runs. wait_ms blocks up to a bounded ceiling (60 s) so a caller need not busy-poll. |
{job_id, session_id, status: "running" | "done" | "error", submitted_at, finished_at, elapsed_ms, code_preview, result: Result | null, error: string | null} |
list_background_runs() |
Enumerate background runs the producer is tracking, newest first. Summaries only — no result payloads. | {jobs: [{job_id, session_id, status, ...}, ...]} |
list_sessions() |
Enumerate live sessions. | [{session_id, started_at, last_used_at, n_obs}, ...] |
reset_session(session_id?) |
Hard-reset a session (clear all). Invalidates all refs scoped to it. |
Result with the cleared state. |
stata_info() |
Report installed Stata. | {stata: {...}, available: bool, capabilities: [...]} |
list_runs(log_dir or origin_path, cell_id?, session_id?, ok?, since?, limit?, offset?) |
Read-only query over persisted run-bundle manifests. Returns newest-first compact summaries of prior runs that landed under <origin dir>/log-files/. since accepts canonical millisecond UTC plus common date/seconds shorthands; offset pages through matches. |
{log_dir, scanned_count, match_count, skipped_count, limit, offset, truncated, runs: [...]} |
These are additions to run(). A minimal client only needs run() plus whichever auxiliaries match the truncation/ref behavior the producer can emit.
schema_version follows semver-major.minor (currently "1.0"). The major bumps on breaking changes; the minor bumps on additive changes that consumers may want to detect.
Breaking changes (major bump, e.g., "1.0" → "2.0"):
- Removing a field
- Renaming a field, including renaming an
error.kindvalue - Changing a field's type or required-ness
- Tightening an enum (removing a value)
Additive changes (minor bump, e.g., "1.0" → "1.1"):
- Adding optional fields
- Adding new
error.kindvalues (consumers MUST treat unknown asunknown) - Adding new
Warning.kindvalues - Adding new auxiliary tools
Non-bumping changes:
- Implementation changes that don't alter the wire shape
- Documentation clarifications
- Adding entries to
capabilities
capabilities registry (v1.0):
| Capability | Meaning |
|---|---|
log_truncation |
Producer can emit truncated: true and supports get_log. |
graph_ref |
Producer captures graphs and supports get_graph. |
matrix_ref |
Producer can emit large matrices as refs and supports get_matrix. |
multi_session |
Producer supports session_id != "main" and list_sessions. |
subprocess_timeout |
Producer enforces hard wall-clock timeouts by isolating Stata in a worker process. |
inline_graphs |
Producer supports include_graphs: "inline". |
log_files |
Producer can persist immutable per-run .log / .smcl bundles. |
run_artifacts |
Producer can materialize captured graphs and copied table/export outputs into the run bundle. |
notebook_navigation |
Producer registers notebook_outline and notebook_get_cell for read-only .ipynb navigation. |
notebook_search |
Producer registers notebook_locate for snippet/regex/error-text cell search. |
notebook_edit |
Producer registers atomic notebook_edit_cell / notebook_insert_cell / notebook_delete_cell. |
run_index |
Producer registers list_runs to query the on-disk run-bundle manifests. |
origin_echo |
Producer accepts origin_path / origin_kind / origin_label / origin_cell_id and echoes them in result.origin. |
result_budget |
Producer honours include_results / include_estimation / max_coefficients and emits matrix stubs. |
background_runs |
Producer accepts run_in_background and registers stata_run_status / list_background_runs. |
output_tracking |
Producer populates outputs by diffing the working directory around a run. |
log_hygiene |
Producer closes log handles a failed run leaked, so an aborted script cannot poison the session with r(604). |
Consumers detect optional features via capabilities, not by parsing schema_version. Producers may add entries; agents MUST treat unknown capability names as opaque.
Forward-compatibility contract. Consumers (agents, kernels, frontends) MUST:
- Treat unknown
error.kindvalues asunknown. - Treat unknown
Warning.kindvalues asunknown. - Tolerate additional unknown top-level fields (do not error on them).
- Not persist
refstrings across sessions.
When v2 ships, v1 is supported by frontends for at least 6 months. Servers MAY emit v1 to v1-clients and v2 to v2-clients (negotiated on connection or via schema_version requested in the call).
Explicitly not in this version, to keep the surface small:
- Streaming logs. All output is batched at end-of-call.
log.complete: falseis reserved for this in v2. Streams (Stata vs Python vs Mata) may be separated under a futurelog.streamsfield. - Distributed / remote sessions. Sessions are per-process.
session_idreserves:for future host-prefixing. - Authentication / authorization. Local trusted environment is assumed.
- Mata internals. Mata code runs (
stata.run("mata: ...")) but Mata-specific return values aren't surfaced beyond whatr()carries. - Frame-level dataset diffs.
dataset.changedis a single bit, not a diff. s()returns. Rarely useful outside parser-internals.e(sample). Potentially huge indicator vector; not surfaced.- Embedded Python / Mata stdout separation. Their output merges into
login emission order rather than being separately surfaced.
This section tracks how much of the schema is wired up in code. Not normative — the contract above is the contract — but useful as a release checklist.
-
Log
head/tail/truncated/complete/error_window/refwith an in-memory ref store backingget_log. -
results.r/results.eseparation, with native-typed scalars (viasfi.Scalar.getValue), macros (viasfi.Macro.getGlobal), and matrices withrows/cols/valuespopulated fromsfi.Matrix.get+getRowNames/getColNames. Matrices larger thanMATRIX_INLINE_CELL_CAP(default 10,000 cells) dropvaluesand emit amatrix://<request_id>/<r|e>/<name>ref instead, retrievable viaget_matrix(ref). -
results.last_estimation_cmd(mirrorse(cmd)). -
results.estimationtyped coefficient table, copied from verifiedr(table)when possible and otherwise derived from inlinee(b)/e(V). -
datasetblock —n_obs,n_vars,frame,changed,filename, andvariables(capped at 200 entries). -
graphs[]withref+ on-disk capture pipeline; format restricted topng/svg/pdf; PNGwidth/heightparsed from IHDR; best-effortsource_command/source_lineattribution from the submitted code.inlinepopulated wheninclude_graphs="inline". -
Structured
error— 34-kind enum,varname/path/nameextracted from Stata's English error text by regex, structuredcontext({before, failing, after}),commands_executedparsed from pystata's multi-line transcript,suggestionsgenerated bycore.errors.suggestions_for, andrecoverygenerated bycore.errors.recovery_for. -
request_id(uuid4 hex),started_at(ISO 8601 UTC ms),stata_elapsed_ms,capabilities. -
Multi-session via Stata frames —
session_id="main"↔defaultframe; other ids create / route to same-named frames when Stata allows it, or deterministic private frame names when the public id needs mapping. -
Warningis{kind, message}; five built-in patterns (omitted_collinear,convergence,singular,boundary, genericnote) + dedup. -
Request-side options:
log_lines_head,log_lines_tail,include_full_log,include_graphs,graph_format,include_dataset_variables,session_id. -
Auxiliary tools:
get_log(ref),get_graph(ref),get_matrix(ref),list_sessions(),reset_session(session_id?), plus the MCP-levelstata_info. -
LRU eviction on the ref store (default cap 256) keeps long-running producers from growing unboundedly.
-
Subprocess-backed hard timeout and cancellation via the public package API, MCP
stata_run, and the subprocess session pool.timeout_msreturnsok=false,rc=-2,error.kind="timeout"after terminating the worker.cancel(session_id)/clear_cancel(session_id)/is_cancel_pending(session_id)and MCPcancel_sessionreturnok=false,rc=-3,error.kind="cancelled"; an in-flight pool worker is terminated and a not-yet-started run is short-circuited before Stata receives code. -
Command-safety policy (
core.policy). OS-escape / file-deletion commands (shell,winexec,erase,rm,rmdir, and the!shell escape) are screened out of submitted code before Stata runs, at both the subprocess-pool boundary and the in-process runner. A blocked run returnsok=false,rc=-4,error.kind="policy_blocked"without touching Stata. Configurable from the environment (so it crosses the worker boundary):STATA_CODE_COMMAND_POLICY(enforcedefault /warn/off),STATA_CODE_POLICY_ALLOW,STATA_CODE_POLICY_BLOCK. It is a guard rail, not a sandbox. -
Static do-file linting (
core.lint, MCPlint_do,stata-code lint). A Stata-free syntactic check — unbalanced braces, aprogram/mata/pythonblock with noend, a strayend, a dangling///— so an agent can catch a class of mistakes before spending a run. Advisory: a clean result is not a correctness guarantee. -
Bash / plain-terminal surface (
stata-code run). Executes a.dofile,-esnippets, or stdin through the subprocess pool and prints the sameRunResult(text summary or--json), so any agent that can shell out gets the structured error loop.stata-code setupwrites the MCP server entry into Claude Code / Cursor / VS Code configs (opt-in, merges, backs up). -
Console (batch) backend (
core.console,Backend.CONSOLE,stata_code.run_console(),stata-code run --backend console). Drives the Stata command-line executable in batch mode and parses the log plus a marker-delimited results dump into the sameRunResult— typedr()/e()scalars/macros, the estimation matrices (e(b)/e(V)/r(table)), the error taxonomy, warnings, and dataset metadata. Works with Stata 13+ and needs no pystata, so a standalone binary plus the Stata CLI is a Python-free path to typed results. Trade-offs: stateless per call (no in-memory session), no in-process cancel, graphs not captured yet.
- In-process hard timeout.
stata_code.core.runner.execute()still runs inside the caller process and cannot preempt code already insidepystata. Use the package-levelstata_code.run()/execute()or MCP server for subprocess-backed timeouts and cancellation. - Console backend: graphs, sessions, streaming. The console backend does not yet capture graphs or persist state across calls (each run is a fresh batch process); wide/exotic matrices beyond the estimation set are reported by name only.
- Streaming logs (
log.complete: false) — v2 of the schema. - Per-stream log split (
log.streams.{stata, python, mata}) — v2.
Field names in this schema were chosen from public sources only:
- Anthropic MCP specification (top-level transport shape:
content,inputSchema) - Stata Corporation documentation:
r(),e(),_rc,c()system values, frame names, error code list - Jupyter kernel protocol (status / error reply shape)
- General software conventions (
ok,elapsed_ms,truncated,ref, semver, ULID) - POSIX-shell convention (
command_not_found)
No AGPL/GPL Stata project's source was consulted in the design of this schema. See LICENSE-POLICY.md for the project's clean-room policy.