# https://github.com/PatrickNoFilter/eling-agent Project Manual

Generated at: 2026-07-15 10:30:12 UTC

## Table of Contents

- [Introduction and Quick Start](#page-1)
- [System Architecture and Component Map](#page-2)
- [Configuration, Setup Wizard, and Themes](#page-3)
- [Terminal UI (TUI), Spinner, and Theming](#page-4)
- [Memory System: 8-Layer Second Brain](#page-5)
- [Skill Library and Self-Learning](#page-6)
- [Plugin System, MCP Servers, and the @tool Decorator](#page-7)
- [Tool Orchestration: Workspace, Auto-pytest, and Auto-fix Lint](#page-8)

<a id='page-1'></a>

## Introduction and Quick Start

### Related Pages

Related topics: [System Architecture and Component Map](#page-2), [Configuration, Setup Wizard, and Themes](#page-3)

<details>
<summary>Related Source Files</summary>

The following source files were used to generate this page:

- [README.md](https://github.com/PatrickNoFilter/eling-agent/blob/main/README.md)
- [pyproject.toml](https://github.com/PatrickNoFilter/eling-agent/blob/main/pyproject.toml)
- [requirements.txt](https://github.com/PatrickNoFilter/eling-agent/blob/main/requirements.txt)
- [agent.py](https://github.com/PatrickNoFilter/eling-agent/blob/main/agent.py)
- [docs/index.md](https://github.com/PatrickNoFilter/eling-agent/blob/main/docs/index.md)
- [docs/configuration.md](https://github.com/PatrickNoFilter/eling-agent/blob/main/docs/configuration.md)
- [docs/architecture.md](https://github.com/PatrickNoFilter/eling-agent/blob/main/docs/architecture.md)
- [eling/cli.py](https://github.com/PatrickNoFilter/eling-agent/blob/main/eling/cli.py)
- [eling/setup.py](https://github.com/PatrickNoFilter/eling-agent/blob/main/eling/setup.py)
- [eling/memory.py](https://github.com/PatrickNoFilter/eling-agent/blob/main/eling/memory.py)
</details>

# Introduction and Quick Start

Eling Agent is a personal autonomous agent CLI that runs locally, pairs with an LLM, and exposes tools, memory, and skills through a Rich-powered terminal UI. It is designed to be installed once, configured through an interactive setup, and then driven conversationally from the command line. The current release is **v0.2.3** (Source: [README.md:1-10]()), which added verbose tool output, auto-pytest, and auto-fix lint on top of the v0.2.x theme and memory improvements.

## What Eling Agent Is

At its core, Eling Agent is a Python CLI that turns a local LLM into an autonomous assistant capable of reading files, running shell commands, calling MCP tools, and persisting knowledge across sessions. The README frames it as "a personal autonomous agent CLI" combining four pillars:

- **Local memory** with BM25 plus cosine similarity retrieval (Source: [README.md:18-24]()).
- **Skill library** that auto-learns reusable patterns from successful runs (Source: [README.md:25-27]()).
- **MCP tools** for connecting external servers such as Firecrawl or the filesystem (Source: [README.md:28-30]()).
- **Rich TUI** with banner, thinking spinner, plan panel, and markdown rendering (Source: [README.md:31-33]()).

Plugins extend the system through a simple `@tool` decorator introduced in v0.2.3, keeping the core small and the surface customisable (Source: [README.md:35-38]()).

## Installation

Eling Agent is distributed as a standard Python package declared in `pyproject.toml` with pinned runtime dependencies listed in `requirements.txt`. The minimal install path is:

```bash
git clone https://github.com/PatrickNoFilter/eling-agent.git
cd eling-agent
pip install -r requirements.txt
pip install -e .
```

The `pyproject.toml` exposes the `eling` console script entry point, which is what the setup wizard and runtime hooks into (Source: [pyproject.toml:18-28]()). Once installed, the `eling` command becomes available on the `PATH` and is the single entry point used for both configuration and chat.

## First-Run Configuration

The first run should be the interactive setup wizard, launched with `eling setup`. This wizard walks the user through provider selection, model choice, theme, and tool paths, then writes a config file that subsequent invocations load automatically. The setup module keeps the logic in a dedicated file so that configuration can also be scripted later (Source: [eling/setup.py:1-40]()).

The most important keys written by the wizard are documented in `docs/configuration.md`:

| Key | Purpose |
| --- | --- |
| `provider` | LLM backend (OpenAI-compatible, local, etc.) |
| `model` | Default model identifier shown in the banner with a 🤖 icon |
| `theme` | One of 10 palettes: blue, pink, green, yellow, red, white, ocean, twilight, pastel, cobalt |
| `verbose_tool_output` | Toggle full tool args/results in the TUI (added in v0.2.3) |

The theme system introduced in v0.2.0 ensures that borders, panels, markdown, the spinner, and the toolbar all respect the chosen palette (Source: [docs/configuration.md:10-25]()). From v0.2.1 onward, the `theme` key is part of the documented configuration surface (Source: [docs/configuration.md:27-30]()).

## Starting a Session

After setup, launch the agent with the bare `eling` command. The CLI in `eling/cli.py` initialises memory, registers plugins, and renders the banner including the session timer and current model (Source: [eling/cli.py:20-55]()). Inside a session, the following commands are available:

- `/new` — restart the session with a clear screen (added in v0.1.5, Source: [README.md:60-62]()).
- `/skills` — list learned skills from the local skill library.
- `/memory` — inspect what is stored in the BM25/cosine index.
- `/theme` — switch the active palette without restarting.
- Plain text — sent to the model as a user turn, with the agent reasoning, planning, and invoking tools as needed.

Conversation history is preserved across turns, so follow-ups such as "continue" or "do that again" work without re-prompting (Source: [README.md:55-58]()).

## Request Lifecycle

The end-to-end flow from user input to tool execution is short and predictable:

```mermaid
flowchart LR
    A[User input] --> B[eling CLI parses turn]
    B --> C[Memory recall - BM25 + cosine]
    C --> D[LLM call with context]
    D --> E{Tool call?}
    E -- yes --> F[Execute via plugin or MCP]
    F --> G[Auto lint + pytest if files touched]
    G --> D
    E -- no --> H[Stream reply to TUI]
```

The agent loop is implemented in `agent.py`, which owns the turn-taking logic, the tool dispatcher, and the post-tool safety net (Source: [agent.py:30-80]()). As of v0.2.3, that safety net includes Ruff auto-fix and pytest injection so the model can self-correct failing tests without human intervention (Source: [README.md:40-48]()). Memory writes go through `eling/memory.py`, which uses SHA-256 hashing introduced in v0.2.0 to deduplicate stored content (Source: [eling/memory.py:15-45]()).

## Where to Go Next

Once the first session runs cleanly, the rest of the documentation in `docs/` covers the moving parts in depth:

- `docs/architecture.md` — module boundaries and data flow between CLI, agent, memory, and plugins.
- `docs/memory.md` — the eight memory layers (Builtin, Blackbox, Facts/HRR, Code/AST, KB/FTS5, Notion, Continuum, Markdownify) shipped from v0.1.2 onward.
- `docs/plugins.md` — writing your own tool with the `@tool` decorator.
- `docs/mcp.md` — connecting external MCP servers such as `as_brain`, `blackbox`, `continuum`, and `markdownify`.

For users coming from earlier versions, the release notes for v0.1.0 through v0.2.3 are the most reliable changelog and are linked from the README (Source: [README.md:65-70]()).

---

<a id='page-2'></a>

## System Architecture and Component Map

### Related Pages

Related topics: [Introduction and Quick Start](#page-1), [Memory System: 8-Layer Second Brain](#page-5), [Plugin System, MCP Servers, and the @tool Decorator](#page-7)

<details>
<summary>Related Source Files</summary>

The following source files were used to generate this page:

- [agent.py](https://github.com/PatrickNoFilter/eling-agent/blob/main/agent.py)
- [provider.py](https://github.com/PatrickNoFilter/eling-agent/blob/main/provider.py)
- [mcp_client.py](https://github.com/PatrickNoFilter/eling-agent/blob/main/mcp_client.py)
- [memory.py](https://github.com/PatrickNoFilter/eling-agent/blob/main/memory.py)
- [skills.py](https://github.com/PatrickNoFilter/eling-agent/blob/main/skills.py)
- [plugins/__init__.py](https://github.com/PatrickNoFilter/eling-agent/blob/main/plugins/__init__.py)
- [config.py](https://github.com/PatrickNoFilter/eling-agent/blob/main/config.py)
- [tui.py](https://github.com/PatrickNoFilter/eling-agent/blob/main/tui.py)
- [docs/architecture.md](https://github.com/PatrickNoFilter/eling-agent/blob/main/docs/architecture.md)
</details>

# System Architecture and Component Map

Eling Agent is a personal autonomous CLI agent that wires together an LLM provider, a layered memory subsystem, an MCP tool bridge, a self-learning skill library, and a Rich-based TUI around a single conversational loop. This page describes the runtime topology and the responsibilities of each subsystem, based on the source files listed above.

## High-Level Loop and Control Flow

The `agent.py` module is the orchestrator. It receives user input, builds a context window from memory and skills, calls the LLM via `provider.py`, parses the model's response for tool calls, and dispatches each call to either a local plugin or a remote MCP server. After each tool round, post-processing hooks (auto-pytest on touched tests, Ruff auto-fix lint, verbose tool output capture) run before the next model turn. `Source: [agent.py:1-120]()`

```mermaid
flowchart TB
    User -->|input| TUI[tui.py\nRich UI]
    TUI --> Agent[agent.py\norchestrator]
    Agent --> Memory[memory.py\n8-layer recall]
    Agent --> Skills[skills.py\nlearned patterns]
    Agent --> Provider[provider.py\nLLM API]
    Provider --> Agent
    Agent -->|tool call| Plugins[plugins/\n@tool decorators]
    Agent -->|tool call| MCP[mcp_client.py\nstdio servers]
    Plugins --> Agent
    MCP --> Agent
    Agent -->|final reply| TUI
```

## Core Subsystems

**LLM Provider.** `provider.py` abstracts model selection (configurable via `eling setup`) and exposes a uniform call interface. The agent passes the assembled prompt and tool schema, and receives either text or structured tool-call deltas. `Source: [provider.py:1-80]()`

**Memory.** `memory.py` implements an 8-layer memory stack: Builtin, Blackbox (flight recorder with 11-metric context efficiency scoring), Facts/HRR, Code (AST), KB (FTS5), Notion, Continuum, and Markdownify. Retrieval combines BM25 keyword scoring with cosine similarity over embeddings, and a SHA-256 content-hash dedup layer prevents re-ingestion of identical chunks. `Source: [memory.py:1-200]()`

**Skills.** `skills.py` is the self-learning library. After successful tool sequences the agent extracts candidate skills; a quality gate rejects bodies shorter than 50 characters and generic names like "fix" or "debug". Unused skills (zero hits) are pruned automatically. System prompts are example-driven (e.g., live-elapsed-timer, system-health-check) to bias extraction toward reusable patterns. `Source: [skills.py:1-160]()`

**MCP Bridge.** `mcp_client.py` spawns stdio-based MCP servers declared in `config.py` (`as_brain`, `blackbox`, `continuum`, `markdownify`) and exposes their tools under the same interface as local plugins, so `agent.py` does not need to distinguish between local and remote capabilities. `Source: [mcp_client.py:1-100]()`

## Extension Points and Presentation

**Plugins.** `plugins/__init__.py` registers the `@tool` decorator introduced in v0.2.3. Functions decorated with `@tool` are introspected for their signature, docstring, and type hints and are surfaced to the model as callable tools. This is the primary hook for adding capabilities without editing the agent loop. `Source: [plugins/__init__.py:1-60]()`

**Configuration.** `config.py` is the single source of truth for theme, model selection, MCP server declarations, `verbose_tool_output`, and skill thresholds. The setup wizard (`eling setup`) writes here; v0.2.0 added the `[2] Theme` menu exposing 10 palettes (blue, pink, green, yellow, red, white, ocean, twilight, pastel, cobalt). `Source: [config.py:1-140]()`

**TUI.** `tui.py` renders the Rich-based interface: banner with session timer and 🤖 model badge, dim reasoning panels for chain-of-thought, plan panels, markdown rendering, and the thinking spinner. All chrome (borders, panels, spinner, toolbar) respects the active theme. The `/new` command clears the screen and restarts the session (v0.1.5). `Source: [tui.py:1-180]()`

## Data Flow Summary

A user turn is shaped as: `User → TUI → Agent → (Memory + Skills) → Provider → Agent → {Plugins ∪ MCP} → Agent → TUI`. Conversation history persists within the session so that "continue" works across turns (v0.1.1). After every tool round the agent runs the post-tool checks (pytest on touched files, Ruff auto-fix) and re-injects any failures into the next model prompt so the model can self-correct before producing a final reply. `Source: [docs/architecture.md:1-80]()`

This layered design keeps the orchestrator (`agent.py`) thin, lets new capabilities be added as either `@tool`-decorated functions or MCP servers, and confines user-visible state (theme, model, verbose flags) to a single configuration file.

---

<a id='page-3'></a>

## Configuration, Setup Wizard, and Themes

### Related Pages

Related topics: [Introduction and Quick Start](#page-1), [Terminal UI (TUI), Spinner, and Theming](#page-4)

<details>
<summary>Related Source Files</summary>

The following source files were used to generate this page:

- [config.example.json](https://github.com/PatrickNoFilter/eling-agent/blob/main/config.example.json)
- [src/eling/cli.py](https://github.com/PatrickNoFilter/eling-agent/blob/main/src/eling/cli.py)
- [src/eling/config.py](https://github.com/PatrickNoFilter/eling-agent/blob/main/src/eling/config.py)
- [src/eling/setup.py](https://github.com/PatrickNoFilter/eling-agent/blob/main/src/eling/setup.py)
- [src/eling/themes.py](https://github.com/PatrickNoFilter/eling-agent/blob/main/src/eling/themes.py)
- [src/eling/tui/app.py](https://github.com/PatrickNoFilter/eling-agent/blob/main/src/eling/tui/app.py)
- [docs/configuration.md](https://github.com/PatrickNoFilter/eling-agent/blob/main/docs/configuration.md)
- [docs/index.md](https://github.com/PatrickNoFilter/eling-agent/blob/main/docs/index.md)
</details>

# Configuration, Setup Wizard, and Themes

Eling Agent centralizes every user-tunable knob (provider credentials, memory limits, TUI behavior, color palette) inside a single JSON document that is loaded once at startup, mutated through an interactive wizard, and rendered by a theme-aware Textual application. This page covers how that document is structured, how the wizard writes to it, and how the theme system applies the chosen palette to every UI element.

## Configuration File Layout

The runtime configuration lives in a JSON file on disk. `config.example.json` ships with the repository and is the canonical reference for every supported key; the wizard copies and edits a sibling file under the user's workspace so the example is never modified in place.

Source: [config.example.json:1-60]()

The schema is intentionally flat. Top-level keys are grouped by subsystem:

| Key group | Examples | Purpose |
|---|---|---|
| `model` | `provider`, `name`, `api_key_env` | Selects which LLM the agent calls |
| `memory` | `max_items`, `dedup_hash`, `recency_decay` | Bounds and decay settings for the memory layer |
| `tool` | `verbose_tool_output`, `auto_pytest`, `auto_fix_lint` | Toggles per-tool and post-tool behavior |
| `theme` | `name` | One of the ten registered palette names |

The loader is the single source of truth for defaults. When a key is missing, `config.py` falls back to a built-in default defined next to each field, so users only need to override what they want to change.

Source: [src/eling/config.py:1-80]()

A small dataclass-style wrapper exposes the parsed document as typed attributes (`cfg.theme`, `cfg.tool.verbose_tool_output`, etc.) so downstream code never re-parses JSON. The wrapper also normalizes legacy keys and emits warnings when an unknown key is found.

Source: [src/eling/config.py:81-150]()

## Setup Wizard

The wizard is invoked with `eling setup` and is the supported way to produce a working `config.json` from scratch. It is a numbered menu rendered with the same theme primitives as the rest of the TUI, so the wizard itself reflects whatever palette is currently active.

Source: [src/eling/cli.py:40-90]()

Menu entries (selectable with bracket numbers, e.g. `[2]`):

1. **Model** — prompts for provider, model name, and the environment variable that holds the API key; writes them to the `model` group of the config file.
2. **Theme** — opens an inline palette picker; on selection it both updates `theme.name` and hot-reloads the active stylesheet so the change is visible immediately.
3. **Memory** — configures retention limits, dedup hashing, and the recency decay curve.
4. **Tools** — toggles `verbose_tool_output`, `auto_pytest`, `auto_fix_lint`, and related flags introduced through the v0.2.x line.

Source: [src/eling/setup.py:1-120]()

The wizard always writes atomically: it builds the next config in memory, validates it against the same schema the loader uses, then replaces the on-disk file in a single `os.replace` call. Cancelling mid-flow leaves the previous config untouched.

Source: [src/eling/setup.py:120-180]()

## Theme System

Themes are Python data, not external files. Each palette is declared in `themes.py` as a mapping from a semantic role (e.g. `border`, `panel`, `markdown.fg`, `spinner`, `toolbar.accent`) to a hex color or Rich/Textual style tuple. This keeps distribution simple — no asset path resolution, no CSS — and makes the palette list trivially testable.

Source: [src/eling/themes.py:1-90]()

Ten palettes are bundled and selectable by `theme.name` in the config:

- `blue`, `pink`, `green`, `yellow`, `red`, `white`
- `ocean`, `twilight`, `pastel`, `cobalt`

Source: [src/eling/themes.py:90-160]()

The Textual app applies the palette by mounting a single `ElingTheme` stylesheet that interpolates the role→color map into every CSS rule that references a semantic role. Because the map is rebuilt on `on_mount`, changing `theme.name` and remounting is enough to re-skin the entire UI without restarting the process.

Source: [src/eling/tui/app.py:50-130]()

The exact set of themed elements, listed in the docs:

- Borders, panels, and toolbars
- Markdown rendering (headings, code blocks, quotes, links)
- The thinking spinner and progress indicators
- The reasoning panel dim style introduced in v0.1.1
- The banner, agent/model display, and session timer

Source: [docs/configuration.md:1-80]()

```mermaid
flowchart LR
    A[config.example.json] -->|shipped template| B[User config.json]
    B -->|parsed by| C[config.py loader]
    C -->|typed wrapper| D[Agent core]
    C -->|theme.name| E[themes.py palette]
    E -->|stylesheet| F[tui/app.py]
    G[eling setup wizard] -->|atomic write| B
    G -->|hot reload| F
```

## Interaction Between the Three

A typical first-run flow ties everything together: `eling setup` writes `config.json` from the template, `config.py` validates and wraps it, `themes.py` resolves the chosen palette, and `tui/app.py` paints the Textual interface. Subsequent edits can be made either by re-running the wizard or by hand — the loader is forgiving and treats unknown keys as warnings rather than errors.

Source: [src/eling/cli.py:1-40]()

If the `theme` key is missing or names an unknown palette, `themes.py` falls back to `blue`, which is also the default in `config.example.json`. This mirrors the same "fail soft, log a warning" pattern used by every other config key.

Source: [docs/index.md:1-60]()

The combined result is a config surface that is small enough to read end-to-end, a wizard that keeps the file in sync with the schema, and a theme layer that recolors every chrome element with a single string change.

---

<a id='page-4'></a>

## Terminal UI (TUI), Spinner, and Theming

### Related Pages

Related topics: [Configuration, Setup Wizard, and Themes](#page-3), [Tool Orchestration: Workspace, Auto-pytest, and Auto-fix Lint](#page-8)

<details>
<summary>Related Source Files</summary>

The following source files were used to generate this page:

- [tui.py](https://github.com/PatrickNoFilter/eling-agent/blob/main/tui.py)
- [src/eling/config.py](https://github.com/PatrickNoFilter/eling-agent/blob/main/src/eling/config.py)
- [src/eling/themes.py](https://github.com/PatrickNoFilter/eling-agent/blob/main/src/eling/themes.py)
- [src/eling/cli.py](https://github.com/PatrickNoFilter/eling-agent/blob/main/src/eling/cli.py)
- [src/eling/setup.py](https://github.com/PatrickNoFilter/eling-agent/blob/main/src/eling/setup.py)
- [src/eling/spinner.py](https://github.com/PatrickNoFilter/eling-agent/blob/main/src/eling/spinner.py)
- [docs/configuration.md](https://github.com/PatrickNoFilter/eling-agent/blob/main/docs/configuration.md)
</details>

# Terminal UI (TUI), Spinner, and Theming

The Terminal UI subsystem gives Eling Agent its interactive Rich-backed experience: a banner with session telemetry, a live thinking spinner, plan/reasoning panels, and Markdown rendering. The theming layer sits on top of the TUI so every chrome element — borders, panels, markdown styles, the spinner and the toolbar — derives its colors from a single, user-selectable palette, while remaining easy to disable for non-interactive or "compact" runs.

## Architecture & Module Responsibilities

The TUI is composed of small, focused modules rather than one monolith.

- `tui.py` owns the Rich `Console`, the `Panel` / `Markdown` / `Live` rendering primitives, and the orchestration of spinner → tool output → assistant reply transitions. Source: [tui.py:1-80]().
- `src/eling/themes.py` defines the 10 named palettes (`blue`, `pink`, `green`, `yellow`, `red`, `white`, `ocean`, `twilight`, `pastel`, `cobalt`) as plain dataclasses / dicts of Rich style names. Source: [src/eling/themes.py:1-60]().
- `src/eling/spinner.py` encapsulates the spinner factory, picking the right `Spinner` frames and color from the active theme. Source: [src/eling/spinner.py:1-40]().
- `src/eling/config.py` persists the chosen `theme` key and the `verbose_tool_output` toggle, both surfaced in `docs/configuration.md`. Source: [src/eling/config.py:120-180]().
- `src/eling/setup.py` exposes the interactive `eling setup` wizard where option `[2] Theme` writes the palette name back to the user config. Source: [src/eling/setup.py:30-95]().

At boot, `cli.py` resolves the configured theme, applies it to the global Rich `Console`, then hands the configured console to `tui.py` so every render call inherits the palette. Source: [src/eling/cli.py:50-90]().

## Theme System

The theme is a single string identifier stored in user config (`theme: "ocean"`) that maps to a palette dictionary. Every UI surface reads from this dictionary at render time, never hard-coding color names.

Key behaviors verified by the source:

- **10 built-in palettes**: `blue`, `pink`, `green`, `yellow`, `red`, `white`, `ocean`, `twilight`, `pastel`, `cobalt`. Source: [src/eling/themes.py:10-55]().
- **Selection path**: `eling setup → [2] Theme` lists the palettes and writes the chosen slug to config. Source: [src/eling/setup.py:55-80]().
- **Coverage**: borders, panels, Markdown code blocks, headings, the spinner, and the toolbar all pull from the same palette — no element is hardcoded. Source: [tui.py:90-160]().

| Element        | Style key from theme       | Render site                  |
|----------------|----------------------------|------------------------------|
| Banner border  | `theme.primary`            | `tui.py:render_banner`       |
| Spinner        | `theme.accent`             | `src/eling/spinner.py`       |
| Plan panel     | `theme.secondary`          | `tui.py:render_plan`         |
| Markdown       | `theme.markdown.code` etc. | `tui.py:render_markdown`     |
| Toolbar        | `theme.muted`              | `tui.py:render_toolbar`      |

This indirection means switching themes in config requires no code change — restart the agent and the next render uses the new palette.

## Spinner & Live Tool Output

The "thinking" spinner is a `rich.spinner.Spinner` driven by a `Live` context. When the agent enters a tool round, the spinner is replaced by a tool panel showing the tool name and (when enabled) its full args/result text. This dual state is the heart of the TUI's responsiveness.

Implementation details:

- The spinner frames and color are derived from the active theme so a `pink` theme shows a pink dotframe and a `cobalt` theme shows a cobalt dots variant. Source: [src/eling/spinner.py:12-35]().
- `verbose_tool_output` (config boolean) controls whether the full tool argument and result strings are printed inside the panel; when disabled only a one-line summary is shown. Source: [src/eling/config.py:150-170]().
- A TUI threading fix added `import threading` to coordinate the spinner `Live` thread with the agent loop, preventing interleaved writes. Source: [tui.py:30-45]() (referenced in v0.1.5 changelog).
- In non-TUI / "compact" runs, the spinner is not started and the screen is cleared on startup instead of mounting the Live display. Source: [tui.py:200-230]().

## CLI Integration & Configuration Surface

The TUI is the default rendering mode; turning it off means plain text output. The setup wizard and config keys are the only user-facing knobs.

- **Default vs compact**: `cli.py` inspects config flags and either constructs a `Live`-backed console or a plain one. Source: [src/eling/cli.py:60-110]().
- **`/new` command**: restarts the session and explicitly clears the screen, which matters when switching from compact → TUI mode mid-process. Source: [tui.py:240-260]().
- **Config keys** documented in `docs/configuration.md`:
  - `theme` — one of the 10 palette names.
  - `verbose_tool_output` — boolean, default `false`; flipped to `true` in v0.2.3 to expose full tool args/results in the TUI. Source: [docs/configuration.md:25-45]().
- **Session timer**: surfaced in the banner, user prompt and assistant panel via a small ⏱ indicator, kept dim through `theme.muted`. Source: [tui.py:110-140]().
- **Reasoning panels**: the model's chain-of-thought is rendered in a compact, dim panel using `theme.muted` so it does not visually compete with the final answer. Source: [tui.py:160-195]().

Together, these modules keep the interactive feel of the agent (banner, live spinner, panels, toolbar, themed chrome) orthogonal to the agent loop itself — designers can change a palette, and every screen updates on the next render without touching tool, memory, or planner code.

---

<a id='page-5'></a>

## Memory System: 8-Layer Second Brain

### Related Pages

Related topics: [System Architecture and Component Map](#page-2), [Skill Library and Self-Learning](#page-6)

<details>
<summary>Related Source Files</summary>

The following source files were used to generate this page:

- [memory.py](https://github.com/PatrickNoFilter/eling-agent/blob/main/memory.py)
- [textsim.py](https://github.com/PatrickNoFilter/eling-agent/blob/main/textsim.py)
- [src/eling/brain.py](https://github.com/PatrickNoFilter/eling-agent/blob/main/src/eling/brain.py)
- [src/eling/layers/builtin.py](https://github.com/PatrickNoFilter/eling-agent/blob/main/src/eling/layers/builtin.py)
- [src/eling/layers/blackbox.py](https://github.com/PatrickNoFilter/eling-agent/blob/main/src/eling/layers/blackbox.py)
- [src/eling/layers/code.py](https://github.com/PatrickNoFilter/eling-agent/blob/main/src/eling/layers/code.py)
- [src/eling/layers/facts.py](https://github.com/PatrickNoFilter/eling-agent/blob/main/src/eling/layers/facts.py)
- [src/eling/layers/kb.py](https://github.com/PatrickNoFilter/eling-agent/blob/main/src/eling/layers/kb.py)
- [src/eling/layers/notion.py](https://github.com/PatrickNoFilter/eling-agent/blob/main/src/eling/layers/notion.py)
- [src/eling/layers/continuum.py](https://github.com/PatrickNoFilter/eling-agent/blob/main/src/eling/layers/continuum.py)
- [src/eling/layers/markdownify.py](https://github.com/PatrickNoFilter/eling-agent/blob/main/src/eling/layers/markdownify.py)
</details>

# Memory System: 8-Layer Second Brain

## Overview

The Memory System is the persistence and retrieval backbone of eling-agent. Introduced in v0.1.2 as the "Eling Memory Library," it is structured as an **8-Layer Second Brain** — a fan-out of specialized memory backends coordinated by a central `brain` orchestrator. Each layer owns a different representation (keyword, vector, AST, AST-aware facts, full-text, remote docs, episodic trace, rendered markdown) and exposes the same `store` / `query` / `forget` interface defined in `memory.py`. Source: [memory.py:1-40]()

The system was later hardened in v0.2.0 with **SHA-256 content deduplication** so the same fact never lands in two layers under different keys. Source: [memory.py:40-120](). Subsequent releases extended retrieval quality through scoring heuristics and skill self-learning rather than redesigning the layer graph itself.

## The 8 Layers

All layers live under `src/eling/layers/` and are registered with the brain at startup. They are independent — the agent can enable or disable any subset through configuration.

| # | Layer | File | Purpose |
|---|-------|------|---------|
| 1 | **Builtin** | `src/eling/layers/builtin.py` | In-process scratchpad for the current session and recent turns |
| 2 | **Blackbox** | `src/eling/layers/blackbox.py` | Flight recorder; captures every tool call and computes 11-metric context efficiency |
| 3 | **Facts (HRR)** | `src/eling/layers/facts.py` | Holographic Reduced Representations for compositional atomic facts |
| 4 | **Code (AST)** | `src/eling/layers/code.py` | AST-aware indexing of source files for symbol-level code retrieval |
| 5 | **KB (FTS5)** | `src/eling/layers/kb.py` | SQLite FTS5 full-text knowledge base for documents and notes |
| 6 | **Notion** | `src/eling/layers/notion.py` | Remote bridge into Notion workspaces and pages |
| 7 | **Continuum** | `src/eling/layers/continuum.py` | Long-term autobiographical memory that spans sessions |
| 8 | **Markdownify** | `src/eling/layers/markdownify.py` | Renders retrieved blobs into clean markdown for LLM ingestion |

Sources: [src/eling/layers/builtin.py:1-60](), [src/eling/layers/blackbox.py:1-80](), [src/eling/layers/code.py:1-80](), [src/eling/layers/kb.py:1-60](), [src/eling/layers/notion.py:1-60](), [src/eling/layers/continuum.py:1-60](), [src/eling/layers/markdownify.py:1-60]().

The Blackbox layer is special: it does not primarily answer queries but **records** them. It feeds a causal scoring trace back to the brain so the agent can later audit which layer contributed to a successful outcome. Source: [src/eling/layers/blackbox.py:80-160]()

## Architecture & Query Flow

The brain is a thin coordinator, not a store. On every user turn, the agent calls `brain.query(prompt)`, which:

1. Hashes the normalized prompt (SHA-256) to short-circuit duplicate queries. Source: [memory.py:40-120]()
2. Dispatches the prompt in parallel to every enabled layer.
3. Collects ranked `(item, score, layer_id)` tuples.
4. Normalizes and merges the lists with cross-layer reciprocal-rank fusion.
5. Hands the top-k merged context to the Markdownify layer before injecting into the model prompt. Source: [src/eling/brain.py:80-160]()

```mermaid
flowchart LR
    User[User prompt] --> Brain[brain.py]
    Brain -->|SHA-256 dedup| Cache[(Query cache)]
    Brain --> B[builtin]
    Brain --> X[blackbox]
    Brain --> F[facts HRR]
    Brain --> C[code AST]
    Brain --> K[KB FTS5]
    Brain --> N[notion]
    Brain --> T[continuum]
    B --> Merge[RRF merge]
    X --> Merge
    F --> Merge
    C --> Merge
    K --> Merge
    N --> Merge
    T --> Merge
    Merge --> Md[markdownify]
    Md --> LLM[Model context]
```

Layers are registered via a decorator so plugins can add new layers without touching `brain.py`. Source: [src/eling/brain.py:1-80]()

## Retrieval, Scoring, and Community Notes

Retrieval is **hybrid by default**: BM25 keyword scores from the KB layer are combined with cosine similarity over embeddings produced for the Facts and Code layers. The shared text-similarity helper lives in `textsim.py` and is reused by every layer that produces vectors. Source: [textsim.py:1-60]()

Scoring across layers is normalized before merging so a 0.9 cosine hit and a BM25 hit from another layer can be ranked on a common scale. The Blackbox layer additionally tracks **11 context-efficiency metrics** (latency, token cost, recall, redundancy, etc.) so the brain can later down-weight layers that consistently return low-value hits. Source: [src/eling/layers/blackbox.py:80-160]()

Notable behavior reported by the community:

- **v0.2.0** introduced SHA-256 dedup so the same normalized content is never stored twice across the eight layers. Source: [memory.py:40-120]()
- **v0.2.2** added a *quality gate* and *auto-forget* on top of memory: skills with `<50 char` bodies or generic names are rejected, and unused skills are pruned after a retention window — both rely on the same dedup hash to detect "use." Source: [memory.py:120-200]()
- The memory surface is also exposed to MCP clients through four servers (`as_brain`, `blackbox`, `continuum`, `markdownify`), letting external tools query the brain without going through the agent loop.

In short, the 8-Layer Second Brain is not a single database — it is a *federation* of eight specialized stores, unified by a hash-based dedup layer, hybrid BM25/cosine retrieval, and a Blackbox flight recorder that scores how well the federation is actually serving the agent.

---

<a id='page-6'></a>

## Skill Library and Self-Learning

### Related Pages

Related topics: [Memory System: 8-Layer Second Brain](#page-5), [Tool Orchestration: Workspace, Auto-pytest, and Auto-fix Lint](#page-8)

<details>
<summary>Related Source Files</summary>

The following source files were used to generate this page:

- [skills.py](https://github.com/PatrickNoFilter/eling-agent/blob/main/skills.py)
- [src/eling/zero_plugin/SKILL.md](https://github.com/PatrickNoFilter/eling-agent/blob/main/src/eling/zero_plugin/SKILL.md)
- [src/eling/decay.py](https://github.com/PatrickNoFilter/eling-agent/blob/main/src/eling/decay.py)
- [src/eling/snapshot.py](https://github.com/PatrickNoFilter/eling-agent/blob/main/src/eling/snapshot.py)
- [docs/configuration.md](https://github.com/PatrickNoFilter/eling-agent/blob/main/docs/configuration.md)
- [README.md](https://github.com/PatrickNoFilter/eling-agent/blob/main/README.md)
</details>

# Skill Library and Self-Learning

The Skill Library is the agent's long-term store of *procedural* knowledge: named, reusable patterns the agent has already discovered and validated in prior runs. When a future request matches an existing skill, the agent retrieves the recorded approach instead of re-deriving it from scratch, which short-circuits the usual tool round-trip and keeps the prompt focused.

Skills sit alongside, but are distinct from, the short-term conversation history and the BM25/cosine fact memory. Conversation history is per-session; fact memory is declarative; skills are *how-to* knowledge attached to a trigger phrase and an example.

## Skill File Format and Storage

Every skill is a single Markdown file with YAML frontmatter. The shipped `zero_plugin` bundle ships a reference skill so the agent is never empty at first run.

A skill record carries:

- **frontmatter** — `name`, `description`, `tags`, `use_count`, `last_used`
- **body** — the procedure itself, with at least one concrete example

Source: [src/eling/zero_plugin/SKILL.md:1-40]()

Learned skills are persisted to a per-user skills directory and indexed for retrieval. The same loader that reads the bundled `SKILL.md` reads user skills, so the agent treats shipped and learned skills uniformly.

Source: [skills.py:1-120]()

## Self-Learning Loop

After every tool round, the agent runs a self-learning pass that asks the model to extract any reusable pattern surfaced during the turn. The loop is:

1. Observe tool calls, arguments, and outcomes for the current turn.
2. Prompt the model with an example-driven template (e.g. `live-elapsed-timer`, `system-health-check`) to propose a skill candidate.
3. Validate the candidate against the quality gate.
4. Persist accepted skills, then update the search index.

Source: [skills.py:120-260]()

The extraction prompt was tightened in v0.2.2 — the release notes flag "Smarter skill self-learning" with a "Better prompt" and "Quality gate". The example-driven template replaced the earlier free-form prompt, which measurably improved the relevance of extracted skills.

Source: [README.md:1-50]()

## Quality Gate and Decay

Not every candidate is admitted. A strict gate runs before persistence:

- **Minimum body length** — bodies shorter than 50 characters are rejected.
- **Name specificity** — generic names like `fix`, `debug`, or `help` are blocked because they carry no discriminative signal at retrieval time.
- **Structural validity** — the YAML frontmatter must parse and required fields must be present.

Source: [skills.py:260-340]()

Skills that pass the gate but are never used are pruned. The decay module scans the library on a schedule and removes any skill whose `use_count` is still zero after a grace period, keeping the library lean and the prompt budget small.

Source: [src/eling/decay.py:1-80]()

Decay is paired with a snapshot subsystem: before any pruning pass, the current library state is written to a snapshot file, letting the user roll back if a useful skill was incorrectly removed.

Source: [src/eling/snapshot.py:1-60]()

## Lifecycle Diagram

The end-to-end skill lifecycle, from proposal to possible rollback:

```mermaid
flowchart LR
    A[Tool round completes] --> B[Extract candidate]
    B --> C{Quality gate}
    C -- pass --> D[Persist + index]
    C -- fail --> X[Discard]
    D --> E{In use?}
    E -- yes --> F[Increment use_count + last_used]
    E -- no, after grace --> G[Snapshot library]
    G --> H[Prune zero-use skills]
    H --> I[Optional rollback from snapshot]
```

## Retrieval and Reuse

When a new request arrives, the agent looks up the most relevant skills and injects their bodies into the system prompt. Each successful reuse increments `use_count` and updates `last_used`, which both rewards the skill and protects it from the decay pass — used skills survive, unused skills eventually go.

Source: [skills.py:340-420]()

## Configuration

Skill behavior is tunable through the standard config file. The keys most relevant to this page are:

| Key | Purpose | Default |
| --- | --- | --- |
| `skill_min_body_chars` | Minimum body length to admit a skill | `50` |
| `skill_blocked_names` | Generic names rejected at the gate | `["fix", "debug", "help"]` |
| `skill_decay_days` | Grace period before unused-skill pruning | enabled |
| `skill_decay_min_uses` | Minimum `use_count` to survive decay | `1` |

Source: [docs/configuration.md:1-80]()

## Summary

The Skill Library turns successful agent runs into compounding knowledge. The v0.2.2 release made extraction sharper with an example-driven prompt, tightened the quality gate so low-value skills stop being recorded, and added decay plus snapshots so the library stays small and recoverable. Together these mechanisms make the agent's expertise durable across sessions without bloating the prompt.

---

<a id='page-7'></a>

## Plugin System, MCP Servers, and the @tool Decorator

### Related Pages

Related topics: [System Architecture and Component Map](#page-2), [Tool Orchestration: Workspace, Auto-pytest, and Auto-fix Lint](#page-8)

<details>
<summary>Related Source Files</summary>

The following source files were used to generate this page:

- [plugins/__init__.py](https://github.com/PatrickNoFilter/eling-agent/blob/main/plugins/__init__.py)
- [plugins/shell_plugin.py](https://github.com/PatrickNoFilter/eling-agent/blob/main/plugins/shell_plugin.py)
- [plugins/eling_integration.py](https://github.com/PatrickNoFilter/eling-agent/blob/main/plugins/eling_integration.py)
- [mcp_client.py](https://github.com/PatrickNoFilter/eling-agent/blob/main/mcp_client.py)
- [src/eling/mcp_server.py](https://github.com/PatrickNoFilter/eling-agent/blob/main/src/eling/mcp_server.py)
- [src/eling/as_brain/mcp_server.py](https://github.com/PatrickNoFilter/eling-agent/blob/main/src/eling/as_brain/mcp_server.py)
</details>

# Plugin System, MCP Servers, and the @tool Decorator

Eling Agent exposes its tool surface through two complementary mechanisms: a **first-party plugin system** (with the new `@tool` decorator introduced in v0.2.3) and an **MCP (Model Context Protocol) client/server stack** that allows external tools — including the bundled eling ecosystem servers — to be invoked by the agent loop. Together, these layers let the model call arbitrary Python functions or remote MCP tools without changing core agent code.

## 1. Purpose and Scope

The plugin and MCP layers solve one problem: giving the LLM a discoverable, schema-described set of side-effecting functions (run shell, read memory, query Notion, scrape a page, etc.) that it can call during the autonomous loop. Plugins are in-process Python functions; MCP servers are out-of-process daemons that speak JSON-RPC. Both are surfaced to the model with the same tool-call interface so the agent does not need to know which is which.

Per the v0.1.0 release notes the agent ships with a "Plugin system — extend" capability, and v0.2.3 highlights the new `@tool` plugin decorator as a first-class way to author plugins.

Source: [plugins/__init__.py](https://github.com/PatrickNoFilter/eling-agent/blob/main/plugins/__init__.py)
Source: [mcp_client.py](https://github.com/PatrickNoFilter/eling-agent/blob/main/mcp_client.py)

## 2. Plugin System and the `@tool` Decorator

The plugin registry lives in `plugins/__init__.py`. It defines a `Plugin` protocol (name, description, JSON-schema for arguments, async `run(**kwargs) -> str`) and a `PluginRegistry` that the agent loop queries to build the tool list for each LLM turn. Plugins are registered at import time or by placing a `*_plugin.py` module under the `plugins/` directory.

The `@tool` decorator (v0.2.3) is a thin convenience that wraps an async function into a `Plugin` instance:

```python
from plugins import tool

@tool(name="shell", description="Run a shell command in the workspace")
async def shell(cmd: str, timeout: int = 30) -> str:
    ...
```

The decorator infers the JSON schema from the function signature (via `inspect` and `typing.get_type_hints`), captures the docstring as the tool description, and produces a `Plugin` object that the registry stores by `name`. This removes the boilerplate that earlier plugins (e.g. `shell_plugin.py`) had to write by hand. Legacy plugins still work — they implement the `Plugin` class directly — so the registry accepts both styles.

`shell_plugin.py` is the canonical example: it wraps `asyncio.subprocess` with timeouts and stderr capture, registers itself as `name="shell"`, and is what the agent calls when the model wants to run a command.

`eling_integration.py` bridges the plugin layer to the eling memory library: it exposes memory read/write/search as plugins so the model can recall prior facts without going through MCP.

Source: [plugins/__init__.py](https://github.com/PatrickNoFilter/eling-agent/blob/main/plugins/__init__.py)
Source: [plugins/shell_plugin.py](https://github.com/PatrickNoFilter/eling-agent/blob/main/plugins/shell_plugin.py)
Source: [plugins/eling_integration.py](https://github.com/PatrickNoFilter/eling-agent/blob/main/plugins/eling_integration.py)

## 3. MCP Client and Server Topology

MCP is used for tools that benefit from process isolation or that ship as part of the broader eling ecosystem. `mcp_client.py` implements an async MCP client that:

1. Spawns configured servers as subprocesses (stdio transport) or connects over HTTP/SSE.
2. Calls `tools/list` on each server at startup and merges the returned tool descriptors into the global tool list, namespaced by server (e.g. `as_brain.search`, `blackbox.flight`).
3. Dispatches `tools/call` requests by routing on the namespaced name back to the originating server.

The configuration is driven from the user config file under an `mcp_servers:` key — each entry specifies the command, args, env, and an enable flag. The client is fault-tolerant: a server that crashes or times out is logged and excluded, but the agent loop continues with the remaining tools.

On the server side, `src/eling/mcp_server.py` is the generic entry point, and `src/eling/as_brain/mcp_server.py` is the concrete `as_brain` server introduced in v0.1.2. Per the v0.1.2 release, four MCP servers ship with the repo: `as_brain`, `blackbox`, `continuum`, and `markdownify`. Each one re-exports a slice of the eling memory library as MCP tools (8-layer memory, flight recorder, AST code index, etc.).

Source: [mcp_client.py](https://github.com/PatrickNoFilter/eling-agent/blob/main/mcp_client.py)
Source: [src/eling/mcp_server.py](https://github.com/PatrickNoFilter/eling-agent/blob/main/src/eling/mcp_server.py)
Source: [src/eling/as_brain/mcp_server.py](https://github.com/PatrickNoFilter/eling-agent/blob/main/src/eling/as_brain/mcp_server.py)

## 4. Lifecycle and Tool Dispatch Flow

```mermaid
flowchart LR
    A[Agent startup] --> B[Load plugins/*]
    A --> C[mcp_client.connect_all]
    B --> D[PluginRegistry]
    C --> D
    D --> E[Merge into tool list]
    E --> F[LLM turn]
    F --> G{Tool call?}
    G -- yes, local --> H[Plugin.run]
    G -- yes, remote --> I[mcp_client.call]
    H --> J[Append result to messages]
    I --> J
    J --> F
    G -- no --> K[Final answer]
```

At agent startup the plugin modules are imported (side-effect registration), then `mcp_client` brings up the configured servers. Both sources feed the same `PluginRegistry`, which the agent loop serializes into the OpenAI-style `tools=` payload. When the model returns a `tool_calls` block, the dispatcher checks the namespaced name: prefixed names go to `mcp_client.call`, bare names resolve against the plugin registry. Results are stringified and appended to the message history before the next LLM call.

This unified dispatch is why a user can mix `@tool`-decorated functions with remote MCP servers without changing prompts or agent code — the registry normalizes both into the same descriptor shape.

Source: [plugins/__init__.py](https://github.com/PatrickNoFilter/eling-agent/blob/main/plugins/__init__.py)
Source: [mcp_client.py](https://github.com/PatrickNoFilter/eling-agent/blob/main/mcp_client.py)

---

<a id='page-8'></a>

## Tool Orchestration: Workspace, Auto-pytest, and Auto-fix Lint

### Related Pages

Related topics: [Terminal UI (TUI), Spinner, and Theming](#page-4), [Plugin System, MCP Servers, and the @tool Decorator](#page-7)

<details>
<summary>Related Source Files</summary>

The following source files were used to generate this page:

- [agent.py](https://github.com/PatrickNoFilter/eling-agent/blob/main/agent.py)
- [plugins/shell_plugin.py](https://github.com/PatrickNoFilter/eling-agent/blob/main/plugins/shell_plugin.py)
- [plugins/eling_integration.py](https://github.com/PatrickNoFilter/eling-agent/blob/main/plugins/eling_integration.py)
- [src/eling/hooks.py](https://github.com/PatrickNoFilter/eling-agent/blob/main/src/eling/hooks.py)
- [src/eling/permissions.py](https://github.com/PatrickNoFilter/eling-agent/blob/main/src/eling/permissions.py)
- [src/eling/privacy.py](https://github.com/PatrickNoFilter/eling-agent/blob/main/src/eling/privacy.py)
- [src/eling/workspace.py](https://github.com/PatrickNoFilter/eling-agent/blob/main/src/eling/workspace.py)
</details>

# Tool Orchestration: Workspace, Auto-pytest, and Auto-fix Lint

## Overview

Tool orchestration is the layer that sits between the agent's reasoning loop and the filesystem: it decides which paths are reachable, normalizes inputs around the active workspace, and runs post-tool feedback hooks (tests, linters) so the model sees the consequences of its edits before the next round. It is implemented across `agent.py`, the plugin layer under `plugins/`, and the `src/eling/` core modules that hold cross-cutting hooks, permissions, and privacy policies.

The three pillars described here are:

- **Workspace scoping** — confining every tool call to a project root and handling edge cases like circular symlinks.
- **Auto-pytest** — automatically executing `pytest` on touched test files after each tool round and feeding failures back to the model.
- **Auto-fix lint** — running `ruff --fix` on safe issues, re-checking, and reporting what remains unfixable.

Together they form the self-correcting loop that lets the agent iterate without explicit step-by-step prompting from the user.

## Workspace Scoping and Path Resolution

The workspace is the root directory inside which every shell, file, and edit tool operates. The plugin entry point resolves incoming paths against the workspace and rejects anything that escapes it.

The shell plugin receives a command and resolves it through the workspace normalizer before exec:

`plugins/shell_plugin.py` rewrites relative paths against the workspace root, strips `..` traversal, and canonicalizes symlinks. v0.1.5 notes "Workspace fixes — handle circular symlinks" as a notable fix, meaning the resolver detects cycles during `Path.resolve()` and falls back to an un-resolved absolute path instead of looping (`Source: [agent.py:142-178]()`).

Cross-cutting checks live in the core helpers:

| Concern | Module | Behavior |
|---------|--------|----------|
| Path resolution | `src/eling/workspace.py` | Canonicalizes, detects symlink cycles, returns the safe root-relative form |
| Permission gates | `src/eling/permissions.py` | Allow/deny policies per tool, with workspace as default scope |
| Privacy redaction | `src/eling/privacy.py` | Strips secrets before tool output is logged or returned |

Hook registration happens through `src/eling/hooks.py`, which exposes `pre_tool` and `post_tool` callbacks. The workspace module registers a `pre_tool` hook so every tool call passes through the same path guard (`Source: [src/eling/hooks.py:44-72]()`).

## Auto-pytest Loop

After every tool round (one complete pass of tool calls before the model sees results again), the `post_tool` hook chain evaluates which files were touched. If any of them is a test file, `pytest` is invoked scoped to that file, and failures are returned to the model as additional context.

The detection rule is straightforward: a file is considered a touched test file when its path matches common pytest discovery patterns (`test_*.py`, `*_test.py`) or lives under a `tests/` directory and has been written in this round. The hook then runs `pytest -x --no-header -q <path>` with a short timeout, captures stdout and stderr, and packages them as a structured failure message (`Source: [agent.py:210-268]()`).

The integration with the agent loop is configured in `plugins/eling_integration.py`, which wires `pytest_runner` into the hook chain and ensures failures are appended to the conversation rather than replacing successful tool results (`Source: [plugins/eling_integration.py:88-124]()`). Verbose control sits behind the `verbose_tool_output` config flag introduced in v0.2.3 — when enabled, the full pytest output is shown in the TUI; otherwise only the summary line is rendered.

A typical tool round looks like this:

```mermaid
sequenceDiagram
    participant Model
    participant Hook as post_tool hook
    participant Pytest
    participant Context

    Model->>Hook: edit(tests/test_utils.py)
    Hook->>Hook: detect touched test file
    Hook->>Pytest: pytest -x tests/test_utils.py
    Pytest-->>Hook: failure report
    Hook->>Context: inject failures as tool result
    Context-->>Model: next round sees pytest failures
```

When there are no touched test files, the hook is a no-op, so non-code commands (web fetches, memory reads) do not pay any test-discovery cost (`Source: [src/eling/hooks.py:96-118]()`).

## Auto-fix Lint Pipeline

The lint pipeline mirrors the test pipeline but uses Ruff. After the tool round, `post_tool` runs `ruff check --fix <touched_paths>` to apply only the safe fixes (import sorting, unused imports, mechanical rewrites). The hook then re-runs `ruff check <touched_paths>` without `--fix` to confirm what was resolved and to capture the remaining unsolvable issues (`Source: [agent.py:272-318]()`).

Three outcomes are possible:

- **Clean** — no remaining diagnostics; the hook returns an empty result.
- **Auto-fixed** — Ruff rewrote files and the new state has zero issues; the tool result notes which fixes were applied.
- **Unfixable** — issues remain after `--fix`; these are returned verbatim so the model can reason about them on the next round.

The plugin layer exposes this through `plugins/shell_plugin.py`, where `lint_ruff` is registered as a callable tool and `post_tool` orchestration is enabled by default (`Source: [plugins/shell_plugin.py:55-90]()`). Configuration flags — verbose output, auto-pytest on/off, auto-fix on/off — are read from the agent config and consulted inside the hook chain (`Source: [src/eling/permissions.py:33-58]()`).

## Closing the Loop

The three pieces compose into one feedback loop: every tool round is bounded by the workspace, every edit is checked by Ruff, and every test file edit is exercised by pytest. Failures are surfaced as structured tool results that the model can act on without leaving the conversation. This is what makes the agent's edits self-correcting rather than fire-and-forget, and is the mechanism behind the "auto ruff check" behavior first noted in v0.1.1 and the v0.2.3 release headline features.

---

<!-- evidence_pipeline_checked: true -->
<!-- evidence_injected: true -->

---

## Pitfall Log

Project: PatrickNoFilter/eling-agent

Summary: Found 7 structured pitfall item(s), including 0 high/blocking item(s). Top priority: Identity risk - Identity risk requires verification.

## 1. Identity risk - Identity risk requires verification

- Severity: medium
- Evidence strength: runtime_trace
- Finding: Project evidence flags a identity risk. Review the linked source before relying on this workflow.
- User impact: May increase setup, validation, or first-run risk for the user.
- Repro command: `pip install eling`
- Evidence: identity.distribution | https://github.com/PatrickNoFilter/eling-agent

## 2. Capability evidence risk - Capability evidence risk requires verification

- Severity: medium
- Evidence strength: source_linked
- Finding: README/documentation is current enough for a first validation pass.
- User impact: May increase setup, validation, or first-run risk for the user.
- Evidence: capability.assumptions | https://github.com/PatrickNoFilter/eling-agent

## 3. Maintenance risk - Maintenance risk requires verification

- Severity: medium
- Evidence strength: source_linked
- Finding: Project evidence flags a maintenance risk. Review the linked source before relying on this workflow.
- User impact: May increase setup, validation, or first-run risk for the user.
- Evidence: evidence.maintainer_signals | https://github.com/PatrickNoFilter/eling-agent

## 4. Security or permission risk - Security or permission risk requires verification

- Severity: medium
- Evidence strength: source_linked
- Finding: no_demo
- User impact: May increase setup, validation, or first-run risk for the user.
- Evidence: downstream_validation.risk_items | https://github.com/PatrickNoFilter/eling-agent

## 5. Security or permission risk - Security or permission risk requires verification

- Severity: medium
- Evidence strength: source_linked
- Finding: no_demo
- User impact: May increase setup, validation, or first-run risk for the user.
- Evidence: risks.scoring_risks | https://github.com/PatrickNoFilter/eling-agent

## 6. Maintenance risk - Maintenance risk requires verification

- Severity: low
- Evidence strength: source_linked
- Finding: issue_or_pr_quality=unknown。
- User impact: May increase setup, validation, or first-run risk for the user.
- Evidence: evidence.maintainer_signals | https://github.com/PatrickNoFilter/eling-agent

## 7. Maintenance risk - Maintenance risk requires verification

- Severity: low
- Evidence strength: source_linked
- Finding: release_recency=unknown。
- User impact: May increase setup, validation, or first-run risk for the user.
- Evidence: evidence.maintainer_signals | https://github.com/PatrickNoFilter/eling-agent

<!-- canonical_name: PatrickNoFilter/eling-agent; human_manual_source: deepwiki_human_wiki -->
