NEWn1n v2.0.1 is live! Enterprise Unified LLM API Gateway with 500+ AI Models, up to 90% off, Try now

Your AI Agent Needs No Framework: A 200-Line TypeScript Harness

Authors
  • avatar
    Name
    Nino
    Occupation
    Senior Tech Editor

Most developers fall into the trap of reaching for heavy, opinionated frameworks the moment they decide to build an AI agent. They import massive abstractions, configure sprawling state machines, and spend days debugging why a graph-based workflow isn't routing correctly. But here is the secret: your agent does not need a framework. It needs a loop, a tool registry, three limits, and one approval gate.

That is just over 200 lines of TypeScript, and every line is code you can read, test, and change when the agent misbehaves. A framework earns its place later, when you need durable runs, multi-agent routing, or observability you do not want to build. It does not earn its place on day one, when you have one agent and five tools and the framework is the biggest thing in your repository.

When consuming high-performance endpoints from providers like DeepSeek-V3 or Claude 3.5 Sonnet through reliable aggregators like n1n.ai, keeping your architecture transparent ensures maximum execution speed and zero unnecessary overhead.

The Anatomy of an Agentic Harness

Strip the marketing away and a reliable harness has six parts:

  1. The loop: One while loop that owns the message history and decides when to stop.
  2. The tool registry: A map from tool name to a description, a schema, and a handler. Nothing else in the system knows how tools work.
  3. The limits: Max turns, max cost, and a repeated-call guard. These are the whole safety story for a first agent.
  4. The model interface: One method, complete(), so swapping models means swapping an adapter, not rewriting the agent.
  5. The verifier: A check that runs on the final answer before the run counts as done.
  6. The approval gate: Write actions and money actions wait for a human yes. Reads run free.

Notice what is missing: no graph DSL, no agent class hierarchy, no plugin system, no state machine framework.

The Core TypeScript Implementation

Here is the core harness. It is provider-agnostic, has no dependencies, and gives you total control over execution. For production deployments, you can route your requests seamlessly via n1n.ai to maintain optimal latency.

// harness.ts - the loop, the registry, and the limits. No dependencies.
export type Message = \{
  role: "user" | "assistant" | "tool";
  content: string;
  toolCallId?: string;
  toolCalls?: ToolCall[];
\};

export type ToolCall = \{
  id: string;
  name: string;
  args: unknown;
\};

export type ModelResponse = \{
  text: string;
  toolCalls: ToolCall[];
  costUsd: number;
\};

export interface ModelClient \{
  complete(messages: Message[], tools: ToolDef[]): Promise<ModelResponse>;
\}

export type ToolDef = \{
  name: string;
  description: string;
  parameters: Record<string, unknown>;
  needsApproval: boolean;
  run: (args: unknown) => Promise<unknown>;
\};

export type Limits = \{
  maxTurns: number;
  maxCostUsd: number;
  approve?: (toolName: string, args: unknown) => Promise<boolean>;
  verify?: (answer: string) => boolean;
\};

export type AgentResult =
  | \{ status: "done"; answer: string; turns: number; costUsd: number \}
  | \{ status: "turn_limit" | "cost_limit" | "repeated_call" | "blocked" | "failed_verification"; turns: number; costUsd: number \};

function sameCall(a: ToolCall, b: ToolCall): boolean \{
  return a.name === b.name && JSON.stringify(a.args) === JSON.stringify(b.args);
\}

export async function runAgent(
  task: string,
  model: ModelClient,
  tools: ToolDef[],
  limits: Limits,
): Promise<AgentResult> \{
  const messages: Message[] = [\{ role: "user