Skip to main content
Version: 2026.1

Shipping Presets from a Bundle

Audience: developers of Pimcore bundles who want to ship preset agents or skills with their bundle so that projects installing the bundle get them out of the box.

Bundles contribute presets declaratively via two config keys:

  • pimcore_agent.agents.paths - parent directories of agent YAML files
  • pimcore_agent.skills.paths - parent directories of skill subdirs

Both keys accept a list of paths. Every contribution goes into the bundle preset layer - the lowest priority layer in the three-layer merge model. Project container config and Pimcore Studio edits always win by name, so upgrading a bundle never clobbers an administrator's edits. For the full merge model see README → The merge model and Architecture → Configuration System.

Step 1 - Ship the preset files

Drop agent YAMLs and skill directories inside your bundle. Two layouts work well, pick whichever matches your bundle's convention:

# layout A - bundles that use a top-level config/ directory
config/
├── agents/
│ └── my-agent.yaml # one agent per file; filename = agent name
└── skills/
└── my-skill/
└── SKILL.md # one directory per skill
# layout B - bundles that use src/Resources/
src/Resources/config/
├── agents/
│ └── my-agent.yaml
└── skills/
└── my-skill/
└── SKILL.md

Each agent YAML follows the full schema documented in Agents. Each skill directory contains a SKILL.md file with YAML front-matter as described in Skills. Nested resources alongside SKILL.md (schemas, templates, examples) are picked up automatically and travel with the skill through the export pipeline.

Step 2 - Register the paths

There are two registration patterns. Which one you use depends on whether your bundle requires the PimcoreAgentBundle or treats it as an optional integration.

Pattern A - Hard dependency on PimcoreAgentBundle

Use Pimcore's auto-loaded bundle config file. Pimcore's BundleConfigLocator scans every registered bundle for a file at <bundle-root>/config/pimcore/config.yaml (or <bundle-root>/Resources/config/pimcore/config.yaml) and loads its contents as extension config at container build time.

Create config/pimcore/config.yaml inside your bundle:

# <bundle-root>/config/pimcore/config.yaml
pimcore_agent:
agents:
paths:
- '%kernel.project_dir%/vendor/my-vendor/my-bundle/config/agents'
skills:
paths:
- '%kernel.project_dir%/vendor/my-vendor/my-bundle/config/skills'

That is the entire registration - no PHP code. This is how the PimcoreAgentBundle itself ships its own general and data-management presets.

When to use: your bundle always requires PimcoreAgentBundle; there is no mode in which the bundle runs without it.

Pattern B - Optional dependency on PimcoreAgentBundle

If your bundle can run stand-alone (without the PimcoreAgentBundle installed), an auto-loaded config.yaml would break it: Symfony throws when a config file references an unknown extension. Use a conditionally-loaded file instead.

Create a config file under any filename other than config.yaml. Pimcore's auto-loader only scans config.yaml, so a different name is safe from auto-loading:

# src/Resources/config/pimcore/pimcore_agent.yaml
pimcore_agent:
agents:
paths:
- "%kernel.project_dir%/vendor/my-vendor/my-bundle/src/Resources/config/agents"
skills:
paths:
- "%kernel.project_dir%/vendor/my-vendor/my-bundle/src/Resources/config/skills"

Then load it from your bundle's Extension::prepend(), guarded by hasExtension('pimcore_agent'):

// src/DependencyInjection/MyBundleExtension.php
use Symfony\Component\Config\FileLocator;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Extension\PrependExtensionInterface;
use Symfony\Component\DependencyInjection\Loader\YamlFileLoader;
use Symfony\Component\HttpKernel\DependencyInjection\Extension;

class MyBundleExtension extends Extension implements PrependExtensionInterface
{
public function prepend(ContainerBuilder $container): void
{
$loader = new YamlFileLoader(
$container,
new FileLocator(__DIR__ . '/../Resources/config')
);

if ($container->hasExtension('pimcore_agent')) {
$loader->load('pimcore/pimcore_agent.yaml');
}
}
}

The guard makes the registration a no-op when PimcoreAgentBundle is not installed. This is the pattern the Pimcore Data Importer uses to ship its data-import preset without forcing every data-importer installation to also install PimcoreAgentBundle.

When to use: your bundle integrates with PimcoreAgentBundle but should not require it.

Runtime behaviour worth knowing

Two things about the load path are worth calling out because they affect how you iterate on a preset in practice:

  • Your preset files are re-parsed on every export request - no compile-time caching, no cache:clear required after editing a preset YAML.
  • Presets never appear in the Pimcore Studio agent editor tree - they live next to the CRUD stack, not inside it. To customise a preset, create a user entry with the same name (user wins by full-object replacement; every entry is tagged source: 'bundle' or source: 'user').

For the full load flow (export endpoint, skill materialization, atomic registry swap, background retry), see Architecture → Configuration System.

When you still need cache:clear: only when you add a new path to pimcore_agent.agents.paths (or skills.paths) via a bundle's config.yaml - the path list itself is a compiled container parameter. Editing a file at an already-registered path does not require a cache clear.

Testing your bundle's presets

After dropping in your preset files:

  1. First-time setup only - if you just registered a new pimcore_agent.agents.paths entry via a bundle config.yaml, clear the Symfony cache so the container picks up the new path list:
    docker compose exec php bin/console cache:clear
    You do NOT need this step for subsequent YAML edits at an already-registered path - only when the path list itself changes.
  2. Verify your paths reached the container:
    docker compose exec php bin/console debug:container \
    --parameter=pimcore_agent.agents.paths --format=json
  3. Hit the export endpoint (use your shared admin token):
    curl -s -H "Authorization: Bearer $AGENT_SERVER_ADMIN_TOKEN" \
    http://localhost/pimcore-studio/api/bundle/agent/configurations/export | jq
    Your bundle's presets should show up with "source": "bundle".
  4. Trigger an agent-server reload (it also runs automatically on any Pimcore Studio CRUD action):
    curl -X POST \
    -H "Authorization: Bearer $AGENT_SERVER_ADMIN_TOKEN" \
    http://localhost/agent-server/api/admin/reload-agents
  5. Open the Pimcore Studio chat panel and confirm your presets appear in the agent dropdown.
  6. Iterating on an existing preset - after edits 1 is skipped entirely. Just re-run steps 4 and 5 (or let the auto-reload fire).