# How PageAgent Manages Memory and History Events Across Execution Steps

> Discover how PageAgent manages memory and history events across execution steps. Learn about its persistent history array and real time synchronization for LLM context and UI.

- Repository: [Alibaba/page-agent](https://github.com/alibaba/page-agent)
- Tags: internals
- Published: 2026-03-09

---

**PageAgent maintains a persistent `history` array inside `PageAgentCore` that stores every execution step as a typed `HistoricalEvent`, emitting `historychange` events to synchronize LLM context and UI rendering in real time.**

The `alibaba/page-agent` repository implements a centralized memory architecture where all task progress is recorded in a single source of truth. This system enables the LLM to retain context across multi-step automation tasks while providing immediate visual feedback through event-driven UI updates.

## History Data Structure

PageAgent defines all memory records through the `HistoricalEvent` union type in [`packages/core/src/types.ts`](https://github.com/alibaba/page-agent/blob/main/packages/core/src/types.ts). This type encompasses five distinct event categories that capture the complete execution lifecycle:

```ts
// packages/core/src/types.ts
export type HistoricalEvent =
  | AgentStepEvent          // step with reflection & tool action
  | ObservationEvent       // persistent observation text
  | UserTakeoverEvent      // user manually edited the page
  | RetryEvent             // LLM retry information
  | AgentErrorEvent;       // fatal error info

```

Each **step event** (`AgentStepEvent`) stores critical metadata including `stepIndex` for ordering, a `reflection` object containing the three-part evaluation model (`evaluation_previous_goal`, `memory`, `next_goal`), and the `action` details with tool name, input, and LLM output. **Observation events** (`ObservationEvent`) capture transient notes such as URL changes or wait-time warnings that provide context without representing agent decisions.

## Recording Events During Execution

The `PageAgentCore` class in [`packages/core/src/PageAgentCore.ts`](https://github.com/alibaba/page-agent/blob/main/packages/core/src/PageAgentCore.ts) appends events to the `history` array at three distinct phases of the execution loop.

### Observations (Pre-Step)

Before each LLM call, the private method `#handleObservations` generates contextual notes and pushes them to memory:

```ts
await this.#handleObservations(step);

```

This method constructs temporary warnings—such as step-remaining notifications or URL change alerts—and records them via:

```ts
this.history.push({ type: 'observation', content });
this.#emitHistoryChange();

```

### Step Results

After the LLM returns a macro-tool result, the core creates a comprehensive step record:

```ts
this.history.push({
  type: 'step',
  stepIndex: step,
  reflection,
  action,
  usage: result.usage,
  rawResponse: result.rawResponse,
  rawRequest: result.rawRequest,
});
this.#emitHistoryChange();

```

### Retry and Error Handling

When the LLM requires retry attempts or encounters fatal errors, dedicated events preserve the failure context:

```ts
this.history.push({
  type: 'retry',
  message: `LLM retry attempt ${attempt} of ${maxAttempts}`,
  attempt,
  maxAttempts,
});
this.#emitHistoryChange();

this.history.push({
  type: 'error',
  message,
  rawResponse: error,
});
this.#emitHistoryChange();

```

## Emitting the historychange Event

Every mutation to the `history` array triggers the private `#emitHistoryChange` method:

```ts
#emitHistoryChange(): void {
  this.dispatchEvent(new Event('historychange'));
}

```

This event-driven architecture ensures that any registered `EventTarget` listener receives immediate notification of state changes. The UI panel and other external components subscribe to this event to maintain synchronization with the internal memory state without polling.

## Using History as LLM Context

When constructing prompts for the next execution step, `PageAgentCore` transforms the `history` array into an XML-formatted `<agent_history>` block. This serialization provides the LLM with chronological access to previous reflections and actions:

```ts
prompt += '<agent_history>\n';
for (const event of this.history) {
  if (event.type === 'step') { /* render step fields */ }
  else if (event.type === 'observation') {
    prompt += `<sys>${event.content}</sys>\n`;
  }
  // user_takeover, retry, error are omitted from LLM context
}
prompt += '</agent_history>\n\n';

```

Because the `reflection` object within each step contains the agent's previous evaluation, memory, and next-goal planning, the LLM receives a **structured memory** of its own reasoning chain. User takeovers, retries, and errors are intentionally excluded from this context to prevent contamination of the reasoning trajectory.

## UI Rendering of Persistent Memory

The panel component in [`packages/ui/src/panel/Panel.ts`](https://github.com/alibaba/page-agent/blob/main/packages/ui/src/panel/Panel.ts) maintains visual consistency by subscribing to the same `historychange` events:

```ts
this.#agent.addEventListener('historychange', this.#onHistoryChange);

```

When the event fires, the `#renderHistory` method rebuilds the DOM directly from `agent.history`:

```ts
const history = this.#agent.history;
for (const event of history) {
  items.push(...this.#createHistoryCards(event));
}
this.#historySection.innerHTML = items.join('');

```

This ensures the visual timeline presented to users remains a faithful representation of the internal `history` array, with every observation, step, and error rendered in chronological order.

## Memory vs. Activity Distinction

PageAgent strictly separates **persistent memory** from **transient activity**:

- **Memory (`history`)**: Stored in `PageAgentCore.history`, included in LLM prompts, and emitted via `historychange` events. This data survives across execution steps and influences future agent decisions.
- **Activity (`activity` events)**: Transient UI feedback (states like `thinking`, `executing`, `executed`, `retrying`, `error`) that provides visual status indicators without affecting the LLM context or long-term memory.

This distinction is defined in [`packages/core/src/types.ts`](https://github.com/alibaba/page-agent/blob/main/packages/core/src/types.ts) and implemented through separate emission methods (`#emitHistoryChange` versus `#emitActivity`).

## Practical Implementation Examples

### Adding Custom Observations Inside Tools

Tools can inject contextual notes into the persistent memory using the `pushObservation` method:

```ts
// packages/core/src/tools/myTool.ts
export const myTool = tool({
  description: 'Clicks an element and records why.',
  inputSchema: z.object({ selector: z.string() }),
  async execute(this: PageAgentCore, { selector }) {
    await this.pageController.clickElementBySelector(selector);
    
    // Record reasoning for future LLM context
    this.pushObservation(`Clicked element matching "${selector}"`);
  },
});

```

This observation will be included in the next `<agent_history>` block sent to the LLM.

### Reading Memory from Execution Hooks

Developers can inspect the persistent history through the `onAfterStep` hook:

```ts
const agent = new PageAgentCore({ ... });
agent.config.onAfterStep = async (core, history) => {
  const lastStep = history[history.length - 1] as AgentStepEvent;
  console.log('Memory after step:', lastStep.reflection.memory);
};
await agent.execute('Summarize the page');

```

The hook receives the complete `history` array, enabling custom logging, analysis, or conditional logic based on the agent's accumulated memory.

## Summary

- PageAgent stores all execution state in a single `history` array within `PageAgentCore`, ensuring consistent memory across steps.
- The `HistoricalEvent` union type provides type-safe storage for steps, observations, user takeovers, retries, and errors.
- Every mutation emits a `historychange` event, enabling real-time synchronization between the core engine and UI components.
- The LLM receives historical context through an XML-serialized `<agent_history>` block built from the `history` array, excluding transient retries and errors from the reasoning context.
- The architecture cleanly separates persistent memory (affecting LLM decisions) from transient activity events (providing UI feedback only).

## Frequently Asked Questions

### How does PageAgent ensure the LLM remembers previous steps?

PageAgent serializes the entire `history` array into an `<agent_history>` XML block within each prompt. This block contains previous step reflections—including the agent's self-evaluation, memory notes, and next-goal planning—giving the LLM direct access to its own reasoning chain from prior execution steps.

### What is the difference between an ObservationEvent and an AgentStepEvent?

An `ObservationEvent` captures external state changes or system notes (such as URL navigation or wait warnings) that provide context but do not represent agent decisions. An `AgentStepEvent` represents a complete LLM reasoning cycle, including the reflection output, tool action taken, and token usage statistics.

### Can custom tools modify the agent's memory?

Yes. Custom tools can call `this.pushObservation()` to append `ObservationEvent` entries to the history. These observations persist across steps and appear in subsequent LLM prompts within the `<agent_history>` block, allowing tools to inject domain-specific context into the agent's memory.

### Why are retry and error events excluded from LLM context?

Retry events (`RetryEvent`) and fatal errors (`AgentErrorEvent`) are recorded in `history` for debugging and UI display purposes, but intentionally omitted from the `<agent_history>` prompt block. This exclusion prevents the LLM from incorporating transient failure loops or error states into its permanent reasoning context, maintaining focus on successful execution trajectories.