| name | ax-agent |
| description | This skill helps an LLM generate correct AxAgent code using @ax-llm/ax. Use when the user asks about agent(), child agents, namespaced functions, discovery mode, shared fields, llmQuery(...), or RLM code execution. |
| version | 19.0.17 |
AxAgent Codegen Rules (@ax-llm/ax)
Use this skill to generate AxAgent code. Prefer short, modern, copyable patterns. Do not write tutorial prose unless the user explicitly asks for explanation.
Use These Defaults
- Use
agent(...), not new AxAgent(...).
- Prefer
fn(...) for host-side function definitions instead of hand-writing JSON Schema objects.
- Prefer namespaced functions such as
utils.search(...) or kb.find(...).
- Assume the child-agent module is
agents unless agentIdentity.namespace is set.
- If
functions.discovery is true, discover callables from modules before using them.
- In stdout-mode RLM, use one observable
console.log(...) step per non-final actor turn.
- For long RLM tasks, prefer
contextPolicy: { preset: 'adaptive' } so older successful turns collapse into checkpoint summaries while live runtime state stays visible.
Mental Model
Treat AxAgent as a long-running JavaScript REPL that the actor steers over multiple turns, not as a fresh script generator on every turn.
- Successful code leaves variables, functions, imports, and computed values available in the runtime session.
- The actor should continue from existing runtime state instead of recreating prior work.
Action Log, Live Runtime State, and checkpoint summaries only control what the actor can see again in the prompt.
- Rebuild state only after an explicit runtime restart notice or when you intentionally need to overwrite a value.
Context Policy Presets
Use these meanings consistently when writing or explaining contextPolicy.preset:
full: Keep prior actions fully replayed. Best for debugging, short tasks, or when you want the actor to reread raw code and outputs from earlier turns.
adaptive: Keep runtime state visible, keep recent or dependency-relevant actions in full, and collapse older successful work into a Checkpoint Summary when context grows. This is the default recommendation for long multi-turn tasks.
lean: Most aggressive compression. Keep Live Runtime State, checkpoint older successful work, and summarize replay-pruned successful turns instead of showing their full code blocks. Use when token pressure matters more than raw replay detail.
Practical rule:
- Start with
adaptive for most long RLM tasks.
- Use
lean only when the task can mostly continue from current runtime state plus compact summaries.
- Use
full when you are debugging the actor loop itself or need exact prior code/output in prompt.
Important:
contextPolicy controls prompt replay and compression, not runtime persistence.
- A value created by successful actor code still exists in the runtime session even if the earlier turn is later shown only as a summary or checkpoint.
- Used discovery docs are replay artifacts too:
adaptive and lean can hide old listModuleFunctions(...) / getFunctionDefinitions(...) output after the actor successfully uses the discovered callable.
- Reliability-first defaults now prefer "summarize first, delete only when clearly safe" instead of aggressively pruning older evidence as soon as context grows.
Critical Rules
- Use
agent(...) factory syntax for new code.
- If
agentIdentity.namespace is set, call child agents through that module, not agents.
- If
functions.discovery is true, call listModuleFunctions(...) first, then getFunctionDefinitions(...), then call only discovered functions.
- In stdout-mode RLM, non-final turns must emit exactly one
console.log(...) and stop immediately after it.
- Never combine
console.log(...) with final(...) or ask_clarification(...) in the same actor turn.
- If a host-side
AxAgentFunction needs to end the current actor turn, use extra.protocol.final(...) or extra.protocol.askClarification(...).
- If a child agent needs parent inputs such as
audience, use fields.shared or fields.globallyShared.
llmQuery(...) failures may come back as [ERROR] ...; do not assume success.
- If
contextPolicy.state.summary is on, rely on the Live Runtime State block for current variables instead of re-reading old action log code.
- If
contextPolicy.preset is 'adaptive' or 'lean', assume older successful turns may be replaced by a Checkpoint Summary and that replay-pruned successful turns may appear as compact summaries instead of full code blocks.
Canonical Pattern
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);
Child Agents And Module Namespace
Default child-agent module:
const writer = agent('draft:string -> revision:string', {
agentIdentity: {
name: 'Writer',
description: 'Polishes drafts',
},
contextFields: [],
});
const coordinator = agent('query:string -> answer:string', {
agents: { local: [writer] },
contextFields: [],
});
Generated runtime call:
const result = await agents.writer({ draft: '...' });
Custom child-agent module:
const writer = agent('draft:string -> revision:string', {
agentIdentity: {
name: 'Writer',
description: 'Polishes drafts',
},
contextFields: [],
});
const coordinator = agent('query:string -> answer:string', {
agentIdentity: {
name: 'Coordinator',
description: 'Routes work',
namespace: 'team',
},
agents: { local: [writer] },
contextFields: [],
});
Generated runtime call:
const result = await team.writer({ draft: '...' });
Rules:
- Default child-agent module is
agents.
- If
agentIdentity.namespace is set, that becomes the child-agent module.
- Do not hardcode
agents.<name>(...) when a custom namespace is configured.
Tool Functions And Namespaces
import { f, fn } from '@ax-llm/ax';
const tools = [
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: {
local: [
{
namespace: 'kb',
title: 'Knowledge Base',
selectionCriteria: 'Use for handbook and documentation lookups.',
description: 'Handbook and documentation search helpers.',
functions: tools.map(({ namespace: _namespace, ...tool }) => tool),
},
],
},
contextFields: [],
});
Generated runtime call:
const snippets = await kb.findSnippets({ topic: 'severity' });
Rules:
- Prefer namespaced functions.
- Default function namespace is
utils when no namespace is set.
- Use the runtime call shape
await <namespace>.<name>({...}).
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:
import { f, fn } from '@ax-llm/ax';
const workflowTools = [
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(),
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(...) or extra.protocol.askClarification(...) only inside host-side function handlers.
- Inside actor-authored JavaScript, keep using the runtime globals
final(...) and ask_clarification(...).
- Do not model these protocol completions as normal registered tool functions or discovery entries.
Discovery Mode
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'],
agents: { local: [writer] },
functions: {
discovery: true,
local: tools,
},
});
Discovery APIs:
await listModuleFunctions(modules: string | string[])
await getFunctionDefinitions(functions: string | string[])
Both return Markdown.
listModuleFunctions(...) only lists modules that actually have callable entries.
- Grouped modules render in the Actor prompt as
<namespace> - <selection criteria> when criteria is provided.
- If a requested module does not exist,
listModuleFunctions(...) returns a per-module markdown error without failing the whole call.
getFunctionDefinitions(...) may include argument comments from schema descriptions and fenced code examples from AxAgentFunction.examples.
Rules:
- Call
listModuleFunctions(...).
- If you need multiple modules, use one batched array call such as
listModuleFunctions(['timeRange', 'schedulingOrganizer']).
- Log or inspect the returned markdown directly. Do not wrap it in JSON or custom objects.
- If you need multiple callable definitions, prefer one batched
getFunctionDefinitions([...]) call.
- Do not split discovery into separate calls with
Promise.all(...).
- Inspect the logged result.
- Call
getFunctionDefinitions(...) for only the callables you plan to use.
- Inspect the logged result.
- Call discovered functions and child agents.
Examples:
const modules = await listModuleFunctions(['team', 'kb', 'utils']);
console.log(modules);
const defs = await getFunctionDefinitions(['team.writer', 'kb.findSnippets']);
console.log(defs);
Do not:
- Do not guess callable names when discovery mode is on.
- Do not assume sub-agents live under
agents if agentIdentity.namespace is configured.
- Do not dump large pre-known tool definitions into actor code when discovery mode is enabled.
- Do not use
Promise.all(...) to fan out discovery calls across modules or definitions.
- Do not convert discovery markdown into JSON before logging or using it.
- If used discovery docs disappear from later prompts under
adaptive or lean, call listModuleFunctions(...) or getFunctionDefinitions(...) again when you need to re-open them.
RLM Actor Code Rules
Use these rules when generating actor JavaScript for RLM in stdout mode:
- Treat each actor turn as exactly one observable step.
- Inspect what already exists before recomputing it. If a prior turn successfully created a value, prefer reusing that runtime value.
- If you need to inspect a value, compute it or read it,
console.log(...) it, and stop immediately after that console.log(...).
- On the next turn, continue from the existing runtime state and use the logged result from
Action Log only as evidence for what happened.
- If the prompt contains
Live Runtime State, treat it as the canonical view of current variables.
- Errors from child-agent or tool calls appear in
Action Log; inspect them and fix the code on the next turn.
- Non-final turns should contain exactly one
console.log(...).
- Final turns should call
final(...) or ask_clarification(...) without console.log(...).
- Do not write a complete multi-step program in one actor turn.
- Do not re-declare or recompute values just because older turns are summarized; only rebuild after an explicit runtime restart or when you intentionally want a new value.
- Do not assume older successful turns remain fully replayed; adaptive or lean policies may collapse them into a
Checkpoint Summary block or compact action summaries.
Small reuse example:
Turn 1:
const customers = await kb.findCustomers({ segment: 'active' });
console.log(customers.length);
Turn 2:
const topCustomers = customers.slice(0, 3);
console.log(topCustomers);
Reason: turn 2 reuses customers from the persistent runtime. Live Runtime State or summaries may change how turn 1 is shown in the prompt, but they do not remove the value from the runtime session.
RLM Test Harness
Use agent.test(code, contextFieldValues?, options?) when the user wants to validate JavaScript snippets against the actual AxAgent runtime environment without running the full Actor/Responder loop.
import { AxJSRuntime, agent, f, fn } from '@ax-llm/ax';
const runtime = new AxJSRuntime();
const tools = [
fn('sum')
.description('Return the sum of the provided numeric values')
.namespace('math')
.arg('values', f.number('Value to add').array())
.returns(f.number('Sum of all values'))
.handler(async ({ values }) =>
values.reduce((total, value) => total + value, 0)
)
.build(),
];
const harness = agent('query:string -> answer:string', {
contextFields: ['query'],
runtime,
functions: { local: tools },
contextPolicy: { preset: 'adaptive' },
});
const output = await harness.test(
'console.log(await math.sum({ values: [3, 5, 8] }))',
{ query: 'sum the values' }
);
console.log(output);
Rules:
test(...) creates a fresh runtime session per call.
- It exposes the same runtime globals the actor would see for configured
contextFields: inputs, non-colliding top-level aliases, namespaced functions, child agents, and llmQuery.
- In
AxJSRuntime, do not rely on calling inspect_runtime() from inside test(...) snippets yet; prefer checking runtime globals directly inside the snippet.
- It returns the formatted runtime output string.
- It throws on runtime failures instead of returning LLM-style error strings.
- Do not call
final(...) or ask_clarification(...) inside test(...) snippets.
- Pass only
contextFields values to test(...); it is not a general way to inject arbitrary non-context inputs.
- If the snippet uses
llmQuery(...), provide an AI service through the agent config or options.ai.
RLM Adaptive Replay
Prefer this configuration for long, multi-turn runtime analysis:
const analyst = agent(
'context:string, question:string -> answer:string, findings:string[]',
{
contextFields: ['context'],
runtime: new AxJSRuntime(),
maxTurns: 10,
contextPolicy: {
preset: 'adaptive',
summarizerOptions: {
model: 'summary-model',
modelConfig: { temperature: 0.2, maxTokens: 180 },
},
state: {
summary: true,
inspect: true,
inspectThresholdChars: 8_000,
maxEntries: 6,
maxChars: 1_200,
},
checkpoints: {
enabled: true,
triggerChars: 12_000,
},
expert: {
pruneErrors: true,
rankPruning: { enabled: true, minRank: 2 },
tombstones: {
model: 'summary-model',
modelConfig: { maxTokens: 80 },
},
},
},
}
);
Rules:
- Use
preset: 'full' when the actor should keep seeing raw prior code and outputs with minimal compression.
- Use
preset: 'adaptive' when the task needs runtime state across many turns but older successful work should collapse into checkpoint summaries while important recent steps can still stay fully replayed.
- Use
preset: 'lean' when you want more aggressive compression and can rely mostly on current runtime state plus checkpoint summaries and compact action summaries.
- Use
state.summary to inject a compact Live Runtime State block into the actor prompt. The block is structured and provenance-aware: variables are rendered with compact type/size/preview metadata, and when Ax can infer it, a short source suffix like from t3 via db.search is included. Combine maxEntries with maxChars so large runtime objects do not dominate the prompt.
- Use
state.inspect with inspectThresholdChars so the actor is reminded to call inspect_runtime() when replayed action history starts getting large.
adaptive and lean hide used discovery docs by default; set contextPolicy.pruneUsedDocs: false if you want to keep replaying them.
full keeps used discovery docs by default; set contextPolicy.pruneUsedDocs: true if you want the same cleanup there.
- Use
summarizerOptions to tune the internal checkpoint-summary AxGen program.
- If you configure
expert.tombstones, treat the object form as options for the internal tombstone-summary AxGen program.
- Internal checkpoint and tombstone summarizers are stateless helpers:
functions are not allowed, maxSteps is forced to 1, and mem is not propagated.
- Built-in
adaptive and lean presets no longer enable destructive rank pruning by default. Opt in with expert.rankPruning only when you want lower-value successful turns deleted instead of summarized.
- If you want a quick local demo of the rendered
Live Runtime State block, run src/examples/rlm-live-runtime-state.ts.
Good pattern:
Turn 1:
const defs = await getFunctionDefinitions(['kb.findSnippets']);
console.log(defs);
Turn 2:
const snippets = await kb.findSnippets({ topic: 'severity' });
console.log(snippets);
Turn 3:
final({ answer: '...' });
Invalid pattern:
const defs = await getFunctionDefinitions(['kb.findSnippets']);
console.log(defs);
const snippets = await kb.findSnippets({ topic: 'severity' });
final(snippets);
Reason: this mixes observation and follow-up work in one turn.
Shared Fields
If a child agent requires a parent field such as audience, prefer shared fields:
const writingCoach = agent(
'draft:string, audience:string -> revision:string',
{
agentIdentity: {
name: 'Writing Coach',
description: 'Polishes summaries for a target audience',
},
contextFields: [],
}
);
const analyst = agent(
'context:string, audience:string, query:string -> answer:string',
{
agents: { local: [writingCoach] },
fields: { shared: ['audience'] },
contextFields: ['context'],
}
);
Generated runtime call:
const polished = await agents.writingCoach({ draft: summary });
Rules:
- Use
fields.shared for direct children.
- Use
fields.globallyShared for all descendants.
- Do not manually thread a parent field on every child call when shared fields fit the use case.
Shared Agents And Shared Functions
Use grouped config:
const parent = agent('query:string -> answer:string', {
agents: {
local: [worker],
shared: [logger],
globallyShared: [auditor],
},
functions: {
local: [searchTool],
shared: [scoreTool],
globallyShared: [traceTool],
},
contextFields: [],
});
Rules:
agents.shared and functions.shared propagate one level down.
agents.globallyShared and functions.globallyShared propagate to all descendants.
- Use
excluded when a child should not receive a propagated field, agent, or function.
llmQuery(...) Rules
Available forms:
await llmQuery(query, context?)
await llmQuery({ query, context? })
await llmQuery([{ query, context }, ...])
Rules:
llmQuery(...) forwards only the explicit context argument.
- Parent inputs are not automatically available to
llmQuery(...) children.
- Single-call
llmQuery(...) may return [ERROR] ... on non-abort failures.
- Batched
llmQuery([...]) returns per-item [ERROR] ....
- If a result starts with
[ERROR], inspect or branch on it instead of assuming success.
Example:
const summary = await llmQuery('Summarize this incident', inputs.context);
if (summary.startsWith('[ERROR]')) {
console.log(summary);
} else {
console.log(summary);
}
Short API Reference
agentIdentity
agentIdentity?: {
name: string;
description: string;
namespace?: string;
}
name is normalized to camelCase for child-agent function names.
namespace changes the child-agent module from default agents to a custom module such as team.
AxAgentOptions
{
contextFields: readonly (string | { field: string; promptMaxChars?: number })[];
agents?: {
local?: AxAnyAgentic[];
shared?: AxAnyAgentic[];
globallyShared?: AxAnyAgentic[];
excluded?: string[];
};
fields?: {
local?: string[];
shared?: string[];
globallyShared?: string[];
excluded?: string[];
};
functions?: {
local?: AxFunction[];
shared?: AxFunction[];
globallyShared?: AxFunction[];
excluded?: string[];
discovery?: boolean;
};
runtime?: AxCodeRuntime;
maxSubAgentCalls?: number;
maxRuntimeChars?: number;
maxBatchedLlmQueryConcurrency?: number;
maxTurns?: number;
contextPolicy?: AxContextPolicyConfig;
actorFields?: string[];
actorCallback?: (result: Record<string, unknown>) => void | Promise<void>;
inputUpdateCallback?: (currentInputs: Record<string, unknown>) => Promise<Record<string, unknown> | undefined> | Record<string, unknown> | undefined;
mode?: 'simple' | 'advanced';
recursionOptions?: Partial<Omit<AxProgramForwardOptions, 'functions'>> & {
maxDepth?: number;
};
actorOptions?: Partial<AxProgramForwardOptions & { description?: string }>;
responderOptions?: Partial<AxProgramForwardOptions & { description?: string }>;
}
Examples
Fetch these for full working code:
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.
- Do not combine
console.log(...) with final(...).
- Do not forget
fields.shared when child agents depend on parent inputs.