Agent Inbox

repository·main·Indexed 21 days ago

https://github.com/langchain-ai/agent-inbox

A UI tool for human-in-the-loop interactions with LangGraph agents. It enables users to view, edit, accept, or respond to interrupts generated during agent execution. The tool provides TypeScript and Python interrupt schemas, a configuration interface for LangGraph deployments, and a set of React components and hooks (such as useInboxes) to manage agent inboxes and thread views.

Tokens
5.7K
Snippets
15
Records
20
Agent score
77%

What's inside Agent Inbox

  1. How Human Interrupts and Responses work

    main

    The Agent Inbox uses a specific schema to facilitate human-in-the-loop interactions.

    Interrupt Fields (HumanInterrupt)

    • action_request: Contains the action (header text) and args (e.g., tool call arguments).
    • config: Controls which actions are available to the user via allow_ignore, allow_respond, allow_edit, and allow_accept.
    • description: A detailed string (supports Markdown) providing context or instructions.

    Response Types (HumanResponse)

    When a user interacts with the inbox, the interrupt() function returns a list containing a HumanResponse object. The type determines the payload in args:

    • accept: Sends an ActionRequest where all args keys are converted to strings. Matches the original action_request structure.
    • edit: Sends an ActionRequest where args values are strings containing the user's edits.
    • response: Sends a single string in the args field.
    • ignore: Returns null for the args field.
  2. Install and Setup Agent Inbox

    main

    To run the Agent Inbox locally, clone the repository and install dependencies using yarn.

    Prerequisites:

    • Node.js and yarn installed.
    • A running LangGraph deployment (local or via LangGraph Platform).
    • A LangSmith API key.

    Setup Steps:

    1. Clone the repo: git clone https://github.com/langchain-ai/agent-inbox.git
    2. Navigate to the directory: cd agent-inbox
    3. Install dependencies: yarn install
    git clone https://github.com/langchain-ai/agent-inbox.git
    cd agent-inbox
    yarn install
  3. Configure Agent Inbox to connect to LangGraph

    main

    After running the application, you must configure it to connect to your LangGraph deployment via the browser's local storage:

    1. LangSmith API Key: Click the "Settings" button in the sidebar and enter your key.
    2. Create an Inbox: Open the settings popover (bottom left in the sidebar) and click "Add Inbox". Fill in the following:
      • Assistant/Graph ID (required): The name of your LangGraph graph or an assistant ID.
      • Deployment URL (required): The URL of your LangGraph deployment.
      • Name (optional): A label for the inbox.
  4. Troubleshoot Agent Inbox issues

    main

    Connection Issues

    • Verify LANGSMITH_API_KEY is correct.
    • Ensure the Deployment URL is accessible.
    • Confirm your LangGraph deployment is actually running.

    Schema Validation Errors

    • Ensure all required fields are present in the HumanInterrupt object.
    • Verify field types match the defined schema.
    • Ensure you are extracting the first object from the list returned by the interrupt() function.

    'Open in Studio' button failure

    • This requires LangGraph deployment fields made available after 04/18/2025. If your graph hasn't created a new revision since then, it won't work.
    • Solution: Create a new revision of your graph, delete the existing inbox in the Agent Inbox UI, and re-add it.
  5. Implement interrupts in LangGraph (Python)

    main

    Use the interrupt function from langgraph.types. Pass a HumanInterrupt dictionary to the function. The function returns a list of responses; access the first element to handle the user's input.

    from typing import TypedDict, Literal, Optional, Union
    from langgraph.types import interrupt
    
    def my_graph_function(state: MyGraphState):
        # Extract the last tool call from the `messages` field in the state
        tool_call = state["messages"][-1].tool_calls[0]
        
        # Create an interrupt
        request: HumanInterrupt = {
            "action_request": {
                "action": tool_call['name'],
                "args": tool_call['args']
            },
            "config": {
                "allow_ignore": True,
                "allow_respond": True,
                "allow_edit": False,
                "allow_accept": False
            },
            "description": _generate_email_markdown(state) # Generate a detailed markdown description.
        }
        
        # Send the interrupt request, and extract the first response.
        # The Agent Inbox will always respond with a list of `HumanResponse` objects.
        response = interrupt(request)[0]
        
        if response['type'] == "response":
            # Do something with the response
            pass
        
        # ...rest of function
  6. Implement interrupts in LangGraph (TypeScript)

    main

    Instead of raising exceptions, use the interrupt function from @langchain/langgraph. Pass a HumanInterrupt object to the function. Note that interrupt returns an array of responses; you typically want the first element.

    import { interrupt } from "@langchain/langgraph";
    import { HumanInterrupt, HumanResponse } from "@langchain/langgraph/prebuilt";
    
    function myGraphFunction(state: MyGraphState) {
      // Extract the last tool call from the `messages` field in the state
      const toolCall = state.messages[state.messages.length - 1].tool_calls[0];
      
      // Create an interrupt
      const request: HumanInterrupt = {
        action_request: {
          action: toolCall.name,
          args: toolCall.args
        },
        config: {
          allow_ignore: true,
          allow_respond: true,
          allow_edit: false,
          allow_accept: false
        },
        description: _generateEmailMarkdown(state) // Generate a detailed markdown description.
      };
    
      // Send the interrupt request, and extract the first response.
      // The Agent Inbox will always return an array of `HumanResponse` objects.
      const response = interrupt<HumanInterrupt, HumanResponse[]>(request)[0];
      
      if (response.type === "response") {
        // Do something with the response
      }
    }
  7. Configure AgentInbox via URL query parameters

    main

    The AgentInbox component uses URL query parameters to drive its state and navigation. You can control the view and pagination by manipulating the following parameters:

    • inbox: Sets the current inbox status (e.g., interrupted). This determines which list of threads is displayed.
    • offset: The starting index for pagination.
    • limit: The number of items to display per page.
    • threadId (via VIEW_STATE_THREAD_QUERY_PARAM): When this parameter is present, the component switches from the list view to the ThreadView for the specified thread ID.

    Note: The component automatically synchronizes these parameters. For example, if you change the inbox status, it will ensure offset and limit are also present in the URL.

  8. Understand ThreadData and Thread Statuses

    main

    The ThreadData type is a discriminated union used to represent the state of a LangGraph thread within the Agent Inbox. The status field acts as the discriminator to determine which properties are available.

    Available Statuses:

    • idle | busy | error: Represented by GenericThreadData. These states do not contain interrupts.
    • interrupted: Represented by InterruptedThreadData. Contains an array of HumanInterrupt[].
    • human_response_needed: Represented by HumanResponseNeededThreadData. This state indicates the agent is specifically waiting for a human to resolve an existing interrupt and contains HumanInterrupt[].

    EnhancedThreadStatus extends the standard ThreadStatus from @langchain/langgraph-sdk with the custom `

  9. Python Interrupt Schema

    main

    Use these TypedDicts to define interrupts in your Python LangGraph project to ensure compatibility with Agent Inbox.

    class HumanInterruptConfig(TypedDict):
        allow_ignore: bool
        allow_respond: bool
        allow_edit: bool
        allow_accept: bool
    
    class ActionRequest(TypedDict):
        action: str
        args: dict
    
    class HumanInterrupt(TypedDict):
        action_request: ActionRequest
        config: HumanInterruptConfig
        description: Optional[str]
    
    class HumanResponse(TypedDict):
        type: Literal['accept', 'ignore', 'response', 'edit']
        args: Union[None, str, ActionRequest]
  10. TypeScript Interrupt Schema

    main

    Use these interfaces to define interrupts in your TypeScript LangGraph project to ensure compatibility with Agent Inbox.

    export interface HumanInterruptConfig {
      allow_ignore: boolean;
      allow_respond: boolean;
      allow_edit: boolean;
      allow_accept: boolean;
    }
    
    export interface ActionRequest {
      action: string;
      args: Record<string, any>;
    }
    
    export interface HumanInterrupt {
      action_request: ActionRequest;
      config: HumanInterruptConfig;
      description?: string;
    }
    
    export type HumanResponse = {
      type: "accept" | "ignore" | "response" | "edit";
      args: null | string | ActionRequest;
    };
  11. Use the useInboxes hook to manage agent inboxes

    main

    The useInboxes hook provides a complete interface for managing a collection of AgentInbox objects stored in local storage. It synchronizes the inbox state with the browser's URL query parameters and local storage, ensuring that the selected inbox is reflected in the URL.

    Key Features:

    • Automatic Initialization: On mount, it runs a backfill process (runInboxBackfill) and loads inboxes from local storage.
    • Selection Sync: Automatically selects an inbox based on the AGENT_INBOX_PARAM in the URL. If no param is present, it selects the first available inbox or the one marked selected: true.
    • Persistence: All additions, deletions, and updates are persisted to local storage using the AGENT_INBOXES_LOCAL_STORAGE_KEY.
    • URL Management: Updates query parameters like AGENT_INBOX_PARAM, OFFSET_PARAM, LIMIT_PARAM, and INBOX_PARAM to maintain application state via the URL.

    Returned Methods:

    • getAgentInboxes(preloadedInboxes?: AgentInbox[]): Loads inboxes from storage and handles selection logic.
    • addAgentInbox(agentInbox: AgentInbox): Adds a new inbox and marks it as selected.
    • deleteAgentInbox(id: string): Removes an inbox by ID. If the deleted inbox was the selected one, the first remaining inbox is automatically selected.
    • changeAgentInbox(id: string, replaceAll?: boolean): Switches the currently selected inbox.
    • updateAgentInbox(updatedInbox: AgentInbox): Updates the properties of an existing inbox while preserving its selection state.

    Note: The hook uses router.refresh() and router.push() from next/navigation to update the UI and URL without full page reloads.

    import { useInboxes } from "@/components/agent-inbox/hooks/use-inboxes";
    
    function MyComponent() {
      const { agentInboxes, addAgentInbox, changeAgentInbox } = useInboxes();
    
      const handleAdd = () => {
        addAgentInbox({ name: "New Agent Inbox" }); // id is automatically generated if missing
      };
    
      return (
        <div>
          {agentInboxes.map(inbox => (
            <button key={inbox.id} onClick={() => changeAgentInbox(inbox.id)}>
              {inbox.name} {inbox.selected ? "(Selected)" : ""}
            </button>
          ))}
          <button onClick={handleAdd}>Add Inbox</button>
        </div>
      );
    }
  12. Convert LangChain messages for assistant-ui

    main

    Use convertLangchainMessages to transform LangChain BaseMessage objects into a format compatible with @assistant-ui/react. This is specifically designed to be used as a callback for the useExternalMessageConverter hook.

    Supported Message Types:

    • system -> { role: 'system', ... }
    • human -> { role: 'user', ... }
    • ai -> { role: 'assistant', ... } (includes mapping of tool_calls to tool-call content parts)
    • tool -> { role: 'tool', ... }

    Constraints:

    • Only text-based content is supported. If message.content is not a string, the function will throw an error: "Only text messages are supported".
    import { convertLangchainMessages } from './convert_messages';
    
    // Example usage within an assistant-ui context
    const converter = convertLangchainMessages;
    // This converter can then be passed to useExternalMessageConverter