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 MCP clients, native runtime modules, subscriptions, tasks, or authentication use ax-mcp alongside this skill. 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.
71
88%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
View guide
Passed
No findings from the security scan
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:
ax-agent-rlm skill.ax-agent-observability skill.ax-agent-memory-skills skill.agent.optimize(...), use the ax-agent-optimize skill.ax-mcp skill.agent(...), not new AxAgent(...).f() signatures over hand-written signature objects.ai, judgeAI, and agentIdentity on the agent(...) config when you want instance defaults or child-agent metadata.fn(...) for host-side function definitions instead of hand-writing JSON Schema objects.utils.search(...) or kb.find(...).functions: [...]. They land under their agentIdentity.namespace (or utils if unset), exactly like a fn() tool.discover(...) before using callables whose docs are not already in the prompt.functions: [...] for specialist delegation; do not model that as recursive llmQuery(...).bubbleErrors only for fatal infrastructure errors that should abort .forward().Map user intent to agent shape before writing code:
agent(...) with local functions, no extra observability.functions: [...] list and set each child's agentIdentity.namespace when you want a specific runtime call site such as team.writer(...).bubbleErrors with error classes; those errors propagate through function handlers, actor code, and llmQuery(...) sub-queries to .forward().llmQuery(...)" -> use ax-agent-rlm.ax-agent-observability.discover({ skills }), or loaded/used tracking" -> use ax-agent-memory-skills.agent(...) factory syntax for new code.functions: [...] list. Each child's agentIdentity.namespace (or utils, the default) determines the runtime call site, e.g. await team.writer({...}).discover(...) before using callables whose docs are not already in the prompt.autoUpgrade is ON by default: large tool catalogs auto-enable discovery, and oversized undeclared input values are auto-kept runtime-only with a truncated prompt preview. Explicit functionDiscovery and declared contextFields always win; set autoUpgrade: false to opt out.directResponse is ON by default ('auto'): when a task needs no user-provided functions, the distiller ends the run with respond(task, evidence) and the executor stage is skipped (zero executor model calls). Function-less agents run respond-only every time; agents with functions offer respond under a conservative covenant (no live/fresh-state asks, no side effects, nothing a listed function/module domain covers). Set directResponse: 'off' to always run the executor.AxAgentFunction needs to end the current actor turn, use extra.protocol.final(...) or extra.protocol.askClarification(...).forward() and streamingForward() flows, askClarification(...) throws AxAgentClarificationError; it does not go through the responder.error.getState() from the thrown AxAgentClarificationError, then call agent.setState(savedState) before the next forward(...).bubbleErrors bypass actor-loop catch blocks and propagate directly to the caller of .forward().inputs.<field> or use inputUpdateCallback when many calls need the same value.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);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.
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 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(...):
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:
const result = await team.writer({ draft: '...' });Without agentIdentity.namespace, the child lands under utils.<name> like any other tool:
const result = await utils.writer({ draft: '...' });Rules:
functions: [...], the same array as fn(...) tools.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.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.
inputs
llmQuery
final
askClarification
reportSuccess
reportFailure
inspectRuntime
discover
recallPick any other lowercase identifier such as utils, kb, tools, team, or db.
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:
const snippets = await kb.findSnippets({ topic: 'severity' });Rules:
utils when no namespace is set.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.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(...):
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: [],
});Attach MCP/UCP clients through the native execution context. Ax initializes them once, exposes mcp.<namespace> / ucp.<namespace> runtime modules, and propagates them through actor stages, llmQuery, RLM, and child agents:
Use ax-mcp for constructing those clients, transport/authentication policy,
server-initiated handlers, resource subscriptions, task continuations, and
recording/replay. Keep this section focused on Agent attachment and discovery.
const parent = agent('query:string -> answer:string', {
mcp: [memoryClient, searchClient],
mcpInheritance: 'all',
functionDiscovery: true,
contextFields: [],
});
// A child can restrict inheritance to selected namespaces or `none`.
await parent.forward(llm, { query }, { mcpInheritance: ['memory'] });Rules:
{ namespace, title, description, functions: [...] }.selectionCriteria is optional but useful in discovery mode; it tells the actor when to choose that module.namespace, title, selectionCriteria, and description show up in discover(...) module docs.relevanceRanking (default ON — set false to opt out): a deterministic local ranker that injects an advisory ### Likely Relevant shortlist into the executor turn (dynamic, non-cached field — the cached prompt stays byte-stable). Enabled by default after its A/B gate passed on both small and frontier models and implemented in the generated language ports through AxIR Core. Details in ax-agent-memory-skills; outcomes observable via the relevance_ranking context event (ax-agent-observability).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.functions: [...] either flat or grouped. Runtime validation rejects mixed plain function entries and group objects.fn(...) tools and child agents directly.childAgent.getFunction().functions; use mcp so tasks, resources, subscriptions, elicitation, sampling, authorization, cancellation, and protocol metadata remain available.AxMCPEventSource and an
explicit authenticated wake route. MCP sessions are not tenant identity;
supply identity from the application's authenticated token mapping.client.inspectCatalog()
and choose an explicit resourceSubscriptions policy. Omission means none;
'all' selects all discovered concrete resources; selectors can use names,
descriptions, MIME types, URIs, and annotations. Templates are not expanded..wakeInput(...) plan, or reuse an
eventInput() plan. Callback mapInput is still signature-validated and
cannot inject undeclared Agent fields. Use multiple matching routes to wake
multiple Agents with independent state, authorization, retries, and runs.AxUCPWebhookEventSource and map
verified profile/account state to Ax tenant identity after request
verification. Never derive tenant identity from the order payload.Use this pattern when the actor should call a namespaced function, but the host-side function implementation should decide to end the turn:
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.extra.protocol.final(...), extra.protocol.askClarification(...), or extra.protocol.guideAgent(...) only inside host-side function handlers.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.Use this pattern when the actor should pause for user input and continue later from the same runtime state.
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(...).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:
askClarification("What dates should I use?").askClarification({
question: 'Which route should I use?',
type: 'single_choice',
choices: ['Fastest', 'Scenic'],
});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.{ label, value? } objects.question are actor-turn runtime errors, not successful clarification completions.State notes:
runtimeBindings restores execution state; runtimeEntries, actionLogEntries, and checkpointState restore prompt context.getState() / setState(...).inputs, tools, and protocol helpers are rebuilt fresh and are not part of saved state.setState(...); do not share one mutable resumed instance across unrelated concurrent conversations.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.
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.llmQuery(...) sub-query propagates immediately to .forward().bubbleErrors for fatal infrastructure errors such as DB down, auth failure, or quota exceeded.bubbleErrors for expected recoverable errors; let those return as [ERROR] ... strings so the actor can handle them.AxAgentClarificationError and AxAIServiceAbortedError always bubble up unconditionally.There are two ways to end a successful run through the responder:
final(message) when no extra context object is needed, or final(task, context) when you gathered evidence.extra.protocol.final(...) with the same one-arg or two-arg forms.Rules:
final(message) when the actor already knows the answer and no extra context object is needed.final(task, context) when context was gathered and needs synthesis into output fields.extra.protocol.final(...) instead of a separate respond API.final(...) forms.askClarification(...) when the user must provide more information to continue.Enable discovery mode when you want the actor to discover modules and fetch callable definitions on demand:
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): voidawait discover(items: string[]): voidawait discover({ tools?: string | string[], skills?: string | string[] }): void when onSkillsSearch is configuredDiscovery 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.discover(...) with every module, callable, and skill you need.Promise.all(...).discover(...) for that module or function and call only the exact discovered qualified name.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:
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:
const polished = await team.writingCoach({
draft: summary,
audience: inputs.audience,
});Rules:
inputUpdateCallback to inject the value before each executor turn.Factory shape:
agent(signature, {
ai,
judgeAI,
agentIdentity,
contextFields,
functions,
functionDiscovery,
autoUpgrade,
playbook,
citations,
...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.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.
autoUpgrade is ON by default: the agent applies both knobs above on the user's behalf based on character counts, so forgetting them no longer floods prompts.
functionDiscovery is left unset and the estimated inline docs of discoverable functions exceed ~10k chars, discovery is enabled automatically. An explicit functionDiscovery: true | false always wins.contextMetadata entry, while the full value stays addressable as inputs.<field> in the code runtime (the responder stage gets the same preview). Fields declared in contextFields keep their declared config.autoUpgrade?: boolean | {
functionDiscovery?: boolean | { aboveFunctionDocChars?: number }; // default 10_000
contextFields?: boolean | { promoteAboveChars?: number; previewChars?: number }; // 8_000 / 1_200
}Rules:
autoUpgrade: false (or disable one side) to restore fully manual behavior.contextFields explicitly when they can be large.field_auto_promoted context event (onContextEvent) with the field name, original size, and preview size; use it to observe what was kept out of the prompt.These construction-time options are portable across TypeScript and the generated Python, Java, C++, Go, and Rust packages (both default off):
playbook: attach an ACE playbook at construction. learn is on by default — after each run that produced failure signals (error turns, dead-ends, failing tool calls) one bounded update curates durable avoidance rules that ride the next run's actor prompt; zero LLM cost on clean runs. TypeScript seeds a prior session with playbook: { playbook: snapshot }; generated packages accept their full { playbook, artifact } snapshot under seed. Persist via onUpdate, read the live handle with getPlaybook() (or the language-shaped equivalent), gate with learn: { minSignals, dedupe }, or disable with learn: false. To grow the same playbook from a task set with a held-out verify gate, use the agent-bound playbook evolve method — see ax-playbook.citations: add an optional evidenceCitations: string[] responder output listing which evidence entries (top-level keys of the curated evidence, plus memory ids) the answer relied on. Validated in-pipeline — the model cannot cite evidence it never collected (existence, not entailment). Pass true, or { field?, surface?: 'output' | 'hidden', includeMemoryIds?, onCitations? }.Stage guidance is portable too. setInstruction replaces the stage-owned actor
instruction and addActorInstruction appends an additive rule. Both are real
optimization components; rebuilding the split programs no longer discards them.
Generated packages use their normal casing conventions (set_instruction in
Python/C++/Rust and SetInstruction in Go).
The generated-language observer and evolve spellings are:
| Language | Citations observer | Verified agent playbook evolve |
|---|---|---|
| Python | citations.onCitations | agent.playbook().evolve(dataset, options) |
| Java | citations.onCitations (Consumer) | agent.playbook(null).evolve(dataset, options) |
| C++ | set_citations_observer(...) | agent.get_playbook()->evolve(dataset, options) |
| Go | citations.onCitations (func([]Value)) | agent.GetPlaybook().EvolveAgent(ctx, dataset, options) |
| Rust | set_citations_observer(...) | playbook.evolve_agent(&mut agent, client, dataset, options) |
C++ and Rust use set_playbook_observer(...) for construction-time learning
updates; Python, Java, and Go accept the playbook.onUpdate callback in their
native configuration map.
Use these method groups as the compact AxAgent surface map:
forward(ai, values, options?) and streamingForward(ai, values, options?).skills, onUsedMemories, and onUsedSkills; use ax-agent-memory-skills for details.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-live.ts for provider-backed persistence and finite-evolve usage.getChatLog(), getUsage(), getStagedUsage(), resetUsage(), and getTraces(); use ax-agent-observability for details.setDemos(...), namedPrograms(), namedProgramInstances(), optimize(...), applyOptimization(...), getOptimizableComponents(), and applyOptimizedComponents(...); use ax-agent-optimize for tuning details.playbook() returns an agent-aware playbook handle (update(...), render(), state/load methods, and verified dataset evolution); getPlaybook() reads the current handle. Generated packages expose the same behavior with language-shaped method names. Use ax-playbook for details.Rules:
getFunction() requires agentIdentity because the agent needs function metadata when used as a child tool..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.agent.optimize(...) and agent.applyOptimization(...).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:
judgeOptions as part of the optimize workflowmetric when scoring is mechanical; use the built-in judge only when run quality needs qualitative reviewax-agent-optimizeFetch these for full working code:
RLM examples are listed in ax-agent-rlm. Memory/skills examples are listed in ax-agent-memory-skills.
AxEventRuntime can wake or resume an Agent while preserving its logical
state. Use createProgram(instance) for multi-tenant Agents; one mutable Agent
object must not serve multiple instance keys concurrently. Clarification and
remote task completion are represented as owned continuations, not synthetic
user turns.
Declare the Agent signature on
eventTarget('id').createProgram(signature, factory) and map event values with
eventPath. The runtime verifies every created Agent against that signature
before invoking it. Fan-out uses multiple matching routes so each Agent keeps
its own authorization, instance serialization, retry policy, and run record.
new AxAgent(...) for new code unless explicitly required.agents.*.ax-agent-rlm.console.log(...) with final(...).bubbleErrors for ordinary recoverable tool errors.discover() from the distiller or responder stages.await discover(...); read the next prompt instead.discover() calls or wrap them in Promise.all.If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.