GitHub - enzeberg/agent-cli: A local-first, component-based AI Agent state management system designed for modularity, cost-efficiency, and observability.

A local-first, component-based AI Agent state management system designed for modularity, cost-efficiency, and observability. Inspired by the robust design principles of advanced AI agents, LocalAgent provides a solid foundation for building intelligent, stateful applications.

Table of Contents

Features

  • Component System: Modular and extensible components (WorkingHistory, Memory, Todo, SubAgent) with isolated state management.
  • Local-first Persistence: Automatic state serialization to local JSON files, ensuring data persistence and low latency.
  • Context Management: Intelligent prompt assembly with budget-aware truncation and dynamic model selection for cost-efficiency.
  • Observability: Event-driven architecture for real-time monitoring, debugging, and state snapshots.
  • Real-world Tooling: Integration with external tools (e.g., File I/O, Weather API) for practical agent capabilities.
  • Interactive CLI: Enhanced command-line interface with typewriter effects, spinners, and colored logs for a rich user experience.

Architecture Overview

The LocalAgent Framework is built around a central Agent Orchestrator (LocalAgent) that manages the agent's lifecycle, state, and execution loop. It leverages a Component System for modularity, a StateManager for persistent state, and an EventBus for decoupled communication. Interactions with Large Language Models (LLMs) are handled via an LLMProvider, and external capabilities are exposed through a ToolRegistry.

graph TD
    subgraph Agent Core [The Execution Loop]
        A[User Input / External Event] --> B(Agent Orchestrator)
        B --> C{Context Assembly}
        C --> D[LLM Call]
        D --> E{LLM Response}
        E --> F(Tool Execution)
        F --> G{State Update}
        G --> H(Event Bus: StateChange)
        H --> I(Observability / Debugger)
        G --> B
    end

    subgraph Components [State Providers]
        J[WorkingHistory]
        K[Memory]
        L[Todo]
        M[SubAgent]
    end

    subgraph State & Context Management
        N[Local Storage]
        O[Context Manager]
        P[Model Router]
    end

    subgraph Observability
        Q[Snapshot / Debugger]
    end

    style B fill:#f9f,stroke:#333,stroke-width:2px
    style C fill:#ccf,stroke:#333,stroke-width:2px
    style D fill:#afa,stroke:#333,stroke-width:2px
    style E fill:#ccf,stroke:#333,stroke-width:2px
    style F fill:#f9f,stroke:#333,stroke-width:2px
    style G fill:#ccf,stroke:#333,stroke-width:2px
    style H fill:#ffc,stroke:#333,stroke-width:2px
    style I fill:#ffc,stroke:#333,stroke-width:2px
    style Q fill:#ffc,stroke:#333,stroke-width:2px

    B -- manages --> J
    B -- manages --> K
    B -- manages --> L
    B -- manages --> M
    C -- queries --> J
    C -- queries --> K
    C -- queries --> L
    C -- queries --> M
    C -- uses --> O
    C -- consults --> P
    D -- calls --> LLMProvider
    E -- contains --> ToolCalls
    F -- uses --> ToolRegistry
    G -- updates --> N
    H -- notifies --> Q
    N -- stores --> AgentState
    O -- builds --> Prompt
    P -- selects --> LLMModel
    ToolRegistry -- executes --> ExternalTools
Loading

Core Concepts

Agent Lifecycle

The agent follows a well-defined lifecycle to ensure predictable and recoverable execution:

State (Enum) Description Transition Triggers
INITIALIZED Configuration loaded, components registered, but task not yet started. agent.start(task)
RUNNING Main execution loop is active (Context -> LLM -> Action -> State Update). LLM response, tool execution completion.
PAUSED Execution temporarily halted, state serialized to local disk. agent.pause(), system interruption (e.g., user input needed).
RESUMED State reloaded from disk, execution loop restarted. agent.resume()
COMPLETED Task objective achieved. LLM signals completion.
FAILED An unrecoverable error occurred. Tool execution failure, LLM call error.

Component System

Components are the building blocks of the agent's intelligence. The framework adopts a component-based architecture for low coupling and high extensibility.

  • Component Interface: All components must implement the IComponent interface, defining core methods like initialize(), getState(), updateState(), and renderPromptFragment().
  • Dependency Management: Components can declare their dependencies (e.g., static dependencies = ['Memory', 'WorkingHistory']). The LocalAgent ensures dependencies are initialized in the correct order.
  • State Isolation: Each component manages its own state slice, which is part of the overall agent state.

State Management

The StateManager is responsible for centralizing and persisting the agent's state.

  • Local-first Persistence: The entire agent state, including all component states, is automatically serialized to local JSON files, ensuring data integrity and allowing for session recovery.
  • Snapshots: The StateManager supports taking snapshots of the current state, enabling time-travel debugging and easy restoration to previous points.

Event Bus

The EventBus (an EventEmitter instance) is the primary mechanism for asynchronous, decoupled communication between components and the observability layer. Key events include agent:state_change, component:state_updated, llm:call_start, tool:execution_end, etc. This design promotes loose coupling and simplifies maintenance.

Economic Considerations

The framework is designed to balance performance (effectiveness) with cost (economy).

Context Compression & Smart Truncation

The ContextManager implements a multi-layered strategy:

  1. Priority-based Truncation: When the prompt size approaches the token limit, the manager truncates content based on a predefined priority order:
    • Lowest Priority: Oldest tool outputs, verbose logs.
    • Medium Priority: Older WorkingHistory messages.
    • Highest Priority: System prompt, Todo list, and Memory summaries (never truncated).
  2. History Summarization: The Memory component actively monitors the WorkingHistory's token count. When it exceeds a threshold (e.g., 50% of the total budget), it triggers a weaker model to summarize the older half of the history, replacing raw messages with a concise summary string.

Model Selection Strategy

A ModelRouter dynamically selects the appropriate LLM based on the task and current state:

Task Type Model Choice Rationale
Core Reasoning (Planning, Tool Selection) Strong Model (e.g., GPT-4o, Claude 3.5 Sonnet) Requires high-level reasoning and complex instruction following.
Summarization (History, Tool Output) Weak Model (e.g., GPT-3.5, Claude 3 Haiku) Simple text-to-text transformation; highly cost-effective.
State Validation (e.g., checking task completion) Weak Model Low complexity, high-frequency checks.
Code Generation/Refactoring Strong Model (or specialized code model) Requires high fidelity and correctness.

Observability & Debugging

The framework is designed with full observability in mind, crucial for debugging complex agent behaviors.

  • Event System: The global EventBus broadcasts events for every significant operation, allowing external listeners (e.g., UI, logging services, debuggers) to subscribe without coupling to the agent's core logic.
  • Step-by-step Execution: The LocalAgent can be configured to pause after each LLM turn, allowing developers to inspect the state before proceeding.
  • State Snapshots: The StateManager's takeSnapshot() method serializes the entire state to a JSON file, enabling time-travel debugging by loading snapshots and resuming execution from that precise point.

Getting Started

Configuration

  1. Copy .env.example to .env.
  2. Fill in your OPENAI_API_KEY and other optional settings. Note: If no API key is provided, the framework will run in Mock Mode for demonstration.

Installation

Running Examples

  • Main Demo: Runs a basic agent interaction.

bun start

- **Document Analysis Example**: Agent reads a local file, summarizes its content, and adds key facts to memory.
```bash
bun example:doc
  • Weather Report Example: Agent checks weather for a city and provides personalized suggestions based on user preferences in memory.

bun example:weather


## Project Structure

- `src/core.ts`: Defines core interfaces (`IComponent`, `AgentState`), `StateManager`, and `EventBus`.
- `src/agent.ts`: The `LocalAgent` Orchestrator, managing the execution loop, component registration, and tool invocation.
- `src/components.ts`: Implementations of built-in components like `WorkingHistory`, `TodoComponent`, `MemoryComponent`, and `SubAgent`.
- `src/llm.ts`: `LLMProvider` for abstracting LLM interactions, supporting real API calls and mock responses.
- `src/tools.ts`: `ToolRegistry` and implementations of `FileReadTool` and `WeatherTool`.
- `src/ui.ts`: Utility for enhanced CLI output (typewriter effects, spinners, colors).
- `src/index.ts`: Main entry point for the basic demo.
- `src/example_doc_analysis.ts`: Example demonstrating document analysis with `FileReadTool`.
- `src/example_weather.ts`: Example demonstrating weather inquiry with `WeatherTool`.
- `data/`: Directory for persistent agent state and live status dashboards.

## Contributing

Contributions are welcome! Please feel free to open issues or submit pull requests.

## License

This project is licensed under the MIT License.