This repo is standalone-tested (typecheck clean, unit suite green against a fake Railway API + a real in-process relay/bridge). The items below are what the integrating agent must wire before real Railway runs work end to end.
The plugin starts the run service with start command animus-env-bridge
(override: ANIMUS_ENV_BRIDGE_COMMAND), but
ghcr.io/launchapp-dev/animus:v0.7.0-rc.2 does not include that
entrypoint. Either:
- Preferred: add
@launchapp-dev/animus-env-transportto the base image build (npm install -g @launchapp-dev/animus-env-transportor copy the package and symlinkdist/bridge-cli.jsto/usr/local/bin/animus-env-bridge), bumping the image tag andDEFAULT_IMAGEhere; or - have the plugin inject it at prepare: set
ANIMUS_ENV_BRIDGE_COMMANDto a bootstrap that fetches the bridge (e.g.npx -y @launchapp-dev/animus-env-transportonce it is published to npm) — slower cold start, no image rebuild.
The transport carries a container→daemon reverse RPC channel
(BridgeClient.request(method, params) → RelayServer.onReverseRpc). It is
scaffolded: this plugin does not yet pass an onReverseRpc handler, so
reverse calls answer ReverseNotWired (-32011). To close the gap:
- decide the method namespace (suggested:
mcp/call,subject/*) and implement a daemon-side proxy handler in this plugin that forwards to the kernel's MCP surface for the run's Actor; - have the in-container harness point its
animusMCP endpoint at the bridge (the bridge already multiplexes those frames over the same socket).
Unit tests mock fetch / the Railway API. To run the gated integration suite
(environment.test.ts, bottom describe.skipIf):
RAILWAY_TOKEN— API token with service create/delete on the projectRAILWAY_PROJECT_ID,RAILWAY_ENVIRONMENT_IDANIMUS_ENV_RELAY_PUBLIC_URL— awss://URL reachable FROM Railway that routes to this plugin's relay port (ANIMUS_ENV_RELAY_PORT); on Railway this is the daemon service's public domain with the relay port exposed.
The GraphQL field shapes (serviceCreate / serviceInstanceUpdate /
serviceInstanceDeployV2 / serviceDelete / project.services) were written
against the public backboard v2 API but are integration-pending — verify
against a live project and adjust src/railway.ts builders if the schema
drifted.
The kernel (ao-cli v0.7.0-rc.2 EnvironmentClient + resolve_environment)
routes EnvironmentSpec.kind → plugin. Register this plugin for
kind = "railway" (subject_kind × harness → "railway" where remote execution
is wanted), and add it to the relevant animus.toml / install docs
(animus plugin install launchapp-dev/animus-environment-railway).
Phase 2's exec seam is gated behind ANIMUS_ENVIRONMENT_EXEC (166). Once the
workflow runner executes phases through EnvironmentClient, the streamed
environment/output notifications from this plugin land in the run transcript
unchanged — no plugin-side work expected, but verify the runner honors
exec_stream for long-lived agent phases and passes timeout_secs.
- Teardown/GC:
teardownis idempotent (missing service = success);RailwayEnvironment.gcOrphans()sweeps this instance's orphanedanimus-run-<instanceId>-*services (never another live instance's);gcOrphans({ allInstances: true })also reaps crashed-instance leftovers — safe only when a single plugin instance manages the project. Schedule one of these (daemon housekeeping) once routing lands. - Secrets: the per-run relay token is minted daemon-side and injected as
ANIMUS_ENV_RUN_TOKEN; rotate by teardown/prepare.RAILWAY_TOKENbelongs in the OS keychain viaanimus secret set RAILWAY_TOKEN. - Relay lifetime: one
RelayServerper plugin process, shared by runs; handles die with the process, so a plugin restart orphans running services — that is what the GC sweep is for.