Robert Brennanandopenhands 491a3f6cdf Lazily load conversation history: 50 most recent first, paginate on s… (#252)
* Lazily load conversation history: 50 most recent first, paginate on scroll

Previously every conversation page loaded its entire event history in a single
REST call (`limit: 100` plus a WebSocket `resend_all` replay), which is slow
and wasteful for long-running conversations.

Now:
- `EventService.searchEvents` takes an options bag (`limit`, `pageId`,
  `sortOrder`, `timestampGte`, `timestampLt`) and returns the raw `EventPage`
  so callers can paginate. Both local and cloud-proxy paths forward the
  params.
- `useConversationHistory` fetches only the most recent
  `INITIAL_HISTORY_PAGE_SIZE` (50) events with `sort_order='TIMESTAMP_DESC'`
  and reverses them to chronological order.
- `useLoadOlderEvents` is a new on-demand hook that paginates older events
  via `timestamp__lt = <oldest known timestamp>`. `ChatInterface` triggers it
  when the user scrolls within 80px of the top, shows a loading spinner, and
  preserves the visible scroll position by adjusting `scrollTop` by the
  inserted height delta.
- `ConversationWebSocketProvider` waits for the REST query to settle before
  opening the main socket, then subscribes with `resend_mode='since'` and
  `after_timestamp = <latest preloaded event timestamp>`, falling back to
  `resend_mode='all'` if REST returned no events or errored. The legacy
  count-based history loading detection is dropped for the main connection.
- Event store gained a bulk `addEvents` action (used for the initial REST
  seed and for "scroll-up" pagination) that re-sorts by timestamp once at
  the end so prepending an older page stays O(N log N) overall.

Co-authored-by: openhands <openhands@all-hands.dev>

* Render Messages when any renderable events exist

The previous gate (`conversationUserEventsExist`) hid the chat whenever the
loaded window contained no `source: 'user'` events. With the new lazy
"50 most recent" REST fetch, long agent runs between user turns can push
the original prompt out of the initial window, leaving Messages unmounted.
Without rendered messages there is no scroll container content, so
'scroll up to load older' never fires — the user appears stuck on a blank
chat.

Switch the gate to `renderableEvents.length > 0`. The empty-state
ChatSuggestions block above keeps its own `!userEventsExist &&
!hasSubstantiveAgentActions` gate, so brand-new conversations still show
suggestions instead of an empty chat.

Adds a regression test for the bug and a sibling test that the scroll
handler issues `searchEvents({ timestamp__lt })` when the user scrolls
near the top.

Co-authored-by: openhands <openhands@all-hands.dev>

* Handle paginated history edge cases

Co-authored-by: openhands <openhands@all-hands.dev>

* Trigger loadOlder when pinned at top or list doesn't overflow

The browser only dispatches a 'scroll' event while `scrollTop` is
actually changing. Two paginated-history cases never triggered:

  1. The user is already at `scrollTop = 0` and tries to wheel further
     up (overscroll). No scroll event fires.
  2. The initial 50-event window is short enough to fit on screen, so
     there is no scrollbar at all and nothing to scroll.

In both cases the user had to scroll down a bit and back up to coax
loadOlder into firing.

Replace `handleScrollForPagination` with a single `maybeLoadOlder`
predicate that fires when `scrollTop <= threshold` OR
`scrollHeight <= clientHeight + threshold`, and wire it from three
sources:

  - onScroll  (existing path: user scrolls near the top)
  - onWheel   (catches overscroll-at-top: scrollTop == 0 and deltaY < 0)
  - useEffect (catches no-overflow: re-runs after each rendered page so
              we keep filling until the viewport overflows or the
              server has no more older events)

Tests cover all three trigger paths.

Co-authored-by: openhands <openhands@all-hands.dev>

* Fix paginated history follow-up issues

Co-authored-by: openhands <openhands@all-hands.dev>

* Harden paginated history responses

Co-authored-by: openhands <openhands@all-hands.dev>

---------

Co-authored-by: openhands <openhands@all-hands.dev>
2026-05-10 14:07:09 -07:00
2026-05-10 12:19:28 -07:00
2026-04-24 17:33:22 -04:00

agent-canvas

Warning

This project is in sandbox phase. It may be vibecoded, untested, or out of date. OpenHands takes no responsibility for the code or its support. Learn more.

Agent Canvas is a web frontend for managing agents. You can:

  • ⌨️ prompt them manually
  • 🕐 run them on a schedule
  • ⚡ trigger them automatically—e.g. from Slack or GitHub.

Agents can run anywhere:

  • 🧑‍💻 on your laptop
  • 🖥️ on a remote virtual machine
  • ☁️ in our hosted cloud
  • 🏢 or inside your company’s infrastructure

You can work with any agent (e.g. Claude Code, Codex) or connect directly to an LLM (e.g. Anthropic, OpenAI, Gemini, Mistral, Minimax, Kimi).

If you have questions or feedback, please open a GitHub issue or join the #proj-agent-canvas channel in Slack

Quickstart

Prerequisites:

  • Node.js 22.12.x or later
  • npm
  • Docker

Set $PROJECT_PATH to the directory on your machine where your projects live (e.g. /path/to/your/projects). The agent server will mount this directory so the agent can read and edit your code.

export PROJECT_PATH=/path/to/your/projects
git clone https://github.com/OpenHands/agent-canvas.git
cd agent-canvas
npm install
npm run dev:docker

Access the UI at http://localhost:8000

Without Docker

Warning

This runs the agent-server directly on the machine you're installing on--the agent will have full access to your filesystem!

Prerequisites:

  • Node.js 22.12.x or later
  • npm
  • uv (for running the agent server via uvx)
git clone https://github.com/OpenHands/agent-canvas.git
cd agent-canvas
npm install
npm run dev:dangerously-dockerless

Access the UI at http://localhost:8000

Architecture

Agent Canvas is powered by the OpenHands Agent Server, a REST API for running multiple agents on a single machine. Each Agent Server runs on a single host/port; the Agent Canvas can connect to multiple Agent Servers and easily flip between them.

You can run an Agent Server anywhere:

  • Directly on your laptop (be careful!)
  • Inside a Docker container
  • On a dedicated machine like a Mac Mini
  • On a virtual machine in the cloud
  • Inside a Kubernetes Pod
  • Inside OpenHands Cloud (our commercial offering)

The Agent Server is often paired with an Automation Server, which lets you set up agents that run on a schedule or in response to events.

image

More documentation

For contributor and developer workflows, including frontend-only mode, mock mode, environment variables, and build/test commands, see DEVELOPMENT.md.

S
Languages
TypeScript 93.7%
JavaScript 4.7%
Python 0.9%
Shell 0.3%
CSS 0.2%
Other 0.1%