Creating Bindings
How to create a custom binding for a new platform
Creating Bindings
If AgenTopology does not ship a binding for your platform, you can create one. A binding implements the BindingTarget interface and tells AgenTopology how to generate config files, map models, and handle permissions for your target platform.
The BindingTarget interface
interface BindingTarget {
name: string;
scaffold(ast: TopologyAST, outputDir: string): GeneratedFile[];
resolveModel(modelId: string): string;
resolvePermission(permission: string): string;
resolveHookEvent(event: string): string;
validate(ast: TopologyAST): ValidationResult;
}
interface GeneratedFile {
path: string;
content: string;
}
interface ValidationResult {
valid: boolean;
errors: Array<{
rule: string;
message: string;
location?: string;
}>;
}Implementing each method
scaffold()
This is the core method. It takes a parsed AST and an output directory, then generates all the files your platform needs.
Your scaffold function should:
- Iterate over
ast.agentsand generate per-agent config files - Generate a root config file if your platform needs one
- Write all files to
outputDir - Return the list of generated files
scaffold(ast: TopologyAST, outputDir: string): GeneratedFile[] {
const files: GeneratedFile[] = [];
// Generate root config
files.push({
path: "config.yaml",
content: this.generateRootConfig(ast),
});
// Generate per-agent configs
for (const agent of ast.agents) {
files.push({
path: `agents/${agent.name}.yaml`,
content: this.generateAgentConfig(agent),
});
}
// Write files to disk
for (const file of files) {
const fullPath = path.join(outputDir, file.path);
mkdirSync(path.dirname(fullPath), { recursive: true });
writeFileSync(fullPath, file.content);
}
return files;
}resolveModel()
Maps AgenTopology's generic model identifiers to your platform's model names. AgenTopology uses short names like sonnet, opus, haiku, and flash. Your binding translates these to the actual model IDs your platform expects.
resolveModel(modelId: string): string {
const models: Record<string, string> = {
"opus": "my-platform/large-model",
"sonnet": "my-platform/medium-model",
"haiku": "my-platform/small-model",
"flash": "my-platform/fast-model",
};
return models[modelId] ?? modelId;
}resolvePermission()
Maps AgenTopology permission levels to your platform's permission system. Common permission levels are read, write, read-write, and execute.
resolvePermission(permission: string): string {
const permissions: Record<string, string> = {
"read": "PERM_READ",
"write": "PERM_WRITE",
"read-write": "PERM_READ | PERM_WRITE",
"execute": "PERM_EXEC",
};
return permissions[permission] ?? permission;
}resolveHookEvent()
Maps AgenTopology hook event names to your platform's event system. Common events are on-start, on-complete, on-error, and on-retry.
resolveHookEvent(event: string): string {
const events: Record<string, string> = {
"on-start": "lifecycle.started",
"on-complete": "lifecycle.finished",
"on-error": "lifecycle.failed",
"on-retry": "lifecycle.retrying",
};
return events[event] ?? event;
}validate()
Runs platform-specific validation rules. This is called in addition to the core validator. Return errors for anything your platform cannot support.
validate(ast: TopologyAST): ValidationResult {
const errors = [];
// Example: platform only supports up to 5 agents
if (ast.agents.length > 5) {
errors.push({
rule: "max-agents",
message: "This platform supports a maximum of 5 agents",
});
}
// Example: platform requires all agents to have tools
for (const agent of ast.agents) {
if (!agent.tools || agent.tools.length === 0) {
errors.push({
rule: "tools-required",
message: `Agent '${agent.name}' must have at least one tool`,
location: `agent:${agent.name}`,
});
}
}
return { valid: errors.length === 0, errors };
}Complete example
Here is a full binding skeleton for a hypothetical platform:
import { writeFileSync, mkdirSync } from "fs";
import * as path from "path";
import type { BindingTarget, GeneratedFile } from "agentopology";
export const myPlatformBinding: BindingTarget = {
name: "my-platform",
scaffold(ast, outputDir) {
const files: GeneratedFile[] = [];
// Root config
const config = {
name: ast.name,
version: ast.meta?.version ?? "1.0.0",
agents: ast.agents.map(a => ({
name: a.name,
model: this.resolveModel(a.model),
role: a.role,
})),
};
files.push({
path: "topology.json",
content: JSON.stringify(config, null, 2),
});
// Per-agent prompt files
for (const agent of ast.agents) {
files.push({
path: `agents/${agent.name}/prompt.md`,
content: `# ${agent.name}\n\nRole: ${agent.role}\nModel: ${this.resolveModel(agent.model)}\n`,
});
}
for (const file of files) {
const fullPath = path.join(outputDir, file.path);
mkdirSync(path.dirname(fullPath), { recursive: true });
writeFileSync(fullPath, file.content);
}
return files;
},
resolveModel(modelId) {
const models: Record<string, string> = {
opus: "my-platform-xl",
sonnet: "my-platform-lg",
haiku: "my-platform-sm",
};
return models[modelId] ?? modelId;
},
resolvePermission(permission) {
return permission.toUpperCase().replace("-", "_");
},
resolveHookEvent(event) {
return event.replace("on-", "event.");
},
validate(ast) {
const errors = [];
if (ast.agents.length > 10) {
errors.push({
rule: "max-agents",
message: "my-platform supports a maximum of 10 agents",
});
}
return { valid: errors.length === 0, errors };
},
};Registering your binding
Once created, you can use your binding directly:
import { parse, validate } from "agentopology";
import { myPlatformBinding } from "./my-platform-binding";
const ast = parse(source);
const result = validate(ast);
if (result.valid) {
const platformResult = myPlatformBinding.validate(ast);
if (platformResult.valid) {
myPlatformBinding.scaffold(ast, "./output");
}
}What's next
- See the bindings overview for built-in bindings
- Read the scaffold guide for usage patterns
- Check platform guides for platform-specific details