Architecture

Straden is a Laravel 13 / Livewire application packaged as a single Docker image. At deploy time that image runs as four services, alongside three stock data stores. See the interactive diagram for a visual walkthrough.


Services

ServiceImageRole
appstradenWeb UI and MCP server on FrankenPHP (Caddy + PHP 8.4). Handles HTTPS, proxies WebSockets to Reverb, runs migrations on boot.
reverbstradenLaravel Reverb WebSocket server.
runnerstradenLaravel Horizon queue worker. Executes k6 (v2.0.0), AI agent jobs and repository syncs.
schedulerstradenphp artisan schedule:work — polls running tests.
postgrespostgres:17-alpineApplication database.
redisredis:7-alpineQueues, cache and sessions.
influxdbinfluxdb:1.8k6 time-series metrics (database k6).

The image entrypoint reads STRADEN_ROLE (app, reverb, runner or scheduler) and becomes that process. All four Straden services share the storage volume, which holds scripts, run logs, cloned repositories and the generated instance secrets.

Request path

Browsers talk only to the app container. Caddy serves the Livewire UI and forwards /app/* and /apps/* to Reverb, so WebSockets use the same origin as the page (wss://your-domain/app/...). There is no separate websocket port to expose and no environment baked into the frontend build.

MCP clients call POST /mcp on the same container, authenticated with a Sanctum token.

Life of a run

  1. Queued — starting a script (UI or MCP start-run) creates a runs row with status queued and dispatches RunTestJob to the runs queue.

  2. Running — Horizon on the runner picks the job up, resolves the script's options into a run config and starts k6 in its own session:

    BASH
    k6 run script.js \
      --summary-export=<run>.json \
      --summary-trend-stats='avg,min,med,max,p(90),p(95),p(99)' \
      --out influxdb=http://influxdb:8086/k6 \
      --tag run_id=… --tag test_id=… --tag script_id=…
    

    TARGET_URL is passed as an environment variable. The k6 PID, console log and exit code are written to files on the shared volume.

  3. Live view — the run page tails the log and charts metrics straight from InfluxDB. Status changes and notifications arrive over Reverb.

  4. Finalised — every 30 seconds the scheduler queues CheckRunningTestsJob. When a k6 process has exited, the summary is parsed and the exit code mapped to a status:

    k6 exit codeStatus
    0passed
    99, 104failed (thresholds crossed)
    105, 130, 137, 143aborted
    anything elseerror

    A run whose only problem was a teardown() timeout after a clean load (0% failed requests) is still passed, with a warning.

  5. Insight — optionally, GenerateRunInsightJob asks the Run Insight agent for a structured report.

Background jobs

JobQueuePurpose
RunTestJobrunsStart k6 for a queued run. One at a time per script.
CheckRunningTestsJobrunsFinalise runs whose k6 process has exited. Scheduled every 30s.
CancelRunJobrunsSend SIGTERM to a running k6 process (runs on the runner, where the process lives).
ChatAgentJobdefaultRun a Test / Script agent turn and broadcast the streamed response.
GenerateRunInsightJobdefaultProduce a run insight report.
SyncRepositoryJobdefaultClone or pull a repository and index its file tree.

Realtime channels

ChannelUsed for
App.Models.User.{id}Notifications (run completed, insight ready, sync finished…)
test.{testId}Streamed agent chat events for a test
project.{projectId}Repository sync progress

Data model

User → Project → Test → Script → Run → RunInsight. Projects also own Repository and Connector records; connectors can be attached to individual tests. AI provider credentials and settings are instance-wide and encrypted with APP_KEY.