Chapter 13

Define an Agent by a Contract, Not a Profession

The name "architect" doesn't set the allowed inputs, writes, and handoffs. An executable contract makes the role checkable and bounds the blast radius. The role must answer engineering questions: which task kinds it accepts, which tools it sees, where it can write, to whom it hands off, what output schema it returns, and how many resources it gets. The prompt describes qualitative behavior within these boundaries but doesn't replace 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;

Each contract field is checked in its own place - and that's worth keeping in view.

Field · Purpose · Where to check

  • accepts - Allowed kinds of work item; Before the model call
  • allowedWritePrefixes - Artifact ownership; On every write
  • allowedHandoffs - Allowed topology edges; When creating a handoff
  • output schema - A machine-readable result; Before fan-in
  • budget - Calls, tokens, time, and cost; In the control plane

The capability check is also done by code, not a request in the 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 {
Good specialization is different boundaries, not different names. A specialist differs by its own context, tools, permissions, output schema, or evaluation rubric. If only the name and one line of character prompt differ, it's probably one function with a parameter. And version the contract together with the eval cases: a change of allowed tools or write scope is a security change, not a cosmetic prompt edit.

The contract sets the role's boundaries. The first of them by cost and risk is context, and it must be isolated.