Hmbown/CodeWhale · error · Error

Pet Engine observation window is full.

Error message

Pet Engine observation window is full.

What it means

PetEngineTelemetry.add appends lifecycle events to an in-memory observation journal capped at 8192 entries. It throws 'Pet Engine observation window is full.' when the cap is reached, because the journal prunes old spans only inside observe() (which trims spans older than 12.8 s). This is a safety bound so a long-lived or stalled observer cannot grow memory without limit.

Solutions

  1. Route events through observe() instead of calling add() directly, so pruning runs on each observation
  2. Ensure timestamps (at) advance monotonically so the 12.8 s retention filter evicts old spans
  3. Reset or re-instantiate the PetEngineTelemetry when replaying an unrelated session
  4. If replaying, batch into a fresh instance or clone() per tape segment

Example fix

// before
telemetry.add('tool', 'tool', now); // called directly in a loop
// after
telemetry.observe({ event: 'tool_call_started', tool_call_id: id, tool_name: name }, now); // prunes via observe
Defensive patterns

Strategy: try-catch

Validate before calling

if (telemetry.events.length >= 8192) {
  telemetry = new PetEngineTelemetry(); // or route the next add through observe() so pruning runs
}

Try / catch

try {
  telemetry.add(name, category, at);
} catch (err) {
  if (err.message === 'Pet Engine observation window is full.') {
    telemetry = new PetEngineTelemetry(); // reset the journal
    telemetry.add(name, category, at);
  } else throw err;
}

Prevention

When it happens

Trigger: Calling add() (directly or via observe()/pulse()) after 8192 events have accumulated without any pruning pass, e.g. feeding events with timestamps that never advance (so the age-based filter keeps everything) or calling add() directly in a tight loop.

Common situations: Replaying a long session tape where all events share nearly the same timestamp, so the endTime >= at - 12800 filter never evicts anything; a clock that stalls at a fixed value; hammering add() outside observe() in a test or custom integration.

Understand the failure class

Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22). Data as JSON: /api/errors/51c469c71c75f5d0. Report an issue: GitHub.

Appendix: source

Thrown at crates/tui/src/tui/pet_watch/pet-native.js:1694

        const cue = ([key, e]) => ({
            ...(key.startsWith('tool:') ? (0, codewhale_js_1.toolActivity)(e.name) : key.startsWith('thinking:')
                ? { kind: 'thinking', label: 'Thinking' } : key.startsWith('agent:')
                ? { kind: 'delegating', label: 'Coordinating agents' } : { kind: 'responding', label: 'Writing the response' }),
            tool: key.startsWith('tool:') ? e.name.replace(/[^a-zA-Z0-9_.:-]/g, '').slice(0, 96) : null,
            sinceMs: e.startTime,
        });
        const active = spans.filter(([key]) => !key.startsWith('agent:')).slice(-4).reverse().map(cue);
        const error = [...this.events].reverse().find(e => e.category === 'error' && fresh(e));
        const primary = this.waiting && fresh(this.waiting)
            ? { kind: 'waiting', label: 'Waiting for you', tool: null, sinceMs: this.waiting.startTime }
            : error ? { kind: 'error', label: 'An operation failed', tool: null, sinceMs: error.startTime }
                : active[0] ?? (parallel ? cue(spans.find(([key]) => key.startsWith('agent:')))
                    : { kind: 'unknown', label: 'Activity unobserved', tool: null, sinceMs: at });
        return { ...primary, observed: primary.kind !== 'unknown', parallel, active };
    }
    add(name, category, at, agentId = 'parent', continuation = false) {
        if (this.events.length >= 8192)
            throw new Error('Pet Engine observation window is full.');
        const e = { schemaVersion: 1, id: `engine:${this.sequence++}`, traceId: 'foreground',
            startTime: at, endTime: at, name, category, agentId, status: 'running',
            attributes: continuation ? { 'whalesong.continuation': true } : {} };
        this.events.push(e);
        return e;
    }
    pulse(key, at) {
        const e = this.active.get(key);
        if (!e)
            return;
        // A resumed stream does not assert coverage across its silent interval.
        if (at - e.endTime > pet_telemetry_js_1.PET_BIN_MS * 2) {
            this.active.set(key, this.add(e.name, e.category, at, e.agentId, true));
        }
        else
            e.endTime = at;
    }
    /** Transactional batch copy; failed validation cannot accept half a packet. */

View on GitHub (pinned to 73e0f67d83)