# Alfred

An orchestrator for my own AI coding-agent sessions. It starts, tracks and coordinates Claude Code, OpenCode and Codex, and keeps a record of everything they did.

- **sheet**: 3.1
- **status**: complete
- **role**: solo project
- **stack**: Python, MCP, Git, tmux
- **source**: being prepared for release

## Why

I use several coding agents every day. Running more than one at a time by hand means losing track of which session is doing what, which branch it is on, and whether its work has been reviewed. Alfred makes that state explicit.

## Shape

Alfred is a Python CLI. Domain logic sits in the middle and knows nothing about the outside world. Everything it touches, the agent tools, Git, tmux and the filesystem, is an adapter behind an interface, and all of them are wired together in one place at startup.

> why one composition root Tests can build the same core with fake adapters, and swapping one agent tool for another is a one-line change.

The agents are driven through custom MCP servers and tool adapters, so Alfred talks to each of them through the same small set of operations.

## State machines

Tasks and agent sessions each have their own finite state machine. Transitions are written as an explicit table instead of being scattered through `if` statements, so an illegal move is an error at the point it is attempted, not a strange state discovered later.

```
# Simplified. The real tables have more states.
TASK_TRANSITIONS = {
    ("queued",    "start"):   "running",
    ("running",   "submit"):  "in_review",
    ("running",   "fail"):    "failed",
    ("in_review", "approve"): "done",
    ("in_review", "reject"):  "running",
}

def transition(state: str, event: str) -> str:
    try:
        return TASK_TRANSITIONS[(state, event)]
    except KeyError:
        raise IllegalTransition(state, event) from None
```

## Decisions

### D1 An append-only event log

Every transition is recorded as an event and never edited afterwards. When a session goes wrong, the history of what happened is still there to read.

### D2 Roll back when the tracker sync fails

Alfred mirrors task state to an external tracker. If that sync fails, the local transition is rolled back, so the two never disagree about where a task is.

### D3 Plain JSON for state

State lives in JSON files. It is easy to inspect, easy to diff, and enough for one person's workload. A database would be the first change if this ever ran for a team.

[← All projects](https://0xshriram.dev/projects.md)

---
Canonical: https://0xshriram.dev/projects/alfred.html
