Short, runnable .agent patterns for the agentic.dev/v0 resource model. For the full spec, CLI behaviour, and field semantics, see DESIGN_DOC.md; for the grammar, LANGUAGE.md; for terfyn test fixture format, TESTING.md.
Under ADR 007, .agent is the only executable source — every example below is authored in .agent, and every examples/* project is .agent-only. YAML appears here only where the compiled/interchange form is worth showing.
A checked-in OpenAI support_snippet project (section 4) lives under examples/example1/; its project name is the directory (example1). From the repository root, pass --project examples/example1 to terfyn (or cd there and use --project .).
terfyn fmt formats every .agent source under the project root to canonical form (4-space indent, normalized spacing) and normalizes any YAML still in the project closure. --check exits 1 if any file would change (CI). .agent comments are not preserved on rewrite — commit or branch before running.
terfyn fmt --project my-agent-system
terfyn fmt --check --project .terfyn init my-agent-systemCreates a single-file .agent project — no project.yaml, no YAML resources:
my-agent-system/
main.agent # a starter agent, a `default` policy, and the hello workflow
A Terfyn project is authored entirely in .agent (ADR 002 / ADR 007; grammar reference): agents, workflows, tools, and policies are all .agent declarations, discovered and compiled automatically from any .agent file under the project root. The project name is the directory name and built-in model providers need no configuration, so nothing else is required.
YAML is no longer a project source. Under ADR 007
.agentis the only executable source: aproject.yamlhanded tovalidate/plan/apply/runis refused with aterfyn migrate --to-agenthint.terfyn export --format yamlstill emits YAML, but as a one-way interchange output that is never re-loaded as source; machine producers build the graph through the typed ResourceGraph ingress instead of a second source language. To convert a legacy YAML project, runterfyn migrate --to-agent(it raises declaratives and workflows).
A .agent project needs no Project declaration — the project name is the directory and built-in model providers resolve with no configuration. Two optional top-level singletons tune project-wide behaviour (declare each at most once, in any .agent file under the root).
defaults sets fallbacks any agent/workflow inherits when it omits the field. defaults.model is namespace/model_id (a built-in namespace like mock/openai, or a provider alias). Optional defaults.runtime sets where a workflow runs: the built-in local engine (or omit for implicit local), or an external agent runtime such as claude-code (see EXTERNAL_RUNTIME.md and section 9); a workflow that omits runtime inherits it.
defaults {
policy default
model mock/gpt-4
runtime local
}
limits {
maxToolInputBytes 262144 // 256 KiB; truncate by default
maxToolOutputBytes 262144 // 256 KiB
maxCheckpointBytes 1048576 // 1 MiB; fail closed (never truncate checkpoints)
toolInputExceedPolicy truncate
toolOutputExceedPolicy truncate
checkpointExceedPolicy fail
}
limits is the project-wide execution-limit baseline; a Tool's own limits { … } block overrides individual fields at top precedence. maxStateBytes is an alias for maxCheckpointBytes. When truncate is set, long string fields are shortened in-place (top-level keys preserved); fail aborts the step. checkpointExceedPolicy must be fail — truncating durable checkpoint state is rejected by terfyn validate.
A policy, a native tool, and a workflow whose single step calls a tool — all .agent declarations in any .agent file under the project root. A tool call is <tool>.<operation>(args); a workflow step is a binding or the trailing return.
policy default {
execution {
maxWallClockSeconds 300
maxTotalCostUsd 5
}
}
tool helper {
type native // built-in operations such as echo (see the design doc for names)
safety { sideEffects false }
}
workflow hello(input: any) policy default {
return helper.echo(message: "hello")
}
Run the usual loop from the parent of the project directory:
terfyn validate --project my-agent-system
terfyn plan --project my-agent-system
terfyn apply --project my-agent-system --auto-approve
terfyn run workflow/hello --project my-agent-systemFor MCP servers exposed over HTTP (streamable HTTP transport: one POST per JSON-RPC message), give the tool an mcp block with transport "http" and a url (must be http:// or https://). Optional headers are string key/value pairs — use env:VAR_NAME for a secret rather than an inline literal.
tool remote_mcp {
type mcp
mcp {
transport "http"
url "https://mcp.example.com/v1/mcp"
headers {
"Authorization" "env:MCP_BEARER_TOKEN"
}
}
}
Security
- Prefer HTTPS in production. The default Go client performs normal TLS certificate verification against the system trust store; do not disable verification for MCP calls.
- stdio and http are mutually exclusive in the
mcpblock: setcommandonly for stdio,urlonly for HTTP (validated atterfyn validate). - Workflow trace events for tool steps record
usesand cost, not HTTP headers or resolved env values; keep custom logging of MCP traffic free of secrets. A literal secret in a header is flagged at apply/run and never resolved to a stored value.
This is a small but end-to-end project: a native echo step supplies fixed “policy” text, then gpt-4o-mini drafts a one-line customer reply. You need a valid OpenAI API key and outbound HTTPS to api.openai.com.
Repo copy: examples/example1/ — terfyn validate --project examples/example1 from the repo root, or terfyn validate --project . after cd examples/example1.
The runtime calls OpenAI’s /v1/chat/completions endpoint. The agent must answer with a single JSON object (no markdown fences); the engine parses that object and exposes its fields to spec.output.
totalCostUsd on runs is accumulated from each step’s reported cost. Native tools report 0. For OpenAI and Anthropic, the client estimates USD from API usage token counts × approximate standard-tier per-million rates for known models (for example gpt-4o-mini, gpt-4o, claude-sonnet-4-20250514, claude-haiku-4-5-…). Dated snapshots match; a newer version id that only shares a shorter prefix stays at 0 until added in internal/models/cost.go. Verify against OpenAI pricing and Anthropic pricing. Cache-read and batch rates are not applied.
The whole project is one .agent file — examples/example1/main.agent:
example1/
main.agent # policy, native tool, support_writer agent, and support_snippet workflow
policy default {
execution {
maxTotalCostUsd 5
maxWallClockSeconds 300
}
}
tool helper {
type native
safety {
sideEffects false
}
}
agent support_writer {
model openai/gpt-4o-mini
policy default
constraints {
timeoutSeconds 60
}
instructions """
You draft short customer-facing email lines for a storefront.
You receive JSON in the user message: product name and a return-policy line from internal systems.
Respond with one JSON object only (no markdown, no code fences).
Use exactly this shape: {"product": "<the product name, echoed back>", "subject": "<=8 words>", "line": "<=25 words, friendly>"}
"""
}
workflow support_snippet(input: any) policy default {
context = helper.echo(product: input.product, policy_line: "30-day returns on all SKUs; free outbound shipping on defects.")
snippet = support_writer(product: context.echo.product, return_policy: context.echo.policy_line)
return snippet
}
Switching to Anthropic (Claude) — point the agent at anthropic/<model id>; anthropic is a built-in namespace whose key comes from env:ANTHROPIC_API_KEY, and the runtime calls Anthropic’s Messages API (POST /v1/messages). No declaration is needed for the built-in — just model anthropic/claude-sonnet-4-20250514.
Identity-linked keys: if your ANTHROPIC_API_KEY is identity-linked (not scoped to one workspace), Anthropic returns HTTP 400: anthropic-workspace-id is required …. Declare a provider alias to add workspaceIdFrom (same env:VAR form as apiKeyFrom); the adapter sends it as the anthropic-workspace-id header:
provider anthropic {
type anthropic
apiKeyFrom "env:ANTHROPIC_API_KEY"
workspaceIdFrom "env:ANTHROPIC_WORKSPACE_ID"
}
Structured JSON output: there is no response_format: json_object equivalent in this adapter — agents rely on instructions (one JSON object, no markdown fences, as above). If you give the agent an output <Type> with a schemas/<Type>.json, the engine still validates the assistant text as JSON after generation.
The workflow composes the echo step’s payload into the model via context.echo.… (which lowers to ${steps.context.output.echo.…}; see §13.1 in DESIGN_DOC.md). Because it references input.product, terfyn run workflow/support_snippet requires --input product=...; otherwise interpolation fails with undefined path "input.product". For a zero-argument demo, replace input.product on the context step with a literal (e.g. product: "ACME USB-C hub") and thread context.echo.product onward.
If you copied the files to another folder, point --project at that path instead. For the in-repo example, from the repository root use examples/example1 (the directory path), not only the project name example1.
export OPENAI_API_KEY="sk-..." # required for any step that calls the model
terfyn validate --project examples/example1
terfyn plan --project examples/example1
terfyn apply --project examples/example1 --auto-approve
# Checked-in example1 workflow uses ${input.product} on the context step:
terfyn run workflow/support_snippet --project examples/example1 --input product="ACME USB-C hub"
# After switching the workflow to a literal product + steps.context... (see above), you can omit --input.Default run output is still Run ID + status. To see the workflow spec.output object ( product, subject, line, etc.):
terfyn logs --run <run-id> --project examples/example1After the trace table, the CLI prints Workflow output (from spec.output) as indented JSON when the run succeeded and output_json is non-empty.
Or list recent runs as JSON (includes output on each run):
terfyn logs -o json --project examples/example1terfyn logs --run <id> -o json also includes top-level input, output, and workflowName alongside events. Chained events include prevHash and hash when present (issue #116).
To verify trace integrity after a run:
terfyn audit verify --project examples/example1 --run <run-id>See docs/AUDIT_CHAIN.md.
Optional: give the agent an output <Type> with a matching schemas/<Type>.json so replies are validated with JSON Schema; see internal/engine/testdata/wfproj/schemas/ and DESIGN_DOC.md.
Declare an Environment resource and pass -e / --env to validate, plan, or apply when you want overrides (for example stricter or looser policy limits).
A checked-in dev / staging / prod overlay project lives under examples/env-overlays/: validate -e <env>, apply -e dev, then plan -e prod --from-env dev for the promotion risk delta.
Declare the overlay in .agent (in any .agent file under the project root):
environment staging {
overrides {
policies {
default {
execution {
maxWallClockSeconds 600
}
}
}
}
}
Then select it with -e:
terfyn validate --project my-agent-system -e stagingAn agent selects its model with model <provider>/<name>:
model (agent) |
Meaning |
|---|---|
mock/gpt-4 |
Deterministic mock string (no network) |
openai/gpt-4o-mini |
OpenAI API model id gpt-4o-mini via the built-in openai namespace |
The segment before / is a provider namespace. Built-in namespaces (anthropic, openai, gemini, grok, kimi, mock) need no declaration and read their credential from the conventional environment variable; declare a top-level provider <alias> { … } only for a custom endpoint or credential. An unknown provider fails at runtime with an error from the model registry.
To run terfyn on pull_request (install binary, validate / plan / apply / run,
with --approve tool.github.pull_request.post_comment so a real review comment is posted), see
GITHUB_ACTIONS.md and the template under
examples/pr-review-github-actions/ (a .agent
project — main.agent — with OpenAI gpt-4o-mini and the Actions template). The
template also appends a job summary (GITHUB_STEP_SUMMARY), optional Actions cache for the
SQLite file, and an optional gh pr comment pointer job (skipped by default when disabled). In
this repo the PR workflow is .github/workflows/terfyn-pr-review.yml; manual publish for an
arbitrary owner / repo / number is .github/workflows/terfyn-pr-review-publish.yml.
Fixture-style terfyn test (no API keys) is examples/regression-test/ with sample job .github/workflows/terfyn-test.yml. See TESTING.md.
examples/implement-review-loop/ is the flagship
.agent program: an Implementer and an independent Reviewer pass a structured CodingState
through a bounded while … limit 3, authored entirely in .agent (agent prompts in
instructions, typed CodingState input/output, the workspace tool, both policies, and per-agent
capability grants) — main.agent plus a schemas/CodingState.json, no project.yaml.
It demonstrates deterministic bounded control around nondeterministic agents:
- the loop runs at most three implement/review rounds (the bound is explicit in source and enforced by the runtime);
- the Reviewer is granted the read ops +
run_testsbut neither write op (write_file,edit), so a Reviewer that attempts to modify the workspace is denied by capability, not by its prompt; terfyn plansurfaces granting the Reviewerwrite_fileas anAUTONOMOUS authority WIDENEDrisk — reviewable beforeapply.
terfyn validate --project examples/implement-review-loop
terfyn plan --project examples/implement-review-loopThe example's README walks through the capability boundary and the plan authority-widening output;
mock/gpt-4 keeps it reproducible with no API keys.
examples/external-runtime-reviewer/ is the
flagship for the external agent-runtime epic (#335):
the same reviewed .agent runs on Terfyn's own engine or an external CLI agent
(--runtime claude-code), and the authority is identical either way — the runtime is replaceable;
the authority is not.
A read-only Reviewer is granted read_file + run_tests. The workspace tool also declares
write_file, but the Reviewer is not granted it, so under the external runtime the per-run Terfyn
MCP server's tools/list is exactly { read_file, run_tests } — write_file is never advertised
and the external model cannot select it.
terfyn plan --project examples/external-runtime-reviewer # effect bound: workspace.write "unreachable"
terfyn test --project examples/external-runtime-reviewer # forbidEffect Reviewer → workspace.write: passThe README reproduces "the model literally cannot select the operation" offline via plan + test,
and shows the --runtime claude-code run. See EXTERNAL_RUNTIME.md for the
AgentRuntime boundary and the grant-is-not-a-builtin rule, and
ADR 006 for the RuntimeTarget decision.