From 732bd2262f58c68aff4aae50e35561e2d4e7da30 Mon Sep 17 00:00:00 2001 From: Binhan Chu Date: Tue, 6 Oct 2026 17:15:27 -0400 Subject: [PATCH] docs: describe agent-requested context resets and history retrieval Document the development-branch setup, history scope, handoff and file responsibilities, and bounded recovery semantics. Co-authored-by: openhands --- sdk/arch/condenser.mdx | 93 ++++++++++++++++------ sdk/arch/tool-system.mdx | 38 ++++++++- sdk/guides/context-condenser.mdx | 130 ++++++++++++++++++++++++++++++- 3 files changed, 236 insertions(+), 25 deletions(-) diff --git a/sdk/arch/condenser.mdx b/sdk/arch/condenser.mdx index f5702ce09..8006f6e09 100644 --- a/sdk/arch/condenser.mdx +++ b/sdk/arch/condenser.mdx @@ -3,7 +3,7 @@ title: Condenser description: High-level architecture of the conversation history compression system --- -The **Condenser** system manages conversation history compression to keep agent context within LLM token limits. It reduces long event histories into condensed summaries while preserving critical information for reasoning. For more details, read the [blog here](https://openhands.dev/blog/openhands-context-condensensation-for-more-efficient-ai-agents). +The **Condenser** system manages the active conversation view used as LLM input. A condensation can remove events from that view and optionally insert a summary. The original events remain in the event log. For the default summarizing strategy, read the [context condensation blog](https://openhands.dev/blog/openhands-context-condensensation-for-more-efficient-ai-agents). **Source:** [`openhands-sdk/openhands/sdk/context/condenser/`](https://github.com/OpenHands/software-agent-sdk/tree/main/openhands-sdk/openhands/sdk/context/condenser) @@ -13,7 +13,7 @@ The Condenser system has four primary responsibilities: 1. **History Compression** - Reduce event lists to fit within context windows 2. **Threshold Detection** - Determine when condensation should trigger -3. **Summary Generation** - Create meaningful summaries via LLM or heuristics +3. **Optional Summary Generation** - Create a summary when required by the strategy 4. **View Management** - Transform event history into LLM-ready views ## Architecture @@ -72,7 +72,7 @@ flowchart TB | **[`LLMSummarizingCondenser`](https://github.com/OpenHands/software-agent-sdk/blob/main/openhands-sdk/openhands/sdk/context/condenser/llm_summarizing_condenser.py)** | LLM summarization | Uses LLM to generate summaries | | **[`NoOpCondenser`](https://github.com/OpenHands/software-agent-sdk/blob/main/openhands-sdk/openhands/sdk/context/condenser/no_op_condenser.py)** | No-op implementation | Returns view unchanged | | **[`PipelineCondenser`](https://github.com/OpenHands/software-agent-sdk/blob/main/openhands-sdk/openhands/sdk/context/condenser/pipeline_condenser.py)** | Multi-stage pipeline | Chains multiple condensers | -| **[`View`](https://github.com/OpenHands/software-agent-sdk/blob/main/openhands-sdk/openhands/sdk/context/view.py)** | Event view | Represents history for LLM | +| **[`View`](https://github.com/OpenHands/software-agent-sdk/blob/main/openhands-sdk/openhands/sdk/context/view/view.py)** | Event view | Represents active-branch history for the LLM | | **[`Condensation`](https://github.com/OpenHands/software-agent-sdk/blob/main/openhands-sdk/openhands/sdk/event/condenser.py)** | Condensation event | Metadata about compression | ## Condenser Types @@ -100,7 +100,7 @@ Uses an LLM to generate summaries of conversation history: ```mermaid %%{init: {"theme": "default", "flowchart": {"nodeSpacing": 30, "rankSpacing": 40}} }%% flowchart LR - View["Long View
120+ events"] + View["Long View"] Check["Threshold
exceeded?"] Summarize["LLM Summarization"] Summary["Summary Text"] @@ -131,10 +131,53 @@ flowchart LR 6. **Next Step:** `View.from_events()` filters forgotten events and inserts summary **Configuration:** -- **`max_size`:** Event count threshold before condensation triggers (default: 120) -- **`keep_first`:** Number of initial events to preserve verbatim (default: 4) +- **`max_size`:** Event count threshold before condensation triggers (default: 240) +- **`keep_first`:** Number of initial events to preserve verbatim (default: 2) - **`llm`:** LLM instance for summarization (often cheaper model than reasoning LLM) +### AgentResetCondenser (Development Branch) + + +This optional strategy is documented against the +[`feat/issue-4916-agent-reset` development branch](https://github.com/cbinhan/software-agent-sdk/tree/feat/issue-4916-agent-reset). +See the [configuration and recovery guide](/sdk/guides/context-condenser#agent-requested-reset-development-branch) +for setup and release availability. + + +`AgentResetCondenser` returns an unchanged view during ordinary steps. A successful +`new_context` tool call causes the harness to persist a `CondensationRequest` with +a `trigger_action_id` after the complete tool batch is committed. The condenser +then returns the existing `Condensation` event with `summary=None` and the real +triggering response ID. No extra model summary is generated for this path. + +```mermaid +flowchart LR + Call["new_context with optional handoff"] --> Batch["Commit complete tool batch"] + Batch --> Request["CondensationRequest: trigger_action_id"] + Request --> Reset["Condensation: no summary"] + Reset --> View["Apply to active view"] + History["Original active-branch events"] --> Retrieval["conversation_history"] + Retrieval --> Later["Later tool result in model input"] +``` + +The successful `NewContextObservation` stores the input boundary captured by the +harness. The reset protects system events, user input after that boundary, and +the triggering response's complete tool batch. Missing boundaries preserve all +user input. `View.manipulation_indices` expands these protections to provider-safe +tool and thinking-loop blocks. + +`View` derives the pending request and reminder state by replaying events. Applying +a condensation consumes the request; only a changed view re-enables a reminder. +The event log remains the source of truth across reopening and forking. History +retrieval reads the active branch, including events omitted from the view, rather +than searching every branch in the log. + +The capacity fallback uses `LLMSummarizingCondenser.hard_context_reset()` on only +the removable blocks, preserves all system/user input, and remaps the summary +offset into the retained full view. Its bounded internal retries do not create an +unbounded agent retry loop: one generation attempt permits one outer rescue. +The guide describes failure conditions and explicit application condensation. + ### PipelineCondenser Chains multiple condensers in sequence: @@ -203,7 +246,7 @@ flowchart TB - **Who:** Agent (on LLM context window error) or application code - **Purpose:** Force compression when context limit exceeded -### Condensation Workflow +### Summarizing Condensation Workflow ```mermaid %%{init: {"theme": "default", "flowchart": {"nodeSpacing": 30, "rankSpacing": 40}} }%% @@ -249,21 +292,22 @@ flowchart TB 4. **Condensation Creation:** Create `Condensation` event with `forgotten_event_ids` and summary 5. **Return to Agent:** Condenser returns `Condensation` (not `View`) 6. **History Update:** Agent adds `Condensation` to event log and exits step -7. **Next Step:** `View.from_events()` ([source](https://github.com/OpenHands/software-agent-sdk/blob/main/openhands-sdk/openhands/sdk/context/view.py)) processes Condensation to filter events and insert summary +7. **Next Step:** `View.from_events()` ([source](https://github.com/OpenHands/software-agent-sdk/blob/main/openhands-sdk/openhands/sdk/context/view/view.py)) processes Condensation to filter events and insert summary ## View and Condensation ### View Structure -A `View` represents the conversation history as it will be sent to the LLM: +A `View` projects the selected active branch into the events used to build model +input. It is distinct from both the complete event log and the active branch: ```mermaid %%{init: {"theme": "default", "flowchart": {"nodeSpacing": 30}} }%% flowchart LR - Events["Full Event List
+ Condensation events"] + Events["Active Branch Events
+ Condensation events"] FromEvents["View.from_events()"] Filter["Filter forgotten events"] - Insert["Insert summary"] + Insert["Insert optional summary"] View["View
LLMConvertibleEvents"] Convert["events_to_messages()"] LLM["LLM Input"] @@ -282,9 +326,13 @@ flowchart LR **View Components:** - **`events`:** List of `LLMConvertibleEvent` objects (filtered by Condensation) - **`unhandled_condensation_request`:** Flag for pending manual condensation -- **`condensations`:** List of all Condensation events processed - **Methods:** `from_events()` creates view from raw events, handling Condensation semantics +The development reset strategy also uses `pending_condensation_request` to retain +the request's action reference and `context_window_reminded` to avoid repeating +the same preparation reminder. Both are derived by replay, not a second history +store. + ### Condensation Event When condensation occurs, a `Condensation` event is created: @@ -308,9 +356,10 @@ flowchart LR ``` **Condensation Fields:** -- **`forgotten_event_ids`:** List of event IDs to filter out -- **`summary`:** Compressed text representation of forgotten events -- **`summary_offset`:** Index where summary event should be inserted +- **`forgotten_event_ids`:** Set of event IDs to filter out of the view +- **`summary`:** Optional text representation of forgotten events +- **`summary_offset`:** Optional insertion index in the view after filtering +- **`llm_response_id`:** Real response ID that caused the condensation; for a normal agent reset, this is the response containing `new_context` - Inherits from `Event`: `id`, `timestamp`, `source` ## Rolling Window Pattern @@ -320,7 +369,7 @@ flowchart LR ```mermaid %%{init: {"theme": "default", "flowchart": {"nodeSpacing": 30, "rankSpacing": 40}} }%% flowchart TB - View["Current View
120+ events"] + View["Current View"] Check["Count Events"] Compare{"Count >
max_size?"} @@ -328,11 +377,11 @@ flowchart TB Keep["Keep All Events"] Split["Split Events"] - Head["Head
First 4 events"] - Middle["Middle
~56 events"] - Tail["Tail
~56 events"] + Head["Head
First keep_first events"] + Middle["Middle
Older complete blocks"] + Tail["Tail
Recent complete blocks"] Summarize["LLM Summarizes Middle"] - Result["Head + Summary + Tail
~60 events total"] + Result["Head + Summary + Tail"] View --> Check Check --> Compare @@ -355,10 +404,10 @@ flowchart TB ``` **Rolling Window Strategy:** -1. **Keep Head:** Preserve first `keep_first` events (default: 4) - usually system prompts +1. **Keep Head:** Preserve first `keep_first` events (default: 2) - usually system prompts 2. **Keep Tail:** Preserve last `target_size - keep_first - 1` events - recent context 3. **Summarize Middle:** Compress events between head and tail into summary -4. **Target Size:** After condensation, view has `max_size // 2` events (default: 60) +4. **Target Size:** Aim for `max_size // 2` events (default: 120), subject to safe manipulation boundaries ## Component Relationships diff --git a/sdk/arch/tool-system.mdx b/sdk/arch/tool-system.mdx index 114a89318..2ae34791d 100644 --- a/sdk/arch/tool-system.mdx +++ b/sdk/arch/tool-system.mdx @@ -115,7 +115,7 @@ flowchart TB **Tool System Boundary:** - **Input**: `dict[str, Any]` (JSON arguments) → validated `Action` instance - **Output**: `Observation` instance with structured result -- **No knowledge of**: Events, LLM messages, conversation state +- **Event ownership**: The agent wraps calls and results in events. Executors may receive the current `LocalConversation` when they need conversation state. ### Tool Definition @@ -216,6 +216,42 @@ flowchart TB | **Direct Instantiation** | Stateless tools with no configuration needs | `finish`, `think`, simple utilities | | **Subclass with Factory** | Tools requiring runtime state or configuration | `execute_bash`, `file_editor`, `glob`, `grep` | +### History and Context Tools (Development Branch) + + +These optional tools target the SDK's +[`feat/issue-4916-agent-reset` development branch](https://github.com/cbinhan/software-agent-sdk/tree/feat/issue-4916-agent-reset), +not an established released SDK or Agent Server contract. + + +`conversation_history` retrieves committed message and tool text from the current +conversation's active branch. It works independently with any condenser, including +no condenser. Forks can retrieve inherited history, while sibling branches and +other conversations are outside the tool's scope. + +| Command | Inputs | Result | +| --- | --- | --- | +| `search` | Literal, case-insensitive `query`; optional `before_event_id` | Up to five newest-first matches with event IDs and snippets of at most 400 characters. Continue with `next_before_event_id`. | +| `read` | `event_id`; optional character `offset` | Up to 4,000 characters and a `next_offset` for paging. An offset at or beyond the end returns an empty page. | + +The tool reports whether source events were in the active view at snapshot time. +A batch shares one branch/view snapshot captured before its workers start; direct +`execute_tool` calls take their own snapshot. System/internal events, provider +reasoning, and history tool calls/results are excluded. Results are historical +data, not new instructions. Retrieving history does not alter the active view; +the resulting observation enters context through the usual tool-result path. + +`new_context` belongs to the opt-in `AgentResetCondenser` strategy. Its optional +`handoff` stays in the real action arguments. The executor returns a result, and +the agent requests the reset only after the complete tool batch is committed. +It does not provide a separate notes store: use ordinary workspace file tools +for durable notes. Settings add both required tools automatically for this +strategy; direct `Agent` construction must include them explicitly. + +See [Agent-Requested Reset](/sdk/guides/context-condenser#agent-requested-reset-development-branch) +for setup, batch/confirmation behavior, capacity recovery, and both runnable +development examples. + ### Tool Annotations Tools include optional `ToolAnnotations` based on the [Model Context Protocol (MCP) spec](https://github.com/modelcontextprotocol/modelcontextprotocol) that provide behavioral hints to LLMs: diff --git a/sdk/guides/context-condenser.mdx b/sdk/guides/context-condenser.mdx index 5ffadcefb..d8854d26b 100644 --- a/sdk/guides/context-condenser.mdx +++ b/sdk/guides/context-condenser.mdx @@ -15,7 +15,7 @@ A **context condenser** is a crucial component that addresses one of the most pe - **⏱️ Slower Response Times**: Larger contexts take longer to process - **📉 Reduced Effectiveness**: LLMs become less effective when dealing with excessive irrelevant information -The context condenser solves this by intelligently summarizing older parts of the conversation while preserving essential information needed for the agent to continue working effectively. +The context condenser controls which conversation events remain in the model's active context. The default strategy summarizes older events. Condensation changes the active view, while the original events remain in the conversation history. An optional [agent-requested reset strategy](#agent-requested-reset-development-branch) is also available on the development branch described below. ## Default Implementation: `LLMSummarizingCondenser` @@ -64,7 +64,7 @@ This architecture allows you to implement custom condensation logic tailored to Create a `LLMSummarizingCondenser` to manage the context. The condenser will automatically truncate conversation history when it exceeds max_size, and replaces the dropped events with an LLM-generated summary. -This condenser triggers when there are more than `max_context_length` events in +This condenser triggers when there are more than `max_size` events in the conversation history, and always keeps the first `keep_first` events (system prompts, initial user messages) to preserve important context. @@ -230,6 +230,132 @@ print(f"EXAMPLE_COST: {cost}") +## Agent-Requested Reset (Development Branch) + + +The history tool and reset strategy below target the SDK's +[`feat/issue-4916-agent-reset` development branch](https://github.com/cbinhan/software-agent-sdk/tree/feat/issue-4916-agent-reset). +They require an SDK, and an Agent Server when used remotely, containing that +implementation. These instructions do not imply support in an existing release. +The default summarizing condenser is unchanged. + + +### Retrieve History Independently + +`conversation_history` can be enabled with any condenser, or without one: + +```python icon="python" +from openhands.sdk import Agent, Tool + +agent = Agent(llm=llm, tools=[Tool(name="conversation_history")]) +``` + +The agent can search committed message and tool text on the current conversation's +active branch, including events omitted from its active view. It can then read a +matching event by ID in pages. This tool does not reset the context or change the +default tool list. Cross-process access requires conversation persistence. +See the [history tool contract](/sdk/arch/tool-system#history-and-context-tools-development-branch) +for pagination and excluded fields. + +### Enable Agent-Requested Resets + + + + +```python icon="python" +from openhands.sdk import OpenHandsAgentSettings +from openhands.sdk.settings import AgentResetCondenserSettings + +settings = OpenHandsAgentSettings( + llm=llm, + tools=[], + condenser=AgentResetCondenserSettings(), +) +agent = settings.create_agent() +``` + +The settings factory adds the required `new_context` and `conversation_history` +tools when missing, without mutating `settings.tools`. Add ordinary file tools +to the settings if the agent should save notes in the workspace. + + + + +```python icon="python" +from openhands.sdk import Agent, Tool +from openhands.sdk.context.condenser import AgentResetCondenser + +agent = Agent( + llm=llm, + condenser=AgentResetCondenser(), + tools=[Tool(name="new_context"), Tool(name="conversation_history")], +) +``` + +Direct `Agent` construction requires both tools explicitly. Conversation +initialization raises an error if either is missing. + + + + +The serialized setting is `{"condenser_kind": "agent_reset", "enabled": true}`. +This mode has no event-count threshold, fixed retained-event count, or separate +summary-model setting. + +### Handoff, Files, and History + +| Mechanism | Purpose | +| --- | --- | +| `new_context(handoff="...")` | Carry the current task, progress, constraints, and next steps in the real tool-call arguments. The handoff is optional and is not rewritten by another model. | +| Ordinary file tools | Save durable notes or artifacts in the workspace and refer to their paths in the handoff. File lifetime follows the workspace's own persistence. | +| `conversation_history` | Retrieve original message and tool text when the next context needs more detail. The model chooses the query and event to read. | + +A successful reset takes effect after the entire tool batch finishes. The SDK +retains the system prompt, the complete triggering batch and its results, and +user messages that arrived after the model input was captured. Already-processed +user messages can leave the active view. If the saved input boundary is missing, +the reset conservatively retains all user messages. It also respects tool-batch +and thinking-loop boundaries required by providers. + +Normal resets make no additional summary call. The handoff stays in its original +action arguments; it is not copied into a synthetic message. Multiple resets in +one batch coalesce, and a `finish` call takes precedence over reset. A rejected +call, or an interrupted batch that never commits a reset request, is not restored +as a fresh reset on resume. A request already committed before interruption can +be completed on resume. Original events remain in the active branch's history. + +### Capacity and Recovery + +When a known model input limit is at least 80% used but not yet exceeded, the SDK +adds one reminder to prepare a handoff or files and call `new_context`. A real +context change enables the next reminder; a reset that removes nothing does not. +The reminder does not guarantee that the model will reset in time. + +At a hard input limit, or after a provider context-overflow error, the SDK can +attempt one summary rescue and retry the current model-generation attempt once. +That rescue reuses the existing summarizer, whose default bounded loop allows up +to five summary attempts and shortens event text after failed attempts. These +internal attempts are distinct from the single outer rescue. The summary must +reduce input size; if protected content cannot fit, summarization fails, or the +retried request still exceeds capacity, the run stops with an error. + +The rescue summarizes only removable complete blocks and preserves the system +prompt and all user messages. An explicit application request through +`conversation.condense()` also uses this summary path. A normal `new_context` +request whose protected handoff/batch and input cannot fit fails instead of +silently summarizing away that required batch. + +### Development Examples + +- [History retrieval with the existing summarizing condenser](https://github.com/cbinhan/software-agent-sdk/tree/feat/issue-4916-agent-reset/examples/01_standalone_sdk/conversation_history): scripted retrieval, paging, and reopening persisted history. +- [Agent reset with files and history](https://github.com/cbinhan/software-agent-sdk/tree/feat/issue-4916-agent-reset/examples/01_standalone_sdk/agent_reset): an offline run executes real file operations and resets with scripted responses; an optional live run demonstrates the phase transition. + +The offline examples validate the execution paths, not model recall quality or +autonomous decisions about when to reset. History retrieval excludes system and +internal events, provider reasoning, and history tool calls/results. It does not +search other conversations or sibling branches, infer missing facts, or plan +retrieval automatically. + ## Next Steps - **[LLM Metrics](/sdk/guides/metrics)** - Track token usage reduction and analyze cost savings