Skip to content

feat(ag-ui): host runtimes on Railway via ag-ui-dev deployment#580

Merged
blove merged 21 commits into
mainfrom
claude/ag-ui-railway-deployment
Jun 6, 2026
Merged

feat(ag-ui): host runtimes on Railway via ag-ui-dev deployment#580
blove merged 21 commits into
mainfrom
claude/ag-ui-railway-deployment

Conversation

@blove

@blove blove commented Jun 5, 2026

Copy link
Copy Markdown
Contributor

Summary

  • New deployments/ag-ui-dev/ Railway deployment hosting every cockpit/ag-ui/*/python/ topic at /agent/<topic> from one FastAPI app.
  • Generated by scripts/generate-ag-ui-deployment-config.ts (mirrors scripts/generate-shared-deployment-config.ts); CI drift-checks deployments/ag-ui-dev/.
  • Defense in depth: Vercel edge middleware (origin allowlist + Upstash rate-limit + X-Internal-Token injection) on each ag-ui example; Railway FastAPI middleware verifies the same token; OpenAI project hard cap is the final backstop.
  • Healthcheck on /ok; production smoke tests probe it + the two examples routes.

Spec: docs/superpowers/specs/2026-06-05-ag-ui-railway-deployment-design.md
Plan: docs/superpowers/plans/2026-06-05-ag-ui-railway-deployment.md

Architecture

Browser → examples.threadplane.ai/ag-ui/<topic>/agent/...
       → Vercel edge middleware (origin allow + ratelimit + inject token)
       → Vercel rewrite to ag-ui-dev.up.railway.app/agent/<topic>/...
       → Railway FastAPI middleware (verify token)
       → add_langgraph_fastapi_endpoint mounted at /agent/<topic>
       → LangGraph compiled graph

Pre-flight required before merge

These cloud-side steps must be done by a human before the deploy workflow can succeed (none are code changes):

  1. Create Upstash Redis (free tier, us-east-1) — note URL + token
  2. Generate AG_UI_INTERNAL_TOKEN via openssl rand -hex 32
  3. Create Railway project + service ag-ui-dev, generate domain, project token → GitHub secret RAILWAY_TOKEN
  4. Set Railway env vars: OPENAI_API_KEY, AG_UI_INTERNAL_TOKEN, UPSTASH_REDIS_REST_URL, UPSTASH_REDIS_REST_TOKEN
  5. Set the same AG_UI_INTERNAL_TOKEN + UPSTASH_* vars on both Vercel projects (cockpit-ag-ui-interrupts-angular, cockpit-ag-ui-streaming-angular)
  6. OpenAI hard cap $50/mo

Test plan

  • CI: existing checks (build, lint, generator unit tests) pass on PR.
  • Manual (pre-merge): railway up from deployments/ag-ui-dev/ succeeds; curl https://ag-ui-dev.up.railway.app/ok returns {"ok":true}; curl https://ag-ui-dev.up.railway.app/agent/interrupts without token returns 401.
  • Post-merge: Deploy AG-UI Railway workflow fires on push to main, runs drift check + railway up.
  • Post-merge: production smoke tests pass (apps/cockpit/e2e/production-smoke.spec.ts describe block "ag-ui Railway runtime").
  • Post-merge: open cockpit.threadplane.ai/ag-ui/interrupts/... Run tab, trigger an approval interrupt, confirm refund flow completes end-to-end against the Railway backend.

🤖 Generated with Claude Code

blove and others added 14 commits June 5, 2026 15:51
Spec for hosting the AG-UI FastAPI runtimes on Railway so the cockpit
"Run" tab can reach them. Mirrors the langgraph deployment pattern
(generated multi-topic app + drift-checked CI), adds defense-in-depth
(Vercel edge origin + rate limit + internal token, Railway middleware,
OpenAI spend cap) above the langgraph proxy's current posture.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…pit pattern

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@vercel

vercel Bot commented Jun 5, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
threadplane Ready Ready Preview, Comment Jun 6, 2026 4:48am

Request Review

cpSync was copying project.json into deployments/ag-ui-dev/deps/<topic>/,
which made nx see duplicate project definitions (e.g.
cockpit-ag-ui-interrupts-python defined in both cockpit/ag-ui/.../python
and deployments/.../deps/interrupts). Broke `nx show projects --affected`
and the CI scope job downstream.

Filter project.json + tsconfig*.json out of the cpSync. Regenerate to
drop the stale copies under deps/.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…y.app)

Railway's default domain scheme is <service>-<environment>.up.railway.app,
not <service>.up.railway.app. The created service resolves to
ag-ui-dev-production.up.railway.app. Update vercel.json rewrites + smoke
test default to match.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Taking the highest version per package across two uv-locked requirements
produces internally-inconsistent sets: e.g. interrupts pins
websockets==16.0 (compatible with its langgraph-sdk==0.3.15), streaming
pins langgraph-sdk==0.4.2 (which requires websockets<16). Union picked
both, broke pip resolution.

Parse `# via cockpit-*` markers to identify direct deps from each example
(fastapi, ag-ui-langgraph, langchain-openai, langgraph, langsmith,
uvicorn) and emit only those. Pip resolves transitives at install time.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…eption)

HTTPException raised inside Starlette BaseHTTPMiddleware doesn't hit
FastAPI's handler — it bubbles up and Starlette returns 500. Live deploy
confirmed: /agent/interrupts without token returned 500 instead of 401.

Switch to returning JSONResponse directly. Verified on live deploy:
401 returns correctly for missing/wrong tokens.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
blove and others added 2 commits June 5, 2026 21:34
…ple projects

Discovered late that examples.threadplane.ai is served by a single
Vercel project (threadplane-examples) populated via the Build Output
API by scripts/assemble-examples.ts — not by per-example Vercel
projects. The per-example vercel.json rewrites + middleware.ts files
from earlier commits were dead code (Vercel never consumes them).

Changes:
- Revert per-example vercel.json rewrites to original buildCommand-only
- Delete cockpit/ag-ui/*/angular/middleware.ts + tsconfig.middleware.json
- Drop @vercel/edge from root package.json (kept @upstash/* — needed by
  the new Node serverless proxy)
- Restore peerDependencies to original shape (@threadplane/* only)
- Add ag-ui to capabilities + SPA route regex in assemble-examples.ts
- Add new route above filesystem handle:
    /ag-ui/<topic>/agent[/rest] → /api/ag-ui-proxy/<topic>[/rest]
- New scripts/ag-ui-proxy.ts: Node Vercel function that does origin
  allowlist + Upstash rate limit (fail-open if creds missing) +
  X-Internal-Token injection + streaming-aware fetch to the Railway
  service. Bundled by assemble-examples into
  .vercel/output/functions/api/ag-ui-proxy/[[...path]].func
- Bump ag-ui Angular bundle budgets to 1mb/1.5mb (interrupts exceeds
  the previous 500kb/1mb default by ~20kb)
- gitignore deploy/ (assemble-examples build output)
- Update spec §5 + §5b to match the actual architecture

The ag-ui-dev Railway service is unchanged and already live; this PR
just makes the Vercel side actually reach it. Verified locally:
assemble-examples runs end-to-end, produces 33 example dirs + both
proxy functions, route table includes ag-ui ordering.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@blove blove enabled auto-merge (squash) June 6, 2026 04:39
@blove blove merged commit 69f02c6 into main Jun 6, 2026
46 checks passed
blove added a commit that referenced this pull request Jun 8, 2026
* docs(spec): cockpit examples local-dev + validator cleanup

Fixes the serve/deploy gaps found in the wiring audit (metadata is symmetric;
tooling isn't): local runtime resolution never points the iframe at localhost
(env var never set ''), stale 8123 backend port, serve-example mis-launches
ag-ui as langgraph dev, serve-target asymmetry, and validator coverage gaps
(registry<->ports parity, smoke omits chat+render, dead fallback code).

Scoped to local-dev + serve ergonomics + validators only. ag-ui production is
owned by PR #580 (Railway); this lands after #580 and rebases onto it.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs(plan): cockpit examples local-dev + validator cleanup

8 tasks: rebase onto post-#580 main, registry-derived backendCommand +
local runtime env in serve-example (TDD), serve-* targets -> aliases,
registry<->ports parity assertion, per-product smoke coverage + chat/render
reps, remove dead wiring-spec fallback, manual local-serve acceptance.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(cockpit): registry-derived backendCommand + local runtime env for serve-example

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* refactor(cockpit): serve-example uses registry backend cmd + local runtime env, drops 8123

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* refactor(cockpit): serve-* targets delegate to serve-example (single source of truth)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* test(cockpit): assert capability-registry ports match ports.mjs

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* test+ci(cockpit): smoke job must represent every product; add chat+render reps

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(cockpit): give chat+render smoke reps real smoke targets; assert listed projects have them

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* refactor(cockpit): drop dead 'cap not in registry' fallback in wiring spec

All caps are now in cockpit/ports.mjs so the try/catch fallback and
parseNumberProperty helper are dead code; call portsFor directly.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
blove added a commit that referenced this pull request Jun 9, 2026
* docs(spec): ag-ui railway deployment design

Spec for hosting the AG-UI FastAPI runtimes on Railway so the cockpit
"Run" tab can reach them. Mirrors the langgraph deployment pattern
(generated multi-topic app + drift-checked CI), adds defense-in-depth
(Vercel edge origin + rate limit + internal token, Railway middleware,
OpenAI spend cap) above the langgraph proxy's current posture.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* test(scripts): failing tests for ag-ui deployment generator

* feat(scripts): ag-ui deployment generator (server.py, requirements.txt, deps/)

* chore(scripts): drop unused existsSync import

* chore(ag-ui-dev): commit generated server.py + requirements.txt + deps/

* feat(ag-ui-dev): Dockerfile + entrypoint watchdog + railway.json

* docs(ag-ui-dev): operator README

* feat(cockpit/ag-ui): add vercel rewrites to ag-ui-dev railway backend

* feat(cockpit/ag-ui/interrupts): vercel edge middleware (origin + rate limit + token)

* fix(cockpit/ag-ui/interrupts): hoist middleware deps to root per cockpit pattern

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(cockpit/ag-ui/streaming): vercel edge middleware (origin + rate limit + token)

* ci: deploy ag-ui-dev to Railway on push to main

* test(smoke): probe ag-ui-dev railway healthcheck + examples routes

* docs(plan): ag-ui railway deployment implementation plan

* fix(ag-ui-dev): exclude nx project.json + tsconfig from staged deps

cpSync was copying project.json into deployments/ag-ui-dev/deps/<topic>/,
which made nx see duplicate project definitions (e.g.
cockpit-ag-ui-interrupts-python defined in both cockpit/ag-ui/.../python
and deployments/.../deps/interrupts). Broke `nx show projects --affected`
and the CI scope job downstream.

Filter project.json + tsconfig*.json out of the cpSync. Regenerate to
drop the stale copies under deps/.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(ag-ui): use actual Railway domain (ag-ui-dev-production.up.railway.app)

Railway's default domain scheme is <service>-<environment>.up.railway.app,
not <service>.up.railway.app. The created service resolves to
ag-ui-dev-production.up.railway.app. Update vercel.json rewrites + smoke
test default to match.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(ag-ui-dev): emit only direct deps in requirements.txt

Taking the highest version per package across two uv-locked requirements
produces internally-inconsistent sets: e.g. interrupts pins
websockets==16.0 (compatible with its langgraph-sdk==0.3.15), streaming
pins langgraph-sdk==0.4.2 (which requires websockets<16). Union picked
both, broke pip resolution.

Parse `# via cockpit-*` markers to identify direct deps from each example
(fastapi, ag-ui-langgraph, langchain-openai, langgraph, langsmith,
uvicorn) and emit only those. Pip resolves transitives at install time.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(ag-ui-dev): return JSONResponse from auth middleware (not HTTPException)

HTTPException raised inside Starlette BaseHTTPMiddleware doesn't hit
FastAPI's handler — it bubbles up and Starlette returns 500. Live deploy
confirmed: /agent/interrupts without token returned 500 instead of 401.

Switch to returning JSONResponse directly. Verified on live deploy:
401 returns correctly for missing/wrong tokens.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(ag-ui): integrate Vercel side via assemble-examples, not per-example projects

Discovered late that examples.threadplane.ai is served by a single
Vercel project (threadplane-examples) populated via the Build Output
API by scripts/assemble-examples.ts — not by per-example Vercel
projects. The per-example vercel.json rewrites + middleware.ts files
from earlier commits were dead code (Vercel never consumes them).

Changes:
- Revert per-example vercel.json rewrites to original buildCommand-only
- Delete cockpit/ag-ui/*/angular/middleware.ts + tsconfig.middleware.json
- Drop @vercel/edge from root package.json (kept @upstash/* — needed by
  the new Node serverless proxy)
- Restore peerDependencies to original shape (@threadplane/* only)
- Add ag-ui to capabilities + SPA route regex in assemble-examples.ts
- Add new route above filesystem handle:
    /ag-ui/<topic>/agent[/rest] → /api/ag-ui-proxy/<topic>[/rest]
- New scripts/ag-ui-proxy.ts: Node Vercel function that does origin
  allowlist + Upstash rate limit (fail-open if creds missing) +
  X-Internal-Token injection + streaming-aware fetch to the Railway
  service. Bundled by assemble-examples into
  .vercel/output/functions/api/ag-ui-proxy/[[...path]].func
- Bump ag-ui Angular bundle budgets to 1mb/1.5mb (interrupts exceeds
  the previous 500kb/1mb default by ~20kb)
- gitignore deploy/ (assemble-examples build output)
- Update spec §5 + §5b to match the actual architecture

The ag-ui-dev Railway service is unchanged and already live; this PR
just makes the Vercel side actually reach it. Verified locally:
assemble-examples runs end-to-end, produces 33 example dirs + both
proxy functions, route table includes ag-ui ordering.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs(plan): postscript documenting rework + bugs caught during execution

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
blove added a commit that referenced this pull request Jun 9, 2026
* docs(spec): cockpit examples local-dev + validator cleanup

Fixes the serve/deploy gaps found in the wiring audit (metadata is symmetric;
tooling isn't): local runtime resolution never points the iframe at localhost
(env var never set ''), stale 8123 backend port, serve-example mis-launches
ag-ui as langgraph dev, serve-target asymmetry, and validator coverage gaps
(registry<->ports parity, smoke omits chat+render, dead fallback code).

Scoped to local-dev + serve ergonomics + validators only. ag-ui production is
owned by PR #580 (Railway); this lands after #580 and rebases onto it.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs(plan): cockpit examples local-dev + validator cleanup

8 tasks: rebase onto post-#580 main, registry-derived backendCommand +
local runtime env in serve-example (TDD), serve-* targets -> aliases,
registry<->ports parity assertion, per-product smoke coverage + chat/render
reps, remove dead wiring-spec fallback, manual local-serve acceptance.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(cockpit): registry-derived backendCommand + local runtime env for serve-example

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* refactor(cockpit): serve-example uses registry backend cmd + local runtime env, drops 8123

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* refactor(cockpit): serve-* targets delegate to serve-example (single source of truth)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* test(cockpit): assert capability-registry ports match ports.mjs

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* test+ci(cockpit): smoke job must represent every product; add chat+render reps

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(cockpit): give chat+render smoke reps real smoke targets; assert listed projects have them

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* refactor(cockpit): drop dead 'cap not in registry' fallback in wiring spec

All caps are now in cockpit/ports.mjs so the try/catch fallback and
parseNumberProperty helper are dead code; call portsFor directly.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant