Skip to main content
Actions are tasks you trigger from the dashboard - generating summaries, exporting metrics, calling external APIs, or any side-effect that doesn’t fit the eval (pass/fail) or enrichment (key-value) model. Actions are never auto-run. They appear as buttons in the Actions panel on the session page and run only when clicked.

Register an action

app.action('session-summary', ({ stats, evalResults }) => {
  const passed = Object.values(evalResults).filter(r => r.pass).length;
  const total = Object.keys(evalResults).length;
  return {
    output: [
      `${stats.turnCount} turns, ${stats.toolCallCount} tool calls`,
      `Duration: ${stats.duration}`,
      `Models: ${stats.models.join(', ') || 'unknown'}`,
      `Evals: ${passed}/${total} passed`,
    ].join('\n'),
    status: 'success',
    message: 'Summary generated',
  };
});

Return type

interface ActionResult {
  output?: string;                   // Free-form text rendered in a monospace block with copy button
  status: 'success' | 'error';      // Determines the icon in the results panel
  message?: string;                  // Short summary shown in the UI
}

Context

Actions receive an extended context that includes cached eval and enrichment results:
FieldTypeDescription
entriesobject[]Raw JSONL entries
statsEvalLogStatsComputed session stats
projectNamestringProject name
sessionIdstringSession ID
sourcestring"session" or "agent-{id}"
evalResultsRecord<string, EvalRunResult>Cached eval results for the session
enrichmentResultsRecord<string, EnrichRunResult>Cached enrichment results
Actions can read which evals passed, pull enrichment data, and combine it with raw session entries.

Options

OptionTypeDefaultDescription
conditionConditionFunction-Per-action gate. Returns false → skipped.
scope'session' | 'subagent' | 'both''session'Where the action appears
subagentTypestring-Only show for subagents of this type
cachebooleantrueSet to false for side-effect actions that should always re-run

Examples

Export enrichment data to a file

For actions with side effects, set cache: false so the action always re-executes:
app.action('write-report', async ({ projectName, sessionId, stats, evalResults }) => {
  const fs = await import('fs/promises');
  const report = {
    projectName,
    sessionId,
    turns: stats.turnCount,
    evals: Object.fromEntries(
      Object.entries(evalResults).map(([name, r]) => [name, { pass: r.pass, score: r.score }])
    ),
    timestamp: new Date().toISOString(),
  };
  await fs.appendFile('session-reports.jsonl', JSON.stringify(report) + '\n');
  return {
    status: 'success',
    message: 'Report appended to session-reports.jsonl',
    output: 'Written to session-reports.jsonl',
  };
}, { cache: false });

Conditional action

// Only show for sessions that used tools
app.action('tool-inventory', ({ entries }) => {
  const toolNames = [...new Set(
    entries
      .filter(e => e.type === 'assistant' && Array.isArray(e.message?.content))
      .flatMap(e => e.message.content.filter(b => b.type === 'tool_use').map(b => b.name))
  )];
  return {
    output: toolNames.length > 0
      ? `Tools used:\n${toolNames.map(t => `  - ${t}`).join('\n')}`
      : 'No tools used',
    status: 'success',
  };
}, { condition: ({ stats }) => stats.toolCallCount > 0 });

Subagent-scoped action

app.action('agent-report', ({ entries, source, stats }) => {
  const myEntries = entries.filter(e => e._source === source);
  return {
    output: [
      `Source: ${source}`,
      `Entries: ${myEntries.length}`,
      `Turns: ${stats.turnCount}`,
      `Tool calls: ${stats.toolCallCount}`,
    ].join('\n'),
    status: 'success',
    message: `Agent ${source}: ${myEntries.length} entries`,
  };
}, { scope: 'subagent' });

UI behavior

The Actions panel appears on session pages when matching actions are registered.
  • Shows all registered actions as idle on initial load; cached results display immediately
  • Run button per action; Run All button in the panel header
  • output is rendered in a scrollable monospace block with a copy-to-clipboard button
  • A “cached” badge appears for results served from cache
  • Panel collapses by default; error count and loading spinners are visible in the header even when collapsed