moeru-ai/airi · error · Error

Action limit exceeded: max ${this.maxActionsPerTurn} actions

Error message

Action limit exceeded: max ${this.maxActionsPerTurn} actions per turn

What it means

Thrown by JavaScriptPlanner.runAction() when this.activeRun.actionCount has already reached this.maxActionsPerTurn (constructor default 5, configurable via options.maxActionsPerTurn). Each tool invocation increments actionCount including skip. The limit prevents runaway scripts from spawning unbounded Minecraft actions in a single turn.

Source

Thrown at integrations/minecraft/src/cognitive/conscious/js-planner.ts:1408

    const params: Record<string, unknown> = {}
    for (const [index, key] of keys.entries()) {
      if (index >= args.length)
        break
      params[key] = args[index]
    }

    return params
  }

  private async runAction(tool: string, params: Record<string, unknown>): Promise<ActionRuntimeResult> {
    if (!this.activeRun)
      throw new Error('Tool calls are only allowed during REPL evaluation')

    if (this.activeRun.sawSkip && tool !== 'skip')
      throw new Error('skip() cannot be mixed with other tool calls in the same script')

    if (this.activeRun.actionCount >= this.maxActionsPerTurn)
      throw new Error(`Action limit exceeded: max ${this.maxActionsPerTurn} actions per turn`)

    if (tool === 'skip')
      this.activeRun.sawSkip = true

    this.activeRun.actionCount++

    if (tool === 'skip') {
      const action: ActionInstruction = { tool: 'skip', params: {} }
      const runtimeResult: ActionRuntimeResult = {
        action,
        ok: true,
        result: 'Skipped turn',
      }
      this.activeRun.executed.push(runtimeResult)
      return runtimeResult
    }

    const validation = this.validateAction(tool, params)

View on GitHub (pinned to 27111382b4)

Solutions

  1. Split the work across multiple planner turns instead of one large script.
  2. Raise maxActionsPerTurn in the JavaScriptPlannerOptions if the task genuinely needs more actions per turn.
  3. Break out of loops once the goal is achieved rather than consuming the whole budget.
  4. Prefer batch-capable actions (e.g. collectBlocks with a count) over repeated single calls.

Example fix

// before
//   for (const p of manyTargets) { await use('mineBlockAt', { position: p }) }  // throws at #6
//
// after (option A — raise the limit)
//   new JavaScriptPlanner({ maxActionsPerTurn: 12 })
// after (option B — split across turns)
//   await use('mineBlockAt', { position: manyTargets[0] })  // continue next turn
Defensive patterns

Strategy: validation

Validate before calling

// Track calls against the budget before each use():
// let calls = 0
// const BUDGET = 5  // match maxActionsPerTurn
// function budgetedUse(name: string, params = {}) {
//   if (calls >= BUDGET) { log('action budget exhausted; defer to next turn'); return skip() }
//   calls++
//   return use(name, params)
// }

Prevention

When it happens

Trigger: A script loops calling use() more than maxActionsPerTurn times; a long sequential plan in one script instead of across turns; skip counting against a tight budget after several real actions; maxActionsPerTurn lowered via options while the prompt still encourages long scripts.

Common situations: Model writes a for-loop over many targets in a single turn; budget too low for the task; repeatedly retrying failed actions until the limit is hit; not splitting work across turns.

Related errors


AI-assisted analysis of moeru-ai/airi@27111382b4 (2026-08-12). Data as JSON: /api/errors/669a35d66f0c5d54. Report an issue: GitHub.