feat(ag-ui): host runtimes on Railway via ag-ui-dev deployment#580
Merged
Conversation
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>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
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>
…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>
4 tasks
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
deployments/ag-ui-dev/Railway deployment hosting everycockpit/ag-ui/*/python/topic at/agent/<topic>from one FastAPI app.scripts/generate-ag-ui-deployment-config.ts(mirrorsscripts/generate-shared-deployment-config.ts); CI drift-checksdeployments/ag-ui-dev/.X-Internal-Tokeninjection) on each ag-ui example; Railway FastAPI middleware verifies the same token; OpenAI project hard cap is the final backstop./ok; production smoke tests probe it + the two examples routes.Spec:
docs/superpowers/specs/2026-06-05-ag-ui-railway-deployment-design.mdPlan:
docs/superpowers/plans/2026-06-05-ag-ui-railway-deployment.mdArchitecture
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):
AG_UI_INTERNAL_TOKENviaopenssl rand -hex 32ag-ui-dev, generate domain, project token → GitHub secretRAILWAY_TOKENOPENAI_API_KEY,AG_UI_INTERNAL_TOKEN,UPSTASH_REDIS_REST_URL,UPSTASH_REDIS_REST_TOKENAG_UI_INTERNAL_TOKEN+UPSTASH_*vars on both Vercel projects (cockpit-ag-ui-interrupts-angular,cockpit-ag-ui-streaming-angular)Test plan
railway upfromdeployments/ag-ui-dev/succeeds;curl https://ag-ui-dev.up.railway.app/okreturns{"ok":true};curl https://ag-ui-dev.up.railway.app/agent/interruptswithout token returns 401.Deploy AG-UI Railwayworkflow fires on push to main, runs drift check +railway up.apps/cockpit/e2e/production-smoke.spec.tsdescribe block "ag-ui Railway runtime").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