[PR #299] [CLOSED] feat(deepagents): async agents experiments #336

Closed
opened 2026-06-05 17:22:43 -04:00 by yindo · 0 comments
Owner

📋 Pull Request Information

Original PR: https://github.com/langchain-ai/deepagentsjs/pull/299
Author: @christian-bromann
Created: 3/11/2026
Status: Closed

Base: mainHead: cb/async-agents


📝 Commits (2)

📊 Changes

45 files changed (+5817 additions, -6 deletions)

View changed files

examples/observer/basic-companion.ts (+134 -0)
examples/observer/interactive-companion.ts (+140 -0)
examples/observer/middleman-acp-attach.ts (+96 -0)
examples/observer/middleman-web-ui.ts (+78 -0)
📝 examples/package.json (+7 -6)
libs/deepagents-ui/README.md (+27 -0)
libs/deepagents-ui/package.json (+72 -0)
libs/deepagents-ui/src/client/app.ts (+137 -0)
libs/deepagents-ui/src/client/index.html (+12 -0)
libs/deepagents-ui/src/client/main.ts (+5 -0)
libs/deepagents-ui/src/client/session.ts (+177 -0)
libs/deepagents-ui/src/client/styles.css (+46 -0)
libs/deepagents-ui/src/index.ts (+10 -0)
libs/deepagents-ui/src/middleman.test.ts (+88 -0)
libs/deepagents-ui/src/middleman.ts (+142 -0)
libs/deepagents-ui/src/types.ts (+47 -0)
libs/deepagents-ui/src/vite-server.test.ts (+66 -0)
libs/deepagents-ui/src/vite-server.ts (+154 -0)
libs/deepagents-ui/src/web-ui.int.test.ts (+123 -0)
libs/deepagents-ui/src/web-ui.ts (+35 -0)

...and 25 more files

📄 Description

This PR adds an experimental observer/companion stack for long-running Deep Agents.

It introduces a session-scoped activity bus that lets external consumers inspect what a running agent is doing, plus an optional conversational companion agent that can answer questions about the run and queue lightweight steering commands.

Motivation

Long-running agents are hard to work with once they are in flight. Today, users can mostly do one of two things:

  1. Watch the stream passively.
  2. Interrupt the run and restart with new instructions.

This change explores a middle ground:

  • observe the current state of a running agent
  • inspect recent cross-thread activity
  • queue lightweight steering like reminders or todo requests without tearing down the run

The design is intentionally built around a shared session/event model rather than a single read-only observer abstraction, so it can later back UI tails, CLIs, websocket transports, and higher-level agent orchestration patterns.

What’s Included

Observer middleware

Adds createObserverMiddleware() in libs/deepagents/src/middleware/observer.ts.

The middleware:

  • records model_response activity events after model calls
  • records tool_result activity events after tool calls
  • extracts file-touch metadata for common file tools
  • reads queued control commands before model calls
  • injects queued steering commands into the next model step
  • records control_applied events when commands are claimed

This is implemented as best-effort middleware so observation failures do not break the main run.

Session handle

Adds createSessionHandle() in libs/deepagents/src/observer/handle.ts.

The session handle provides the non-LLM control/read API:

  • getSnapshot() for aggregated session/thread state
  • getEvents() for cursor-based activity paging
  • send() for queueing steering commands

This is the core primitive behind the feature.

Companion agent

Adds createCompanionAgent() in libs/deepagents/src/observer/agent.ts.

The companion agent:

  • always gets an observe_agent tool
  • optionally gets a steer_agent tool when allowSteering: true
  • is intended to be a conversational layer over the session handle
  • can answer questions about current work and queue lightweight steering commands

Observer tools

Adds createObserveTool() and createSteerTool() in libs/deepagents/src/observer/tool.ts.

These tools expose:

  • structured observation of session snapshots + activity events
  • lightweight command queueing for reminders, messages, todo requests, and guidance

Types and store helpers

Adds shared types and store helpers under libs/deepagents/src/observer/.

This includes:

  • ActivityEvent
  • ControlCommand
  • SessionSnapshot
  • SessionEventPage
  • SessionHandle
  • event/control namespace helpers
  • event writing + eviction
  • event pagination
  • control-command persistence + claiming

Examples

Adds runnable examples in examples/observer/:

  • basic-companion.ts
  • interactive-companion.ts

These demonstrate:

  • running a main Deep Agent in the background
  • creating a shared session handle
  • attaching a conversational companion
  • asking questions about the run
  • queueing lightweight steering commands interactively

API Overview

Middleware

const observerMiddleware = createObserverMiddleware({
  store,
  sessionId,
  enableControl: true,
});

Session handle

const session = createSessionHandle({
  sessionId,
  store,
  getState: (threadId) =>
    agent.getState({ configurable: { thread_id: threadId } }),
});

const snapshot = await session.getSnapshot({ scope: "all" });
const events = await session.getEvents({ limit: 20 });

await session.send({
  kind: "reminder",
  target: "active",
  payload: { text: "Before continuing, update the docs too." },
});

Companion agent

const companion = createCompanionAgent({
  session,
  checkpointer,
  allowSteering: true,
});

Current Limitations / Follow-ups

This implementation is a strong first slice, but a few areas are still follow-up work rather than fully complete behavior:

  • Subagent activity is not yet automatically instrumented by propagating the observer middleware into spawned subagents.
  • Steering commands are queued and injected as model-visible guidance; they are not yet enforced as structured mutations of todo/guidance state.
  • "active" targeting is currently lightweight and should become more precise once we have stronger active-thread tracking.
  • Exactly-once command claiming across concurrent threads/processes will need stronger store-level claim semantics than the current read/update loop.
  • Thread lifecycle events (thread_started, thread_completed, thread_failed) are defined in the model but not yet fully emitted, so running/idle inference can be improved.

Why this direction

The important architectural choice here is making the primitive session-based and event-driven rather than coupling everything to a single observer agent.

That gives us:

  • a reusable control/read surface for non-LLM consumers
  • a path toward async agent UX
  • a foundation for future UI streaming and websocket transport
  • a cleaner way to reason about agent families instead of only one root thread

Example Use Cases

  • “What is the agent doing right now?”
  • “What files has it touched so far?”
  • “What todos are still pending?”
  • “Before you continue, add a todo to update the docs.”
  • “Remember to add tests before you finish.”

Notes

This should be viewed as an experimental first pass at async observation + lightweight steering for Deep Agents, with the core abstractions now in place for follow-up work on subagent propagation, stronger control semantics, and more robust cross-thread concurrency handling.


🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.

## 📋 Pull Request Information **Original PR:** https://github.com/langchain-ai/deepagentsjs/pull/299 **Author:** [@christian-bromann](https://github.com/christian-bromann) **Created:** 3/11/2026 **Status:** ❌ Closed **Base:** `main` ← **Head:** `cb/async-agents` --- ### 📝 Commits (2) - [`11957ac`](https://github.com/langchain-ai/deepagentsjs/commit/11957aca8dc99c0c5bf30eb3c69631d52c1552e6) feat(deepagents): async agents experiments - [`d731d81`](https://github.com/langchain-ai/deepagentsjs/commit/d731d81113576cc65039458adb84a94e2f23a351) ui ### 📊 Changes **45 files changed** (+5817 additions, -6 deletions) <details> <summary>View changed files</summary> ➕ `examples/observer/basic-companion.ts` (+134 -0) ➕ `examples/observer/interactive-companion.ts` (+140 -0) ➕ `examples/observer/middleman-acp-attach.ts` (+96 -0) ➕ `examples/observer/middleman-web-ui.ts` (+78 -0) 📝 `examples/package.json` (+7 -6) ➕ `libs/deepagents-ui/README.md` (+27 -0) ➕ `libs/deepagents-ui/package.json` (+72 -0) ➕ `libs/deepagents-ui/src/client/app.ts` (+137 -0) ➕ `libs/deepagents-ui/src/client/index.html` (+12 -0) ➕ `libs/deepagents-ui/src/client/main.ts` (+5 -0) ➕ `libs/deepagents-ui/src/client/session.ts` (+177 -0) ➕ `libs/deepagents-ui/src/client/styles.css` (+46 -0) ➕ `libs/deepagents-ui/src/index.ts` (+10 -0) ➕ `libs/deepagents-ui/src/middleman.test.ts` (+88 -0) ➕ `libs/deepagents-ui/src/middleman.ts` (+142 -0) ➕ `libs/deepagents-ui/src/types.ts` (+47 -0) ➕ `libs/deepagents-ui/src/vite-server.test.ts` (+66 -0) ➕ `libs/deepagents-ui/src/vite-server.ts` (+154 -0) ➕ `libs/deepagents-ui/src/web-ui.int.test.ts` (+123 -0) ➕ `libs/deepagents-ui/src/web-ui.ts` (+35 -0) _...and 25 more files_ </details> ### 📄 Description This PR adds an experimental observer/companion stack for long-running Deep Agents. It introduces a session-scoped activity bus that lets external consumers inspect what a running agent is doing, plus an optional conversational companion agent that can answer questions about the run and queue lightweight steering commands. ## Motivation Long-running agents are hard to work with once they are in flight. Today, users can mostly do one of two things: 1. Watch the stream passively. 2. Interrupt the run and restart with new instructions. This change explores a middle ground: - observe the current state of a running agent - inspect recent cross-thread activity - queue lightweight steering like reminders or todo requests without tearing down the run The design is intentionally built around a shared session/event model rather than a single read-only observer abstraction, so it can later back UI tails, CLIs, websocket transports, and higher-level agent orchestration patterns. ## What’s Included ### Observer middleware Adds `createObserverMiddleware()` in `libs/deepagents/src/middleware/observer.ts`. The middleware: - records `model_response` activity events after model calls - records `tool_result` activity events after tool calls - extracts file-touch metadata for common file tools - reads queued control commands before model calls - injects queued steering commands into the next model step - records `control_applied` events when commands are claimed This is implemented as best-effort middleware so observation failures do not break the main run. ### Session handle Adds `createSessionHandle()` in `libs/deepagents/src/observer/handle.ts`. The session handle provides the non-LLM control/read API: - `getSnapshot()` for aggregated session/thread state - `getEvents()` for cursor-based activity paging - `send()` for queueing steering commands This is the core primitive behind the feature. ### Companion agent Adds `createCompanionAgent()` in `libs/deepagents/src/observer/agent.ts`. The companion agent: - always gets an `observe_agent` tool - optionally gets a `steer_agent` tool when `allowSteering: true` - is intended to be a conversational layer over the session handle - can answer questions about current work and queue lightweight steering commands ### Observer tools Adds `createObserveTool()` and `createSteerTool()` in `libs/deepagents/src/observer/tool.ts`. These tools expose: - structured observation of session snapshots + activity events - lightweight command queueing for reminders, messages, todo requests, and guidance ### Types and store helpers Adds shared types and store helpers under `libs/deepagents/src/observer/`. This includes: - `ActivityEvent` - `ControlCommand` - `SessionSnapshot` - `SessionEventPage` - `SessionHandle` - event/control namespace helpers - event writing + eviction - event pagination - control-command persistence + claiming ### Examples Adds runnable examples in `examples/observer/`: - `basic-companion.ts` - `interactive-companion.ts` These demonstrate: - running a main Deep Agent in the background - creating a shared session handle - attaching a conversational companion - asking questions about the run - queueing lightweight steering commands interactively ## API Overview ### Middleware ```ts const observerMiddleware = createObserverMiddleware({ store, sessionId, enableControl: true, }); ``` ### Session handle ```ts const session = createSessionHandle({ sessionId, store, getState: (threadId) => agent.getState({ configurable: { thread_id: threadId } }), }); const snapshot = await session.getSnapshot({ scope: "all" }); const events = await session.getEvents({ limit: 20 }); await session.send({ kind: "reminder", target: "active", payload: { text: "Before continuing, update the docs too." }, }); ``` ### Companion agent ```ts const companion = createCompanionAgent({ session, checkpointer, allowSteering: true, }); ``` ## Current Limitations / Follow-ups This implementation is a strong first slice, but a few areas are still follow-up work rather than fully complete behavior: - Subagent activity is not yet automatically instrumented by propagating the observer middleware into spawned subagents. - Steering commands are queued and injected as model-visible guidance; they are not yet enforced as structured mutations of todo/guidance state. - `"active"` targeting is currently lightweight and should become more precise once we have stronger active-thread tracking. - Exactly-once command claiming across concurrent threads/processes will need stronger store-level claim semantics than the current read/update loop. - Thread lifecycle events (`thread_started`, `thread_completed`, `thread_failed`) are defined in the model but not yet fully emitted, so running/idle inference can be improved. ## Why this direction The important architectural choice here is making the primitive session-based and event-driven rather than coupling everything to a single observer agent. That gives us: - a reusable control/read surface for non-LLM consumers - a path toward async agent UX - a foundation for future UI streaming and websocket transport - a cleaner way to reason about agent families instead of only one root thread ## Example Use Cases - “What is the agent doing right now?” - “What files has it touched so far?” - “What todos are still pending?” - “Before you continue, add a todo to update the docs.” - “Remember to add tests before you finish.” ## Notes This should be viewed as an experimental first pass at async observation + lightweight steering for Deep Agents, with the core abstractions now in place for follow-up work on subagent propagation, stronger control semantics, and more robust cross-thread concurrency handling. --- <sub>🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.</sub>
yindo added the pull-request label 2026-06-05 17:22:43 -04:00
yindo closed this issue 2026-06-05 17:22:43 -04:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: langchain-ai/deepagentsjs#336