Skip to content

Instantly share code, notes, and snippets.

@roninjin10
Last active January 21, 2026 14:16
Show Gist options
  • Select an option

  • Save roninjin10/3a1ccc971c33390b7fe69f7d92a3aa8f to your computer and use it in GitHub Desktop.

Select an option

Save roninjin10/3a1ccc971c33390b7fe69f7d92a3aa8f to your computer and use it in GitHub Desktop.
Smithers
#!/usr/bin/env bun
/** @jsxImportSource smithers-orchestrator */
/**
* Build Smithers-Py E2E
*
* This workflow implements the smithers-py Python library based on the spec in issues/smithers-py.md
* Uses a Ralph loop to iteratively build each milestone:
* - M0: Core runtime skeleton (Plan IR, JSX runtime, XML serializer, state stores, tick loop)
* - M1: Runnable nodes + event handlers (Claude executor, PydanticAI integration)
* - M2: MCP server MVP (resources, tools, transports)
* - M3: GUI MVP (Zig-webui + Solid.js)
* - M4: Advanced workflow constructs (Ralph/While, Phase, Step, Parallel)
*/
import {
createSmithersRoot,
createSmithersDB,
SmithersProvider,
Ralph,
Phase,
Step,
Claude,
If,
useSmithers,
Smithers,
createOrchestrationPromise,
signalOrchestrationCompleteByToken,
} from "smithers-orchestrator";
import { useQueryValue } from "smithers-orchestrator/db";
// Create orchestration promise externally so we can pass token to SmithersProvider
const { promise: orchestrationPromise, token: orchestrationToken } = createOrchestrationPromise();
// Function to manually signal completion (called by Ralph onComplete)
function signalComplete() {
signalOrchestrationCompleteByToken(orchestrationToken);
}
const db = createSmithersDB({ path: ".smithers/build-smithers-py.db" });
// Resume or start new execution
let executionId: string;
const incomplete = db.execution.findIncomplete();
if (incomplete) {
executionId = incomplete.id;
console.log("Resuming execution:", executionId);
} else {
executionId = db.execution.start("Build Smithers-Py", "build-smithers-py.tsx");
db.state.set("milestone", "M0", "init");
db.state.set("currentPhaseIndex", 0, "init");
}
const SPEC_PATH = "issues/smithers-py.md";
type Milestone = "M0" | "M1" | "M2" | "M3" | "M4" | "M5" | "M6" | "COMPLETE";
// ============================================================================
// Hooks
// ============================================================================
function useMilestone(): Milestone {
const { reactiveDb } = useSmithers();
const { data } = useQueryValue<string>(
reactiveDb,
"SELECT json_extract(value, '$') as v FROM state WHERE key = 'milestone'"
);
return (data as Milestone) ?? "M0";
}
function useSetMilestone(): (m: Milestone) => void {
const { db } = useSmithers();
return (m) => {
db.state.set("milestone", m, `transition_to_${m}`);
db.state.set("currentPhaseIndex", 0, "reset_phase_for_milestone");
};
}
// ============================================================================
// M0: Core Runtime Skeleton
// ============================================================================
const M0_PROMPTS = {
research: `Read the full spec at ${SPEC_PATH} focusing on M0 requirements:
- Plan IR (Pydantic models for nodes)
- JSX runtime (jsx() function)
- XML serializer
- Volatile + SQLite state with snapshot/write-queue/commit
- Simple tick loop with no runnable nodes
Also study the TypeScript implementation patterns in:
- src/reconciler/ (React reconciler patterns)
- src/db/ (SQLite schema and state management)
- src/components/ (component structure)
Output a structured implementation plan for M0.`,
packageStructure: `Create the smithers_py Python package foundation:
1. Create directory: smithers_py/
2. Create pyproject.toml (python >=3.11, pydantic, pydantic-ai, aiosqlite)
3. Create smithers_py/__init__.py with placeholder exports
4. Create smithers_py/py.typed (PEP 561 marker)
5. Create basic README.md
Run: cd smithers_py && python -c "import smithers_py" to verify.`,
planIR: `Create Pydantic node models (${SPEC_PATH}):
- smithers_py/nodes/base.py: NodeBase(BaseModel), Node type alias
- smithers_py/nodes/text.py: TextNode
- smithers_py/nodes/structural.py: IfNode, PhaseNode, StepNode, RalphNode
- smithers_py/nodes/runnable.py: ClaudeNode with event callbacks
Use Pydantic v2 patterns. Write tests in smithers_py/nodes/test_nodes.py`,
jsxRuntime: `Create smithers_py/jsx_runtime.py:
1. jsx(type_, props, *children) - lookup intrinsics or call component
2. Fragment component for grouping
3. INTRINSICS registry mapping tag names to node classes
4. Validate event props only on observable nodes
5. Write tests in smithers_py/test_jsx_runtime.py`,
xmlSerializer: `Create smithers_py/serialize/xml.py:
- serialize_to_xml(node) -> XML string
- Tags are node types, props become attributes, children become nested elements
- Write tests comparing expected XML output`,
stateStores: `Create state stores (${SPEC_PATH}):
- smithers_py/state/base.py: StateStore Protocol
- smithers_py/state/volatile.py: VolatileStore (in-memory)
- smithers_py/state/sqlite.py: SqliteStore (persistent)
Both: get, set, snapshot, enqueue, commit. Write tests.`,
dbSchema: `Create database layer matching Smithers TS schema:
- smithers_py/db/schema.sql: executions, state, transitions, render_frames, tasks, agents, tool_calls
- smithers_py/db/database.py: SmithersDB class
- smithers_py/db/migrations.py: run_migrations()
Reference src/db/schema.ts`,
tickLoop: `Create smithers_py/engine/tick_loop.py:
Phases: State Snapshot -> Render (pure) -> Reconcile -> Commit -> Execute -> Effects -> Flush
class TickLoop with async run(). Context object (ctx.v, ctx.state, ctx.db, ctx.now(), ctx.frame_id).
Write integration test verifying XML frame saved and state snapshot isolation.`,
verify: `Run all M0 tests:
1. cd smithers_py && python -m pytest -v
2. python -c "from smithers_py import *; print('Imports OK')"
3. Create smithers_py/test_m0_integration.py
If tests pass, move to M1.`,
};
function M0_CoreRuntime() {
const setMilestone = useSetMilestone();
return (
<>
<Phase name="M0-Research">
<Step name="analyze-spec">
<Claude model="opus" maxTurns={20} allowedTools={["Read", "Glob", "Grep"]}>
{M0_PROMPTS.research}
</Claude>
</Step>
</Phase>
<Phase name="M0-Package-Structure">
<Step name="create-package">
<Claude model="sonnet" maxTurns={40} permissionMode="acceptEdits">
{M0_PROMPTS.packageStructure}
</Claude>
</Step>
</Phase>
<Phase name="M0-Plan-IR">
<Step name="create-node-models">
<Claude model="sonnet" maxTurns={50} permissionMode="acceptEdits">
{M0_PROMPTS.planIR}
</Claude>
</Step>
</Phase>
<Phase name="M0-JSX-Runtime">
<Step name="create-jsx-runtime">
<Claude model="sonnet" maxTurns={40} permissionMode="acceptEdits">
{M0_PROMPTS.jsxRuntime}
</Claude>
</Step>
</Phase>
<Phase name="M0-XML-Serializer">
<Step name="create-xml-serializer">
<Claude model="sonnet" maxTurns={30} permissionMode="acceptEdits">
{M0_PROMPTS.xmlSerializer}
</Claude>
</Step>
</Phase>
<Phase name="M0-State-Stores">
<Step name="create-state-stores">
<Claude model="sonnet" maxTurns={50} permissionMode="acceptEdits">
{M0_PROMPTS.stateStores}
</Claude>
</Step>
</Phase>
<Phase name="M0-Database-Schema">
<Step name="create-db-schema">
<Claude model="sonnet" maxTurns={40} permissionMode="acceptEdits">
{M0_PROMPTS.dbSchema}
</Claude>
</Step>
</Phase>
<Phase name="M0-Tick-Loop">
<Step name="create-tick-loop">
<Claude model="sonnet" maxTurns={50} permissionMode="acceptEdits">
{M0_PROMPTS.tickLoop}
</Claude>
</Step>
</Phase>
<Phase name="M0-Verification">
<Step name="run-tests">
<Claude
model="sonnet"
maxTurns={30}
allowedTools={["Read", "Bash", "Grep"]}
onFinished={(r) => {
if (r.output.includes("passed") || r.output.includes("PASSED")) {
setMilestone("M1");
}
}}
>
{M0_PROMPTS.verify}
</Claude>
</Step>
</Phase>
</>
);
}
// ============================================================================
// M1: Runnable Nodes + Event Handlers
// ============================================================================
const M1_PROMPTS = {
research: `Research PydanticAI for M1 (${SPEC_PATH}):
- pydantic-ai docs, Agent class, structured output, tool/function calling, retry
- Agent runtime requirements, executors, event enforcement, task leases
Output implementation plan for ClaudeNode executor.`,
executor: `Create Claude executor using PydanticAI:
- smithers_py/executors/base.py: Executor Protocol, AgentResult
- smithers_py/executors/claude.py: ClaudeExecutor with model selection, streaming, db persistence
- smithers_py/executors/retry.py: rate-limit coordinator, error classification
- Update tick_loop.py: find_runnable(), execute()
Write tests with mocked Claude responses.`,
events: `Implement event handler system:
1. tick_loop execute phase: call onFinished/onError, handle stale results
2. Enforce events only on observable nodes (not If/Phase/Step)
3. State actions model: deterministic flush order
4. Commit phase: atomic writes, record transitions
5. Test event -> state -> re-render flow`,
verify: `Run M1 tests:
1. python -m pytest smithers_py/ -v
2. Create smithers_py/test_m1_integration.py
If tests pass, move to M2.`,
};
function M1_RunnableNodes() {
const setMilestone = useSetMilestone();
return (
<>
<Phase name="M1-Research">
<Step name="study-pydantic-ai">
<Claude model="opus" maxTurns={20} allowedTools={["Read", "Grep", "Bash"]}>
{M1_PROMPTS.research}
</Claude>
</Step>
</Phase>
<Phase name="M1-Claude-Executor">
<Step name="create-claude-executor">
<Claude model="sonnet" maxTurns={50} permissionMode="acceptEdits">
{M1_PROMPTS.executor}
</Claude>
</Step>
</Phase>
<Phase name="M1-Event-Handlers">
<Step name="implement-event-system">
<Claude model="sonnet" maxTurns={40} permissionMode="acceptEdits">
{M1_PROMPTS.events}
</Claude>
</Step>
</Phase>
<Phase name="M1-Verification">
<Step name="run-m1-tests">
<Claude
model="sonnet"
maxTurns={30}
allowedTools={["Read", "Bash", "Grep"]}
onFinished={(r) => {
if (r.output.includes("passed") || r.output.includes("PASSED")) {
setMilestone("M2");
}
}}
>
{M1_PROMPTS.verify}
</Claude>
</Step>
</Phase>
</>
);
}
// ============================================================================
// M2: MCP Server MVP
// ============================================================================
const M2_PROMPTS = {
research: `Research Python MCP SDK (${SPEC_PATH}):
- MCP SDK docs, resources/tools patterns, stdio + HTTP transports
- Security: Origin validation, localhost binding, DNS rebinding, sessions
Output implementation plan for MCP server.`,
resources: `Create smithers_py/mcp/resources.py:
- executions: list/get by ID (smithers://executions/{id})
- frames: list/get XML (smithers://executions/{id}/frames/{seq})
- state: snapshot (smithers://executions/{id}/state)
- agents: list/get with stream events
- stream: SSE-style live updates`,
tools: `Create smithers_py/mcp/tools.py:
- start_execution, tick, run_until_idle, stop
- pause/resume, set_state, restart_from_frame, get_frame`,
transports: `Create smithers_py/mcp/server.py:
- McpCore: handle JSON-RPC, compose resources/tools, session management
- StdioTransport: newline-delimited JSON-RPC
- HttpTransport: localhost only, Origin validation, auth token
- CLI entry: python -m smithers_py serve --transport stdio|http`,
verify: `Test MCP server:
1. python -m pytest smithers_py/mcp/ -v
2. Test stdio transport with JSON-RPC
3. Verify resources, tools, auth work
If tests pass, move to M3.`,
};
function M2_MCPServer() {
const setMilestone = useSetMilestone();
return (
<>
<Phase name="M2-Research">
<Step name="study-mcp-sdk">
<Claude model="opus" maxTurns={20} allowedTools={["Read", "Bash", "Grep"]}>
{M2_PROMPTS.research}
</Claude>
</Step>
</Phase>
<Phase name="M2-MCP-Resources">
<Step name="create-mcp-resources">
<Claude model="sonnet" maxTurns={50} permissionMode="acceptEdits">
{M2_PROMPTS.resources}
</Claude>
</Step>
</Phase>
<Phase name="M2-MCP-Tools">
<Step name="create-mcp-tools">
<Claude model="sonnet" maxTurns={50} permissionMode="acceptEdits">
{M2_PROMPTS.tools}
</Claude>
</Step>
</Phase>
<Phase name="M2-MCP-Transports">
<Step name="create-transports">
<Claude model="sonnet" maxTurns={50} permissionMode="acceptEdits">
{M2_PROMPTS.transports}
</Claude>
</Step>
</Phase>
<Phase name="M2-Verification">
<Step name="test-mcp">
<Claude
model="sonnet"
maxTurns={30}
allowedTools={["Read", "Bash", "Grep"]}
onFinished={(r) => {
if (r.output.includes("passed") || r.output.includes("OK")) {
setMilestone("M3");
}
}}
>
{M2_PROMPTS.verify}
</Claude>
</Step>
</Phase>
</>
);
}
// ============================================================================
// M3: GUI MVP (Zig-webui + Solid.js)
// ============================================================================
const M3_PROMPTS = {
research: `Research GUI implementation (${SPEC_PATH}):
- Zig-webui for native shell, Solid.js for frontend
- Architecture: Zig spawns Python MCP, passes auth token to UI
Output implementation plan.`,
implementation: `Build smithers-py GUI MVP:
1. smithers_gui/: Zig project with webui, spawns Python MCP, opens webview
2. smithers_ui/: Solid.js with signals
- ExecutionList, FrameTimeline, PlanViewer, StateInspector, AgentPanel, ControlPanel
3. Connect to MCP via fetch with auth token`,
verify: `Verify GUI builds:
1. cd smithers_gui && zig build (if available)
2. cd smithers_ui && bun install && bun run build
If Zig not available, just verify Solid.js builds. Move to M4.`,
};
function M3_GUI() {
const setMilestone = useSetMilestone();
return (
<>
<Phase name="M3-Research">
<Step name="research-gui-stack">
<Claude model="opus" maxTurns={20} allowedTools={["Read", "Bash", "Grep"]}>
{M3_PROMPTS.research}
</Claude>
</Step>
</Phase>
<Phase name="M3-GUI-Implementation">
<Step name="build-gui">
<Smithers
plannerModel="opus"
executionModel="sonnet"
timeout={3600000}
keepScript
context={`Building GUI for smithers-py. MCP on localhost. Zig-webui + Solid.js. Spec: ${SPEC_PATH}`}
>
{M3_PROMPTS.implementation}
</Smithers>
</Step>
</Phase>
<Phase name="M3-Verification">
<Step name="test-gui">
<Claude
model="sonnet"
maxTurns={20}
allowedTools={["Read", "Bash"]}
onFinished={(r) => {
if (r.output.includes("OK") || r.output.includes("builds")) {
setMilestone("M4");
}
}}
>
{M3_PROMPTS.verify}
</Claude>
</Step>
</Phase>
</>
);
}
// ============================================================================
// M4: Advanced Workflow Constructs
// ============================================================================
const M4_PROMPTS = {
research: `Research advanced constructs (${SPEC_PATH}):
- Study src/components/: While.tsx, Phase.tsx, Step.tsx, Parallel.tsx
- Ralph/While loop semantics, Phase/Step gating, Effects, Node identity
Plan Python equivalents.`,
identity: `Implement stable node identity:
- smithers_py/engine/identity.py: compute_node_id, explicit id prop precedence
- smithers_py/engine/reconciler.py: diff_trees, mount/unmount detection
- smithers_py/lint/identity_lint.py: warn missing ids
- Persist script_hash, git_commit, engine_version for resume`,
ralph: `Implement Ralph/While loop:
- smithers_py/nodes/ralph.py: RalphNode with condition, max_iterations, required id
- smithers_py/engine/ralph_loop.py: iteration tracking
- smithers_py/components/while_context.py: WhileContext, signal_complete
Write tests for iteration behavior.`,
phaseStep: `Implement Phase/Step sequencing:
- smithers_py/nodes/phase.py: PhaseNode (only active renders children)
- smithers_py/nodes/step.py: StepNode (sequential within phase)
- smithers_py/engine/phase_registry.py: track/advance/persist phase index
Tests for phase advancement.`,
parallel: `Implement Parallel execution:
- smithers_py/nodes/parallel.py: ParallelNode
- tick_loop: find_runnable returns all Parallel children, asyncio.gather
Test concurrent execution.`,
effects: `Implement effects as first-class nodes:
- smithers_py/nodes/effect.py: EffectNode(id, deps, run, cleanup)
- smithers_py/engine/effects.py: EffectRegistry, dep tracking, cleanup
- ctx.use_effect() in smithers_py/context.py
Test effect runs, cleanup, loop detection.`,
verify: `Final M4 verification:
1. python -m pytest smithers_py/ -v --tb=short
2. Create smithers_py/test_e2e.py with full workflow
If tests pass, move to M5.`,
};
function M4_AdvancedConstructs() {
const setMilestone = useSetMilestone();
return (
<>
<Phase name="M4-Research">
<Step name="study-advanced">
<Claude model="opus" maxTurns={20} allowedTools={["Read", "Grep"]}>
{M4_PROMPTS.research}
</Claude>
</Step>
</Phase>
<Phase name="M4-Node-Identity">
<Step name="create-identity-system">
<Claude model="sonnet" maxTurns={40} permissionMode="acceptEdits">
{M4_PROMPTS.identity}
</Claude>
</Step>
</Phase>
<Phase name="M4-Ralph-While">
<Step name="create-ralph">
<Claude model="sonnet" maxTurns={50} permissionMode="acceptEdits">
{M4_PROMPTS.ralph}
</Claude>
</Step>
</Phase>
<Phase name="M4-Phase-Step">
<Step name="create-phase-step">
<Claude model="sonnet" maxTurns={50} permissionMode="acceptEdits">
{M4_PROMPTS.phaseStep}
</Claude>
</Step>
</Phase>
<Phase name="M4-Parallel">
<Step name="create-parallel">
<Claude model="sonnet" maxTurns={40} permissionMode="acceptEdits">
{M4_PROMPTS.parallel}
</Claude>
</Step>
</Phase>
<Phase name="M4-Effects">
<Step name="create-effects">
<Claude model="sonnet" maxTurns={50} permissionMode="acceptEdits">
{M4_PROMPTS.effects}
</Claude>
</Step>
</Phase>
<Phase name="M4-Verification">
<Step name="final-tests">
<Claude
model="sonnet"
maxTurns={30}
allowedTools={["Read", "Bash", "Grep"]}
onFinished={(r) => {
if (r.output.includes("passed")) {
setMilestone("M5");
}
}}
>
{M4_PROMPTS.verify}
</Claude>
</Step>
</Phase>
</>
);
}
// ============================================================================
// M5: Harness UI (Production-Grade)
// ============================================================================
const M5_PROMPTS = {
research: `Research harness UI requirements (${SPEC_PATH} sections 9-11):
- 3-pane execution detail, operator actions, MCP streaming, saved views
Output implementation plan.`,
mcpResources: `Expand MCP resources (spec 10.3):
- Full resource set: executions, frames, events, nodes, artifacts, approvals, scripts, health
- Pydantic models in smithers_py/mcp/models.py
- Cursor-based pagination`,
mcpTools: `Expand MCP tools (spec 10.4):
- Execution control: start, pause, resume, stop, terminate, export
- Node control: cancel, retry, rerun_from, fork_from_frame
- State control: set, update, approval.respond
- Audit logging, confirmation for dangerous actions`,
streaming: `Implement MCP streaming (spec 10.5):
- smithers_py/mcp/notifications.py: notification classes
- SSE endpoint, event IDs, Last-Event-ID, bounded buffer
- Emit: frame.created, node.updated, task.updated, agent.stream, approval.requested`,
uiComponents: `Build Solid.js harness UI (smithers_ui/):
- Navigation: Sidebar, top bar
- Pages: Dashboard, Executions, Execution Detail (3-pane), Run, Approvals
- Connect to MCP, SSE streaming for live updates`,
operatorActions: `Implement operator action UI:
- ExecutionActions, NodeActions, StateEditor, ForkFromFrame
- ConfirmationDialog, two-step confirmation, audit trail`,
failureViews: `Implement failure views (Temporal-style):
- FailureDetector in smithers_py/engine/failure_detector.py
- smithers://failures resource
- FailureView UI page, dashboard integration, saved views`,
verify: `Test harness UI:
1. python -m pytest smithers_py/mcp/ -v
2. cd smithers_ui && bun run build
3. Integration: MCP server + UI + streaming
If tests pass, move to M6.`,
};
function M5_HarnessUI() {
const setMilestone = useSetMilestone();
return (
<>
<Phase name="M5-Research">
<Step name="study-harness-spec">
<Claude model="opus" maxTurns={20} allowedTools={["Read", "Grep"]}>
{M5_PROMPTS.research}
</Claude>
</Step>
</Phase>
<Phase name="M5-MCP-Resources">
<Step name="expand-mcp-resources">
<Claude model="sonnet" maxTurns={50} permissionMode="acceptEdits">
{M5_PROMPTS.mcpResources}
</Claude>
</Step>
</Phase>
<Phase name="M5-MCP-Tools">
<Step name="expand-mcp-tools">
<Claude model="sonnet" maxTurns={50} permissionMode="acceptEdits">
{M5_PROMPTS.mcpTools}
</Claude>
</Step>
</Phase>
<Phase name="M5-Streaming">
<Step name="implement-mcp-streaming">
<Claude model="sonnet" maxTurns={50} permissionMode="acceptEdits">
{M5_PROMPTS.streaming}
</Claude>
</Step>
</Phase>
<Phase name="M5-UI-Components">
<Step name="build-solid-components">
<Smithers
plannerModel="opus"
executionModel="sonnet"
timeout={3600000}
keepScript
context={`Building harness UI. Spec: ${SPEC_PATH} 9.2-9.5. MCP provides resources/tools/streaming. Solid.js signals.`}
>
{M5_PROMPTS.uiComponents}
</Smithers>
</Step>
</Phase>
<Phase name="M5-Operator-Actions">
<Step name="build-action-ui">
<Claude model="sonnet" maxTurns={50} permissionMode="acceptEdits">
{M5_PROMPTS.operatorActions}
</Claude>
</Step>
</Phase>
<Phase name="M5-Failure-Views">
<Step name="build-failure-views">
<Claude model="sonnet" maxTurns={40} permissionMode="acceptEdits">
{M5_PROMPTS.failureViews}
</Claude>
</Step>
</Phase>
<Phase name="M5-Verification">
<Step name="test-harness">
<Claude
model="sonnet"
maxTurns={30}
allowedTools={["Read", "Bash", "Grep"]}
onFinished={(r) => {
if (r.output.includes("passed") || r.output.includes("OK")) {
setMilestone("M6");
}
}}
>
{M5_PROMPTS.verify}
</Claude>
</Step>
</Phase>
</>
);
}
// ============================================================================
// M6: Artifacts System
// ============================================================================
const M6_PROMPTS = {
research: `Research artifacts system (${SPEC_PATH} section 11):
- Types: markdown, table, progress, link, image
- Key vs keyless semantics, progress update in-place
- DB schema with partial unique constraint
Output implementation plan.`,
types: `Create artifact types:
- smithers_py/artifacts/types.py: ArtifactType enum, type classes
- smithers_py/artifacts/models.py: ArtifactRecord
- Update DB schema with artifacts table`,
api: `Create ArtifactSystem API:
- smithers_py/artifacts/system.py: ArtifactSystem class
- Methods: markdown, table, progress, link, image
- Key semantics: None=keyless, string=upsert
- Add ctx.artifact to Context`,
mcp: `Add MCP artifact resources:
- smithers://executions/{id}/artifacts, smithers://artifacts/{id}
- Streaming: artifact.created, artifact.updated
- Tools: artifact.list, artifact.get, artifact.delete`,
ui: `Build artifact UI (smithers_ui/src/components/artifacts/):
- ArtifactRenderer, ArtifactList, ArtifactHistory
- Global Artifacts page, Execution Detail tab
- Progress artifact live updates via SSE`,
verify: `Test artifacts:
1. python -m pytest smithers_py/artifacts/ -v
2. Integration: workflow with artifacts -> DB -> MCP
3. UI: bun run build, verify rendering
If tests pass, move to COMPLETE.`,
};
function M6_Artifacts() {
const setMilestone = useSetMilestone();
return (
<>
<Phase name="M6-Research">
<Step name="study-artifacts-spec">
<Claude model="opus" maxTurns={15} allowedTools={["Read", "Grep"]}>
{M6_PROMPTS.research}
</Claude>
</Step>
</Phase>
<Phase name="M6-Artifact-Types">
<Step name="create-artifact-models">
<Claude model="sonnet" maxTurns={40} permissionMode="acceptEdits">
{M6_PROMPTS.types}
</Claude>
</Step>
</Phase>
<Phase name="M6-Artifact-API">
<Step name="create-artifact-system">
<Claude model="sonnet" maxTurns={50} permissionMode="acceptEdits">
{M6_PROMPTS.api}
</Claude>
</Step>
</Phase>
<Phase name="M6-Artifact-MCP">
<Step name="add-artifact-resources">
<Claude model="sonnet" maxTurns={40} permissionMode="acceptEdits">
{M6_PROMPTS.mcp}
</Claude>
</Step>
</Phase>
<Phase name="M6-Artifact-UI">
<Step name="build-artifact-components">
<Claude model="sonnet" maxTurns={50} permissionMode="acceptEdits">
{M6_PROMPTS.ui}
</Claude>
</Step>
</Phase>
<Phase name="M6-Verification">
<Step name="test-artifacts">
<Claude
model="sonnet"
maxTurns={30}
allowedTools={["Read", "Bash", "Grep"]}
onFinished={(r) => {
if (r.output.includes("passed") || r.output.includes("OK")) {
setMilestone("COMPLETE");
}
}}
>
{M6_PROMPTS.verify}
</Claude>
</Step>
</Phase>
</>
);
}
// ============================================================================
// Main Workflow
// ============================================================================
function BuildSmithersPy() {
const milestone = useMilestone();
return (
<>
<If condition={milestone === "M0"}>
<M0_CoreRuntime />
</If>
<If condition={milestone === "M1"}>
<M1_RunnableNodes />
</If>
<If condition={milestone === "M2"}>
<M2_MCPServer />
</If>
<If condition={milestone === "M3"}>
<M3_GUI />
</If>
<If condition={milestone === "M4"}>
<M4_AdvancedConstructs />
</If>
<If condition={milestone === "M5"}>
<M5_HarnessUI />
</If>
<If condition={milestone === "M6"}>
<M6_Artifacts />
</If>
<If condition={milestone === "COMPLETE"}>
<Phase name="Complete">
<Step name="summary">
<Claude
model="sonnet"
maxTurns={10}
allowedTools={["Read", "Glob"]}
onFinished={() => console.log("smithers-py build complete!")}
>
{`Summarize: list smithers_py/ files, count LOC, list features, suggest next steps.`}
</Claude>
</Step>
</Phase>
</If>
</>
);
}
function AppContent() {
const { db } = useSmithers();
const milestone = useMilestone();
console.log(`\n=== Smithers-Py Builder [${milestone}] ===\n`);
return (
<Ralph
id="build-smithers-py"
condition={() => {
const m = db.state.get("milestone");
return m !== "COMPLETE";
}}
maxIterations={100}
onIteration={(i) => console.log(`\n--- Iteration ${i + 1} ---`)}
onComplete={(iterations, reason) => {
console.log(`\n=== Build Complete ===`);
console.log(`Iterations: ${iterations}`);
console.log(`Reason: ${reason}`);
signalComplete();
}}
>
<BuildSmithersPy />
</Ralph>
);
}
function App() {
return (
<SmithersProvider
db={db}
executionId={executionId}
maxIterations={200}
>
<AppContent />
</SmithersProvider>
);
}
// ============================================================================
// Execute
// ============================================================================
const root = createSmithersRoot();
// Render the app (this starts the React reconciler)
await root.render(<App />);
// Wait for effects to execute (useMount in While component)
await new Promise(r => setTimeout(r, 200));
console.log("=== ORCHESTRATION PLAN ===");
console.log(root.toXML());
console.log("===========================\n");
try {
// Wait for orchestration to complete via the external promise
await orchestrationPromise;
db.execution.complete(executionId, { summary: "smithers-py built successfully" });
} catch (err) {
const error = err instanceof Error ? err : new Error(String(err));
console.error("Build failed:", error.message);
db.execution.fail(executionId, error.message);
throw error;
} finally {
root.dispose();
db.close();
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment