Agent This skill helps an LLM generate correct core AxAgent code using @ax-llm/ax. Use when the user asks about agent(), child agents, namespaced functions, discovery mode, clarification, bubbleErrors, host-side final/clarification protocol, or ordinary agent runtime behavior. For RLM/code-runtime work use ax-agent-rlm; for callbacks and telemetry use ax-agent-observability; for recall/memory/skill loading use ax-agent-memory-skills; for agent.optimize(...) use ax-agent-optimize. typescript skills skill-agent src/ax/skills/ax-agent.md skill Agent

AxAgent Codegen Rules (@ax-llm/ax)

This skill helps an LLM generate correct core AxAgent code using @ax-llm/ax. Use when the user asks about agent(), child agents, namespaced functions, discovery mode, clarification, bubbleErrors, host-side final/clarification protocol, or ordinary agent runtime behavior. For RLM/code-runtime work use ax-agent-rlm; for callbacks and telemetry use ax-agent-observability; for recall/memory/skill loading use ax-agent-memory-skills; for agent.optimize(…) use ax-agent-optimize.

Install

Install only this skill for TypeScript:

Shell
npx skills add https://ax-llm.github.io/ax/typescript/ --skill 'ax-agent'

Published skill file: ax-agent/SKILL.md.

Source

Skill Instructions

Use this skill to generate small, correct AxAgent code. Prefer modern factory-style APIs and copyable patterns. Do not write tutorial prose unless the user explicitly asks for explanation.

Your job is to choose the smallest correct AxAgent shape for the user’s needs:

  • If the user wants a normal tool-using assistant, keep the config minimal.
  • If the user wants long-running code execution, use the ax-agent-rlm skill.
  • If the user wants callbacks, logs, tracing, or usage data, use the ax-agent-observability skill.
  • If the user wants dynamic memory retrieval or skill-guide loading, use the ax-agent-memory-skills skill.
  • If the user wants tuning or eval with agent.optimize(...), use the ax-agent-optimize skill.

Use These Defaults

  • Use agent(...), not new AxAgent(...).
  • Prefer string signatures or f() signatures over hand-written signature objects.
  • Put ai, judgeAI, and agentIdentity on the agent(...) config when you want instance defaults or child-agent metadata.
  • Prefer fn(...) for host-side function definitions instead of hand-writing JSON Schema objects.
  • Prefer namespaced functions such as utils.search(...) or kb.find(...).
  • Pass child agents directly in functions: [...]. They land under their agentIdentity.namespace (or utils if unset), exactly like a fn() tool.
  • If discovery is enabled, call discover(...) before using callables whose docs are not already in the prompt.
  • Use explicit child agents in functions: [...] for specialist delegation; do not model that as recursive llmQuery(...).
  • Add bubbleErrors only for fatal infrastructure errors that should abort .forward().

Decision Guide

Map user intent to agent shape before writing code:

  • “Use tools and answer” -> plain agent(...) with local functions, no extra observability.
  • “Need child agents with distinct responsibilities” -> add child agents to the parent’s functions: [...] list and set each child’s agentIdentity.namespace when you want a specific runtime call site such as team.writer(...).
  • “Need tool discovery because names/schemas are not stable” -> enable discovery and generate discovery-first actor code.
  • “Need certain errors to escape the agent loop” -> add bubbleErrors with error classes; those errors propagate through function handlers, actor code, and llmQuery(...) sub-queries to .forward().
  • “Inspect large context with code”, “RLM”, or “llmQuery(...)” -> use ax-agent-rlm.
  • “Need debugging, traces, progress updates, tool-call logs, chat logs, or usage” -> use ax-agent-observability.
  • “Need memories, recall, dynamic skill guides, discover({ skills }), or loaded/used tracking” -> use ax-agent-memory-skills.

Critical Rules

  • Use agent(...) factory syntax for new code.
  • Add child agents to the parent’s functions: [...] list. Each child’s agentIdentity.namespace (or utils, the default) determines the runtime call site, e.g. await team.writer({...}).
  • If discovery is enabled, call discover(...) before using callables whose docs are not already in the prompt.
  • If a host-side AxAgentFunction needs to end the current actor turn, use extra.protocol.final(...) or extra.protocol.askClarification(...).
  • In public forward() and streamingForward() flows, askClarification(...) throws AxAgentClarificationError; it does not go through the responder.
  • When resuming after clarification, prefer error.getState() from the thrown AxAgentClarificationError, then call agent.setState(savedState) before the next forward(...).
  • Errors listed in bubbleErrors bypass actor-loop catch blocks and propagate directly to the caller of .forward().
  • Child agents receive only the arguments the actor passes. Pass parent fields explicitly via inputs.<field> or use inputUpdateCallback when many calls need the same value.
  • Audio input fields are transcribed before agent planner/executor/responder stages by default; internal agent stages receive text transcripts, not base64 audio.

Canonical Pattern

TypeScript
import { agent, ai, f } from '@ax-llm/ax';

const llm = ai({
  name: 'openai',
  apiKey: process.env.OPENAI_APIKEY!,
});

const assistant = agent(
  f()
    .input('query', f.string())
    .output('answer', f.string())
    .build(),
  {
    agentIdentity: {
      name: 'Assistant',
      description: 'Answers user questions',
    },
    contextFields: [],
  }
);

const result = await assistant.forward(llm, { query: 'What is TypeScript?' });
console.log(result.answer);

Audio Inputs And Speech Outputs

Agents can accept audio inputs and return scripted speech artifacts. The runtime transcribes audio input fields before internal stages run, then synthesizes :audio outputs after the final structured response is selected.

TypeScript
const voiceAgent = agent(
  'recording:audio, question:string -> speech:audio, summary:string',
  {
    agentIdentity: {
      name: 'Voice Assistant',
      description: 'Answers spoken requests',
    },
    contextFields: [],
  }
);

const result = await voiceAgent.forward(
  llm,
  {
    recording: { data: base64Wav, format: 'wav' },
    question: 'What should I do next?',
  },
  {
    speech: {
      transcribe: { model: 'gpt-4o-mini-transcribe' },
      speak: { voice: 'alloy', format: 'mp3' },
    },
  }
);

console.log(result.summary);
console.log(result.speech.data);

Use direct ax(...) or .chat() if the model should receive native audio instead of a transcript-first agent pipeline.

Child Agents As Tools

Child agents are passed in the parent’s functions list. There is no separate agents option for new code. Each child agent’s agentIdentity.namespace (or utils, the default) determines where it lands in the actor runtime. With AxJSRuntime, that produces JavaScript call sites such as team.writer(...):

TypeScript
const writer = agent('draft:string -> revision:string', {
  agentIdentity: {
    name: 'Writer',
    description: 'Polishes drafts',
    namespace: 'team',
  },
  contextFields: [],
});

const coordinator = agent('query:string -> answer:string', {
  functions: [writer],
  contextFields: [],
});

Generated runtime call:

JavaScript
const result = await team.writer({ draft: '...' });

Without agentIdentity.namespace, the child lands under utils.<name> like any other tool:

JavaScript
const result = await utils.writer({ draft: '...' });

Rules:

  • Add child agents to functions: [...], the same array as fn(...) tools.
  • Set agentIdentity.namespace on the child to control its runtime call site.
  • onFunctionCall observers receive kind: 'internal' for agent-derived calls and kind: 'external' for user-registered tools.

Reserved namespace names

The agent runtime injects a fixed set of globals into the runtime session. These names cannot be used as agentIdentity.namespace values or as agent-function namespaces.

text
inputs
llmQuery
final
askClarification
reportSuccess
reportFailure
inspectRuntime
discover
recall

Pick any other lowercase identifier such as utils, kb, tools, team, or db.

Tool Functions And Namespaces

TypeScript
import { agent, f, fn } from '@ax-llm/ax';

const findSnippets = fn('findSnippets')
  .description('Find handbook snippets by topic')
  .namespace('kb')
  .arg('topic', f.string('Topic keyword'))
  .returns(f.string('Matching snippet').array())
  .example({
    title: 'Find severity guidance',
    code: 'await kb.findSnippets({ topic: "severity" });',
  })
  .handler(async ({ topic }) => [])
  .build();

const analyst = agent('query:string -> answer:string', {
  functions: [findSnippets],
  contextFields: [],
});

Generated runtime call:

JavaScript
const snippets = await kb.findSnippets({ topic: 'severity' });

Rules:

  • Prefer namespaced functions.
  • Default function namespace is utils when no namespace is set.
  • With AxJSRuntime, use the runtime call shape await <namespace>.<name>({...}). Custom runtimes should expose equivalent namespaced calls through their own formatCallable() guidance.
  • .arg() and .returns() can use Ax field helpers or any Standard Schema v1 validator directly.

Grouped Function Modules

For discovery mode, group functions into modules using the AxAgentFunctionGroup shape when you want a clean namespace tree such as kb.find(...) or metrics.score(...) without setting namespace on every individual fn(...):

TypeScript
const parent = agent('query:string -> answer:string', {
  functions: [
    {
      namespace: 'kb',
      title: 'Knowledge Base',
      selectionCriteria: 'Use for handbook and documentation lookups.',
      description: 'Knowledge base lookups',
      functions: [findSnippetsFn, searchPagesFn],
    },
    {
      namespace: 'workflow',
      title: 'Workflow Controls',
      description: 'Small control functions the actor should always see',
      alwaysInclude: true,
      functions: [completeFn],
    },
  ],
  functionDiscovery: true,
  contextFields: [],
});

MCP clients and other toFunction() providers can be placed directly inside a group after initialization:

TypeScript
await mcpClient.init();

const parent = agent('query:string -> answer:string', {
  functions: [
    {
      namespace: 'memory',
      title: 'Memory MCP',
      description: 'Memory server tools',
      selectionCriteria: 'Use for persistent memory lookup and updates.',
      functions: [mcpClient],
    },
  ],
  functionDiscovery: true,
  contextFields: [],
});

Rules:

  • A group is { namespace, title, description, functions: [...] }.
  • selectionCriteria is optional but useful in discovery mode; it tells the actor when to choose that module.
  • The group’s namespace, title, selectionCriteria, and description show up in discover(...) module docs.
  • Add alwaysInclude: true to a group when discovery mode is on but the actor should always see that group’s full callable definitions inline in the prompt.
  • Keep functions: [...] either flat or grouped. Runtime validation rejects mixed plain function entries and group objects.
  • In flat mode, pass fn(...) tools, child agents, and toFunction() providers directly.
  • In grouped mode, put callable entries and toFunction() providers inside groups. To expose a child agent inside a group, use childAgent.getFunction().

Host-Side Completion From Functions

Use this pattern when the actor should call a namespaced function, but the host-side function implementation should decide to end the turn:

TypeScript
import { f, fn } from '@ax-llm/ax';

const finishReply = fn('finishReply')
  .description('Complete the actor turn with the final reply text')
  .namespace('workflow')
  .arg('reply', f.string('Final reply text'))
  .returns(f.string('Final reply text'))
  .handler(async ({ reply }, extra) => {
    extra?.protocol?.final(reply);
    return reply;
  })
  .build();

const askForOrderId = fn('askForOrderId')
  .description('Complete the actor turn by requesting clarification')
  .namespace('workflow')
  .arg('question', f.string('Clarification question'))
  .returns(f.string('Clarification question'))
  .handler(async ({ question }, extra) => {
    extra?.protocol?.askClarification(question);
    return question;
  })
  .build();

Rules:

  • extra.protocol is only available when the function call comes from an active AxAgent actor runtime session.
  • Use extra.protocol.final(...), extra.protocol.askClarification(...), or extra.protocol.guideAgent(...) only inside host-side function handlers.
  • Inside actor-authored runtime code, use the runtime globals final(...) and askClarification(...) with the syntax documented by the active runtime.
  • extra.protocol.guideAgent(...) is handler-only internal control flow. It stops the current actor turn and appends trusted guidance to guidanceLog for the next iteration.
  • askClarification(...) accepts either a simple string or a structured object with question plus optional UI hints such as type: 'date' | 'number' | 'single_choice' | 'multiple_choice' and choices.

Clarification And Resume State

Use this pattern when the actor should pause for user input and continue later from the same runtime state.

TypeScript
import {
  AxAgentClarificationError,
  AxJSRuntime,
  agent,
  ai,
} from '@ax-llm/ax';

const llm = ai({
  name: 'openai',
  apiKey: process.env.OPENAI_APIKEY!,
});

const tripAgent = agent('request:string, answer?:string -> reply:string', {
  contextFields: [],
  runtime: new AxJSRuntime(),
});

let savedState = tripAgent.getState();

try {
  await tripAgent.forward(llm, {
    request: 'Plan a Lisbon trip',
  });
} catch (error) {
  if (error instanceof AxAgentClarificationError) {
    console.log(error.question);
    savedState = error.getState();
  } else {
    throw error;
  }
}

if (savedState) {
  tripAgent.setState(savedState);
  const resumed = await tripAgent.forward(llm, {
    request: 'Plan a Lisbon trip',
    answer: 'June 1-5',
  });
  console.log(resumed.reply);
}

Public flow rules:

  • forward() and streamingForward() throw AxAgentClarificationError when the actor calls askClarification(...).
  • Successful final(...) completions always continue through the responder in public flows.
  • AxAgentClarificationError.question is the user-facing question text.
  • AxAgentClarificationError.clarification is the normalized structured payload.
  • AxAgentClarificationError.getState() returns the saved continuation state captured at throw time.
  • agent.getState() and agent.setState(...) export or restore continuation state on the agent instance.
  • test(...) is different: it returns structured completion payloads for harness/debug use instead of throwing clarification exceptions.

Structured clarification payloads:

  • String shorthand is allowed: askClarification("What dates should I use?").
  • Structured form is preferred for richer chat UIs:
JavaScript
askClarification({
  question: 'Which route should I use?',
  type: 'single_choice',
  choices: ['Fastest', 'Scenic'],
});
  • Supported type values are text, number, date, single_choice, and multiple_choice.
  • single_choice payloads with missing, empty, or malformed choices are downgraded to a plain clarification question instead of failing the turn.
  • multiple_choice payloads must include at least two valid choices; otherwise the actor turn fails with a corrective runtime error.
  • Choice entries may be strings or { label, value? } objects.
  • Invalid clarification payloads such as a missing question are actor-turn runtime errors, not successful clarification completions.

State notes:

  • runtimeBindings restores execution state; runtimeEntries, actionLogEntries, and checkpointState restore prompt context.
  • Resume does not create a fake rehydration action-log turn; provenance still points to the original actor code that set the value.
  • Only serializable/structured-clone-friendly values are guaranteed to round-trip through getState() / setState(...).
  • Reserved runtime globals such as inputs, tools, and protocol helpers are rebuilt fresh and are not part of saved state.
  • Treat one agent instance as conversation-scoped when using setState(...); do not share one mutable resumed instance across unrelated concurrent conversations.

Bubble Errors

Use bubbleErrors when certain exceptions thrown inside function handlers or llmQuery(...) sub-query calls should propagate all the way out to .forward() instead of being caught by the actor loop and returned as [ERROR] strings.

TypeScript
import { agent, f, fn } from '@ax-llm/ax';

class DatabaseError extends Error {
  constructor(message: string) {
    super(message);
    this.name = 'DatabaseError';
  }
}

const dbTool = fn('queryUsers')
  .description('Query the user database')
  .namespace('db')
  .arg('filter', f.string('Filter expression'))
  .returns(f.string('JSON result'))
  .handler(async ({ filter }) => {
    if (!isConnected()) throw new DatabaseError('DB connection refused');
    return JSON.stringify(await db.query(filter));
  })
  .build();

const myAgent = agent('query:string -> answer:string', {
  contextFields: [],
  functions: [dbTool],
  bubbleErrors: [DatabaseError],
});

Rules:

  • bubbleErrors takes an array of Error constructor classes, checked via instanceof.
  • A matching error thrown inside a function handler, during actor code execution, or inside an llmQuery(...) sub-query propagates immediately to .forward().
  • Use bubbleErrors for fatal infrastructure errors such as DB down, auth failure, or quota exceeded.
  • Do not use bubbleErrors for expected recoverable errors; let those return as [ERROR] ... strings so the actor can handle them.
  • AxAgentClarificationError and AxAIServiceAbortedError always bubble up unconditionally.

Unified Final Signal

There are two ways to end a successful run through the responder:

  1. In actor JS code, call final(message) when no extra context object is needed, or final(task, context) when you gathered evidence.
  2. In function handlers, use extra.protocol.final(...) with the same one-arg or two-arg forms.

Rules:

  • Use final(message) when the actor already knows the answer and no extra context object is needed.
  • Use final(task, context) when context was gathered and needs synthesis into output fields.
  • In function handlers, use extra.protocol.final(...) instead of a separate respond API.
  • The responder still runs for both successful final(...) forms.
  • Use askClarification(...) when the user must provide more information to continue.

Discovery Mode

Enable discovery mode when you want the actor to discover modules and fetch callable definitions on demand:

TypeScript
const analyst = agent('context:string, query:string -> answer:string', {
  agentIdentity: {
    name: 'Analyst',
    description: 'Analyzes long context',
    namespace: 'team',
  },
  contextFields: ['context'],
  functions: [writer, ...tools],
  functionDiscovery: true,
});

Discovery API:

  • await discover(item: string): void
  • await discover(items: string[]): void
  • await discover({ tools?: string | string[], skills?: string | string[] }): void when onSkillsSearch is configured

Discovery returns void; fetched docs render in the next executor prompt.

Rules:

  • discover('kb') loads a module callable list when kb is a discoverable module.
  • discover('kb.findSnippets') loads a full callable definition.
  • discover('lookup') resolves as utils.lookup.
  • discover({ tools: ['kb'], skills: ['release-checklist'] }) loads tool docs and skill bodies in one turn.
  • Call one batched discover(...) with every module, callable, and skill you need.
  • Do not split discovery into separate calls or wrap discovery in Promise.all(...).
  • Read the next prompt’s “Discovered Tool Docs” and “Loaded Skills” sections.
  • If a guessed call fails, stop guessing nearby names. Run discover(...) for that module or function and call only the exact discovered qualified name.

Threading Parent Fields Into Child Agents

If a child agent requires a parent field such as audience, declare it on the child’s signature and pass it explicitly when calling the child from the actor:

TypeScript
const writingCoach = agent('draft:string, audience:string -> revision:string', {
  agentIdentity: {
    name: 'Writing Coach',
    description: 'Polishes summaries for a target audience',
    namespace: 'team',
  },
  contextFields: [],
});

const analyst = agent('context:string, audience:string, query:string -> answer:string', {
  functions: [writingCoach],
  contextFields: ['context'],
});

Generated runtime call:

JavaScript
const polished = await team.writingCoach({
  draft: summary,
  audience: inputs.audience,
});

Rules:

  • Pass parent fields explicitly via the call site.
  • If many children need the same field on every call, use inputUpdateCallback to inject the value before each executor turn.
  • Do not assume auto-propagation; child agents receive only the args the actor passes.

Core API Reference

Factory shape:

TypeScript
agent(signature, {
  ai,
  judgeAI,
  agentIdentity,
  contextFields,
  functions,
  functionDiscovery,
  ...agentOptions,
});
  • ai is an optional default service for the agent instance; .forward(ai, ...) can still pass the runtime service.
  • judgeAI is the optional default judge/teacher service used by optimize flows.
  • agentIdentity controls the user-facing agent identity and child-agent function metadata.
TypeScript
agentIdentity?: {
  name: string;
  description: string;
  namespace?: string;
}
  • name is normalized to camelCase for child-agent function names.
  • name and description are included in the actor and responder prompts as the user-facing agent identity.
  • namespace changes the child-agent module from default utils to a custom module such as team.

Each contextFields entry is either a plain field name string or an object controlling how much of the value is inlined into the distiller prompt:

  • { field, promptMaxChars: N }: inline only when the serialized value is at most N chars; otherwise omit it from the prompt and keep it runtime-only.
  • { field, keepInPromptChars: N, reverseTruncate?: boolean }: always inline a truncated string excerpt; reverseTruncate: true keeps the last N chars.

Use promptMaxChars when partial data is worse than no data. Use keepInPromptChars when a prefix or suffix alone is useful. The two options are mutually exclusive on one field.

Public Surface

Use these method groups as the compact AxAgent surface map:

  • Running: forward(ai, values, options?) and streamingForward(ai, values, options?).
  • Forward-time agent options: skills, onUsedMemories, and onUsedSkills; use ax-agent-memory-skills for details.
  • State and control: getState(), setState(state?), getContextMap(), setContextMap(map?), stop(), getSignature(), setSignature(signature), getFunction(), getId(), and setId(id). Context-map evolve policy lives on AxAgentContextMap (infiniteEvolve, evolveSteps, maxChars), not on the agent config. See src/examples/rlm-context-map.ts for persistence and finite-evolve usage.
  • Observability: getChatLog(), getUsage(), getStagedUsage(), resetUsage(), and getTraces(); use ax-agent-observability for details.
  • Demos and tuning: setDemos(...), namedPrograms(), namedProgramInstances(), optimize(...), applyOptimization(...), getOptimizableComponents(), and applyOptimizedComponents(...); use ax-agent-optimize for tuning details.

Rules:

  • getFunction() requires agentIdentity because the agent needs function metadata when used as a child tool.
  • Prefer .forward(...) for normal runs and .streamingForward(...) only when the caller needs streamed responder output.
  • setSignature(...) must preserve configured contextFields; it throws if a configured context field is missing from the new signature.
  • Treat low-level optimization component methods as advanced hooks; normal examples should use agent.optimize(...) and agent.applyOptimization(...).

Tuning Hand-off

When the user wants agent.optimize(...), judge configuration, eval datasets, saved optimization artifacts, or optimization guidance, use ax-agent-optimize.

Keep this skill focused on building and running agents. For tuning work:

  • use eval-safe tools or in-memory mocks
  • treat judgeOptions as part of the optimize workflow
  • choose a deterministic metric when scoring is objective; use the built-in judge only when run quality needs qualitative review
  • keep runtime authoring guidance here and optimization guidance in ax-agent-optimize

Examples

Fetch these for full working code:

RLM examples are listed in ax-agent-rlm. Memory/skills examples are listed in ax-agent-memory-skills.

Do Not Generate

  • Do not use new AxAgent(...) for new code unless explicitly required.
  • Do not assume child agents are always under agents.*.
  • Do not guess function names in discovery mode.
  • Do not write a full multi-step RLM actor program in one turn; use ax-agent-rlm.
  • Do not combine console.log(...) with final(...).
  • Do not add bubbleErrors for ordinary recoverable tool errors.
  • Do not call discover() from the distiller or responder stages.
  • Do not assign or inspect the return value of await discover(...); read the next prompt instead.
  • Do not loop discover() calls or wrap them in Promise.all.
Docs