Глава 25

Стройте control plane независимо от model provider

Business state, budgets, permissions и acceptance не должны исчезать при смене SDK. Provider adapter отвечает за model loop, а приложение отвечает за систему. Полезно разделить три слоя: domain определяет work items, artifacts и acceptance; control plane управляет graph, budget, state, identity и trace; model adapter превращает provider-specific response, tool call или handoff в ваши typed events.

  1. Domain: task и outcome
  2. Control plane: graph, policy, state
  3. Adapter: SDK run и tools
  4. Provider: model service
  5. Evidence: external gate

Провайдер-независимость держится на узком интерфейсе handler - модель за ним взаимозаменяема.

TypeScript
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;
  run(context: AgentContext): Promise<AgentResult>;
}

export interface BudgetLimits {
  readonly maxCalls: number;
  readonly maxTokens: number;
  readonly maxCostUnits: number;
  readonly maxSimulatedLatencyMs: number;
}

export interface BudgetSnapshot extends BudgetLimits {
  readonly usedCalls: number;

А сам control plane валидирует граф и контракты до запуска и владеет исполнением.

TypeScript
export class Orchestrator {
  readonly #tasks: readonly WorkItem[];
  readonly #handlers: ReadonlyMap<string, AgentHandler>;
  readonly #limits: BudgetLimits;
  readonly #maxConcurrency: number;

  public constructor(input: {
    readonly tasks: readonly WorkItem[];
    readonly handlers: readonly AgentHandler[];
    readonly limits: BudgetLimits;
    readonly maxConcurrency: number;
  }) {
    validateWorkGraph(input.tasks);
    validateHandlers(input.handlers);
    if (!Number.isInteger(input.maxConcurrency) || input.maxConcurrency < 1) {
      throw new Error("maxConcurrency must be a positive integer");
    }
    this.#tasks = input.tasks;
    this.#handlers = new Map(input.handlers.map((handler) => [handler.contract.id, handler]));
    this.#limits = input.limits;
    this.#maxConcurrency = input.maxConcurrency;
  }

  public async run(runId: string): Promise<RunReport> {
    const status: Record<string, TaskStatus> = createInitialStatus(this.#tasks);
    const budget = new BudgetLedger(this.#limits);
    const eventLog = new EventLog();
    const trace = new TraceRecorder();
    const evidence: Evidence[] = [];

OpenAI Agents SDK предлагает agents, agents-as-tools, handoffs, guardrails, sessions, human-in-the-loop и tracing; Claude Agent SDK даёт programmable agent loop на Python и TypeScript. Эти возможности используются через adapters, не отдавая SDK владение вашей domain database и необратимыми эффектами. Граница adapter узкая: нормализовать model output в typed result, превратить tool request в проверяемый host call, передать usage и timing в общий budget/trace, сериализовать pause/resume, но не решать domain acceptance внутри свободного текста.

Практический выигрыш. Стабильные outcome-, budget- и trace-схемы позволяют A/B-тестировать модели и фреймворки на одном task bank, не переписывая систему под каждый SDK.

Control plane развязан от провайдера. Дальше - три главы о конкретных продуктах, и первая различает Claude Code subagents и agent teams.

Ссылки