Глава 13

Определяйте агента контрактом, а не профессией

Название "архитектор" не задаёт допустимые входы, writes и handoffs. Исполняемый контракт делает роль проверяемой и ограничивает blast radius. Роль должна отвечать на инженерные вопросы: какие task kinds она принимает, какие tools видит, куда может писать, кому передаёт управление, какой output schema возвращает и сколько ресурсов получает. Prompt описывает качественное поведение внутри этих границ, но не заменяет enforcement.

TypeScript
  readonly maxAttempts: number;
}

export interface AgentContract {
  readonly id: AgentId;
  readonly accepts: readonly TaskKind[];
  readonly allowedWritePrefixes: readonly string[];
  readonly allowedHandoffs: readonly AgentId[];
}

export interface Usage {
  readonly calls: number;
  readonly tokens: number;
  readonly costUnits: number;
  readonly simulatedLatencyMs: number;
}

export interface ArtifactDraft {
  readonly key: string;
  readonly content: string;
  readonly expectedVersion: number;
}

export interface Artifact extends ArtifactDraft {
  readonly version: number;
  readonly writer: AgentId;
}

export interface Evidence {
  readonly name: string;
  readonly passed: boolean;
  readonly details: string;
}

export interface AgentResult {
  readonly summary: string;
  readonly artifacts: readonly ArtifactDraft[];
  readonly evidence: readonly Evidence[];
  readonly usage: Usage;
}

export interface AgentContext {
  readonly runId: string;
  readonly task: WorkItem;
  readonly inputArtifacts: readonly Artifact[];
  readonly attempt: number;
}

export interface AgentHandler {
  readonly contract: AgentContract;

Каждое поле контракта проверяется в своём месте - и это стоит держать перед глазами.

Поле · Назначение · Где проверять

  • accepts - Допустимые виды work item; До model call
  • allowedWritePrefixes - Владение artifacts; При каждом write
  • allowedHandoffs - Разрешенные ребра topology; При создании handoff
  • output schema - Machine-readable результат; До fan-in
  • budget - Calls, tokens, time и cost; В control plane

Проверку capability тоже делает код, а не просьба в prompt.

TypeScript
  for (const task of tasks) {
    visit(task.id);
  }
}

export function validateHandlers(handlers: readonly AgentHandler[]): void {
  const ids = new Set<string>();
  for (const handler of handlers) {
    if (ids.has(handler.contract.id)) {
      throw new Error(`Duplicate agent handler: ${handler.contract.id}`);
    }
    ids.add(handler.contract.id);
  }
}

export function assertTaskAccepted(contract: AgentContract, task: WorkItem): void {
Хорошая специализация - это разные границы, а не разные имена. Specialist отличается собственным контекстом, tools, permissions, output schema или evaluation rubric. Если отличаются только имя и одна строка character prompt, вероятно, это одна функция с параметром. И версионируйте контракт вместе с eval cases: изменение allowed tools или write scope - это изменение безопасности, а не косметическая правка prompt.

Контракт задаёт границы роли. Первая из них по стоимости и рискам - контекст, и его надо изолировать.