ossa bridge drupal
OSSA Bridge for Drupal AI Agents
Status: Production Ready OSSA Version: v0.2.8 Drupal Version: 11.x+ Required Modules: drupal/ai_agents (1.2.1+)
Overview
This guide implements a bridge between Drupal's ai_agents module and the Open Standard for Software Agents (OSSA) v0.2.8, enabling Drupal agents to be exported, validated, and deployed as OSSA-compliant agent manifests compatible with GitLab Agent Platform, Kubernetes Agent Mesh, and other OSSA-compliant systems.
Architecture
‚ Drupal 11 (blueflyio/llm-platform) ‚
‚ ‚
‚ ‚
‚ ‚ drupal/ai_agents Module ‚ ‚
‚ ‚ ‚ ‚
‚ ‚ Agent Entities (Drupal) ‚ ‚
‚ ‚ Tool Plugins (Drupal) ‚ ‚
‚ ‚ Agent Executor ‚ ‚
‚ ‚ Agent Manager ‚ ‚
‚ ˜ ‚
‚ ‚ ‚
‚ ‚ Export ‚
‚ ¼ ‚
‚ ‚
‚ ‚ OSSA Bridge Service THIS MODULE ‚ ‚
‚ ‚ ‚ ‚
‚ ‚ Manifest Generator ‚ ‚
‚ ‚ OSSA Validator (v0.2.8) ‚ ‚
‚ ‚ Tool Schema Converter ‚ ‚
‚ ‚ Sync Manager ‚ ‚
‚ ˜ ‚
‚ ‚ ‚
¼˜
‚
‚ YAML Export
¼
‚ OSSA Manifest (agent.yaml) ‚
‚ ‚
‚ name: drupal_content_moderator ‚
‚ version: 1.0.0 ‚
‚ description: AI-powered content moderation agent ‚
‚ capabilities: ‚
‚ - text-classification ‚
‚ - content-moderation ‚
‚ tools: ‚
‚ - name: moderate_content ‚
‚ description: Moderate Drupal content ‚
‚ input_schema: {...} ‚
‚ provider: ‚
‚ type: claude ‚
‚ model: claude-sonnet-4 ‚
˜
‚
‚ Deploy
¼
‚ GitLab Agent Platform (OSSA Runtime) ‚
‚ OR Kubernetes Agent Mesh ‚
‚ OR Any OSSA-compliant executor ‚
˜
OSSA v0.2.8 Specification Overview
Key Components:
- Agent Manifest - YAML file describing agent metadata, capabilities, tools
- Tool Schema - JSON Schema for tool inputs/outputs
- Capabilities - Standardized list of what agent can do
- Provider Config - AI model provider and configuration
- Validation Rules - Manifest must validate against OSSA JSON Schema
OSSA Manifest Structure:
# Minimal OSSA v0.2.8 manifest apiVersion: ossa.bluefly.io/v0.2.8 kind: Agent metadata: name: agent-name version: 1.0.0 description: Agent description spec: capabilities: - capability-name tools: - name: tool_name description: Tool description input_schema: type: object properties: {} provider: type: anthropic model: claude-sonnet-4 configuration: temperature: 0.7 max_tokens: 4096
Implementation
Step 1: Create OSSA Bridge Service
File: modules/custom/ai_agents_ossa/src/Service/OssaBridge.php
<?php declare(strict_types=1); namespace Drupal\ai_agents_ossa\Service; use Drupal\ai_agents\Entity\AgentInterface; use Drupal\Core\Entity\EntityTypeManagerInterface; use Symfony\Component\Yaml\Yaml; /** * OSSA Bridge service for exporting Drupal agents to OSSA manifests. */ final class OssaBridge { /** * OSSA specification version. */ private const OSSA_VERSION = 'v0.2.8'; /** * OSSA API version. */ private const API_VERSION = 'ossa.bluefly.io/v0.2.8'; /** * Entity type manager. */ private EntityTypeManagerInterface $entityTypeManager; /** * OSSA validator service. */ private OssaValidator $validator; /** * Tool schema converter service. */ private ToolSchemaConverter $toolConverter; /** * Constructor. */ public function __construct( EntityTypeManagerInterface $entity_type_manager, OssaValidator $validator, ToolSchemaConverter $tool_converter, ) { $this->entityTypeManager = $entity_type_manager; $this->validator = $validator; $this->toolConverter = $tool_converter; } /** * Exports a Drupal agent to OSSA manifest format. * * @param \Drupal\ai_agents\Entity\AgentInterface $agent * The Drupal agent entity. * * @return array * OSSA manifest as array. * * @throws \InvalidArgumentException * If agent cannot be exported to OSSA format. */ public function exportAgent(AgentInterface $agent): array { $manifest = [ 'apiVersion' => self::API_VERSION, 'kind' => 'Agent', 'metadata' => $this->buildMetadata($agent), 'spec' => $this->buildSpec($agent), ]; // Validate against OSSA schema. $this->validator->validate($manifest); return $manifest; } /** * Exports agent to YAML string. * * @param \Drupal\ai_agents\Entity\AgentInterface $agent * The Drupal agent entity. * * @return string * OSSA manifest as YAML string. */ public function exportAgentYaml(AgentInterface $agent): string { $manifest = $this->exportAgent($agent); return Yaml::dump($manifest, 10, 2); } /** * Exports all agents to OSSA manifests. * * @param string $output_dir * Directory to write OSSA manifest files. * * @return int * Number of agents exported. */ public function exportAllAgents(string $output_dir): int { $storage = $this->entityTypeManager ->getStorage('ai_agent'); $agents = $storage->loadMultiple(); $count = 0; foreach ($agents as $agent) { $filename = $output_dir . '/' . $agent->id() . '.agent.yaml'; $yaml = $this->exportAgentYaml($agent); file_put_contents($filename, $yaml); $count++; } return $count; } /** * Builds metadata section of OSSA manifest. * * @param \Drupal\ai_agents\Entity\AgentInterface $agent * The Drupal agent entity. * * @return array * Metadata array. */ private function buildMetadata(AgentInterface $agent): array { return [ 'name' => $agent->id(), 'version' => $agent->get('version')->value ?? '1.0.0', 'description' => $agent->get('description')->value, 'labels' => [ 'source' => 'drupal', 'drupal_bundle' => $agent->bundle(), 'created' => date('Y-m-d', $agent->getCreatedTime()), ], 'annotations' => [ 'drupal.uuid' => $agent->uuid(), 'drupal.url' => $agent->toUrl('canonical', [ 'absolute' => TRUE, ])->toString(), ], ]; } /** * Builds spec section of OSSA manifest. * * @param \Drupal\ai_agents\Entity\AgentInterface $agent * The Drupal agent entity. * * @return array * Spec array. */ private function buildSpec(AgentInterface $agent): array { return [ 'capabilities' => $this->extractCapabilities($agent), 'tools' => $this->extractTools($agent), 'provider' => $this->extractProvider($agent), 'configuration' => $this->extractConfiguration($agent), ]; } /** * Extracts capabilities from Drupal agent. * * @param \Drupal\ai_agents\Entity\AgentInterface $agent * The Drupal agent entity. * * @return array * Array of capability strings. */ private function extractCapabilities(AgentInterface $agent): array { // Map Drupal agent types to OSSA capabilities. $capability_map = [ 'content_moderator' => [ 'text-classification', 'content-moderation', ], 'taxonomy_tagger' => [ 'text-classification', 'entity-tagging', ], 'content_generator' => [ 'text-generation', 'content-creation', ], 'search_assistant' => [ 'semantic-search', 'question-answering', ], ]; $bundle = $agent->bundle(); return $capability_map[$bundle] ?? ['general-purpose']; } /** * Extracts tools from Drupal agent. * * @param \Drupal\ai_agents\Entity\AgentInterface $agent * The Drupal agent entity. * * @return array * Array of tool definitions. */ private function extractTools(AgentInterface $agent): array { $tools = []; $agent_tools = $agent->get('tools')->referencedEntities(); foreach ($agent_tools as $tool) { $tools[] = [ 'name' => $tool->id(), 'description' => $tool->get('description')->value, 'input_schema' => $this->toolConverter ->convertToJsonSchema($tool), 'output_schema' => $this->toolConverter ->convertOutputSchema($tool), ]; } return $tools; } /** * Extracts provider configuration from Drupal agent. * * @param \Drupal\ai_agents\Entity\AgentInterface $agent * The Drupal agent entity. * * @return array * Provider configuration. */ private function extractProvider(AgentInterface $agent): array { $provider_id = $agent->get('provider')->value; $model_id = $agent->get('model')->value; // Map Drupal provider IDs to OSSA provider types. $provider_map = [ 'anthropic' => 'anthropic', 'openai' => 'openai', 'azure_openai' => 'azure', 'gitlab_ai_gateway' => 'gitlab', ]; $provider_type = $provider_map[$provider_id] ?? 'unknown'; return [ 'type' => $provider_type, 'model' => $model_id, ]; } /** * Extracts configuration from Drupal agent. * * @param \Drupal\ai_agents\Entity\AgentInterface $agent * The Drupal agent entity. * * @return array * Configuration array. */ private function extractConfiguration(AgentInterface $agent): array { $config = []; if ($agent->hasField('temperature')) { $config['temperature'] = (float) $agent ->get('temperature')->value; } if ($agent->hasField('max_tokens')) { $config['max_tokens'] = (int) $agent ->get('max_tokens')->value; } if ($agent->hasField('system_prompt')) { $config['system_prompt'] = $agent ->get('system_prompt')->value; } return $config; } }
Step 2: Create OSSA Validator
File: modules/custom/ai_agents_ossa/src/Service/OssaValidator.php
<?php declare(strict_types=1); namespace Drupal\ai_agents_ossa\Service; use JsonSchema\Validator; use JsonSchema\Constraints\Constraint; /** * OSSA manifest validator against v0.2.8 JSON Schema. */ final class OssaValidator { /** * OSSA v0.2.8 JSON Schema. */ private const OSSA_SCHEMA = [ '$schema' => 'http://json-schema.org/draft-07/schema#', 'type' => 'object', 'required' => ['apiVersion', 'kind', 'metadata', 'spec'], 'properties' => [ 'apiVersion' => [ 'type' => 'string', 'pattern' => '^ossa\.bluefly\.io/v[0-9]+\.[0-9]+\.[0-9]+$', ], 'kind' => [ 'type' => 'string', 'enum' => ['Agent'], ], 'metadata' => [ 'type' => 'object', 'required' => ['name', 'version', 'description'], 'properties' => [ 'name' => ['type' => 'string', 'pattern' => '^[a-z0-9_-]+$'], 'version' => ['type' => 'string', 'pattern' => '^[0-9]+\.[0-9]+\.[0-9]+$'], 'description' => ['type' => 'string', 'minLength' => 10], 'labels' => ['type' => 'object'], 'annotations' => ['type' => 'object'], ], ], 'spec' => [ 'type' => 'object', 'required' => ['capabilities', 'tools', 'provider'], 'properties' => [ 'capabilities' => [ 'type' => 'array', 'minItems' => 1, 'items' => ['type' => 'string'], ], 'tools' => [ 'type' => 'array', 'minItems' => 1, 'items' => [ 'type' => 'object', 'required' => ['name', 'description', 'input_schema'], 'properties' => [ 'name' => ['type' => 'string'], 'description' => ['type' => 'string'], 'input_schema' => ['type' => 'object'], 'output_schema' => ['type' => 'object'], ], ], ], 'provider' => [ 'type' => 'object', 'required' => ['type', 'model'], 'properties' => [ 'type' => ['type' => 'string'], 'model' => ['type' => 'string'], ], ], 'configuration' => ['type' => 'object'], ], ], ], ]; /** * Validates OSSA manifest against schema. * * @param array $manifest * OSSA manifest array. * * @throws \RuntimeException * If validation fails. */ public function validate(array $manifest): void { $validator = new Validator(); $manifest_obj = json_decode(json_encode($manifest)); $schema_obj = json_decode(json_encode(self::OSSA_SCHEMA)); $validator->validate( $manifest_obj, $schema_obj, Constraint::CHECK_MODE_TYPE_CAST ); if (!$validator->isValid()) { $errors = []; foreach ($validator->getErrors() as $error) { $errors[] = sprintf( '[%s] %s', $error['property'], $error['message'] ); } throw new \RuntimeException( 'OSSA validation failed: ' . implode('; ', $errors) ); } } /** * Checks if manifest is valid without throwing exception. * * @param array $manifest * OSSA manifest array. * * @return bool * TRUE if valid, FALSE otherwise. */ public function isValid(array $manifest): bool { try { $this->validate($manifest); return TRUE; } catch (\RuntimeException $e) { return FALSE; } } }
Step 3: Create Drush Command for Export
File: modules/custom/ai_agents_ossa/src/Commands/OssaCommands.php
<?php declare(strict_types=1); namespace Drupal\ai_agents_ossa\Commands; use Drupal\ai_agents_ossa\Service\OssaBridge; use Drush\Commands\DrushCommands; /** * Drush commands for OSSA export. */ final class OssaCommands extends DrushCommands { /** * OSSA bridge service. */ private OssaBridge $ossaBridge; /** * Constructor. */ public function __construct(OssaBridge $ossa_bridge) { parent::__construct(); $this->ossaBridge = $ossa_bridge; } /** * Export all Drupal agents to OSSA manifests. * * @param string $output_dir * Output directory for OSSA manifests. * * @command ai:agents:ossa:export * @aliases ossa-export * @usage drush ossa-export /path/to/output * Export all agents to /path/to/output directory */ public function exportAgents(string $output_dir): void { if (!is_dir($output_dir)) { mkdir($output_dir, 0755, TRUE); } $this->output()->writeln('Exporting Drupal agents to OSSA manifests...'); $count = $this->ossaBridge->exportAllAgents($output_dir); $this->output()->writeln( sprintf(' Exported %d agents to %s', $count, $output_dir) ); } /** * Validate an OSSA manifest file. * * @param string $manifest_file * Path to OSSA manifest YAML file. * * @command ai:agents:ossa:validate * @aliases ossa-validate * @usage drush ossa-validate agent.yaml * Validate agent.yaml against OSSA v0.2.8 schema */ public function validateManifest(string $manifest_file): void { if (!file_exists($manifest_file)) { $this->output()->writeln(" File not found: {$manifest_file}"); return; } $yaml = file_get_contents($manifest_file); $manifest = \Symfony\Component\Yaml\Yaml::parse($yaml); try { $this->ossaBridge->getValidator()->validate($manifest); $this->output()->writeln(" Valid OSSA v0.2.8 manifest"); } catch (\RuntimeException $e) { $this->output()->writeln(" Invalid: " . $e->getMessage()); } } }
Usage Examples
Export Single Agent
<?php use Drupal\ai_agents\Entity\Agent; use Drupal\ai_agents_ossa\Service\OssaBridge; // Load agent. $agent = Agent::load('content_moderator'); // Export to OSSA manifest. /** @var \Drupal\ai_agents_ossa\Service\OssaBridge $ossa_bridge */ $ossa_bridge = \Drupal::service('ai_agents_ossa.bridge'); $manifest = $ossa_bridge->exportAgent($agent); // Save to file. $yaml = $ossa_bridge->exportAgentYaml($agent); file_put_contents('/tmp/content_moderator.agent.yaml', $yaml); echo "Exported to: /tmp/content_moderator.agent.yaml\n";
Export All Agents via Drush
# Export all agents to GitLab components directory drush ai:agents:ossa:export \ /path/to/gitlab_components/agents/ # Output: # Exported 12 agents to /path/to/gitlab_components/agents/
Validate OSSA Manifest
# Validate a manifest file drush ai:agents:ossa:validate \ /path/to/agent.yaml # Output: # Valid OSSA v0.2.8 manifest
Sync to GitLab Repository
# Export agents drush ossa-export /tmp/ossa-agents/ # Commit to GitLab cd /path/to/gitlab_components cp /tmp/ossa-agents/*.yaml agents/ git add agents/ git commit -m "feat: sync Drupal agents to OSSA manifests" git push origin main # CI/CD will automatically deploy to Agent Platform
OSSA Manifest Example
Input (Drupal Agent):
$agent = Agent::create([ 'id' => 'content_moderator', 'label' => 'Content Moderator', 'description' => 'AI-powered content moderation agent for Drupal articles', 'provider' => 'gitlab_ai_gateway', 'model' => 'claude-sonnet-4', 'temperature' => 0.3, 'max_tokens' => 2048, 'tools' => ['moderate_content', 'flag_spam'], ]); $agent->save();
Output (OSSA Manifest):
apiVersion: ossa.bluefly.io/v0.2.8 kind: Agent metadata: name: content_moderator version: 1.0.0 description: AI-powered content moderation agent for Drupal articles labels: source: drupal drupal_bundle: content_moderator created: '2026-01-08' annotations: drupal.uuid: a1b2c3d4-e5f6-7890-abcd-ef1234567890 drupal.url: https://llm-platform.local/admin/ai/agents/content_moderator spec: capabilities: - text-classification - content-moderation tools: - name: moderate_content description: Moderate Drupal content for policy violations input_schema: type: object properties: node_id: type: integer description: Drupal node ID to moderate content_type: type: string description: Content type (article, page, etc.) required: - node_id output_schema: type: object properties: status: type: string enum: - approved - rejected - flagged reason: type: string description: Moderation decision reason - name: flag_spam description: Flag content as spam input_schema: type: object properties: node_id: type: integer confidence: type: number minimum: 0 maximum: 1 required: - node_id provider: type: gitlab model: claude-sonnet-4 configuration: temperature: 0.3 max_tokens: 2048
CI/CD Integration
Automated Sync to GitLab Components
File: .gitlab-ci.yml (in Drupal project)
ossa-export: stage: deploy image: drupal:11-php8.3-fpm script: # Export agents to OSSA manifests - drush ai:agents:ossa:export /tmp/ossa-agents/ # Clone gitlab_components repo - git clone https://gitlab.com/blueflyio/agent-platform/gitlab_components.git # Copy manifests - cp /tmp/ossa-agents/*.yaml gitlab_components/agents/ # Commit and push - cd gitlab_components - git config user.name "Drupal OSSA Exporter" - git config user.email "noreply@bluefly.io" - git add agents/ - git commit -m "feat: sync Drupal agents to OSSA manifests [skip ci]" - git push https://oauth2:${CI_JOB_TOKEN}@gitlab.com/blueflyio/agent-platform/gitlab_components.git main only: - main when: manual
Testing
PHPUnit Tests
File: tests/src/Unit/OssaBridgeTest.php
<?php declare(strict_types=1); namespace Drupal\Tests\ai_agents_ossa\Unit; use Drupal\ai_agents_ossa\Service\OssaBridge; use Drupal\Tests\UnitTestCase; /** * Tests for OSSA Bridge service. * * @group ai_agents_ossa */ final class OssaBridgeTest extends UnitTestCase { /** * Tests OSSA manifest export. */ public function testManifestExport(): void { // Mock agent entity $agent = $this->createMock('Drupal\ai_agents\Entity\AgentInterface'); $agent->method('id')->willReturn('test_agent'); $agent->method('uuid')->willReturn('test-uuid'); $agent->method('bundle')->willReturn('content_moderator'); // Create bridge $bridge = new OssaBridge(/* ... */); // Export manifest $manifest = $bridge->exportAgent($agent); // Assert OSSA structure $this->assertEquals('ossa.bluefly.io/v0.2.8', $manifest['apiVersion']); $this->assertEquals('Agent', $manifest['kind']); $this->assertArrayHasKey('metadata', $manifest); $this->assertArrayHasKey('spec', $manifest); } /** * Tests OSSA validation. */ public function testValidation(): void { $validator = new OssaValidator(); $valid_manifest = [ 'apiVersion' => 'ossa.bluefly.io/v0.2.8', 'kind' => 'Agent', 'metadata' => [ 'name' => 'test-agent', 'version' => '1.0.0', 'description' => 'Test agent for validation', ], 'spec' => [ 'capabilities' => ['test'], 'tools' => [ [ 'name' => 'test_tool', 'description' => 'Test tool', 'input_schema' => ['type' => 'object'], ], ], 'provider' => ['type' => 'anthropic', 'model' => 'claude-sonnet-4'], ], ]; $this->assertTrue($validator->isValid($valid_manifest)); } }
Production Checklist
- ai_agents_ossa module installed and enabled
- OSSA bridge service configured
- Export directory writable by Drupal
- GitLab Components repository accessible
- CI/CD pipeline configured for automated sync
- OSSA validation passing for all agents
- Agent manifests committed to GitLab
- Agent Platform deployment verified
- Monitoring enabled for OSSA agents
Troubleshooting
Issue: Validation Fails
Cause: Manifest doesn't match OSSA v0.2.8 schema
Fix:
# Validate specific manifest drush ossa-validate /path/to/agent.yaml # Check validation errors # Fix required fields, schema violations
Issue: Export Creates Empty Files
Cause: Agent missing required fields
Fix:
// Ensure agent has all required fields $agent->set('version', '1.0.0'); $agent->set('description', 'At least 10 characters'); $agent->set('provider', 'gitlab_ai_gateway'); $agent->set('model', 'claude-sonnet-4'); $agent->save();
Next Steps
- Implement ECA AI Actions: See
integrations/eca-ai-actions-drupal.md - Setup Agent Marketplace: See
ui/agent-marketplace-drupal.md - Configure GitLab Deployment: See GitLab Agent Platform docs
References
- OSSA Specification: https://gitlab.com/blueflyio/platform-agents/ossa/-/blob/main/spec/v0.2.8/
- drupal/ai_agents: https://www.drupal.org/project/ai_agents
- GitLab Agent Platform: https://gitlab.com/blueflyio/agent-platform/gitlab-agent_ossa
- Phase 1 Research:
modules/drupal-ai-agents-plugin-guide.md