AxAgent Observability Rules (@ax-llm/ax)
This skill helps an LLM generate correct AxAgent observability code using @ax-llm/ax. Use when the user asks about actorTurnCallback, onContextEvent, agentStatusCallback, onFunctionCall, reportSuccess, reportFailure, getChatLog(), getUsage(), resetUsage(), debug traces, progress updates, or telemetry for AxAgent runs.
Install
Install only this skill for TypeScript:
npx skills add https://ax-llm.github.io/ax/typescript/ --skill 'ax-agent-observability'Published skill file: ax-agent-observability/SKILL.md.
Source
- Source: src/ax/skills/ax-agent-observability.md
- Version:
22.0.3
Skill Instructions
Use this skill when an agent needs runtime visibility, progress reporting, tracing, usage accounting, or chat-log access. For ordinary agent setup use ax-agent. For RLM runtime policy use ax-agent-rlm. For memories and dynamic skill loading use ax-agent-memory-skills.
Choose The Smallest Hook
- Need a quick prompt/runtime trace during development -> start with
debug: true. - Need structured per-turn code, raw runtime result, formatted output, provider thoughts, or actor stage -> use
actorTurnCallback. - Need context-pressure and compaction telemetry -> use
onContextEvent. - Need real-time task progress emitted by actor code -> use
agentStatusCallback. - Need every runtime function call before execution -> use
onFunctionCall. - Need model prompts/responses after a run -> use
getChatLog(). - Need token usage by actor/responder -> use
getUsage()andresetUsage(). - Need usage split by context and task stages -> use
getStagedUsage(). - Need Ax program traces -> use
getTraces(). - Do not add multiple hooks unless the user clearly needs each output stream.
Global Runtime Defaults
OpenTelemetry and debug defaults come from the shared Ax runtime surface:
import { axGlobals, axCreateDefaultColorLogger } from '@ax-llm/ax';
import { trace } from '@opentelemetry/api';
axGlobals.tracer = trace.getTracer('agent-app');
axGlobals.debug = true;
axGlobals.logger = axCreateDefaultColorLogger();These globals are live defaults for future AI, AxGen, AxFlow, and agent-internal model calls. Per-call or explicitly configured options still override axGlobals. Use AxAgent callbacks below when the caller needs structured agent-turn events rather than OpenTelemetry spans or debug logs.
Actor Turn Callback
Use actorTurnCallback when the caller needs structured telemetry for each actor turn.
What it gives you:
code: the normalized JavaScript code the actor producedstage: which actor produced the turn (distillerorexecutor)result: the raw untruncated runtime return value from executing that codeoutput: the formatted action-log output string after Ax normalizes and truncates it for prompt replaythought: the actor model’sthoughtfield whenshowThoughtsis enabled and the provider returns oneexecutorResult: the full actor payload returned by the current actor stage, kept under this historical field name for compatibilityisError: whether the execution path for that turn was treated as an errorusage: token usage for this actor turn onlymodel: model used for this turn when explicitly set throughexecutorModelPolicychatLogMessages: raw ChatML conversation for this turn, populated only when an actor turn callback is set
Use it for:
- debug UIs that want to show code plus raw runtime results
- tracing and analytics
- capturing
thoughtfor internal diagnostics when supported by the provider - storing per-turn execution artifacts without scraping the prompt/action log
Important:
outputis not raw stdout; it is the formatted replay string used in the action log.resultis the raw runtime result before Ax applies type-aware serialization and budget-proportional truncation.thoughtis optional and only appears when the underlyingAxGencall hadshowThoughtsenabled and the provider actually returned a thought field.actionLogEntryCountandguidanceLogEntryCountreflect the live log sizes after the turn is processed, including resumed runs.actorTurnCallbackfires for the configured agent instance. Child agents passed throughfunctions: [...]should define their own callback if you need their internal actor turns; useonFunctionCallon the parent to observe the parent-side child-agent invocation.
Good pattern:
const supportAgent = agent('query:string -> answer:string', {
contextFields: ['query'],
runtime,
actorTurnCallback: ({
stage,
turn,
actionLogEntryCount,
guidanceLogEntryCount,
code,
result,
output,
thought,
isError,
usage,
model,
}) => {
console.log({
turn,
stage,
model,
actionLogEntryCount,
guidanceLogEntryCount,
isError,
code,
rawResult: result,
replayOutput: output,
thought,
usage,
});
},
executorOptions: {
model: 'gpt-5.4-mini',
showThoughts: true,
},
});Callback type:
actorTurnCallback?: (turn: {
stage: 'distiller' | 'executor';
turn: number;
actionLogEntryCount: number;
guidanceLogEntryCount: number;
executorResult: Record<string, unknown>;
code: string;
result: unknown;
output: string;
isError: boolean;
thought?: string;
usage?: AxProgramUsage[];
model?: string;
chatLogMessages?: ReadonlyArray<{ role: string; content: string }>;
}) => void | Promise<void>;
actorTurnCallback?: (turn: {
stage: 'distiller' | 'executor';
turn: number;
actionLogEntryCount: number;
guidanceLogEntryCount: number;
executorResult: Record<string, unknown>;
code: string;
result: unknown;
output: string;
isError: boolean;
thought?: string;
usage?: AxProgramUsage[];
model?: string;
chatLogMessages?: ReadonlyArray<{ role: string; content: string }>;
}) => void | Promise<void>; // deprecated alias
Context Event Observability
Use onContextEvent when the caller needs structured telemetry about prompt pressure and compaction. It does not change model behavior directly; it is for logs, evals, and dashboards.
Events:
budget_check: character-based prompt pressure before an actor turn, with detailed metrics kept out of the actor promptcheckpoint_created/checkpoint_cleared: checkpoint lifecycle events with covered turns and reasontombstone_created: compact resolved-error summary creation
Rules:
contextPressurein the actor prompt is intentionally compact (ok,watch,criticalplus one short instruction).- Budget metrics are character-based for provider neutrality and are exposed through
onContextEvent, not the actor prompt. - Callback errors are swallowed so telemetry cannot break the agent run.
- Do not scrape actor prompts for pressure metrics.
const supportAgent = agent('query:string -> answer:string', {
contextFields: ['query'],
runtime,
contextPolicy: { preset: 'checkpointed', budget: 'balanced' },
onContextEvent: (event) => {
if (event.kind === 'budget_check') {
console.log(event.pressure, event.mutablePromptChars);
}
},
});Type:
onContextEvent?: (event: AxAgentContextEvent) => void | Promise<void>;Agent Status Callback
Use agentStatusCallback when the caller wants real-time progress updates from the actor. When set, the actor can call await reportSuccess(message) and await reportFailure(message) in its JavaScript turns.
const supportAgent = agent('query:string -> answer:string', {
contextFields: ['query'],
runtime,
agentStatusCallback: (message, status) => {
console.log(`[${status}] ${message}`);
},
});Rules:
agentStatusCallbackreceives(message: string, status: 'success' | 'failed').- When set, the actor prompt automatically includes
reportSuccess(message)andreportFailure(message)as available runtime functions. - The actor is instructed to keep the user updated on task progress.
reportSuccessandreportFailureare reserved runtime names when the callback is configured.- Child agents inherit the callback via the RLM config.
Type:
agentStatusCallback?: (
message: string,
status: 'success' | 'failed'
) => void | Promise<void>;On Function Call
Use onFunctionCall when the caller wants to observe every function call the actor makes from the JS runtime. It fires before the underlying function runs.
const supportAgent = agent('query:string -> answer:string', {
contextFields: ['query'],
runtime,
functions: [helperAgent, lookupOrderTool],
onFunctionCall: ({ name, qualifiedName, args, kind }) => {
console.log(`[${kind}] ${qualifiedName}`, args);
},
});Rules:
- Receives
{ name, qualifiedName, args, kind }. nameis the bare function name, e.g.'lookupOrder'.qualifiedNameis the namespaced name as the actor sees it, e.g.'tools.lookupOrder'; for un-namespaced runtime globals it equalsname.argsis the resolved positional/named arguments object (Record<string, unknown>).kindis'external'for caller-registeredfunctions.kindis'internal'for agent-injected globals: child agents,discover,recall, andused.- Fires once per call, before the function executes.
- Errors thrown inside the callback are swallowed so they cannot break the actor loop.
- This is independent from the DSP-layer
onFunctionCallonAxProgramForwardOptions; that hook is for LLM tool-calls and never fires under AxAgent because AxAgent injects functions as runtime globals.
Type:
onFunctionCall?: (call: {
name: string;
qualifiedName: string;
args: Record<string, unknown>;
kind: 'internal' | 'external';
}) => void | Promise<void>;Chat Log, Usage, And Traces
AxAgent exposes actor and responder sub-programs. getChatLog() returns the same flat AxChatLogEntry[] shape as AxGen and AxFlow; use each entry’s optional name field to distinguish distiller, executor, and responder. getUsage() returns token usage split by actor/responder.
getChatLog()
Returns the full normalized chat history after any .forward() call. Each entry is one ai.chat() round-trip. Actor stages accumulate one entry per turn; the responder typically has one entry.
const log = myAgent.getChatLog();
for (const entry of log) {
console.log(entry.name, entry.model);
for (const msg of entry.messages) {
console.log(`[${msg.role}]`, msg.content);
}
}Each AxChatLogEntry captures the full prompt sent to the model and its response:
type AxChatLogMessage =
| { role: 'system'; content: string }
| { role: 'user'; content: string }
| { role: 'assistant'; content: string }
| { role: 'tool'; name: string; content: string };
type AxChatLogEntry = {
name?: string; // e.g. "distiller", "executor", "responder"
model: string;
messages: AxChatLogMessage[];
modelUsage?: AxProgramUsage;
stage?: 'ctx' | 'task';
};getUsage()
Returns token usage split by actor/responder. Each sub-array contains one AxProgramUsage entry per model/run, merged by (ai, model) key.
const usage = myAgent.getUsage();
// { actor: AxProgramUsage[], responder: AxProgramUsage[] }
console.log('Actor tokens:', usage.actor[0]?.tokens);
console.log('Responder tokens:', usage.responder[0]?.tokens);getStagedUsage()
Returns usage split by pipeline stage. The ctx stage has the distiller actor only; the task stage has the executor actor plus responder.
const staged = myAgent.getStagedUsage();
console.log(staged.ctx?.actor);
console.log(staged.task.actor);
console.log(staged.task.responder);getTraces()
Returns Ax program traces for the agent pipeline. Use it when the caller needs trace data rather than chat messages or token summaries.
const traces = myAgent.getTraces();resetUsage()
Resets both actor and responder usage at once:
myAgent.resetUsage();Type signatures:
// AxAgent
agent.getChatLog(): readonly AxChatLogEntry[]
agent.getUsage(): { actor: AxProgramUsage[]; responder: AxProgramUsage[] }
agent.getStagedUsage(): { ctx?: AxAgentUsage; task: AxAgentUsage }
agent.getTraces(): AxProgramTrace[]
agent.resetUsage(): void
// AxGen / AxFlow
gen.getChatLog(): readonly AxChatLogEntry[]
gen.getUsage(): AxProgramUsage[]Do Not Generate
- Do not add both
debug: trueandactorTurnCallbackunless the user wants both unstructured prompt/runtime visibility and structured telemetry. - Do not scrape actor prompts or action logs when a callback exposes the data directly.
- Do not let observability callback failures break the agent run; Ax swallows callback errors for telemetry hooks.
- Do not use DSP-layer
onFunctionCallwhen the user wants AxAgent runtime function calls. - Do not enable
showThoughtsunless the user needs provider thought diagnostics and the provider supports it.