Install
Running the whole stack on a laptop. If you only want to call the API of an existing deployment, you do not need any of this — go to Quickstart.
Requirements
| Version | Why | |
|---|---|---|
| PHP | 8.3 or newer; 8.4 is what it is developed against | The control plane is Laravel 13. |
| Node | 22 or newer; 24 is what it is developed against | The gateway, SDK and CLI are ESM-only. |
| pnpm | 11 | Workspace package manager. |
| Docker | any recent | Postgres 17 + pgvector, Redis, NATS JetStream, MinIO, FreeSWITCH, and the observability stack. |
CallAgent is developed against Laravel Herd for PHP and Docker for the backing services.
The stack
git clone <repo> callagent
cd callagent
pnpm install
pnpm infra:up # docker compose up -d
cp apps/control-plane/.env.example apps/control-plane/.env
php apps/control-plane/artisan key:generate
php apps/control-plane/artisan callagent:generate-credential-key
php apps/control-plane/artisan migrate --seedpnpm infra:up starts Postgres, Redis, NATS, MinIO, FreeSWITCH and the Prometheus / Loki / Tempo / Grafana stack. The SIP test peer and SIPp are behind a sip-lab compose profile and are not started by default:
docker compose --profile sip-lab up -dPorts are shifted on purpose
Postgres is on 5433, Redis on 6380, MinIO on 9002/9003, FreeSWITCH's ESL on 8022. They are moved off the defaults so a local Postgres or Redis you already run is not shadowed. .env.example already points at the shifted ports; if you change one in docker-compose.yml, change it in .env too.
callagent:generate-credential-key is not optional. Tenant provider credentials are encrypted with a key deliberately separate from APP_KEY, so that rotating the application key does not mean re-encrypting every tenant's secrets. Without it, storing a credential fails.
Check it before you go further
php apps/control-plane/artisan callagent:check+-----------------------+--------+----------------------------------+
| Component | Status | Detail |
+-----------------------+--------+----------------------------------+
| PostgreSQL | OK | v17.11 |
| pgvector | OK | v0.8.6 |
| Redis | OK | reachable |
| Object storage | OK | read/write ok |
| Credential encryption | OK | key valid, round-trip ok |
| Voice Gateway | OK | healthy |
| NATS | OK | healthy |
| FreeSWITCH ESL | OK | port open |
+-----------------------+--------+----------------------------------+This is the fastest way to find a wrong port or a missing key. Fix every row before following the quickstart.
php artisan callagent:doctor is the next one to reach for: it diagnoses class-load faults that stop the application booting at all.
doctor can be killed by the fault it is meant to report
It autoloads the application's classes to find a broken one, so a fatal class-load error takes doctor down with it and you get the raw PHP error instead of a report. That is still the answer — read the fatal, it names the file and the line. callagent:check does not autoload the dashboard and keeps working when doctor does not.
Run it
Three processes, three terminals:
pnpm cp:serve # control plane on :8000
pnpm --filter @callagent/voice-gateway dev # voice gateway on :3010
php apps/control-plane/artisan horizon # jobs: webhooks, artifacts, retentionThe queue worker matters more than it looks. Webhook delivery, artifact relocation, retention and post-call transcription are all queued on Redis. Without a worker they never happen, and nothing in the API says so. php artisan queue:work does the same job if you would rather not run Horizon.
Health:
curl http://localhost:8000/api/health
curl http://localhost:3010/health # also lists every provider adapter registeredThe gateway's health response is the authoritative answer to "which providers does this build know how to speak to".
migrate:fresh is not safe in a shared checkout
migrate:fresh, migrate:rollback and db:wipe drop data other work in flight may be using. Plain php artisan migrate is idempotent.
Scheduled work
Several things only happen on a schedule. In development, run the scheduler:
php apps/control-plane/artisan schedule:workIt drives retention (daily at 03:15), analytics rollups, webhook retry re-queueing, the contact-centre queue tick, monitor evaluation, SIP OPTIONS probing and campaign dispatch. A queue that never offers a call to an operator is usually this.
Packages
pnpm build # everything
pnpm --filter @callagent/web-sdk build # browser SDK + widget bundles
pnpm --filter @callagent/cli build # the `callagent` binary
pnpm --filter @callagent/docs-site build # this siteThe CLI
Not published to npm
@callagent/cli and @callagent/web-sdk are private: true workspace packages. There is no npm i -g @callagent/cli and no public CDN bundle. Build from the workspace:
pnpm --filter @callagent/cli build
node apps/cli/dist/index.js --versionLink it if you want the bare callagent command:
cd apps/cli && npm linkThen:
callagent login # prompts, echo suppressed
callagent whoamiSee the CLI reference for every command and flag.
The browser SDK
Same caveat — build it from the workspace and serve the bundle yourself:
pnpm --filter @callagent/web-sdk build
# dist/index.js and dist/widget.js<script src="/your-own-path/widget.js"
data-public-key="ca_pk_…"
data-assistant-id="01JQ…"></script>The public key prefix is ca_pk_. See the widget reference.
Tests
pnpm test # every workspace package
pnpm cp:test # the control plane's Pest suite
pnpm --filter @callagent/voice-gateway testNext
- Quickstart — a published assistant with a number on it.
- What is not built — read before planning around a feature.