Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 37 additions & 19 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
- **Run Agent C (Textual UI)**: `uv run agent-c`
- **Run Console UI**: `uv run run-console`
- **Test all**: `uv run pytest`
- **Test agentc only**: `uv run pytest tests/core/ tests/middleware/ tests/adapters/`
- **Test agentc core**: `uv run pytest tests/core/ tests/middleware/ tests/adapters/`
- **Type check**: `uv run mypy`
- **Lint**: `uv run ruff check`
- **Format**: `uv run ruff format`
Expand All @@ -43,22 +43,37 @@ The project uses an event-driven, layered architecture with clear separation of

Event-driven, layered architecture with:

- **`core/`**: Agnostic agentic logic (types, event loop, agent factory, tools). The `tools/` package provides filesystem operations (with combined ignore patterns), file editing with atomic writes and backups, and command execution. `skill_loader.py` discovers `SKILL.md` files from bundled skills (installed to user data directory) and project directories.
- `types.py`: Event-stream union (`AgentEvent`), `AgentSessionProtocol`, tool result dataclasses, and re-exports of patching types.
- **`core/`**: Agnostic agentic logic (types, event loop, commands). Implements shared protocols and types used by all backends.
- `types.py`: Event-stream union (`AgentEvent`), `AgentSessionProtocol`, `SessionFactoryProtocol`, tool result dataclasses, and re-exports of patching types.
- `config_types.py`: `BackendConfig` and `ModelConfig` for provider/model presets.
- `command_types.py`: `CommandType`, `CommandResult`, and `CommandEffect` for command parsing/execution.
- `command_types.py`: `CommandType`, `CommandResult`, `SessionConfig`, and `CommandEffect` for command parsing/execution.
- `deps.py`: `RunDeps` context for dependency-injected agent runs.
- `config.py`: Centralized system constants (output caps, suffixes, default skill dirs, `DEFAULT_MODEL`, `DEFAULT_PROVIDER_DIRS`). Must be UI-agnostic.
- `loop.py`: `AgentSession` implementing the bidirectional async generator loop and mapping pydantic_ai events to `AgentEvent`.
- `factory.py`: `create_agent` factory assembling the `pydantic_ai.Agent` using the model preset configured in `providers.toml` (default preset: `local-oss` on the `ollama` backend), plus the shared toolset and skills table.
- `commands.py`: Command parsing (`CommandParser`) and effect-based execution (`execute_command`).
- `CommandParser` performs pure parsing without validation
- Commands produce pure `CommandEffect` data containing `SessionConfig`
- Session factories validate model names and apply configuration to create new sessions
- `tool_parsing.py`: Robust JSON/dict argument handling for tool calls.
- `tools/`: Tool package organized by category (see Available Tools section)
- `skill_loader.py`: Discovers `SKILL.md` skills from project directories (`.github/skills`, `.claude/skills`), user directory (`~/.agentc/skills`), and bundled skills (installed to platform-specific user data directory). Earlier directories take precedence.
- `provider_loader.py`: Discovers, loads, and merges `providers.toml` files from repo/user/bundled locations (priority: repo > user > bundled). Dynamically imports provider/model classes and builds instances with API keys, base URLs, and model params.
- `backends/`: Backend implementations (see Backend Structure below)
- `patching/`: Structured file patching engine (see Patching Module below)

- **`core/backends/`**: Backend-specific implementations
- **`pydantic_ai/`**: Pydantic AI backend
- `factory.py`: `create_agent` factory assembling the `pydantic_ai.Agent` using the model preset configured in `providers.toml` (default preset: `ollama-gpt-oss-120b` on the `ollama` backend), plus the shared toolset and skills table.
- `loop.py`: `AgentSession` implementing the bidirectional async generator loop and mapping pydantic_ai events to `AgentEvent`.
- `session_factory.py`: `PydanticAISessionFactory` implementing `SessionFactoryProtocol`.
- `provider_loader.py`: Discovers, loads, and merges `providers.toml` files from repo/user/bundled locations (priority: repo > user > bundled). Dynamically imports provider/model classes and builds instances with API keys, base URLs, and model params.
- `tools/`: Tool package (filesystem, editing, execution) providing filesystem operations with combined ignore patterns, file editing with atomic writes and backups, and command execution.
- **`github_copilot/`**: GitHub Copilot SDK backend
- `loop.py`: `GhAgentSession` implementing `AgentSessionProtocol` using the Copilot SDK.
- `session_factory.py`: `GhCopilotSessionFactory` implementing `SessionFactoryProtocol`.

- **`core/patching/`**: Structured file patching engine
- `types.py`: `PatchHunk`, `PatchPlan`, `FilePatch`, and result types.
- `engine.py`: Anchor-based hunk matching and application logic.
- `transaction.py`: Transactional file modifications with rollback.
- `errors.py`: Patching-specific exception types.

- **`middleware/`**: Cross-cutting concerns (e.g., debouncing)
- `debouncing.py`: `DebouncingMiddleware` for text/thinking delta aggregation (default threshold: 40 characters, configurable).
Expand All @@ -73,11 +88,11 @@ Event-driven, layered architecture with:
- `textual_app.py`: The main Textual `App` implementation.
- Receives model names list via dependency injection from composition root
- Each backend's entry point discovers models using backend-specific mechanisms
- UI layer remains completely backend-agnostic
- UI layer remains completely backend-agnostic, depends on `SessionFactoryProtocol`
- `widgets.py`: Reusable UI components (status bar, approval forms, etc.).

- **`entrypoints/`**: Application composition roots (dependency injection and bootstrapping)
- `run_textual.py`: Pydantic AI backend launcher (entry point: `agent-c`, `run-textual`)
- `run_textual.py`: Pydantic AI backend launcher (entry points: `agent-c`, `run-textual`)
- `run_textual_gh.py`: GitHub Copilot SDK backend launcher (entry point: `run-textual-gh`)
- `run_console.py`: Console UI demo launcher (entry point: `run-console`)

Expand Down Expand Up @@ -118,7 +133,7 @@ Entry points inject concrete factories into the UI layer, which uses the Protoco

## Available Tools

Agent C provides the following tools in the `core/tools/` package:
Agent C provides the following tools in the `core/backends/pydantic_ai/tools/` package:

### Filesystem Tools (`tools/filesystem.py`)

Expand Down Expand Up @@ -194,18 +209,21 @@ When working on `agentc`, follow these rules strictly:

### Testing (Mandatory)
Maintain and update the test suite in `tests/`. Must cover:
- `core.loop`: approval handshake, history, and tool call yielding
- `core.factory`: agent creation with model presets
- `core.backends.pydantic_ai.loop`: approval handshake, history, and tool call yielding
- `core.backends.pydantic_ai.factory`: agent creation with model presets
- `core.backends.github_copilot.loop`: GitHub Copilot SDK session integration
- `core.commands`: command parsing and effect-based execution
- `core.tool_parsing`: robust JSON argument handling
- `core.tools`:
- `core.backends.pydantic_ai.tools`:
- `test_tools_filesystem.py`: list_files, glob_paths, search_files
- `test_tools_editing.py`: read_file, create_file, edit_file, apply_hunks
- `test_tools_execution.py`: run_command
- `test_tool_result.py`: tool result mapping
- `test_ignore_logic.py`: gitignore support integration
- `core.patching`: patching engine and transaction tests
- `core.skill_loader`: skill discovery and skills table rendering
- `core.provider_loader`: provider/model loading and merging
- `core.backends.pydantic_ai.provider_loader`: provider/model loading and merging
- `core.session_factories`: Pydantic AI and GitHub Copilot session factory tests
- `middleware.debouncing`: flush logic and delta aggregation
- `adapters.textual`: mapping to `adapters.messages`
- `adapters.console`: console event mapping and approval flow
Expand Down Expand Up @@ -294,7 +312,7 @@ When making changes, include in your response:

- **Build system**: `uv_build`
- **Entry points**:
- `agentc.entrypoints.run_textual:main` (agent-c command - default Textual UI)
- `agentc.entrypoints.run_console:main` (run-console command)
- `agentc.entrypoints.run_textual:main` (run-textual command)
- `agentc.entrypoints.run_textual_gh:main_sync` (run-textual-gh command)
- `agent-c`: `agentc.entrypoints.run_textual:main` (default Textual UI with Pydantic AI backend)
- `run-textual`: `agentc.entrypoints.run_textual:main` (alias for agent-c)
- `run-textual-gh`: `agentc.entrypoints.run_textual_gh:main_sync` (Textual UI with GitHub Copilot SDK backend)
- `run-console`: `agentc.entrypoints.run_console:main` (Console UI demo)
38 changes: 22 additions & 16 deletions ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,20 +26,26 @@ graph TD

### Layer Responsibilities

**Types Layer** (`types.py`)
- Central `AgentEvent` union (chunks, tool calls, tool results, approvals, done)
- `AgentSessionProtocol` interface
**Types Layer** (`types.py`, `config_types.py`, `command_types.py`)
- Central `AgentEvent` union (chunks, tool calls, tool results, approvals, done) in `types.py`
- `AgentSessionProtocol` and `SessionFactoryProtocol` interfaces in `types.py`
- `BackendConfig` and `ModelConfig` for backend/model presets in `config_types.py`
- `CommandType`, `CommandResult`, `SessionConfig`, `CommandEffect` in `command_types.py`
- Shared dataclasses for cross-layer communication
- `CommandEffect` for effect-based command execution
- `BackendConfig` and `ModelConfig` for backend/model presets

**Core Layer** (`core/`)
- `loop.py`: `AgentSession` implementing bidirectional async generator loop
- `factory.py`: Agent creation with model presets, tools, and skills
- `commands.py`: Command parsing and effect-based execution
- `tools/`: Tool implementations (filesystem, editing, execution)
- `skill_loader.py`: Skill discovery from bundled and project directories
- `provider_loader.py`: Dynamic provider/model loading from TOML configs
- `backends/pydantic_ai/`: Pydantic AI backend implementation
- `loop.py`: `AgentSession` implementing bidirectional async generator loop
- `factory.py`: Agent creation with model presets, tools, and skills
- `provider_loader.py`: Dynamic provider/model loading from TOML configs
- `session_factory.py`: `PydanticAISessionFactory` implementing `SessionFactoryProtocol`
- `tools/`: Tool implementations (filesystem, editing, execution)
- `backends/github_copilot/`: GitHub Copilot SDK backend implementation
- `loop.py`: `GhAgentSession` implementing `AgentSessionProtocol`
- `session_factory.py`: `GhCopilotSessionFactory` implementing `SessionFactoryProtocol`
- `patching/`: Structured file patching engine (anchor-based hunk matching, transactions)

**Middleware Layer** (`middleware/`)
- `debouncing.py`: Text/thinking delta aggregation (40 char threshold default)
Expand All @@ -49,11 +55,12 @@ graph TD
- `console.py`: Translates `AgentEvent` to console callbacks
- Owns UI-specific message types and approval handshake coordination

**UI Layer** (`ui/`)
- `textual_app.py`: Textual TUI application
- `run_textual.py`: Textual UI entry point
- `run_console.py`: Console UI demo entry point
**UI Layer** (`ui/`, `entrypoints/`)
- `textual_app.py`: Textual TUI application (backend-agnostic)
- `widgets.py`: Reusable UI components
- `entrypoints/run_textual.py`: Pydantic AI backend composition root
- `entrypoints/run_textual_gh.py`: GitHub Copilot SDK backend composition root
- `entrypoints/run_console.py`: Console UI demo entry point

## Event Flow

Expand Down Expand Up @@ -184,7 +191,7 @@ Agent C discovers `providers.toml` files in priority order:

Entries from earlier locations override those with the same name later.

### Provider Loader (`core/provider_loader.py`)
### Provider Loader (`core/backends/pydantic_ai/provider_loader.py`)

- **`get_default_provider_dirs()`**: Returns discovery paths in priority order
- **`load_providers(dirs)`**: Discovers and merges backend/model presets with precedence
Expand All @@ -198,10 +205,9 @@ provider_cls = "pydantic_ai.providers.ollama.OllamaProvider"
model_cls = "pydantic_ai.models.openai.OpenAIChatModel"
base_url = "http://localhost:11434/v1"

[models.local-oss]
[models.ollama-gpt-oss-120b]
backend = "ollama"
model_name = "gpt-oss:120b-cloud"
params = {temperature = 0.2}
```

## Design Principles
Expand Down
21 changes: 17 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
# Agent C

A modern code editing assistant powered by [Pydantic AI](https://ai.pydantic.dev/), featuring an event-driven architecture with skills-based prompting, multiple LLM provider support, and a rich Textual TUI.
A modern code editing assistant powered by [Pydantic AI](https://ai.pydantic.dev/) or the [GitHub Copilot SDK](https://github.com/github/copilot-sdk), featuring an event-driven architecture with skills-based prompting, multiple LLM provider support, and a rich Textual TUI.

Hugely inspired by [How to Build an Agent](https://ampcode.com/how-to-build-an-agent) by Thorsten Ball of [AmpCode](https://ampcode.com/).

Agent C uses a layered, event-driven architecture - see [ARCHITECTURE.md](ARCHITECTURE.md) for detailed design and diagrams.
Agent C uses a layered, event-driven architecture with pluggable backends - see [ARCHITECTURE.md](ARCHITECTURE.md) for detailed design and diagrams.

## Features

Expand All @@ -24,7 +24,7 @@ Agent C uses a layered, event-driven architecture - see [ARCHITECTURE.md](ARCHIT

## Quick Start

Agent C defaults to running with Ollama and the `gpt-oss:120b-cloud` model.
Agent C defaults to running with Ollama and the `ollama-gpt-oss-120b` model preset.

### 1. Install Ollama (for local inference)

Expand Down Expand Up @@ -141,14 +141,22 @@ uv run agent-c
uv run run-console
```

### GitHub Copilot SDK Backend

```bash
uv run run-textual-gh
```

Requires the Copilot CLI to be installed and available in PATH.

### Override the Model Preset

Use the `/model` command within the agent:
```
/model claude-sonnet
```

Available presets (bundled): `local-oss`, `gpt-4o-mini`, `claude-sonnet`, `gemini-flash`, `hf-gpt-oss-120b`, `mistral-large`
Available presets (bundled): `ollama-gpt-oss-120b`, `ollama-gpt-oss-20b`, `ollama-kimi-k2-5`, `ollama-glm-4-7`, `gpt-4o-mini`, `claude-sonnet`, `gemini3-flash`, `hf-gpt-oss-120b`, `mistral-large`

### Run Without Installing

Expand Down Expand Up @@ -201,8 +209,13 @@ The agent uses a smart editing strategy:
└── src/
└── agentc/ # Main Implementation
├── core/ # Agnostic agent logic
│ ├── backends/ # Backend implementations
│ │ ├── pydantic_ai/ # Pydantic AI backend (factory, loop, tools)
│ │ └── github_copilot/ # GitHub Copilot SDK backend
│ └── patching/ # Structured file patching engine
├── middleware/ # Cross-cutting concerns (debouncing)
├── adapters/ # UI framework bridges
├── entrypoints/ # Application composition roots
├── ui/ # User interfaces (Textual, Console)
└── providers.toml # Provider configuration
```
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ dependencies = [
license = "MIT"

[dependency-groups]
dev = ["mypy>=1.18.2", "pytest>=8.4.2", "ruff>=0.14.2", "isort>=5.12.0"]
dev = ["mypy>=1.18.2", "pytest>=8.4.2", "ruff>=0.14.2", "isort>=5.12.0", "pytest-cov>=4.1.0"]

[build-system]
requires = ["uv_build>=0.9.6,<0.10.0"]
Expand Down
Loading