Metadata-Version: 2.1
Name: wexample-wex-addon-ai
Version: 13.0.0
Summary: Adds a Claude-backed AI agent to the wex CLI with persistent sessions, engram memory, and MCP access to kernel commands.
Author-Email: weeger <contact@wexample.com>
License: MIT
Classifier: Programming Language :: Python :: 3
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Requires-Python: >=3.10
Requires-Dist: claude-agent-sdk>=0.1.0
Requires-Dist: syrtis-python-client>=6.0.0
Requires-Dist: wexample-api>=6.8.0
Requires-Dist: wexample-wex-addon-app>=30.0.0
Provides-Extra: dev
Requires-Dist: pytest; extra == "dev"
Requires-Dist: pytest-cov; extra == "dev"
Description-Content-Type: text/markdown

# wex_addon_ai

Version: 13.0.0

wex_addon_ai is a wex add-on that attaches a Claude-backed AI agent to the wex CLI, exposed through the `ai agent talk` command. It gives each agent persistent chat sessions, engram memory (facts stored as JSON files alongside source code and recalled across sessions), and MCP access to the full wex kernel command registry so the agent can read and act on the same workspace. It is aimed at developers who already use wex to manage their projects and want an AI collaborator that shares their project's context without manual copy-pasting.

## Table of Contents

- [Installation](#installation)
- [Quickstart](#quickstart)
- [Tests](#tests)
- [Architecture](#architecture)
- [Integration in the Suite](#integration-in-the-suite)
- [Dependencies](#dependencies)
- [Versioning & Compatibility Policy](#versioning--compatibility-policy)
- [License](#license)
- [About us](#about-us)
- [Known Limitations & Roadmap](#known-limitations--roadmap)
- [Status & Compatibility](#status--compatibility)
- [Useful Links](#useful-links)
- [Migration Notes](#migration-notes)

## Installation

```bash
pip install wexample-wex-addon-ai
```

Requires Python >=3.10.

## Quickstart

Install the package:

```bash
pip install wexample-wex-addon-ai
```

The addon registers its commands with wex automatically once installed. From any directory, open an interactive session with the Claude-backed agent:

```bash
wex ai::agent/talk
```

On the first run inside an app directory with no agent defined yet, `talk` writes `.wex/ai/agents/main/agent.yml` — a generalist agent with no specialisation — and opens a chat prompt. Type a message; the agent has MCP access to the full wex command registry and can read and act on the same workspace.

To run an agent headlessly against a single file:

```bash
wex ai::agent/query --agent addon:ai/spelling --file path/to/file.md
```

The `spelling` agent (src/wexample_wex_addon_ai/resources/ai/agents/spelling/agent.yml) reads the file, corrects misspelled words in prose without touching anything else, then commits the result with itself as git author. If nothing needed correcting it records a skip in the engram and exits.

## Tests

This project uses `pytest` for testing and `pytest-cov` for code coverage analysis.

### Installation

First, install the required testing dependencies:
```bash
.venv/bin/python -m pip install pytest pytest-cov
```

### Basic Usage

Run all tests with coverage:
```bash
.venv/bin/python -m pytest --cov --cov-report=html
```

### Common Commands
```bash
# Run tests with coverage for a specific module
.venv/bin/python -m pytest --cov=your_module

# Show which lines are not covered
.venv/bin/python -m pytest --cov=your_module --cov-report=term-missing

# Generate an HTML coverage report
.venv/bin/python -m pytest --cov=your_module --cov-report=html

# Combine terminal and HTML reports
.venv/bin/python -m pytest --cov=your_module --cov-report=term-missing --cov-report=html

# Run specific test file with coverage
.venv/bin/python -m pytest tests/test_file.py --cov=your_module --cov-report=term-missing
```

### Viewing HTML Reports

After generating an HTML report, open `htmlcov/index.html` in your browser to view detailed line-by-line coverage information.

### Coverage Threshold

To enforce a minimum coverage percentage:
```bash
.venv/bin/python -m pytest --cov=your_module --cov-fail-under=80
```

This will cause the test suite to fail if coverage drops below 80%.

## Architecture

The addon is a wex command plugin that wraps the Claude Agent SDK into a session-aware, engram-tracked, MCP-served agent runtime. It contributes two user-facing commands and a shared agent layer that both commands drive.

### Parts

#### Commands

src/wexample_wex_addon_ai/commands/agent/talk.py — `wex ai::agent/talk`. Discovers agents and existing sessions, presents a combined picker, creates a `ClaudeAgent`, optionally resumes a session, then enters the interactive `chat()` loop.

src/wexample_wex_addon_ai/commands/agent/query.py — `wex ai::agent/query`. Headless one-shot mode. Accepts `--file` (single file) or `--selection` (sweep). For each item: checks the engram for freshness, runs `run_one_shot()`, imprints the result, commits file + engram with the agent as git author.

src/wexample_wex_addon_ai/commands/engrams/clear.py and src/wexample_wex_addon_ai/commands/tracking cover engram invalidation and issue-tracking integration respectively.

#### Agent layer

src/wexample_wex_addon_ai/agent/abstract_agent.py owns all agent state and the interaction contract: session lifecycle (`chat`, `run_one_shot`, `run_value_draft`, `run_contract_draft`), system-prompt assembly, subject-stack management, event draining, and the spinner loop. It does not know about the SDK.

src/wexample_wex_addon_ai/agent/claude_agent.py is the Claude SDK backend. It implements `run()` as `asyncio.run(_run_async(prompt))`. Each turn it builds the MCP server map, calls `ClaudeSDKClient`, streams `AssistantMessage` / `ResultMessage` blocks, links `conversation.jsonl`, and updates token counters.

`SyrtisAgent` (src/wexample_wex_addon_ai/agent/syrtis_agent.py) is the concrete leaf, currently a bare subclass of `AbstractAgent`.

#### Agent config

src/wexample_wex_addon_ai/config/agent_config.py is a `Protocol` — the read-only contract every backing must satisfy (`get_name`, `get_model`, `get_allowed_tool_tags`, `get_modes`, `get_engram_cache_nonce`, …).

src/wexample_wex_addon_ai/file/agent_yaml_file.py is the app-level implementation, backed by `.wex/ai/agents/<name>/agent.yml`. `ServiceAgentConfig` (src/wexample_wex_addon_ai/config/service_agent_config.py) wraps the same YAML structure when the file lives in an addon's service directory; it is read-only, and its `get_name()` returns `service:<svc>/<dir>`.

#### MCP servers

Three in-process MCP servers are wired per turn by `ClaudeAgent._run_async`:

- **`wex`** — built by src/wexample_wex_addon_ai/helper/mcp.py. Exposes every kernel command not carrying `human-only` as a tool, filtered by `tools.tags` from the agent config. Command name `app::release/publish` becomes tool name `app__release__publish`. Included in chat mode only.

- **`agent`** — built by src/wexample_wex_addon_ai/helper/agent_tools.py. Always-on tools whose palette depends on mode:
  - **chat**: `commit`, `ask_user_question`, `subject_open/step/close/leave`, `todo_write`, `todo_list`
  - **one-shot**: `todo_write` (optional), `report_done`, `report_skip`
  - **value mode**: `report_value`
  - **contract mode**: `report_contract_section`

- **`phase`** — built from `_active_phase.build_tools(self)` when a phase is active. Gives the phase a dedicated in-band signalling channel without polluting the agent server.

#### Phases

src/wexample_wex_addon_ai/phase/abstract_phase.py defines the lifecycle: `enter`, `exit`, `before_turn`, `after_turn`, `build_tools`, `get_prompt_section`, `get_slash_commands`. At most one phase is active on an agent at a time (`agent._active_phase`). A phase adds its prompt section to the assembled system prompt and its tools to the `phase` MCP server.

#### Slash commands

src/wexample_wex_addon_ai/slash_command/abstract_slash_command.py defines the interface and the global registry. Each `/cmd` class declares `NAME`, `DESCRIPTION`, and `execute(agent, args)`. The registry is a lazy singleton populated on first call. Active phases may extend it via `get_slash_commands()`.

#### Workdirs

Five workdir types declare the expected on-disk layout under `.wex/ai/` (created and verified via `filestate`):

| Workdir | Path (runtime) | Tracked in git |
|---|---|---|
| `AgentsSuiteWorkdir` | `.wex/ai/agents/` | yes |
| `AgentWorkdir` | `.wex/ai/agents/<name>/` | yes, contains `agent.yml` |
| `SessionsRootWorkdir` | `.wex/ai/sessions/` | no — `.gitignore: */` |
| `SessionsSuiteWorkdir` | `.wex/ai/sessions/<slug>/` | no |
| `SessionWorkdir` | `.wex/ai/sessions/<slug>/<id>/` | no, holds `meta.json` + `conversation.jsonl` symlink |
| `EngramsRootWorkdir` | `.wex/ai/engrams/` | yes — `.gitignore: *.lock` only |
| `EngramsSuiteWorkdir` | `.wex/ai/engrams/<slug>/` | yes |

The slug is derived from the qualified agent name: `app:main` → `app-main`, `service:vite/main` → `service-vite-main`.

#### An open subject is stored twice, under two different git regimes

`open_subject` writes the frame into `.wex/ai/sessions/<slug>/<id>/meta.json`, which is gitignored, and the entry into the app's `.wex/journal/subject/<id>.md`, which is committed. Nothing reconciles the two afterwards.

Reverting the journal while the session is still running therefore desynchronises them, and only the versioned half moves: the live stack keeps ids whose files no longer exist, and every subject written afterwards records a `Parent` that resolves to nothing. Observed 2026-08-25 — a `git checkout` over `.wex/journal/` to undo an extraction dropped 18 hand-opened subjects along with it, and the 51 entries deposited by the next run all pointed at an absent parent.

`meta.json` holds titles and ids but no dates, so it cannot rebuild the entries. Restoring the files from git is the only repair.

#### Engram store

src/wexample_wex_addon_ai/engram/engram_store.py is the per-agent persistent memory. Each entry is an `EngramJsonFile` at `<root>/<key>.engram.json`. Public API: `recall(key)` (unlocked, returns `Engram | None`), `imprint(key, ...)` (locked read-modify-write, stamps `last_rev`, `last_decision`, `notes`, `cache_nonce`), `mark_in_progress` / `clear_in_progress` (soft reservation for sweep parallelism).

Freshness is checked by `Engram.is_fresh(current_rev, current_source_revs, current_cache_nonce)`. Setting a new `engram_cache_nonce:` in `agent.yml` invalidates every existing engram for that agent.

#### Resources

src/wexample_wex_addon_ai/resources/ai/shared/context.j2 is the global-scope context template — the outermost layer of every agent's system prompt. It describes the wex workspace model and the operator communication style.

src/wexample_wex_addon_ai/resources/ai/agents ships seven agents usable in any project: `spelling`, `english`, `maintainer`, `author`, `contractor`, `documenter`, `documenter-one-shot`. Each is a directory with `agent.yml` and `context.j2`.

#### Middleware

src/wexample_wex_addon_ai/middleware/optional_app_middleware.py silently injects `app_workdir=None` when the working directory is not a recognized app, instead of emitting the warning the base `AppMiddleware` would. Both `talk` and `query` use it so they work equally inside and outside an app.

### Call path: `wex talk`

1. `ai__agent__talk` calls `discover_agents(app_workdir, kernel)` and `discover_sessions(app_workdir, kernel)`.
2. `AgentsSuiteWorkdir.apply()` and `SessionsRootWorkdir.apply()` verify (and create) the expected filesystem layout.
3. A picker shows existing sessions + "start new"; the user picks an agent config and an optional session to resume.
4. `ClaudeAgent(io, app_workdir, kernel, config_file, debug)` is constructed.
5. If resuming: `agent.resume_session(session_dir)` reads the `conversation.jsonl` symlink to recover `_session_id`, replays the last five messages.
6. `agent.chat()` loops:
   a. `_ensure_session_dir()` creates `.wex/ai/sessions/<slug>/<timestamp>/` on the first prompt.
   b. `_ensure_session_named()` prompts the user if the session has no name yet.
   c. `io.multiline_input()` reads the turn; slash commands are handled by `_handle_slash_command()`.
   d. `_run_user_turn(prompt)` drains pending system messages, fires `phase.before_turn`, calls `_run_with_spinner(prompt)`.
   e. `_run_with_spinner` spawns a worker thread running `self.run(prompt)` → `asyncio.run(_run_async(prompt))`.
   f. `_run_async` assembles `get_system_prompt()`, builds the three MCP servers, calls `ClaudeSDKClient`, streams blocks, links `conversation.jsonl` from `ResultMessage.session_id`, updates token counters and context-window usage.
   g. Back on the main thread: `_bump_session_turn()`, `phase.after_turn()`, token footer rendered.

### Call path: `wex query --agent <name> --file <path>`

1. `ai__agent__query` resolves the agent config by display or qualified name.
2. Creates `ClaudeAgent` (no `chat()` call).
3. Calls `process_file_item(claude_agent, engrams, file_path, key, ...)`:
   a. `git_blob_hash(file_path)` → `pre_rev`.
   b. `engrams.recall(key)` + `is_fresh(pre_rev, source_revs, cache_nonce)` → skip when fresh.
   c. Reads contract section for the file (scope, source list, formatter writing rules) from `.wex/ai/contracts/<key>.contract.yml`.
   d. Calls `claude_agent.run_one_shot(prompt)` — same `_run_user_turn` path as chat, but `_session_dir` is not initialized (sweep sessions don't appear in the `talk` picker), and the system prompt ends with the autonomous-finish instruction.
   e. The `agent` MCP server exposes only `report_done`, `report_skip`, `todo_write` (no commit, no interactive tools).
   f. After the turn: `post_rev = git_blob_hash(file_path)`, `engrams.imprint(key, last_rev=post_rev, last_decision=..., notes=...)`.
   g. Unless `--skip-commit`: `git add` + `git commit --author <agent>` for file + engram.

### System-prompt cascade

`AbstractAgent.get_system_prompt()` assembles parts in this order, each wrapped in a named XML section:

1. **Global** — `resources/ai/shared/context.j2` (shipped, applies to every agent).
2. **User** — `~/.wex/ai/shared/context.j2` (machine-local, unversioned).
3. **Agent** — identity from `agent.yml` + `agents/<name>/context.j2` when present.
4. **App** — name, type, path, env, service list from the app config + `.wex/ai/shared/context.j2`.
5. **Service** — each active service's `<service_dir>/ai/shared/context.j2`; services extended by another declared service are skipped to avoid duplicate text.
6. **Phase** — `_active_phase.get_prompt_section(self)`.
7. **Mode instruction** — one-shot / value / contract closing paragraph appended last.

Templates receive `app`, `agent`, `services`, and `core` variables and may `{% include %}` partials from the app's `.wex/ai/shared/` directory.

## Integration in the Suite

This package is part of the Wexample Suite — a collection of high-quality, modular tools designed to work seamlessly together across multiple languages and environments.

### Related Packages

The suite includes packages for configuration management, file handling, prompts, and more. Each package can be used independently or as part of the integrated suite.

Visit the [Wexample Suite documentation](https://docs.wexample.com) for the complete package ecosystem.

## Dependencies

- claude-agent-sdk: >=0.1.0
- syrtis-python-client: >=6.0.0
- wexample-api: >=6.8.0
- wexample-wex-addon-app: >=30.0.0

## Versioning & Compatibility Policy

Wexample packages follow **Semantic Versioning** (SemVer):

- **MAJOR**: Breaking changes
- **MINOR**: New features, backward compatible
- **PATCH**: Bug fixes, backward compatible

We maintain backward compatibility within major versions and provide clear migration guides for breaking changes.

## License

This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.

Free to use in both personal and commercial projects.

## About us

[Wexample](https://wexample.com) stands as a cornerstone of the digital ecosystem — a collective of seasoned engineers, researchers, and creators driven by a relentless pursuit of technological excellence. More than a media platform, it has grown into a vibrant community where innovation meets craftsmanship, and where every line of code reflects a commitment to clarity, durability, and shared intelligence.

This packages suite embodies this spirit. Trusted by professionals and enthusiasts alike, it delivers a consistent, high-quality foundation for modern development — open, elegant, and battle-tested. Its reputation is built on years of collaboration, refinement, and rigorous attention to detail, making it a natural choice for those who demand both robustness and beauty in their tools.

Wexample cultivates a culture of mastery. Each package, each contribution carries the mark of a community that values precision, ethics, and innovation — a community proud to shape the future of digital craftsmanship.

## Known Limitations & Roadmap

Current limitations and planned features are tracked in the GitHub issues.

See the [project roadmap](https://github.com/wexample/python-wex_addon_ai/issues) for upcoming features and improvements.

## Status & Compatibility

**Maturity**: Production-ready

**Python Support**: >=3.10

**OS Support**: Linux, macOS, Windows

**Status**: Actively maintained

## Useful Links

- **Homepage**: https://github.com/wexample/python-wex-addon-ai
- **Documentation**: [docs.wexample.com](https://docs.wexample.com)
- **Issue Tracker**: https://github.com/wexample/python-wex-addon-ai/issues
- **Discussions**: https://github.com/wexample/python-wex-addon-ai/discussions
- **PyPI**: [pypi.org/project/wexample-wex-addon-ai](https://pypi.org/project/wexample-wex-addon-ai/)

## Migration Notes

When upgrading between major versions, refer to the migration guides in the documentation.

Breaking changes are clearly documented with upgrade paths and examples.
