Deserialization plugins work for custom components at the top level, but not when the custom component sits inside a builtin component's field. An agent whose transforms or toolboxes contain a plugin component fails to deserialize even when a plugin for that component type is registered.
What happens:
- Without a plugin:
No plugin to deserialize component type "FunctionTransform".
- With a plugin registered: the child deserializes fine, but the builtin Agent plugin then calls
createAgent, and AgentSchema validates transforms/toolboxes against closed discriminated unions (MessageTransformUnion, ToolBoxUnion). The unknown discriminator fails the whole parse: Invalid discriminator value. Expected 'MessageSummarizationTransform' | ...
- Registering your own
Agent plugin to work around it isn't possible either: buildComponentTypesToPlugins throws on duplicate component types.
So the serializer can produce documents (via ComponentSerializationPlugin) that no deserializer configuration can read back. Round-trip is broken for custom components. This is what broke agent export/import in AgentFabric (OA-1801): exported agents with a FunctionTransform or ConnectorToolBox attached couldn't be re-imported.
Repro sketch:
const agent = {
...createAgent({ name: 'a', llmConfig, systemPrompt: 'x' }),
transforms: [myFunctionTransform], // componentType: 'FunctionTransform'
};
const doc = new AgentSpecSerializer([new FunctionTransformSerializationPlugin()])
.toJson(agent);
new AgentSpecDeserializer([new FunctionTransformDeserializationPlugin()]).fromJson(doc);
// ZodError: Invalid discriminator value ...
Two things worth noting. The construction API has the same restriction: createAgent rejects custom toolboxes/transforms, so integrators already strip them before calling the factory and reattach after. And pyagentspec doesn't have this problem, its plugin system composes with nested custom components, so the two implementations currently disagree on what round-trips.
Possible fixes:
- Let component-container fields accept a plugin-deserialized
ComponentBase when the component_type isn't in the builtin union.
- Relax
transforms/toolboxes (and similar fields) to accept any ComponentBase, keeping strict validation for builtin types only.
- Allow overriding builtin plugins (last registration wins), so integrators can wrap
Agent deserialization themselves. Least SDK work, but every integrator ends up writing their own strip/reattach.
Whichever shape, the factories should get the same treatment so construction and deserialization stay symmetric.
Deserialization plugins work for custom components at the top level, but not when the custom component sits inside a builtin component's field. An agent whose
transformsortoolboxescontain a plugin component fails to deserialize even when a plugin for that component type is registered.What happens:
No plugin to deserialize component type "FunctionTransform".createAgent, andAgentSchemavalidatestransforms/toolboxesagainst closed discriminated unions (MessageTransformUnion,ToolBoxUnion). The unknown discriminator fails the whole parse:Invalid discriminator value. Expected 'MessageSummarizationTransform' | ...Agentplugin to work around it isn't possible either:buildComponentTypesToPluginsthrows on duplicate component types.So the serializer can produce documents (via
ComponentSerializationPlugin) that no deserializer configuration can read back. Round-trip is broken for custom components. This is what broke agent export/import in AgentFabric (OA-1801): exported agents with aFunctionTransformorConnectorToolBoxattached couldn't be re-imported.Repro sketch:
Two things worth noting. The construction API has the same restriction:
createAgentrejects custom toolboxes/transforms, so integrators already strip them before calling the factory and reattach after. And pyagentspec doesn't have this problem, its plugin system composes with nested custom components, so the two implementations currently disagree on what round-trips.Possible fixes:
ComponentBasewhen thecomponent_typeisn't in the builtin union.transforms/toolboxes(and similar fields) to accept anyComponentBase, keeping strict validation for builtin types only.Agentdeserialization themselves. Least SDK work, but every integrator ends up writing their own strip/reattach.Whichever shape, the factories should get the same treatment so construction and deserialization stay symmetric.