AgenTopology

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:

  1. Iterate over ast.agents and generate per-agent config files
  2. Generate a root config file if your platform needs one
  3. Write all files to outputDir
  4. 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

On this page