Skip to main content
MDX separates prompt text from orchestration logic. Smithers uses it two ways:
  1. Per-step prompts.mdx files with {props.*} interpolation, used as <Task> children.
  2. System prompt composition — A master .mdx template that assembles context from multiple markdown docs.

Setup

// preload.ts
import { mdxPlugin } from "smithers-orchestrator";
mdxPlugin();
# bunfig.toml
preload = ["./preload.ts"]

Per-Step Prompts

{/* components/Review.mdx */}
CODE REVIEW -- Ticket: {props.ticketId} -- {props.ticketTitle}

Reviewer: {props.reviewer}

TICKET DESCRIPTION:
{props.ticketDescription}

ACCEPTANCE CRITERIA:
- {props.acceptanceCriteria}

FILES CHANGED:
Created: {JSON.stringify(props.filesCreated)}
Modified: {JSON.stringify(props.filesModified)}

{props.failingSummary
  ? `VALIDATION FAILURES:\n${props.failingSummary}`
  : "All tests passing."}

**REQUIRED OUTPUT** -- JSON matching this schema:
{props.schema}
// components/Review.tsx
import { Task } from "../smithers";
import { claude } from "../agents";
import ReviewPrompt from "./Review.mdx";

export function Review({ ticket }) {
  return (
    <Task id={`${ticket.id}:review`} output={outputs.review} agent={claude}>
      <ReviewPrompt
        ticketId={ticket.id}
        ticketTitle={ticket.title}
        ticketDescription={ticket.description}
        acceptanceCriteria={ticket.acceptanceCriteria?.join("\n- ") ?? ""}
        filesCreated={["src/auth.ts"]}
        filesModified={["src/index.ts"]}
        failingSummary={null}
        reviewer="claude"
      />
    </Task>
  );
}

Auto-injected {props.schema}

When a <Task> has an output schema (explicit or from createSmithers()), Smithers auto-injects a schema prop containing a human-readable JSON example from the Zod schema. No manual passing required.

System Prompt Composition

1. Standalone docs

prompts/
  system-prompt.mdx     # Master template
  architecture.md       # Architecture docs
  coding-standards.md   # Coding conventions
  git-rules.md          # Git commit conventions
  always-green.md       # "Keep tests passing" rules

2. Component functions

import { readFileSync } from "node:fs";
import { resolve } from "node:path";
import { renderMdx } from "smithers-orchestrator";
import SystemPromptMdx from "./prompts/system-prompt.mdx";

const ROOT = resolve(new URL("../..", import.meta.url).pathname);
const PROMPTS = resolve(new URL("./prompts", import.meta.url).pathname);

function readDoc(path: string): string {
  try { return readFileSync(resolve(ROOT, path), "utf8"); }
  catch { return `[Could not read ${path}]`; }
}

function readPrompt(filename: string): string {
  try { return readFileSync(resolve(PROMPTS, filename), "utf8"); }
  catch { return `[Could not read prompt: ${filename}]`; }
}

const ClaudeMd = () => readDoc("CLAUDE.md");
const Architecture = () => readPrompt("architecture.md");
const CodingStandards = () => readPrompt("coding-standards.md");
const GitRules = () => readPrompt("git-rules.md");
const AlwaysGreen = () => readPrompt("always-green.md");

export const SYSTEM_PROMPT = renderMdx(SystemPromptMdx, {
  components: {
    ClaudeMd,
    Architecture,
    CodingStandards,
    GitRules,
    AlwaysGreen,
  },
});

3. Master template

# My Project

You are building [project description].

## Project Conventions

<ClaudeMd />

## Architecture

<Architecture />

## Coding Standards

<CodingStandards />

## Git Rules

<GitRules />

## Quality Rules

<AlwaysGreen />

## JSON Output Requirement

MUST end response with JSON object in code fence. Format specified in task prompt.
Each .md is standalone, reusable across workflows, and version-controlled. Add or remove sections by adding or removing a component tag.

Conditional Sections

{props.previousAttempt
  ? `PREVIOUS ATTEMPT FAILED:
What was done: ${props.previousAttempt.whatWasDone}
Test output: ${props.previousAttempt.testOutput}
Fix the issues above before proceeding.`
  : "This is the first attempt. Start fresh."}

{props.reviewFixes
  ? `REVIEW FIXES NEEDED:\n${props.reviewFixes}`
  : ""}

Array Rendering

FILES TO MODIFY:
{props.filesToModify.map(f => `- ${f}`).join("\n")}

ACCEPTANCE CRITERIA:
{props.acceptanceCriteria.map((c, i) => `${i + 1}. ${c}`).join("\n")}

REVIEW ISSUES:
{JSON.stringify(props.issues, null, 2)}

Next Steps