Skip to content

TypeScript Agent Class

Python equivalent: Python Expect DSL (the @agent decorator)

The Agent class wraps an agent function, manages trace construction via TraceBuilder, and returns an AgentResult for assertion chains.

import { Agent, agent, delegate, AgentResult } from '@attest-ai/core';

class Agent {
constructor(
name: string,
fn?: (builder: TraceBuilder, args: Record<string, unknown>) => unknown,
)
}
ParameterTypeDescription
namestringAgent identifier. Used as agent_id in traces.
fn(builder: TraceBuilder, args: Record<string, unknown>) => unknownAgent function that receives a TraceBuilder and input arguments. Return value becomes the trace output. Optional — omit when using withTrace().
PropertyTypeDescription
namestringThe agent identifier (read-only).

Executes the agent function synchronously. Creates a TraceBuilder, sets input, runs the function, captures output, and returns an AgentResult.

run(args?: Record<string, unknown>): AgentResult
const myAgent = new Agent("assistant", (builder, args) => {
builder.addLlmCall("gpt-4.1", {
args: { prompt: args.question },
result: { completion: "42" },
});
return { message: "42" };
});
const result = myAgent.run({ question: "What is the answer?" });

Output handling:

  • Object return values are used as-is for trace output.
  • Non-object return values (string, number, etc.) are wrapped as { result: <value> }.
  • undefined returns become { result: null }.

Async version of run(). Use when the agent function performs async operations.

async arun(args?: Record<string, unknown>): Promise<AgentResult>
const asyncAgent = new Agent("fetcher", async (builder, args) => {
const data = await fetchData(args.url);
builder.addToolCall("fetch", {
args: { url: args.url },
result: { data },
});
return { message: JSON.stringify(data) };
});
const result = await asyncAgent.arun({ url: "https://api.example.com/data" });

Creates an AgentResult from a pre-built Trace object, bypassing the agent function entirely. Useful when integrating adapter-captured traces.

withTrace(trace: Trace): AgentResult
import { OpenAIAdapter } from '@attest-ai/core';
const adapter = new OpenAIAdapter("assistant");
const trace = adapter.traceFromResponse(openaiResponse, {
inputMessages: messages,
costUsd: 0.003,
latencyMs: 450,
});
const myAgent = new Agent("assistant");
const result = myAgent.withTrace(trace);

A convenience function that creates an Agent and returns a callable wrapper. The returned function calls Agent.run() internally.

function agent(
name: string,
fn: (builder: TraceBuilder, args: Record<string, unknown>) => unknown,
): (args?: Record<string, unknown>) => AgentResult

The returned wrapper also exposes the underlying Agent instance via .agent.

const myAgent = agent("classifier", (builder, args) => {
builder.addLlmCall("gpt-4.1-mini", {
args: { text: args.text },
result: { completion: "positive" },
});
return { message: "positive", sentiment: "positive" };
});
// Call directly — no .run() needed
const result = myAgent({ text: "I love this product" });
// Access underlying Agent
const agentInstance = (myAgent as any).agent; // Agent instance

Delegates execution to a sub-agent within a parent agent’s run() context. Creates a child TraceBuilder, runs the sub-agent function, and records the delegation as an agent_call step on the parent trace.

async function delegate(
agentId: string,
fn: (child: TraceBuilder) => Promise<void> | void,
): Promise<void>
ParameterTypeDescription
agentIdstringIdentifier for the delegated sub-agent.
fn(child: TraceBuilder) => Promise<void> | voidFunction that builds the child trace. Receives a fresh TraceBuilder.

delegate() uses AsyncLocalStorage to find the parent TraceBuilder. It throws if called outside an Agent.run() or Agent.arun() context.

const orchestrator = new Agent("orchestrator", async (builder, args) => {
builder.addLlmCall("gpt-4.1", {
args: { task: "plan" },
result: { completion: "Delegating to researcher" },
});
await delegate("researcher", (child) => {
child.setInput({ query: args.query });
child.addToolCall("search", {
args: { q: args.query },
result: { results: ["result1", "result2"] },
});
child.setOutput({ message: "Found 2 results" });
});
return { message: "Research complete" };
});
const result = await orchestrator.arun({ query: "attest framework" });

The resulting trace contains an agent_call step with the child’s full trace embedded as sub_trace.


Holds the evaluation outcome: trace, assertion results, and aggregate metrics.

class AgentResult {
constructor(
trace: Trace,
assertionResults?: readonly AssertionResult[],
totalCost?: number,
totalDurationMs?: number,
)
}
PropertyTypeDescription
traceTraceThe captured trace (read-only).
assertionResultsreadonly AssertionResult[]All assertion results after engine evaluation. Empty before evaluation.
totalCostnumberTotal evaluation cost in USD.
totalDurationMsnumberTotal evaluation duration in milliseconds.
PropertyTypeDescription
passedbooleantrue if every assertion has status "pass".
failedAssertionsreadonly AssertionResult[]Assertions that did not pass (soft + hard failures).
hardFailuresreadonly AssertionResult[]Assertions with status "hard_fail".
softFailuresreadonly AssertionResult[]Assertions with status "soft_fail" (warnings).
passCountnumberCount of passed assertions.
failCountnumberCount of failed assertions (soft + hard).
const evaluated = await evaluate(chain);
console.log(`${evaluated.passCount}/${evaluated.assertionResults.length} passed`);
console.log(`Cost: $${evaluated.totalCost.toFixed(4)}`);
if (!evaluated.passed) {
for (const failure of evaluated.hardFailures) {
console.error(`FAIL: ${failure.explanation}`);
}
for (const warning of evaluated.softFailures) {
console.warn(`WARN: ${warning.explanation}`);
}
}

StatusConstantMeaning
"pass"STATUS_PASSAssertion succeeded.
"soft_fail"STATUS_SOFT_FAILAssertion failed but was marked soft: true. Reported as warning.
"hard_fail"STATUS_HARD_FAILAssertion failed. Test should fail.

import { Agent, attestExpect, delegate } from '@attest-ai/core';
import { evaluate } from '@attest-ai/vitest';
import { describe, it, expect } from 'vitest';
const pipeline = new Agent("orchestrator", async (builder, args) => {
builder.addLlmCall("gpt-4.1", {
args: { task: "coordinate" },
result: { completion: "Delegating research and writing" },
});
await delegate("researcher", (child) => {
child.setInput({ topic: args.topic });
child.addToolCall("web_search", {
args: { query: args.topic },
result: { articles: 5 },
});
child.setOutput({ message: "Found 5 relevant articles" });
});
await delegate("writer", (child) => {
child.setInput({ findings: "5 articles" });
child.addLlmCall("gpt-4.1", {
args: { task: "write summary" },
result: { completion: "Summary of findings..." },
});
child.setOutput({ message: "Summary of findings..." });
});
return { message: "Research and summary complete" };
});
describe("multi-agent pipeline", () => {
it("delegates correctly", async () => {
const result = await pipeline.arun({ topic: "AI testing" });
const chain = attestExpect(result)
.agentCalled("researcher")
.agentCalled("writer")
.agentOrderedBefore("researcher", "writer")
.delegationDepth(1)
.aggregateCostUnder(0.10);
const evaluated = await evaluate(chain);
expect(evaluated.passed).toBe(true);
});
});