IntentText for Agents
AI agents produce Markdown. Markdown has no structure for workflows, no typed blocks, no audit trail, no trust chain.
IntentText gives agents a small set of canonical workflow keywords that produce documents machines can execute and humans can read. The executor enforces gate checks and policy rules before a single step runs.
Why not JSON or YAML for tool calls?
The reflex for "structured agent output" is a JSON or YAML tool-call payload. That works
for one machine handing data to another, but it is not a document: a human can't read a
500-line JSON pipeline, it has no audit trail, no approval gates a person can sign, and
nothing makes it tamper-evident. A .it workflow is both — the same file an agent
executes, a human reviews and approves, and anyone verifies offline:
| JSON / YAML tool call | .it workflow | |
|---|---|---|
| Machine-executable | Yes | Yes (executeWorkflow) |
| Human-readable as a document | No | Yes — typed lines, renders to HTML/PDF |
| Human approval gates | No | gate: / route: / require: |
| Tamper-evident audit trail | No | hash-chained approve: + sign:/freeze: |
| Self-verifiable offline | No | verifyDocument / verifyAuditChain |
| Self-validating | Schema, external | checkConformance(source) |
The payoff: one artifact is the plan, the human gate, and the sealed record. No second system holds the approval state, the audit log, or the signed copy — they all travel inside the file the agent produced.
The workflow keywords
Nine canonical keywords (the agent tier) cover the full agent workflow lifecycle:
| Keyword | Purpose |
|---|---|
step: | A unit of work — the primary building block |
decision: | Conditional branching — if/then/else |
gate: | Hard checkpoint — execution blocked until the condition is satisfied |
trigger: | Event-based activation |
result: | Terminal workflow outcome |
policy: | Rule declaration — constraints the executor enforces before running |
audit: | Immutable audit log entry |
ask: | A question the workflow must resolve (human or model) |
context: | Agent execution context — goal and constraints |
Approval routing (contract tier). When a workflow needs named human approvers, declare
them in-file with route: / require: — workflowState(source) then derives who's pending
and who's next, purely from the file. See Approval Workflows.
route: sequential
require: engineering-manager
require: security | when: touches_prod = yes
Related task keywords: task: / done: — task tracking.
Extended workflow keywords (for complex orchestration):
Looping, parallel execution, handoff, retry, wait, checkpoint, and other advanced workflow primitives are available in the x-agent: extension namespace. See Extension keywords →.
Executing a workflow
Use executeWorkflow() from @dotit/core to run a document against a runtime. The executor evaluates policy: blocks first — if a required gate is unmet, execution returns policy_blocked without running any steps.
import { parseIntentText, executeWorkflow } from "@dotit/core";
const doc = parseIntentText(source);
const result = await executeWorkflow(doc, {
// Tool handlers, keyed by each step's `tool:` value. Receive (input, context).
tools: {
validate: async (input, context) => ({ ok: true }),
infra: async (input, context) => "provisioned",
},
// Called when a gate: block is reached — resolve true (approve) or false (reject)
onGate: async (gate, context) => true,
options: { dryRun: false },
});
console.log(result.status); // "completed" | "gate_blocked" | "policy_blocked" | "error" | "dry_run"
console.log(result.context); // collected step outputs
console.log(result.log); // one entry per processed block
Execution result statuses
| Status | Meaning |
|---|---|
completed | All steps executed successfully |
gate_blocked | A gate: check returned passed: false — halted at that gate |
policy_blocked | A policy: requires: gate was not satisfied before execution started |
error | A step threw an unhandled exception |
dry_run | Runtime dryRun: true — returns plan without execution |
A complete agent task plan
title: Data Migration Pipeline
context: agent | goal: Migrate customer data from legacy DB | constraints: Zero downtime, max 4 hours
policy: Migrations require manager approval | requires: gate | gate: manager-approval
section: Preparation
step: Export legacy data | id: export | tool: pg_dump | timeout: 30m
step: Validate export | id: validate | depends: export | tool: checksum_verify
section: Migration
gate: Manager approval | id: manager-approval | approver: engineering-manager | timeout: 72h
step: Create backup | id: backup | depends: manager-approval | tool: pg_backup
step: Run migration scripts | id: migrate | depends: backup | tool: flyway
decision: Migration successful? | if: migrate.exit_code == 0 | then: verify | else: result-fail
section: Verification
step: Verify row counts | id: verify | depends: migrate | tool: row_counter
step: Run integration tests | id: test | depends: verify | tool: pytest
audit: Migration complete | by: DataBot | at: {{now}} | action: migrate
result: Success | status: completed
section: Rollback
result: Migration failed — rollback initiated | id: result-fail | status: error
step: Restore backup | depends: result-fail | tool: pg_restore
Gates and decisions
gate: blocks execution until a condition is satisfied:
gate: Approval received | id: approval | approver: engineering-manager | timeout: 72h
decision: branches the workflow based on a condition:
decision: Budget approved? | if: budget.amount <= 10000 | then: auto_approve | else: manager_review
Gates are evaluated by the checkGate handler in your runtime. If gate: returns passed: false, executeWorkflow returns { status: "gate_blocked", blockingGate: "approval" }.
Policy enforcement
policy: blocks declare constraints the executor enforces before any step runs:
policy: No production writes without approval | requires: gate | gate: prod-approval | action: block
policy: All migrations require a backup step | requires: step | id: backup
If a required gate has not passed, the executor returns policy_blocked without touching any steps. No partial execution — either the policy allows the run, or nothing runs.
Self-validate before handing off
An agent should check its own output before passing it on. checkConformance is read-only
(it never rewrites the document) and reports structural + semantic issues:
import { checkConformance } from "@dotit/core";
const { conformant, errors, warnings } = checkConformance(generatedSource, { level: "lax" });
if (!conformant) throw new Error(`generated invalid .it: ${errors.map((e) => e.message).join("; ")}`);
Use lax (no error-level issues) as a hard gate; strict (no warnings either, e.g. every date ISO 8601) when you certify a spotless artifact. Unknown keywords are never errors — an agent can invent domain vocabulary and still produce a conformant document.
Audit logging — and making the order tamper-evident
Agents write audit: blocks to build a record of what was executed, by whom, and when:
audit: Fetched 12,450 records | by: DataBot | at: 2026-03-06T02:15:00Z | action: export
audit: Migration complete — 0 errors | by: DataBot | at: 2026-03-06T03:45:00Z | action: migrate
For approvals, go one step further: the hash-chained audit trail makes the order
tamper-evident, so nobody can insert, delete, or reorder an approval after the fact.
appendApproval links each approve: line to the previous via prev: sha256:…, and
verifyAuditChain reports the first broken link:
import { appendApproval, verifyAuditChain } from "@dotit/core";
let src = appendApproval(plan, { by: "DataBot", role: "agent", note: "Pre-flight checks passed" });
src = appendApproval(src, { by: "Sarah Chen", role: "engineering-manager", note: "Approved" });
verifyAuditChain(src); // { valid: true, length: 2, chained: 2 }
Seal the plan after the approvals (approve: is part of the hashed body), and the seal
protects the body while the chain protects the order — together, the whole agent-plus-human
decision record is tamper-evident. See Approval Workflows.
Query the audit trail:
dotit query ./logs --type audit --by DataBot --format table
MCP server integration
The IntentText MCP server gives agents direct access to .it files without the need to import @dotit/core directly:
npm install @dotit/mcp
Available MCP tools:
| Tool | Purpose |
|---|---|
parse_intent_text | Parse a .it source to JSON |
render_html | Render to styled HTML |
render_print | Render to print-ready HTML |
query_document | Query blocks with filters |
merge_template | Merge a template with data |
seal_document | Seal a document (sign + freeze) |
verify_document | Verify integrity |
compute_hash | Compute the canonical SHA-256 document hash |
validate_document | Semantic validation beyond syntax |
diff_documents | Diff two document versions |
document_to_source | Convert JSON back to .it source |
extract_workflow | Extract the execution graph |
get_document_history | Read a tracked document's history |
generate_signing_key | Generate an Ed25519 keypair (identity layer) |
sign_document | Add an Ed25519 cryptographic signature |
verify_signatures | Verify Ed25519 signatures |
verify_certification | Verify UTS authority certifications |
Connect to Claude:
{
"mcpServers": {
"intenttext": {
"command": "node",
"args": ["./node_modules/@dotit/mcp/dist/index.js"]
}
}
}
Related: