This guide walks you through running Sico locally. Two paths are supported:
- Docker Compose: fastest path, runs everything in containers.
- Kind + Helm: local Kubernetes, closer to production.
For a developer setup (building services from source, writing code, running tests) see Development.
| Tool | Needed for | Notes |
|---|---|---|
| Docker & Docker Compose | Both paths | Docker Desktop on macOS / Windows, or native Docker on Linux |
make |
Both paths | macOS/Linux native; on Windows, install or run scripts directly |
kind |
Kubernetes path | |
helm |
Kubernetes path | |
kubectl |
Kubernetes path | |
| An LLM API key | Running agents | OpenAI / Azure OpenAI / Anthropic / Gemini / OpenRouter |
git clone https://github.com/microsoft/Sico.git
cd Sico
cp .env.example .envEdit .env:
- Set
DB_PASSWORD,REDIS_PASSWORD, and other secrets (the defaults are only safe for quick local use). - Keep
APP_ENV=developmentfor verbose logs locally.
Before starting the stack, configure at least one LLM model. Create a YAML file under
deploy/config/llmhubs/<your-model>.yaml (use deploy/config/llmhubs/model-template.yaml
or one of the *-template.yaml files as a starting point) and make sure it contains:
default: true # mark this model as the default for the platformSico also supports dynamically registering models via the Model Registry API
(POST /api/sico/llm/models). See LLM Hub docs for the schema and examples,
../examples/README.md for the examples index, and
../examples/llmhubs/README.md for runnable llmhubs samples.
Configure Mem0 for long-term memory:
cp deploy/config/mem0/mem0_config_template.yaml deploy/config/mem0/mem0_config.yaml
# then edit deploy/config/mem0/mem0_config.yaml to fill in embedder / llm credentialsIf you plan to hit sandbox-client endpoints locally, set per-client HMAC secrets in .env:
SANDBOX_CLIENT_SECRET_TEST_CLIENT=change-me-local-secretThis matches the default clientId (test-client) used by examples/sandbox/apply_and_release.py. For a custom client ID, derive the variable name by uppercasing it and replacing - with _, for example my-client -> SANDBOX_CLIENT_SECRET_MY_CLIENT.
Nonce replay protection is stored in Redis, so REDIS_HOST must be reachable.
The default Docker Compose backend now forwards SANDBOX_CLIENT_SECRET_TEST_CLIENT
from the repo-root .env, so copying .env.example to .env is enough for the
default test-client example path.
If you plan to collaborate with the Android Tester, you will need the Android emulator sandbox.
Install MuMu Player (Windows) or set up the Android SDK / AVD (macOS) and start the emulator API service before bringing up the stack. The emulator runs on a GUI host and cannot be containerized.
# Install prerequisites and start the API service.
make emulator-setup
# Verify the API service is running.
make emulator-status
# Bootstrap the default emulator device.
make emulator-bootstrapIf the API service is already running but make emulator-bootstrap reports a missing Java runtime or AVD, rerun
make emulator-setup, then retry make emulator-bootstrap so the host Java/SDK/AVD prerequisites are repaired before
bootstrap.
Set SANDBOX_EMULATOR_BASE_URL in .env so the Backend can reach it. For local
compose/kind it is pre-set to http://host.docker.internal:8000.
See sandbox/emulator/setup/README.md for host setup and lifecycle management. The lower-level service API is documented in sandbox/emulator/README.md.
make compose-up # build images and start the full stackAfter changing service code or local config (.env, deploy/config/) while the compose stack is running, run make compose-up again. If only one service's code or image-scoped config changed, use make compose-up SERVICE=core or make compose-up SERVICE=backend.
This starts:
| Service | Purpose |
|---|---|
| nginx | Reverse proxy at http://localhost:8080 |
| backend | Go service: HTTP :8080, reverse gRPC :50054 |
| core | Python service: gRPC :50053 |
| mysql | Database |
| redis | Cache, locks, blacklist |
make compose-logs # tail logs from all services
make compose-down # stop and remove containersThe compose stack exposes nginx, backend, core, and the supporting infrastructure services they depend on. Frontend source code is not currently published in this repository; this checkout does not include frontend/package.json, and the UI is provided separately as a packaged archive.
Once the compose stack is up, the shortest sandbox example flow is:
python3 -m examples.sandbox.apply_and_releaseThe default example uses SANDBOX_CLIENT_ID=test-client and AGENT_INSTANCE_ID=2.
It can only lease sandboxes that have already been assigned to that instance, so
make sure at least one emulator sandbox is assigned to the seeded tester
instance (2) before running the example.
If the script prints No sandbox was allocated. Nothing to release., the auth
path is working but no matching sandbox is currently assigned or available for
that instance.
make kind-upThis does the following:
- Starts a local Docker registry on port
5000. - Creates or reuses a Kind cluster named
sico. - Builds and loads app images for Backend, Core, and Frontend.
- Creates local credentials and applies infrastructure manifests.
- Deploys app services via local Helm charts.
- Port-forwards the local stack to http://localhost:8080.
After changing the Kind stack while it is running, run make kind-up again. If only one app service's code or image changed, use make kind-restart SVC=core or make kind-restart SVC=backend.
Stop the cluster without deleting data:
make kind-stopTear down and delete local cluster data:
make kind-downThe Docker Compose and Kind stacks expose nginx, backend, core, and supporting
infrastructure. Frontend source code is currently distributed separately as a
packaged archive, so this public checkout does not contain a runnable frontend
source package or frontend/package.json. If you are working from a separate
frontend source checkout, run the React UI in a separate terminal with
cd frontend && pnpm install && pnpm dev, then open
http://localhost:8080/login and sign in with the
default account shown in §4 below.
For API service lifecycle management (make emulator-start / make emulator-status /
make emulator-logs) and explicit device bootstrap (make emulator-bootstrap), see
sandbox/emulator/setup/README.md.
Once the stack is up, try:
# Home
open http://localhost:8080/
# UI login
open http://localhost:8080/login
# Developer interface
open http://localhost:8080/developer
# Swagger / OpenAPI (Backend)
open http://localhost:8080/api/sico/docs/index.html
# Health probe (Backend)
curl http://localhost:8080/api/sico/health
# Health probe (Core)
# Core is internal, but the Backend exposes an aggregated health view.Sign in with the seeded default account (local development only — rotate or remove before exposing the stack outside your machine):
- Username:
operator@sico.local - Password:
operator
In Sico a Digital Worker (DW) has two layers:
- Role — a fixed enum (
Assistant,Android Tester,3D Artist,Product Manager,Marketing) defined inbackend/internal/shared/enum/agent_roles.go. New roles require editing this enum and rebuilding the backend; they cannot be added at runtime. - Skill — a plug-in capability package (
SKILL.md+ scripts/code). Skills can be added at runtime through the HTTP API and are what actually decide how a DW behaves.
So "creating a new DW type" in practice means: author a Skill, then create a SingleAgent (the DW) and attach the skill to it.
| # | Method + Path | Purpose |
|---|---|---|
| 1 | POST /api/sico/rbac/login |
Get a JWT. |
| 2 | POST /api/sico/project/asset (multipart) |
Upload the skill zip; returns assetId. |
| 3 | POST /api/sico/agent/single_agent |
Create the DW (SingleAgent) with a valid role. Returns agentId. |
| 4 | POST /api/sico/skills |
Register the uploaded asset as a skill scoped to the new agentId. Note: projectId and agentId are mutually exclusive — send exactly one. |
| 5 | POST /api/sico/agent/single_agent/deploy |
Create a runnable instance of the DW for the current user. |
Helpful read-only endpoints:
GET /api/sico/agent/roles— list valid role values.GET /api/sico/agent/single_agents?page=1&pageSize=50— list existing DWs (max page size 50).
-
Write a Skill package. Minimum required content is a single
SKILL.mdwith YAML frontmatter at the root of a directory:--- name: web-researcher description: Research a topic on the public web and produce a structured brief. argument-hint: Describe the topic to research and the desired depth. --- # Web Researcher Skill ## When to use - The user asks for a structured research brief on a public topic. ## Workflow 1. Clarify scope. 2. Search with `web_search` (or any available browsing tool). 3. Synthesize: TL;DR + key facts + open questions + source list. 4. Deliver in Markdown.
Reference layout:
backend/internal/embeddata/skills/android-tester/. -
Login and grab a JWT.
curl -s -X POST http://localhost:8080/api/sico/rbac/login \ -H 'Content-Type: application/json' \ -d '{"email":"operator@sico.local","password":"operator"}' # -> data.tokenInfo.accessToken
-
Upload the skill as a project asset. Zip the skill directory and POST it as multipart:
curl -s -X POST http://localhost:8080/api/sico/project/asset \ -H "Authorization: Bearer $TOKEN" \ -F project_id=1 \ -F 'file=@web-researcher.zip;type=application/zip' # -> data.id (the assetId)
-
Create the DW. Pick a role from
GET /api/sico/agent/roles;Assistantis the safe default for new skills.curl -s -X POST http://localhost:8080/api/sico/agent/single_agent \ -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \ -d '{"name":"My Web Researcher","desc":"...","role":"Assistant"}' # -> data.agentId
The name has a per-creator UNIQUE index that ignores soft-delete, so reusing a previously-deleted name will collide — add a suffix or hard-delete the old row first.
-
Attach the skill to the DW. Send exactly one of
projectId/agentId; for a DW-specific skill, sendagentId:curl -s -X POST http://localhost:8080/api/sico/skills \ -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \ -d "{\"agentId\":\"$AGENT_ID\",\"assetId\":$ASSET_ID}" # -> data.skill.status == 2 (SKILL_STATUS_UPLOADED)
-
Deploy a runnable instance for the current user.
curl -s -X POST http://localhost:8080/api/sico/agent/single_agent/deploy \ -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \ -d "{\"agentId\":\"$AGENT_ID\",\"name\":\"My Web Researcher\"}" # -> data.id (instance id), data.employerUsername
After step 6 the new DW type is usable end-to-end — start a conversation with it via POST /api/sico/conversation and stream replies via POST /api/sico/conversation/chat (SSE), targeting the returned agentId / instance.
The repo ships a stdlib-only Python script that performs steps 1–6 against a local stack (defaults to http://localhost:8080, account operator@sico.local / operator, builds a tiny web-researcher skill in memory):
python examples/agent/create_dw_type.pyUseful environment overrides: BASE_URL, SICO_EMAIL, SICO_PASSWORD, PROJECT_ID, DW_TYPE_NAME, DW_TYPE_ROLE, SKILL_NAME, SKILL_DESC, SKILL_DIR (point at an existing skill directory to upload that instead of the inline demo). See examples/agent/create_dw_type.py.
| Symptom | Likely cause |
|---|---|
make compose-up fails on first run |
.env is missing or has a bad variable; run cp .env.example .env |
| Frontend loads but API calls 401 | No user created yet; register through the UI or call the RBAC API |
| Core cannot reach an LLM provider | Secrets not configured in the Model Registry for that model_key |
| Sandbox endpoints return 401 with valid HMAC | Clock skew, missing nonce, or SANDBOX_CLIENT_SECRET_* mismatch |
| Android sandbox calls fail from containers | SANDBOX_EMULATOR_BASE_URL points at an unreachable host |
| Digital Tester shows no device (e.g., no device label in the top-right corner) | The backend has not picked up an available sandbox device. First confirm the Android emulator sandbox (optional) setup steps are completed, then restart the stack: make compose-down followed by make compose-up. |
- Understand what Sico is: Overview
- Understand the moving parts: Technical Report
- Build from source, run tests, regenerate code: Development