{"record":{"id":"51c469c71c75f5d0","repo":"Hmbown/CodeWhale","slug":"pet-engine-observation-window-is-full","errorCode":null,"errorMessage":"Pet Engine observation window is full.","messagePattern":"Pet Engine observation window is full\\.","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"crates/tui/src/tui/pet_watch/pet-native.js","lineNumber":1694,"sourceCode":"        const cue = ([key, e]) => ({\n            ...(key.startsWith('tool:') ? (0, codewhale_js_1.toolActivity)(e.name) : key.startsWith('thinking:')\n                ? { kind: 'thinking', label: 'Thinking' } : key.startsWith('agent:')\n                ? { kind: 'delegating', label: 'Coordinating agents' } : { kind: 'responding', label: 'Writing the response' }),\n            tool: key.startsWith('tool:') ? e.name.replace(/[^a-zA-Z0-9_.:-]/g, '').slice(0, 96) : null,\n            sinceMs: e.startTime,\n        });\n        const active = spans.filter(([key]) => !key.startsWith('agent:')).slice(-4).reverse().map(cue);\n        const error = [...this.events].reverse().find(e => e.category === 'error' && fresh(e));\n        const primary = this.waiting && fresh(this.waiting)\n            ? { kind: 'waiting', label: 'Waiting for you', tool: null, sinceMs: this.waiting.startTime }\n            : error ? { kind: 'error', label: 'An operation failed', tool: null, sinceMs: error.startTime }\n                : active[0] ?? (parallel ? cue(spans.find(([key]) => key.startsWith('agent:')))\n                    : { kind: 'unknown', label: 'Activity unobserved', tool: null, sinceMs: at });\n        return { ...primary, observed: primary.kind !== 'unknown', parallel, active };\n    }\n    add(name, category, at, agentId = 'parent', continuation = false) {\n        if (this.events.length >= 8192)\n            throw new Error('Pet Engine observation window is full.');\n        const e = { schemaVersion: 1, id: `engine:${this.sequence++}`, traceId: 'foreground',\n            startTime: at, endTime: at, name, category, agentId, status: 'running',\n            attributes: continuation ? { 'whalesong.continuation': true } : {} };\n        this.events.push(e);\n        return e;\n    }\n    pulse(key, at) {\n        const e = this.active.get(key);\n        if (!e)\n            return;\n        // A resumed stream does not assert coverage across its silent interval.\n        if (at - e.endTime > pet_telemetry_js_1.PET_BIN_MS * 2) {\n            this.active.set(key, this.add(e.name, e.category, at, e.agentId, true));\n        }\n        else\n            e.endTime = at;\n    }\n    /** Transactional batch copy; failed validation cannot accept half a packet. */","sourceCodeStart":1676,"sourceCodeEnd":1712,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/73e0f67d83c59909b571efdfc88c4bc28c309cb1/crates/tui/src/tui/pet_watch/pet-native.js#L1676-L1712","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Route events through observe() instead of calling add() directly, so pruning runs on each observation","Ensure timestamps (at) advance monotonically so the 12.8 s retention filter evicts old spans","Reset or re-instantiate the PetEngineTelemetry when replaying an unrelated session","If replaying, batch into a fresh instance or clone() per tape segment"],"exampleFix":"// before\ntelemetry.add('tool', 'tool', now); // called directly in a loop\n// after\ntelemetry.observe({ event: 'tool_call_started', tool_call_id: id, tool_name: name }, now); // prunes via observe","handlingStrategy":"try-catch","validationCode":"if (telemetry.events.length >= 8192) {\n  telemetry = new PetEngineTelemetry(); // or route the next add through observe() so pruning runs\n}","typeGuard":null,"tryCatchPattern":"try {\n  telemetry.add(name, category, at);\n} catch (err) {\n  if (err.message === 'Pet Engine observation window is full.') {\n    telemetry = new PetEngineTelemetry(); // reset the journal\n    telemetry.add(name, category, at);\n  } else throw err;\n}","preventionTips":["Always feed events through observe(), never call add() directly","Ensure timestamps advance so the 12.8 s retention filter prunes old spans","Reset the telemetry instance between replays/sessions","Monitor events.length in tests that push many synthetic events"],"tags":["telemetry","resource-limit","state-management"],"backgroundTag":"internal-invariant-violation","analyzedSha":"73e0f67d83c59909b571efdfc88c4bc28c309cb1","analyzedAt":"2026-09-22T01:30:00.501Z","contentChangedAt":"2026-09-22T01:30:00.501Z","schemaVersion":2},"datasetVersion":"2026-09-22T11:17:16.035Z"}