Skip to main content

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
  • age CLI 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:

  1. Copies .env.ics.example.env.ics if .env.ics doesn't exist.
  2. Runs scripts/ics/guard.sh to refuse if the dev stack conflicts.
  3. Runs scripts/ics/ensure-age-key.sh to generate the age identity under ${UPSQUAD_ICS_STATE_DIR:-$HOME/.upsquad/ics}. The compose file bind-mounts that directory read-only into the secret-backend container at /var/run/upsquad-ics, so no manual docker cp step 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)

PortService
21000postgres
21001redis
21002otel-collector (OTLP gRPC)
21010context-engine
21011agent-orchestrator
21012ai-gateway
21013reconciler
21014compliance-engine
21015approval-scheduler
21016audit-verify
21017subagent-coordinator
21018(unbound — jti-rollout-controller removed from ICS, #836)
21019siem-export-worker (HTTP health on /internal/siem-health — batch daemon, no gRPC)
21020reconcile-report (one-shot)
21021schema-coverage-aggregator (one-shot)
21022vault-seed (one-shot)
21023(unbound — check-proto-callsites removed from ICS, #837)
21024secret-backend
21080caveman-ui (browser)
21081gateway (Connect-RPC)

Environment contract

All defaults in .env.ics.example. Key flags:

VarDefaultPurpose
ICS_MODEtrueEnables relaxed sandbox behaviour across binaries
DISABLE_AUTHtrueBypass auth for sandbox only — CI asserts both must be true or both false
ICS_ENABLE_REAL_LLMfalseStubbed LLM responses; real tenant keys when true
SANDBOX_IMAGE_TAGlatestPin to a digest for reproducibility
UPSQUAD_ICS_STATE_DIR$HOME/.upsquad/icsHost-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

SymptomCauseFix
make ics-up refuses with "upsquad-dev is running"Guardrail caught dev+ICS conflictdocker compose -f docker-compose.dev.yml down OR make ics-reset
age-keygen not foundMissing age on PATHbrew install age / apt-get install age
sk-ics-dummy-ignored-by-stub-mode in logsExpected; stub mode is activeNothing to do
Binary crash-loops with relation ... does not existMigrations didn't runCheck docker logs upsquad-ics-migrate — it should exit 0
reconcile-report can't writeBug repro for #828Verify 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.