ICS Quickstart
Interactive Configuration Sandbox — standalone dev loop for the 14
upsquad-core binaries + caveman UI, running end-to-end on a laptop
against a compose-managed Postgres and Redis.
- HLD: #763
- LLD-5 (compose layout): #769
- Bug #828 fixes (2026-04-21): env gaps, migration runner, writable report volume, auto-mounted age key, this doc.
Prerequisites
- Docker Engine 24+ with compose v2 (
docker compose version) - ~8 GB free RAM for the 14 binaries + pgvector + redis + otel-collector
ageCLI on PATH (brew install age/apt-get install age)- You are NOT currently running
docker-compose.dev.yml— the ICS guardrail refuses to start if the dev stack is up and ICS state is also present, to avoid double-loading the daemon (HLD §3.4).
First run (fully scripted, no manual steps)
# From repo root:
./scripts/ics/bootstrap.sh && \
docker compose -f docker-compose.ics.yml --env-file .env.ics up -d
bootstrap.sh is idempotent. It:
- Copies
.env.ics.example→.env.icsif.env.icsdoesn't exist. - Runs
scripts/ics/guard.shto refuse if the dev stack conflicts. - Runs
scripts/ics/ensure-age-key.shto generate the age identity under${UPSQUAD_ICS_STATE_DIR:-$HOME/.upsquad/ics}. The compose file bind-mounts that directory read-only into thesecret-backendcontainer at/var/run/upsquad-ics, so no manualdocker cpstep is needed.
The up -d itself runs a one-shot migrate service that applies
every migration from internal/context/store/migrations/ before any
app service starts. Re-running up -d against an already-migrated DB
is a no-op (migrate records versions in schema_migrations and exits
with "no change" in < 1 second).
Wait for readiness:
./scripts/ics/smoke.sh # blocks up to 5 min; returns 0 when all 17 ports up
# or
make ics-smoke
Open the caveman UI at http://127.0.0.1:21080. It talks to the gateway at http://127.0.0.1:21081, which fans out to the 14 binaries on 21010–21024.
Subsequent runs
docker compose -f docker-compose.ics.yml --env-file .env.ics up -d
.env.ics and the age key persist across runs, so bootstrap is
optional after the first successful bring-up. The Make shortcut
(make ics-up) wraps this with the guardrail + ensure-age-key.
Seed 2 tenants + secret inventory
make ics-seed
Shut down
# Stop containers, keep volumes + age key (fast restart later):
docker compose -f docker-compose.ics.yml --env-file .env.ics down
# equivalently: make ics-down
# Clean teardown — wipes postgres, redis, otel, report, and encrypted
# secret-blob volumes. The age key on the host is ALSO wiped so the
# next `up` regenerates a fresh identity:
docker compose -f docker-compose.ics.yml --env-file .env.ics down -v
# (make ics-reset additionally rm's any dangling upsquad_ics_* volumes
# and will regenerate the age key on next `make ics-up`.)
Ports (loopback only)
| Port | Service |
|---|---|
| 21000 | postgres |
| 21001 | redis |
| 21002 | otel-collector (OTLP gRPC) |
| 21010 | context-engine |
| 21011 | agent-orchestrator |
| 21012 | ai-gateway |
| 21013 | reconciler |
| 21014 | compliance-engine |
| 21015 | approval-scheduler |
| 21016 | audit-verify |
| 21017 | subagent-coordinator |
| 21018 | (unbound — jti-rollout-controller removed from ICS, #836) |
| 21019 | siem-export-worker (HTTP health on /internal/siem-health — batch daemon, no gRPC) |
| 21020 | reconcile-report (one-shot) |
| 21021 | schema-coverage-aggregator (one-shot) |
| 21022 | vault-seed (one-shot) |
| 21023 | (unbound — check-proto-callsites removed from ICS, #837) |
| 21024 | secret-backend |
| 21080 | caveman-ui (browser) |
| 21081 | gateway (Connect-RPC) |
Environment contract
All defaults in .env.ics.example. Key flags:
| Var | Default | Purpose |
|---|---|---|
ICS_MODE | true | Enables relaxed sandbox behaviour across binaries |
DISABLE_AUTH | true | Bypass auth for sandbox only — CI asserts both must be true or both false |
ICS_ENABLE_REAL_LLM | false | Stubbed LLM responses; real tenant keys when true |
SANDBOX_IMAGE_TAG | latest | Pin to a digest for reproducibility |
UPSQUAD_ICS_STATE_DIR | $HOME/.upsquad/ics | Host-side age key directory |
The compose x-ics-env anchor supplies DATABASE_URL, REDIS_URL,
and dummy OPENAI_API_KEY / ANTHROPIC_API_KEY / GOOGLE_API_KEY /
LAYER_SIGNING_KEY / VAULT_PASSPHRASE to every binary. The dummy
external-API keys pass the context-engine startup gate; they are
ignored at runtime because ICS_ENABLE_REAL_LLM=false routes to the
stubbed LLM path.
DO NOT set real API keys in .env.ics unless you're intentionally
exercising the A/B/C/D sourcing modes. Leaked real keys in .env.ics
are still leaked secrets.
Troubleshooting
| Symptom | Cause | Fix |
|---|---|---|
make ics-up refuses with "upsquad-dev is running" | Guardrail caught dev+ICS conflict | docker compose -f docker-compose.dev.yml down OR make ics-reset |
age-keygen not found | Missing age on PATH | brew install age / apt-get install age |
sk-ics-dummy-ignored-by-stub-mode in logs | Expected; stub mode is active | Nothing to do |
Binary crash-loops with relation ... does not exist | Migrations didn't run | Check docker logs upsquad-ics-migrate — it should exit 0 |
reconcile-report can't write | Bug repro for #828 | Verify upsquad_ics_reports volume is mounted and --output-dir=/var/lib/upsquad-ics/reports |
Known remaining TODOs
Nothing at this quickstart level. For deeper flows (secret-backend
rotation, SIEM export against real vault key material, multi-tenant
seed at SCALE > 0), see LLD-2 #769 and LLD-5 #769 §2.3.