Skip to main content

Documentation Index

Fetch the complete documentation index at: https://smithers.sh/llms.txt

Use this file to discover all available pages before exploring further.

import { ExtractPrompt } from "../components/extract-prompt";

type ExtractPromptProps = {
  idPrefix: string;
  prompt?: string;            // if non-trivial, used directly (passthrough)
  cached?: CachedPrompt;      // if present, used directly (cache hit)
  output: OutputTarget;       // where the draft lands (use rctfPromptSchema)
  agent: AgentLike | AgentLike[];
  schema?: "rctf" | "prd" | "freeform";  // default "rctf"
  stakes?: "high" | "low";    // resolves the threshold; default "low"
  threshold?: number;         // overrides stakes
  maxTurns?: number;          // default 10
  currentScore?: number | null;          // caller passes ctx.latest(output)?.score
  latestDraft?: RctfPromptOutput | null; // caller passes ctx.latest(output)
  context?: string;
  scorers?: ScorersMap;       // optional async observability scorers
  passthroughId?: string;
};
import {
  ExtractPrompt,
  MarkdownPromptCache,
  rctfPromptSchema,
} from "../components/extract-prompt";

const cache = new MarkdownPromptCache();

const { Workflow, smithers, outputs } = createSmithers({
  input: z.object({
    prompt: z.string().nullable().default(null),
    cacheKey: z.string().nullable().default(null),
    stakes: z.enum(["high", "low"]).default("low"),
  }),
  draft: rctfPromptSchema,
});

export default smithers((ctx) => {
  const cached = ctx.input.cacheKey ? cache.getSync(ctx.input.cacheKey) : undefined;
  const latest = ctx.latest("draft", "my-flow:extract:draft") as RctfPromptOutput | null;

  if (latest?.resolved && ctx.input.cacheKey) {
    cache.setSync(ctx.input.cacheKey, /* ... build CachedPrompt from latest ... */);
  }

  return (
    <Workflow name="my-flow">
      <ExtractPrompt
        idPrefix="my-flow"
        prompt={ctx.input.prompt ?? undefined}
        cached={cached}
        output={outputs.draft}
        agent={agents.smart}
        stakes={ctx.input.stakes}
        currentScore={latest?.score ?? null}
        latestDraft={latest}
      />
    </Workflow>
  );
});

Three modes

  1. Passthroughprompt is provided and ≥ 32 chars. Emits a static-output task with score: 1.0 and resolved: true. No agent invoked.
  2. Cache hit — caller resolved a cached entry by key. Same passthrough behavior — emits resolved: true, carries over the cached score, and prefixes scoreReason with cached: .
  3. Extraction — wraps <LoopUntilScored> around the drafter task. The drafter uses the R-C-T-F backbone, picks one Socratic principle per turn, asks the user via bun .smithers/scripts/ask-user.ts, self-scores, and sets resolved: true when ready.

Cache I/O is the caller’s responsibility

The component is pure — no side effects in JSX. Caller does cache getSync / setSync from inside the smithers (ctx) => function. See the example above. Drivers shipped:
  • MarkdownPromptCache (default): .smithers/cache/prompts/{slug}.md with YAML frontmatter
  • SqlitePromptCache: opt-in single-table store via bun:sqlite
  • MemoryPromptCache: for tests

Stakes

  • high → threshold 1.0 (every R-C-T-F slot must be strongly filled)
  • low → threshold 0.7
Override via the threshold prop. Stakes is for callers who don’t want to remember exact numbers.

Override semantics

The human always has the final word. If the agent’s score is below threshold and the human types “ship it” via the ask-user CLI, the agent sets:
  • resolved: true
  • overridden: true
  • overrideReason: "<verbatim>"
The cache records overridden: true for audit; the verbatim overrideReason is available only on the live output, not in the cache.