Metadata-Version: 2.4
Name: codex-agent-framework
Version: 0.1.29
Summary: A lightweight event-driven Codex agent runtime.
Author: Baptiste
License-Expression: MIT
Keywords: agent,ai,codex,openai,tools
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: audioop-lts; python_version >= "3.13"
Requires-Dist: beautifulsoup4
Requires-Dist: codex-backend-sdk>=0.3.1
Requires-Dist: fastapi
Requires-Dist: filetype
Requires-Dist: modict
Requires-Dist: mss
Requires-Dist: numpy
Requires-Dist: odfpy
Requires-Dist: openai
Requires-Dist: openpyxl
Requires-Dist: pathspec
Requires-Dist: pillow
Requires-Dist: playwright
Requires-Dist: pydub
Requires-Dist: pypdf
Requires-Dist: pynteract
Requires-Dist: pywinctl
Requires-Dist: python-docx
Requires-Dist: PyYAML
Requires-Dist: regex
Requires-Dist: requests
Requires-Dist: rich
Requires-Dist: textual
Requires-Dist: tiktoken
Requires-Dist: trafilatura
Requires-Dist: uvicorn
Provides-Extra: dev
Requires-Dist: build; extra == "dev"
Requires-Dist: pytest; extra == "dev"
Dynamic: license-file

# codex-agent

`codex-agent-framework` is a local-first Python framework for building and running tool-using AI agents.

Use it as a ready-to-run terminal assistant, or import `Agent` when you want to build your own workflow in Python.

> Early alpha: APIs are still evolving and breaking changes are expected while the architecture settles.

## Quick start

Install the package:

```bash
python -m pip install codex-agent-framework
```

For the recommended local setup, run:

```bash
codex-agent bootstrap -- -y
```

`bootstrap` installs the desktop/browser/tray dependencies, installs the user services, and starts them. It is currently aimed mostly at Debian-like Linux systems. Use an X11 session for the best desktop automation support; Wayland is fine if you do not use the desktop plugin.

Open the assistant:

```bash
codex-agent
```

If a local agent server is already running, the TUI connects to it. Otherwise `codex-agent` starts a temporary server for that TUI session and shuts it down when the TUI exits.

Inside the TUI, start with:

```text
/help
/sessions
/new_session
/load_session latest
/compact
/config
```

Use `/compact` when a long session needs to reduce its active context. It keeps a backend summary and prunes older raw history from the active session file.

Prefer the SDK? Start with this:

```python
from codex_agent import Agent

agent = Agent(session="new")
turn = agent("Summarize this repository in three practical bullet points.")

print(turn.result)
```

## Contents

- [Python API](#python-api)
- [Core concepts](#core-concepts)
- [Extending the agent](#extending-the-agent)
- [Built-in capabilities](#built-in-capabilities)
- [Running modes](#running-modes)
- [Configuration and runtime files](#configuration-and-runtime-files)
- [Development](#development)
- [Recent releases](#recent-releases)
- [Safety notes](#safety-notes)

## Python API

### Minimal agent

```python
from codex_agent import Agent

agent = Agent(session="new", username="Baptiste")
turn = agent("Summarize this repository in three practical bullet points.")

print("completed:", turn.completed)
print("reason:", turn.reason)
print("result:", turn.result)
```

`agent(...)` blocks until the turn completes and returns an `AgentTurn`. Use `turn.result` for the final result; `turn.messages` and `turn.events` contain what happened during the turn.

### Stream events from a turn

Use `agent.stream(...)` when you want live events and the final turn:

```python
from codex_agent import Agent, ResponseContentDeltaEvent

agent = Agent(session="new")
stream = agent.stream("Explain what this project does.")

for event in stream:
    if isinstance(event, ResponseContentDeltaEvent):
        print(event.delta, end="", flush=True)

turn = stream.turn
print("\ncompleted:", turn.completed)
```

`stream.turn` waits for the final `AgentTurn`; it does not expose a partial turn object.

You can get a similar effect with the event bus and a normal blocking call:

```python
from codex_agent import Agent, ResponseContentDeltaEvent

agent = Agent(session="new")

@agent.on(ResponseContentDeltaEvent)
def print_delta(event):
    print(event.delta, end="", flush=True)

turn = agent("Explain what this project does.")
print("\ncompleted:", turn.completed)
```

### Open an interactive session from Python

```python
from codex_agent import Agent

agent = Agent(session="latest")
agent.interact()
```

Voice features are disabled by default. The realtime voice plugin uses Codex/ChatGPT auth through `codex-backend-sdk`; the legacy synchronized TTS stream still uses the OpenAI SDK speech endpoint when explicitly enabled.

```python
from codex_agent import Agent

agent = Agent(
    session="latest",
    builtin_plugins=["realtime", "memory", "planner", "scheduler"],
)
agent.load_builtin_plugin("realtime")
agent.interact()
```

### Add a tool

Tools are actions the model may choose to call.

For a tool without parameters, a plain docstring becomes the tool description:

```python
import subprocess
from codex_agent import Agent, tool

@tool
def list_changed_files() -> list[str]:
    """Return modified or untracked files in the current git repository."""
    output = subprocess.check_output(["git", "status", "--short"], text=True)
    return [line[3:] for line in output.splitlines() if line.strip()]

agent = Agent(session="latest")
agent.add_tool(list_changed_files)
```

For a parameterized tool, use a YAML docstring to describe the schema exposed to the model:

```python
from pathlib import Path
from codex_agent import Agent, tool

@tool
def read_project_note(path: str):
    """
    description: Read a UTF-8 project note file.
    parameters:
      path:
        type: string
        description: Path to the note file, relative to the current project.
    required:
      - path
    """
    return Path(path).read_text(encoding="utf-8")

agent = Agent(session="latest")
agent.add_tool(read_project_note)
agent("Look at my project notes and tell me what changed recently.")
```

### Add live context

Providers inject fresh context before a model call without saving it as permanent conversation history.

```python
from datetime import datetime
from zoneinfo import ZoneInfo
from codex_agent import Agent, provider

@provider
def current_time():
    now = datetime.now(ZoneInfo("Europe/Paris"))
    return f"Current local time: {now:%Y-%m-%d %H:%M:%S %Z}"

agent = Agent(session="latest")
agent.add_provider(current_time)
agent("Given the current time, should I start a long-running task now?")
```

### Add a slash command

Commands are explicit user actions triggered with `/name`.

```python
from codex_agent import Agent, command, get_agent

@command
def repo():
    """Show the active session and current repository hint."""
    agent = get_agent()
    return f"session={agent.current_session_id}; repo=codex-agent"

agent = Agent(session="latest")
agent.add_command(repo)
print(agent("/repo").result)
```

## Core concepts

The framework is built around a few extension points:

| Concept | What it is for |
| --- | --- |
| Session | Saved conversation and agent state. |
| Turn | One user request plus the model/tool loop needed to answer it. |
| Tool | An action the model may choose to call. |
| Provider | Fresh context injected before a response, not saved as chat history. |
| Command | A user-triggered operation such as `/compact` or `/config`. |
| Plugin | A bundle of state, tools, providers, commands, events, hooks, and prompt text. |

### Sessions and turns

```python
Agent(session="new")                  # fresh session
Agent(session="latest")               # newest saved session
Agent(session="<session_id>")          # specific saved session
Agent(session="/path/to/session.json") # session file
```

By default, a turn runs until the agent has completed its tool/model loop. `auto_proceed=False` is an advanced mode for external orchestration: your application can pause after each model step, inspect state, approve or cancel work, and resume explicitly.

```python
agent = Agent(session="new", auto_proceed=False)
turn = agent("Please explore the current repo. Use tools if needed.")

while not turn.completed and turn.reason == "requires_next_step":
    # External monitoring, approval, logging, cancellation, etc. can happen here.
    turn = agent.resume_turn(turn)
```

## Extending the agent

Use runtime extensions when you want to customize an installed agent without forking the repository.

At startup, the agent scans:

```text
~/.codex-agent/plugins/*.py
~/.codex-agent/commands/*.py
```

Files starting with `_` are ignored.

A plugin module may expose:

- a `Plugin` subclass;
- a `register(agent)` function;
- decorated `@tool`, `@provider`, or `@command` callables for small extensions.

Direct runtime `tools/` and `providers/` folders are intentionally not loaded. Put simple runtime tools/providers in a plugin module, or use `agent.add_tool()` and `agent.add_provider()` in SDK code.

### Plugin example

```python
from codex_agent import Agent, Plugin, provider, tool

class ProjectNotesPlugin(Plugin):
    name = "project_notes"
    system_prompt = "# Project Notes\nUse project notes when relevant."

    def __init__(self, agent):
        super().__init__(agent)
        self.notes = []

    @tool
    def remember_note(self, note: str):
        """
        description: Store a project note that should be available in later turns.
        parameters:
          note:
            type: string
            description: The note to remember. Keep it concise and project-specific.
        required:
          - note
        """
        self.notes.append(note)
        return f"Stored {len(self.notes)} note(s)."

    @provider
    def project_notes(self):
        if not self.notes:
            return None
        return "Project notes:\n" + "\n".join(f"- {note}" for note in self.notes)

agent = Agent(session="latest")
agent.add_plugin(ProjectNotesPlugin(agent))
```

### Runtime plugin example

`~/.codex-agent/plugins/git_helpers.py`:

```python
from datetime import datetime
from zoneinfo import ZoneInfo
import subprocess
from codex_agent import provider, tool

@tool
def current_branch() -> str:
    """Return the current git branch."""
    return subprocess.check_output(
        ["git", "branch", "--show-current"],
        text=True,
    ).strip()

@provider
def current_time():
    now = datetime.now(ZoneInfo("Europe/Paris"))
    return f"Current local time: {now:%Y-%m-%d %H:%M:%S %Z}"
```

`~/.codex-agent/commands/repo.py`:

```python
from codex_agent import command, get_agent

@command(name="repo")
def repo(args=""):
    agent = get_agent()
    return f"session={agent.current_session_id}; args={args}"
```

### Events and hooks

`Agent` exposes an event bus for UIs, automation, and plugins:

```python
from codex_agent import Agent, MessageAddedEvent, ToolCallStartEvent

agent = Agent(session="new")

@agent.on(MessageAddedEvent)
def log_message(event):
    print("message", event.message.type)

@agent.on(ToolCallStartEvent)
def log_tool(event):
    print("tool", event.name)
```

Plugins and tools may also emit custom events and expose custom hook points:

```python
from codex_agent import Agent, Event, Plugin, tool

class NoteStoredEvent(Event):
    note = ""

class NotesPlugin(Plugin):
    name = "notes"

    @tool
    def remember_note(self, note: str):
        """
        description: Store a note and notify listeners.
        parameters:
          note:
            type: string
            description: Note to store.
        required:
          - note
        """
        context = self.agent.run_hook("notes.before_store", note=note)
        if context.stopped:
            return "Note rejected."
        note = context.payload.note
        self.agent.emit(NoteStoredEvent, note=note)
        return "Note stored."

agent = Agent(session="new")
agent.add_plugin(NotesPlugin(agent))

@agent.on(NoteStoredEvent)
def log_note(event):
    print("stored note:", event.note)

@agent.add_hook("notes.before_store")
def trim_note(context):
    context.payload.note = context.payload.note.strip()
```

Plugin methods can also subscribe declaratively with `@event_handler(...)` and `@hook_handler(...)`.

## Built-in capabilities

The default agent includes practical local tools grouped as plugins:

| Area | Examples | Purpose |
| --- | --- | --- |
| Files | `read`, `view`, `write`, `edit` | Strict text reads, broad extraction, exact-string edits. |
| Shell | `bash`, `python` | Shell commands and persistent Python execution. |
| Memory | `memory_add`, `memory_search`, `/memory_config` | Durable semantic memory and retrieval context. |
| Planner | `planner_create`, `planner_check` | Persistent named todos surfaced as context. |
| Scheduler | `scheduler_schedule`, `scheduler_restart_and_wakeup` | Future turns and post-restart continuation. |
| Browser | `browser_open`, `browser_click`, `browser_fill` | Persistent Playwright/Chromium automation. |
| Desktop | `desktop_start_session`, `desktop_run_commands` | Screenshot-backed Linux desktop automation. |
| Subagents | `subagents_run` | Ephemeral child agents from predefined profiles. |

Current built-in plugin modules include `files`, `content`, `bash`, `python`, `interface`, `environment`, `context`, `memory`, `planner`, `scheduler`, `browser`, `desktop`, and `subagents`.

The `subagents` plugin ships an `explorer` profile: a read-only codebase inspection agent.

Select built-ins explicitly when you want a smaller agent:

```python
agent = Agent(
    session="latest",
    builtin_plugins=["files", "content", "bash", "python", "environment", "context", "memory", "planner"],
)
```

`None` loads all built-ins, `[]` loads none, and an explicit list selects plugin module names.

## Running modes

You can ignore this section for normal TUI usage. It is for services, automation, and integrations.

| Mode | Entry point | Best for |
| --- | --- | --- |
| Embedded Python | `Agent(...)` | Scripts, notebooks, tests, custom applications. |
| Interactive TUI | `codex-agent` | Daily local assistant usage. |
| Persistent server | `codex-agent start server` | Long-lived local service used by UIs or scripts. |
| Headless CLI | `codex-agent run ...` | Automation and shell pipelines. |
| Process runtime | `codex-agent run --runtime process ...` | One-shot isolated runs without an existing server. |
| Tray/service setup | `codex-agent bootstrap` | Desktop availability independent of a terminal. |

Run a persistent background server:

```bash
codex-agent start server
codex-agent open tui
```

Detached logs are written under `~/.codex-agent/logs/server.log`.

Useful headless commands:

```bash
codex-agent status --json
codex-agent tools
codex-agent config get model
codex-agent run "Run a quick repository health check."
git diff -- README.md | codex-agent run --stdin "Review this documentation diff."
codex-agent run --runtime process "Summarize the current project layout."
codex-agent interrupt "user changed priority"
```

The local server exposes FastAPI REST/SSE endpoints for health, status, sessions, config, messages, tools, wakeups, live events, replay, turns, interrupts, and restart. The TUI uses SSE, tracks event cursors, replays the latest turn when opened mid-session, and reconnects after server restarts or transient stream loss.

## Configuration and runtime files

Configure `Agent` directly:

```python
from codex_agent import Agent

agent = Agent(
    session="latest",
    model="gpt-5.5",
    reasoning={"effort": "medium", "summary": "auto"},
    text={"verbosity": "medium"},
    input_token_limit=128000,
    auto_compact=True,
    web_search_enabled=False,
    image_generation_enabled=False,
    builtin_plugins=None,    # None = all, [] = none, or explicit plugin names
    custom_plugins=None,     # None = all runtime plugins, [] = none
)
```

Or update saved config from the CLI:

```bash
codex-agent config get
codex-agent config set input_token_limit 128000
codex-agent config set builtin_plugins='["environment", "context", "memory", "planner", "scheduler", "realtime"]'
codex-agent config set --no-save text='{"verbosity":"low"}'
```

Saved local state lives in `~/.codex-agent` by default:

```text
sessions/          persisted conversation histories as JSON
workfolder/        generated or uploaded working files
plugins/           user runtime plugins
commands/          user runtime slash commands
browser/           persistent browser profiles and screenshots
logs/              detached server/tray logs
memory.json        durable semantic memory entries
planner.json       persistent named todos
wakeups.json       scheduled autonomous wakeups
agent.config.json  persisted agent configuration
```

Override it with:

```bash
AGENT_RUNTIME_DIR=/tmp/my-codex-agent-runtime codex-agent
```

## Development

Project layout:

```text
codex_agent/                      Python package
codex_agent/agent.py              Central Agent object and high-level public methods
codex_agent/mainloop.py           Turn loop, assistant calls, tool execution, pending results
codex_agent/stream.py             SDK event stream wrapper around an AgentFuture
codex_agent/context.py            Context assembly and token budgeting
codex_agent/sessions.py           Persistent sessions and compaction
codex_agent/tool.py               Tool model, decorators, and manager
codex_agent/provider.py           Provider decorator and manager
codex_agent/plugin.py             Stateful plugin base class and manager
codex_agent/command.py            Slash command registry and dispatch
codex_agent/hooks.py              Generic runtime hook manager
codex_agent/event.py              Event classes and event bus
codex_agent/server/               FastAPI REST/SSE bridge package
codex_agent/tui/                  Textual TUI client
codex_agent/cli/                  Root CLI and headless runners
codex_agent/builtin_plugins/      Built-in capabilities grouped as plugins
tests/                            Test suite
```

Run checks:

```bash
python -m pyflakes codex_agent tests
python -m pytest
```

The current tree validates at:

```text
546 passed
```

Build distributions:

```bash
python -m pip install build twine
rm -rf build dist *.egg-info
python -m build
python -m twine check dist/*
```

### Auth model for backend-backed features

Core LLM calls, memory embeddings, non-streaming STT transcription, and realtime voice use the Codex backend SDK with Codex/ChatGPT auth. Legacy synchronized TTS playback and OpenAI input-token counting still use the OpenAI SDK and may require `openai_api_key` or `OPENAI_API_KEY` when those paths are enabled.

## Recent releases

- `0.1.27`: upgrade the agentic `read` tool with folder trees, recursive folder pattern search, and precise `start_at_line`/`end_at_line` ranges; add visible wrapper/token split stats, compactable turn counts, and refreshed TUI status-bar context telemetry.
- `0.1.26`: align SDK-facing config with Responses API shapes, add the `context` management plugin with selective non-destructive wrapper pruning, keep wrappers out of memory retrieval and compaction payloads, move embeddings/STT/realtime onto Codex backend auth, streamline voice/TTS plugin config, and improve TUI streaming/windowing.
- `0.1.25`: move built-in capabilities further into plugin-scoped modules, add bundled plugin requirement metadata, and replace the TUI conversation pane with a selectable rich chat area that preserves mouse scrolling, copy selection, lazy transcript loading, and bottom-aware auto-scroll.
- `0.1.24`: add slash-command lifecycle events and TUI progress/result rendering so commands such as `/compact` and `/help` show running, done, failed, result, and error feedback instead of going silent.
- `0.1.23`: add SDK event streaming with `Agent.stream()`, root `Agent.add_plugin()`, a faster README flow, the `~/.codex-agent` runtime default, text-first voice defaults, safer prompt templating limited to system messages, smarter `codex-agent` local-server attachment, and clearer TUI tool-call progress lines.
- `0.1.22`: harden FastAPI/SSE and TUI streaming after a stabilization audit; add finite SSE read timeouts, stale-subscriber shutdown, persisted turn-event locking, synchronous replay delta flushes, process-runtime startup guards, isolated event handlers, and shared version metadata.
- `0.1.21`: add structured `AgentTurn` returns with messages/events, resumable non-terminal turns for `auto_proceed=False`, profile-based subagents, hierarchical message/event types, and internal wakeups as developer messages.
- `0.1.20`: make runtime plugins the user-facing extension unit for tools/providers/commands, remove legacy direct runtime tool/provider loading, split tool/provider managers, and keep system-prompt templating on an explicit minimal namespace.

See [`CHANGELOG.md`](CHANGELOG.md) for the full history.

## Safety notes

This project lets an AI assistant act on the local machine. That is useful, but risky.

Recommended practices:

- Use a dedicated runtime directory for experiments.
- Review tool calls before enabling autonomous workflows.
- Avoid running the agent with elevated privileges.
- Keep secrets out of prompts, logs, committed runtime files, and shared session exports.
- Treat browser and desktop automation as real user actions.
- Keep important runtime plugins under version control.

## License

MIT. See [LICENSE](LICENSE).
