Development
Developer workflow reference — how to run the bundle locally, what commands each subsystem exposes, how to trigger reloads. For end-to-end install, see Installation. For when things go wrong, see Troubleshooting. For authentication details (they used to live here) see Architecture → Authentication.
Docker setup
The default docker-compose.yaml runs agent-server in production mode — compiled dist/server.js, prod-only
dependencies, no source bind mount. That is correct for demos and staging.
For local agent-server development with hot reload on .ts changes, create a docker-compose.override.yaml
next to the main compose file:
# docker-compose.override.yaml — bind-mounts source and runs tsx watch
services:
agent-server:
user: '1000:1000'
# Override the Dockerfile build with a bare node image + bind-mounted source
image: node:22-slim
build: !reset null
command: sh -c "npm ci && npx tsx watch src/server.ts"
working_dir: /app
environment:
NODE_ENV: development
# Read/sandbox staged files from a TOP-LEVEL path, not /app/uploads — see
# the staged-files note below.
AGENT_SERVER_UPLOAD_DIR: /agent-uploads
volumes:
- ./dev/pimcore/pimcore-agent-bundle/agent-server:/app
# Preserve runtime state mounts from the main compose file (list fields
# are replaced, not merged, so they must be repeated here).
- ./var/tmp/copilot-state:/app/.copilot-state
# Staged files: TOP-LEVEL mount (a sibling of /app, NOT nested inside it).
- ./var/tmp/agent-uploads:/agent-uploads
# node:22-slim ships no CA certs; the Copilot CLI validates its auth
# token against the system trust store, so mount the host's read-only.
# (The production Dockerfile installs ca-certificates instead.)
- /etc/ssl/certs:/etc/ssl/certs:ro
php:
user: '1000:1000' # set to your uid:gid
environment:
# Hand the agent /agent-uploads paths, matching the agent-server mount above.
AGENT_NODE_STAGING_BASE_DIR: /agent-uploads
supervisord:
user: '1000:1000' # set to your uid:gid
npm ci installs directly into the bind-mounted source directory as uid 1000, so node_modules ends up
host-side with correct ownership and is available to your IDE for autocompletion. No named volume is used to
avoid Docker daemon creating the mount point as root.
Before the first start: create the host directories that back the mounts, so Docker does not create them as root:
mkdir -p var/tmp/copilot-state var/tmp/agent-uploads
mkdir -p dev/pimcore/pimcore-agent-bundle/agent-server/varWhy staged files mount at
/agent-uploads, not/app/uploads. The production compose mounts the host upload dir at/app/uploads. Reusing that path in dev is a trap: the override bind-mounts the whole source dir to/app, so/app/uploadswould be a bind mount nested inside another bind mount. On some storage drivers — notably ZFS — that inner mount silently detaches at runtime (no container restart; Docker's config still lists it, but the kernel has dropped it). Once it's gone, every filestage_file_setwrites is invisible to the agent, which then reports staged files as "inaccessible" and falls back to guessed layout conventions.The fix is to avoid the nesting: mount the upload dir at a top-level path (
/agent-uploads) and point both sides at it — the agent-server viaAGENT_SERVER_UPLOAD_DIR, and the PHP MCP tools viaAGENT_NODE_STAGING_BASE_DIR(the path string they hand the agent; it must equal the agent-server's upload dir). Production is unaffected: both default to/app/uploads, where the single, non-nested prod mount is stable. Verify the dev mount with:docker compose exec agent-server ls /agent-uploads # lists per-session dirs, not an errorIf staging ever breaks anyway,
docker compose restart agent-serverre-attaches the mount in a few seconds — a full Docker restart is never needed.
With the override in place, TypeScript changes are picked up automatically via tsx watch — no container restart,
no rebuild. Agent YAML edits still need a reload:
curl -X POST -H "Authorization: Bearer $AGENT_SERVER_ADMIN_TOKEN" \
http://localhost/agent-server/api/admin/reload-agents
Tip: to wipe all Copilot CLI session state, run
rm -rf var/tmp/copilot-state/*and restart the container.
Agent-server (Node.js)
Working directory: agent-server/. Commands require the dev override shown above — the production image strips
devDependencies (npm ci --omit=dev) and does not contain tsx, eslint, vitest, or typescript.
# Inside the container
docker compose exec agent-server npm test # Run unit tests (Vitest)
docker compose exec agent-server npm run lint # ESLint
docker compose exec agent-server npm run check-types # TypeScript type check
docker compose exec agent-server npm run build # Compile to dist/
# Watch mode (useful during development)
docker compose exec agent-server npm run test:watch
Production build
Without the dev override, changes take effect via an image rebuild:
docker compose up -d --build agent-server
The Dockerfile runs tsc at build time; the runtime layer executes node dist/server.js. No devDependencies in the runtime layer.
Testing
- Framework: Vitest with Node environment.
- Test files:
tests/**/*.test.ts. - Coverage:
npm run test -- --coverage(v8 provider).
Linting
- ESLint with
standard-with-typescriptbase config. - Max 300 lines per file.
- Strict TypeScript rules (explicit return types, consistent type imports).
PHP backend
Commands
# Unit tests
docker compose exec php vendor/bin/phpunit -c dev/pimcore/pimcore-agent-bundle/phpunit.xml.dist
# Static analysis (level 6)
docker compose exec php vendor/bin/phpstan analyse -c dev/pimcore/pimcore-agent-bundle/phpstan.neon
# Code style
docker compose exec php vendor/bin/php-cs-fixer fix dev/pimcore/pimcore-agent-bundle/src/
# Areabrick / document template tooling (see Extending → Areabrick Examples)
docker compose exec php bin/console pimcore-agent:document-schema:names --brick=<brick-id>
docker compose exec php bin/console pimcore-agent:document-schema:lint
# MCP tool registry debugging
docker compose exec php bin/console pimcore-agent:mcp:list-tools # all groups + tools
docker compose exec php bin/console pimcore-agent:mcp:list-tools <group> # single group
docker compose exec php bin/console pimcore-agent:mcp:list-tools -v # include class::method
Testing
- Framework: PHPUnit 12.5+.
- Test directory:
tests/. - Bootstrap:
vendor/autoload.phpfrom project root. - Key test areas: MCP tool registry, compiler pass, server factories, individual tools.
Static analysis
PHPStan level 6 with src/ as the analysis path. Some baseline ignores for iterables and generics.
Code style
PHP-CS-Fixer with PSR-1 / PSR-2 plus Pimcore conventions (short arrays, trailing commas, ordered imports).
Frontend assets
Working directory: assets/.
cd dev/pimcore/pimcore-agent-bundle/assets
npm run dev # Development build (RSBuild)
npm run dev-server # HMR dev server on port 3033
npm run build # Production build
npm run build-api-client # Regenerate RTK Query slices from PHP OpenAPI
npm run lint # ESLint (React + TypeScript)
npm run check-types # TypeScript type check
npm test # Unit tests (Vitest + jsdom)
Build
RSBuild with Module Federation — the bundle registers as a remote plugin consumed by the Studio UI host. Output
lands under public/build/<uuid>/. Old build directories are cleaned up automatically.
API client codegen
Slices that talk to PHP endpoints (agent-config-api-slice.gen.ts, skill-config-api-slice.gen.ts) are generated
from the Studio Backend OpenAPI spec via @rtk-query/codegen-openapi — same pattern as studio-ui-bundle,
copilot-bundle, etc. Source of truth is the OpenApi\Attributes\* annotations on the PHP controllers under src/Controller/Studio/.
Workflow when changing PHP request/response schemas:
# 1. Update the PHP controller / Schema class with new OpenApi attributes
# 2. Clear Symfony cache so swagger-php picks up the change
docker compose exec php bin/console cache:clear --no-warmup
# 3. Refetch the spec (overwrites build/api/docs.jsonopenapi.json)
curl -s -o build/api/docs.jsonopenapi.json http://localhost/pimcore-studio/api/docs/json
# 4. Regenerate the slices (also strips broken external $refs from the global spec)
npm run build-api-client
The agent-server slice (agent-api-slice.ts) stays hand-rolled — it talks to the Node Fastify backend and
uses streaming SSE endpoints that RTK Query does not model.
Testing
- Framework: Vitest with jsdom environment.
- Test files:
js/test/**/*.test.{ts,tsx}. - React Testing Library for component tests.
Linting
ESLint with standard-with-typescript and React-specific rules (jsx-a11y, strict JSX formatting).
Debugging
Agent-server logs
docker compose logs -f agent-server
Structured JSON logging, configurable via AGENT_SERVER_LOG_LEVEL in .env.local:
| Level | What is logged |
|---|---|
error | Task failures, onComplete callback errors, unhandled exceptions. |
warn | Model validation warnings, non-critical issues. |
info (default) | Task lifecycle (started / finished / cancelled), agent switching, session creation. |
debug | HTTP request entry points (session ID, user, message length), stream reconnect, TaskRunner subscriptions, SSE stream open / close. |
verbose | Every individual event from the TaskRunner (event type, seq number, text chunk length). |
Enable detailed per-event logging:
# .env.local
AGENT_SERVER_LOG_LEVEL=verbose
Restart the agent-server container afterwards. env_file is only read at container start, so even with the dev
override in place env-var changes do not hot-reload: docker compose restart agent-server.
PHP MCP debugging
Enable Symfony's debug toolbar or check var/log/dev.log for MCP request / response details. Each MCP tool logs
errors via the injected LoggerInterface.
Swagger UI
Interactive API documentation at /agent-server/api/docs — only when NODE_ENV ≠ production. The
development Docker setup sets NODE_ENV: development, so Swagger UI is enabled automatically:
http://localhost/agent-server/api/docs
In production deployments the docs route is not registered.
Common commands at a glance
| Intent | Command |
|---|---|
| Reload agent configs | curl -X POST -H "Authorization: Bearer $AGENT_SERVER_ADMIN_TOKEN" http://localhost/agent-server/api/admin/reload-agents |
| List available LLM models | curl -H "Authorization: Bearer $AGENT_SERVER_ADMIN_TOKEN" http://localhost/agent-server/api/admin/models |
| Wipe Copilot CLI state | rm -rf var/tmp/copilot-state/* then restart the container |
| Wipe user upload directories | rm -rf var/tmp/agent-uploads/* |
| Inspect registered MCP tool paths | docker compose exec php bin/console debug:container --parameter=pimcore_agent.agents.paths --format=json |
| List MCP tool groups and tools | docker compose exec php bin/console pimcore-agent:mcp:list-tools |
For expected behaviours when things go wrong, see Troubleshooting.