Why
Components currently produce rendered Markdown, and as captures that text as
a string. Agent workflows also need validated values for control flow, such as
verdicts, questions, findings, and lists.
The component contract uses one return path. A component either returns its
rendered text or explicitly declares and produces a typed value. It does not
produce presentation and structured data through separate return channels.
Root documents follow the same contract. A text root produces rendered Markdown;
a value root produces one schema-validated JSON result. Rendered body output may
remain observable while the final value stays the only successful result.
Component modes
Text components
A component without a returns declaration keeps the existing behavior:
- its rendered Markdown is its return value;
<Output> may select which region renders;
- invoking it normally renders that text;
- invoking it with
as binds the text and renders nothing.
The default return type is therefore text.
Value components
A returns declaration switches the component into value mode:
returns:
type: object
properties:
passed: { type: boolean }
revisionPrompt: { type: string }
required: [passed]
additionalProperties: false
The declaration is a JSON Schema and may describe any JSON value: string,
number, boolean, array, object, or null.
A Markdown value component produces exactly one value with a direct top-level
<Return>:
---
returns:
type: object
properties:
passed: { type: boolean }
required: [passed]
additionalProperties: false
---
```js eval
const verdict = { passed: true };
```
<Return value={verdict} />
Value components:
- render nothing;
- must be invoked with
as;
- bind the validated value to
as;
- cannot declare
<Output>;
- contain exactly one direct top-level
<Return>;
- fail structural validation before body effects when
<Return> is missing,
duplicated, misplaced, or combined with <Output>.
Invoking a value component without as is an error. Callers render any desired
presentation from the captured value:
<Review as="review" />
<Show when={review.passed}>
Review passed.
</Show>
<Return> renders nothing and is not available in a text component.
Root documents
A root document uses the same text and value modes as a component, with one
difference: a value root has no caller and therefore does not require as.
A root without returns keeps the existing streaming Markdown behavior. A root
with returns:
- executes its complete body;
- contains exactly one direct top-level
<Return>;
- validates that value against its declared schema;
- returns the validated JSON value from
DocumentExecution;
- does not treat rendered body Markdown as its successful result.
The root output stream remains an observability channel for body output. It is
not a second return value. Library consumers may observe it independently while
the execution completion carries the structured result.
Command-line output
xmd run reserves stdout for the successful result of a value root:
- stdout contains only the final value encoded as valid JSON, followed by a
newline; strings remain quoted JSON strings;
- JSON output bypasses Markdown normalization and terminal formatting;
- without
--verbose, rendered body output is discarded;
- with
--verbose, rendered body output and journal diagnostics go to stderr;
- failures write diagnostics to stderr, exit non-zero, and write no result to
stdout.
Text roots remain unchanged: rendered Markdown goes to stdout, and --verbose
adds diagnostics on stderr.
Function components
Markdown and TypeScript components share the same declaration and validation
contract:
- without
export const returns, a function component returns its existing
rendered string;
- with
export const returns, its generator returns a JSON value validated
against that schema;
- a value function component renders nothing and must be captured with
as.
Root documents remain Markdown files; this change does not make TypeScript
function components executable as roots.
Validation and execution
- The produced value is validated at the component or root boundary before it
reaches the caller or execution result.
- Invalid values fail with component and source context.
- Values are restricted to JSON so capture and replay remain deterministic.
- Persistence remains a separate caller-owned effect; returning a value does
not choose where it is stored.
Inspection
inspectDocument({ path }) returns the document's effective return schema
without executing it:
const description = yield* inspectDocument({ path: "Review.md" });
const schema = description.returns;
DocumentInfo.returns is { type: "string" } for a text document with no
declaration. For a value document it is the validated schema declared under
returns. Inspection rejects an invalid return schema through the same
definition-loading path as execution and performs no document effects.
Acceptance criteria
- Components and roots without
returns preserve existing rendering and string
results.
returns accepts schemas for every JSON value kind.
- A Markdown value component binds its validated
<Return> value through as
and emits no segments.
- A function value component follows the same schema and capture behavior.
- A value root returns its validated JSON value and does not require
as.
DocumentExecution completion exposes the structured root result while its
output stream remains observability rather than a second return value.
xmd run writes only the final JSON result of a successful value root to
stdout.
xmd run --verbose writes value-root body output and journal diagnostics to
stderr without contaminating stdout.
- A failed value root exits non-zero and writes no JSON result to stdout.
- Invalid return values fail at the component or root boundary.
inspectDocument().returns reports the default text schema or the declared
value schema without executing the document.
- Invalid return schemas fail consistently during inspection and execution.
- Missing, duplicate, nested, or misplaced
<Return> declarations fail before
body effects.
<Output> and returns are mutually exclusive.
- Invoking a value component without
as fails before body effects.
- Tests cover scalar, array, object, and null values; schema failure; text-mode
compatibility; function components; root API and CLI behavior; nested
composition; and deterministic replay.
- Author documentation shows a normal text component, a captured value
component, and a structured root result.
Not included
- Separate presentation and structured-return channels on one component.
- TypeScript function components as root documents.
- Non-JSON return values.
- Implicit serialization of structured values into Markdown.
Why
Components currently produce rendered Markdown, and
ascaptures that text asa string. Agent workflows also need validated values for control flow, such as
verdicts, questions, findings, and lists.
The component contract uses one return path. A component either returns its
rendered text or explicitly declares and produces a typed value. It does not
produce presentation and structured data through separate return channels.
Root documents follow the same contract. A text root produces rendered Markdown;
a value root produces one schema-validated JSON result. Rendered body output may
remain observable while the final value stays the only successful result.
Component modes
Text components
A component without a
returnsdeclaration keeps the existing behavior:<Output>may select which region renders;asbinds the text and renders nothing.The default return type is therefore text.
Value components
A
returnsdeclaration switches the component into value mode:The declaration is a JSON Schema and may describe any JSON value: string,
number, boolean, array, object, or null.
A Markdown value component produces exactly one value with a direct top-level
<Return>:Value components:
as;as;<Output>;<Return>;<Return>is missing,duplicated, misplaced, or combined with
<Output>.Invoking a value component without
asis an error. Callers render any desiredpresentation from the captured value:
<Return>renders nothing and is not available in a text component.Root documents
A root document uses the same text and value modes as a component, with one
difference: a value root has no caller and therefore does not require
as.A root without
returnskeeps the existing streaming Markdown behavior. A rootwith
returns:<Return>;DocumentExecution;The root output stream remains an observability channel for body output. It is
not a second return value. Library consumers may observe it independently while
the execution completion carries the structured result.
Command-line output
xmd runreserves stdout for the successful result of a value root:newline; strings remain quoted JSON strings;
--verbose, rendered body output is discarded;--verbose, rendered body output and journal diagnostics go to stderr;stdout.
Text roots remain unchanged: rendered Markdown goes to stdout, and
--verboseadds diagnostics on stderr.
Function components
Markdown and TypeScript components share the same declaration and validation
contract:
export const returns, a function component returns its existingrendered string;
export const returns, its generator returns a JSON value validatedagainst that schema;
as.Root documents remain Markdown files; this change does not make TypeScript
function components executable as roots.
Validation and execution
reaches the caller or execution result.
not choose where it is stored.
Inspection
inspectDocument({ path })returns the document's effective return schemawithout executing it:
DocumentInfo.returnsis{ type: "string" }for a text document with nodeclaration. For a value document it is the validated schema declared under
returns. Inspection rejects an invalid return schema through the samedefinition-loading path as execution and performs no document effects.
Acceptance criteria
returnspreserve existing rendering and stringresults.
returnsaccepts schemas for every JSON value kind.<Return>value throughasand emits no segments.
as.DocumentExecutioncompletion exposes the structured root result while itsoutput stream remains observability rather than a second return value.
xmd runwrites only the final JSON result of a successful value root tostdout.
xmd run --verbosewrites value-root body output and journal diagnostics tostderr without contaminating stdout.
inspectDocument().returnsreports the default text schema or the declaredvalue schema without executing the document.
<Return>declarations fail beforebody effects.
<Output>andreturnsare mutually exclusive.asfails before body effects.compatibility; function components; root API and CLI behavior; nested
composition; and deterministic replay.
component, and a structured root result.
Not included