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 filespimcore_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:clearrequired 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'orsource: '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 topimcore_agent.agents.paths(orskills.paths) via a bundle'sconfig.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:
- First-time setup only - if you just registered a new
pimcore_agent.agents.pathsentry via a bundleconfig.yaml, clear the Symfony cache so the container picks up the new path list:You do NOT need this step for subsequent YAML edits at an already-registered path - only when the path list itself changes.docker compose exec php bin/console cache:clear - Verify your paths reached the container:
docker compose exec php bin/console debug:container \
--parameter=pimcore_agent.agents.paths --format=json - Hit the export endpoint (use your shared admin token):
Your bundle's presets should show up with
curl -s -H "Authorization: Bearer $AGENT_SERVER_ADMIN_TOKEN" \
http://localhost/pimcore-studio/api/bundle/agent/configurations/export | jq"source": "bundle". - 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 - Open the Pimcore Studio chat panel and confirm your presets appear in the agent dropdown.
- Iterating on an existing preset - after edits 1 is skipped entirely. Just re-run steps 4 and 5 (or let the auto-reload fire).
Related docs
- Agents - full agent YAML schema
- Skills - full skill file format
- Architecture → Configuration System → Bundle presets - detailed layer model