Build Log · July 19, 2026

Alloy — Runner Bridge and First Real Claude Execution

Proved Alloy can initiate real Claude Code execution via a separate local runner process (verified twice via CLI/SDK capability spikes, real session IDs, real permission boundary), built the full runner/command/event pipeline end to end and typechecked clean, then pivoted before a live UI click to a screen-by-screen completion approach after the Charge/Ignite mockup gap surfaced live.

  • Alloy
  • ForgeKit OS
  • Runner
  • Execution Bridge
  • Architecture
capability spikes run live (CLI + SDK) confirming existing-subscription auth, zero separate API billing
2

capability spikes run live (CLI + SDK) confirming existing-subscription auth, zero separate API billing

real permission-boundary attempts tested live before finding one that actually worked (disallowedTools, allowedTools+dontAsk both failed silently; PreToolUse hook via --settings verified working)
3

real permission-boundary attempts tested live before finding one that actually worked (disallowedTools, allowedTools+dontAsk both failed silently; PreToolUse hook via --settings verified working)

new authenticated API routes (register/claim/events/complete) — the full runner<->Alloy protocol
4

new authenticated API routes (register/claim/events/complete) — the full runner<->Alloy protocol

real bugs found and fixed live during end-to-end testing: proxy.ts blocking /api/runner/* entirely, bare dotenv/config missing .env.local, createWorkpiece redirecting to a /workpieces/[id] route that was never built
3

real bugs found and fixed live during end-to-end testing: proxy.ts blocking /api/runner/* entirely, bare dotenv/config missing .env.local, createWorkpiece redirecting to a /workpieces/[id] route that was never built

Timeline

  1. Start

    Synthesized a revised Charge from a detailed external chat review, then ran the Session Risk Forecast and a lightweight Arc-lifecycle grep-check against lib/actions.ts

    Confirmed advancePhase() is hard forward-only (PHASE_ORDER.indexOf + Math.min clamp) — a real, evidence-based finding, not assumed from the chat's claim

  2. +30m

    Ran live capability spikes: CLI and SDK, both against a real read-only prompt in products/alloy

    Both confirmed apiKeySource: none (existing subscription, no API billing) with real session IDs and structured stream-json events

  3. +50m

    Discovered the real permission-boundary gap: allowedTools/disallowedTools/permissionMode did not reliably block Bash or PowerShell

    Found and verified the fix live — a PreToolUse hook loaded via --settings, modeled on this repo's own gate-check.mjs pattern, correctly blocked both with real permission_denials evidence

  4. +1.5h

    Built the schema additions (execution_commands, arc_event provenance/source_sequence), ExecutionProvider interface, CLI adapter, and standalone runner process

    tsc clean throughout; migration generated and pushed to the real, already-provisioned Alloy Neon database

  5. +2h

    Built the 4 runner-facing API routes, runner-auth.ts, wired Ignite's real startExecution Server Action, added setPhase() to fix the forward-only phase gap

    tsc clean; fresh Dependency Gate evidence written per new trigger

  6. +2.5h

    Started the real Alloy dev server (discovered already running on port 3911, reused rather than starting a duplicate) and the real runner process

    Found and fixed 3 real bugs live: proxy.ts blocking /api/runner/* via Clerk, bare dotenv/config missing .env.local, createWorkpiece redirecting to a nonexistent /workpieces/[id] route

  7. +3h

    Zeb drove the browser walkthrough live: created a real Workpiece, started a real Arc, reached the real Charge form, advanced to the real Ignite screen with the runner listening

    Runner registered and polled successfully; Ignite button rendered correctly

  8. +3.2h

    Zeb flagged that Charge/Ignite look nothing like their mockups (ChargeLayout.png/IgniteLayout.png) — surfaced a real, pre-existing gap: those mockups show a rich cockpit (Crucible, Anvil, Mechanism Rack, readiness %) that a PRIOR session deliberately did not build, because the underlying data doesn't exist yet and fabricating it would repeat a mistake Zeb had personally corrected before

    Initially answered evasively; corrected after Zeb called it out directly

  9. +3.4h

    Zeb selected 'full mockup parity including fabricated numbers' from an offered menu; Claude paused instead of building it, naming the direct conflict with the app's own documented honesty standard, and asked Zeb to confirm in his own words rather than proceeding on a menu selection that may not have reflected real intent

    Zeb clarified: wants systematic screen-by-screen completion (starting with Home), not a one-off Charge/Ignite patch

  10. End

    Zeb confirmed the capability-spike proof (real Claude execution, real session IDs, verified permission boundary) was sufficient to close this session without a final live UI click; pivoted the next session's focus to screen-by-screen UI completion

    Runner process stopped cleanly; retro written

What shipped

  • products/alloy/lib/db/schema.ts — execution_commands table (durable, idempotent runner commands), arc_events gained source/sourceSequence/rawProviderPayload/occurredAt columns, executions gained cwd, arc_event_type gained 8 runner/provider event types

  • products/alloy/drizzle/migrations/0001_giant_lizard.sql — generated and pushed to the real Alloy Neon database (first real schema deploy for this app)

  • products/alloy/lib/runner-protocol.ts — ExecutionProvider interface, ProviderCapabilities, NormalizedProviderEvent — provider-neutral by construction

  • products/alloy/runner/providers/claude-cli.ts — the CLI adapter, using a verified PreToolUse-hook permission boundary (not CLI flags, which were tested and found unreliable)

  • products/alloy/runner/index.ts — the standalone runner process: registers with Alloy, polls for commands, runs them through the provider, posts real events back

  • products/alloy/runner/package.json + tsconfig.json — a separate, independently-runnable Node package, deliberately outside the root npm workspaces (matches the topology decision that Alloy and the runner never assume they're on the same machine)

  • products/alloy/lib/runner-auth.ts — shared-bearer-token auth for the runner<->Alloy boundary (not Clerk — the runner isn't a browser session)

  • products/alloy/app/api/runner/{register,claim,events,complete}/route.ts — the 4 authenticated API routes implementing the protocol

  • products/alloy/proxy.ts — fixed to exempt /api/runner/* from Clerk's auth.protect() (found live: without this, the runner could never reach Alloy at all)

  • products/alloy/lib/actions.ts — startExecution() (the real Ignite action), setPhase() (fixes the forward-only phase-transition gap found via direct grep), fixed createWorkpiece()'s redirect to a route that never existed

  • products/alloy/components/IgnitePanel.tsx + app/ignite/page.tsx — the real, minimal Ignite screen (button + execution status), intentionally not matching the full IgniteLayout.png mockup — see nextItems