# phren agent (experimental)

> **Status: experimental and unpublished.** `@phren/agent` is **not on npm**
> and is **not** wired into the `phren` CLI — it lives in `experimental/agent/`
> in the monorepo. It is built by the root `pnpm build` and tested by the CI
> `agent-test` job, but maintained at a lower bar than `packages/cli`.
> Everything below assumes a repo checkout.

A coding agent with persistent memory. Its entrypoint is the standalone
`phren-agent` binary built from the workspace — **not** the `phren` CLI. It
reads, writes, and edits your code with tool calling, and starts each session
knowing your project's gotchas, active tasks, and past decisions.

---

## Quickstart

### Install

```bash
git clone https://github.com/alaarab/phren && cd phren
pnpm install
pnpm --filter @phren/agent build     # builds experimental/agent/dist
npm i -g @phren/cli && phren init    # memory store + MCP config (this part IS published)
alias phren-agent="node $(pwd)/experimental/agent/dist/bin.js"
```

### Authentication

Providers are auto-detected from environment variables:

```bash
export OPENROUTER_API_KEY=sk-or-...     # OpenRouter (default)
export ANTHROPIC_API_KEY=sk-ant-...     # Anthropic direct
export OPENAI_API_KEY=sk-...            # OpenAI
```

For Codex (ChatGPT subscription), authenticate via browser:

```bash
phren-agent auth login
```

Ollama requires no key — just a running local server.

### First task

```bash
phren-agent "fix the login bug"                              # one-shot
phren-agent -i                                               # interactive TUI
phren-agent --plan "refactor the database layer"             # review plan first
phren-agent --provider openai-codex --budget 2.00 "add tests" # pick provider, set cost cap
phren-agent --reasoning high "trace the auth race"            # override default medium reasoning
phren-agent --yolo "add input validation"                    # full-auto, no confirmations
```

---

## Providers

Auto-detected from env vars, or forced with `--provider <name>`.

| Provider | Default Model | Env Var / Auth |
|----------|---------------|----------------|
| OpenRouter | claude-sonnet-4-20250514 | `OPENROUTER_API_KEY` |
| Anthropic | claude-sonnet-4-20250514 | `ANTHROPIC_API_KEY` |
| OpenAI | gpt-5.4 | `OPENAI_API_KEY` |
| Codex | gpt-5.4 | `phren-agent auth login` (ChatGPT subscription) |
| Ollama | llama3.3 | Local, no API key needed |

Switch models mid-session with the `/model` command (interactive reasoning level slider).

---

## CLI flags

| Flag | Description |
|------|-------------|
| `<task>` | Task description (one-shot mode) |
| `-i`, `--interactive` | Interactive TUI with streaming, history, tab completion |
| `--provider <name>` | Force provider: `openrouter`, `anthropic`, `openai`, `openai-codex`, `ollama` |
| `--model <id>` | Override the default model for the chosen provider |
| `--reasoning <level>` | Reasoning effort for GPT-5.4/Codex: `low`, `medium`, `high`, `xhigh` |
| `--budget <dollars>` | Max spend in USD (aborts when exceeded) |
| `--plan` | Plan mode: show plan before executing tools |
| `--yolo` | Full-auto permissions — no confirmations |
| `--resume` | Resume last session's conversation (task optional — continues where it left off) |
| `--sandbox <mode>` | Kernel write-fence for shell (bwrap): `off`, `auto` (default), `require` |
| `--no-llm-compact` | Use regex prune summaries instead of LLM compaction |
| `--multi` | Multi-agent TUI mode |
| `--team <name>` | Team mode with shared task coordination |
| `--verbose` | Debug-level logging |
| `--help` | Show help |
| `--version` | Show version |

### Permission modes

| Mode | Behavior | How to set |
|------|----------|------------|
| **suggest** (default) | Agent proposes tool calls, you approve each one | Default |
| **auto-confirm** | Auto-approve safe tools (read, glob, grep), confirm destructive ones | Shift+Tab in TUI |
| **full-auto** | All tools run without confirmation | `--yolo` flag |

Cycle modes during a session with Shift+Tab.

---

## Slash commands

All 23 commands available in the interactive TUI:

| Command | Description |
|---------|-------------|
| `/help` | Show available commands |
| `/model` | Interactive model picker with reasoning slider |
| `/provider` | Show current provider info |
| `/cost` | Show session cost breakdown |
| `/plan` | Show/toggle plan mode |
| `/undo` | Undo last file change |
| `/compact` | Compact context: LLM checkpoint + knowledge promotion (regex fallback) |
| `/review` | Triage the phren review queue (`go` = manual, `auto` = model-assisted) |
| `/context` | Show context window usage |
| `/history` | Show conversation history |
| `/turns` | Show turn count and stats |
| `/clear` | Clear conversation history |
| `/files` | List files touched this session |
| `/cwd` | Show/change working directory |
| `/diff` | Show git diff of session changes |
| `/git` | Run git commands |
| `/spawn` | Spawn a sub-agent (multi-agent mode) |
| `/agents` | List active sub-agents |
| `/preset` | Save/load/list agent presets |
| `/mode` | Toggle input mode (steering vs queue) |
| `/exit` | Exit the agent |

---

## Keyboard shortcuts

Full readline-style editing in the interactive TUI:

| Key | Action |
|-----|--------|
| **Navigation** | |
| Tab | Toggle memory browser / slash command completion |
| Shift+Tab | Cycle permission mode (suggest / auto-confirm / full-auto) |
| Up / Down | Input history |
| Left / Right | Move cursor |
| Alt+Left / Alt+Right | Jump word |
| Ctrl+A | Move to start of line |
| Ctrl+E | Move to end of line |
| **Editing** | |
| Ctrl+U | Kill entire line |
| Ctrl+K | Kill from cursor to end |
| Ctrl+W | Delete word backward |
| Alt+Backspace | Delete word backward |
| Delete | Delete character at cursor |
| **Tab completion** | |
| Tab (with `/` prefix) | Complete slash commands |
| Tab (in bash mode) | Complete file paths |
| **Modes** | |
| `!` | Enter bash mode (run shell commands) |
| Escape | Exit bash mode / clear input |
| Ctrl+C | Progressive: clear input, then warn, then quit |
| Ctrl+D | Clean exit |

---

## Tools

The agent has access to these built-in tools:

### File operations
- **read_file** — Read file contents (with line range support)
- **write_file** — Write or create files
- **edit_file** — Surgical string replacements in files
- **glob** — Find files by pattern
- **grep** — Search file contents with regex

### Shell and git
- **shell** — Run shell commands (with timeout and safety checks)
- **git_status** — Show working tree status
- **git_diff** — Show staged/unstaged changes
- **git_commit** — Create commits

### Web
- **web_fetch** — Fetch URL contents
- **web_search** — Search the web

### Phren memory
- **phren_search** — Search findings across all projects
- **phren_add_finding** — Capture a finding
- **phren_add_task** — Create a task
- **phren_get_tasks** — List tasks for a project
- **phren_complete_task** — Mark a task done

---

## Multi-agent mode

Spawn and coordinate multiple agents from a single TUI.

```bash
phren-agent --multi                             # start multi-agent TUI
phren-agent --team myproject "build X"          # team mode with shared tasks
```

In the multi-agent TUI:

| Command | Description |
|---------|-------------|
| `/spawn <name> <task>` | Create a new sub-agent |
| `/agents` | List active agents with status |
| `/kill <name>` | Terminate an agent |
| `/broadcast <msg>` | Message all agents |
| `1-9` | Switch between agent panes |

Agents run as child processes with IPC messaging and shared task coordination.

---

## Memory integration

The agent is deeply integrated with phren's memory layer:

**On startup:**
- Loads project truths (always-injected facts)
- Loads active tasks and recent findings
- Reads CLAUDE.md for project conventions
- Restores prior session summary (with `--resume`)

**During a session:**
- Searches phren for relevant context when approaching new problems
- Captures findings as it discovers patterns, pitfalls, and decisions
- Creates and completes tasks

**On session end:**
- Saves session summary and checkpoint
- Records edited files and test state for exact resume
- Mines the transcript for durable knowledge (same graduated pipeline as
  compaction, below) and writes a searchable session note

---

## Compaction with knowledge promotion

When the conversation approaches 75% of the context window (or on `/compact`),
the agent asks the *same provider* for a structured checkpoint via prefix
replay: the summarization request reuses the conversation's own system prompt
and message prefix byte-identical, so the provider's KV cache covers
everything except the final instruction. The response carries the summary plus
candidate knowledge items, routed by the model's own confidence:

| Confidence | Destination |
|---|---|
| ≥ 0.8 | `FINDINGS.md` immediately, with agent/session provenance + citation |
| 0.5 – 0.8 | Review queue (`review.md`), with provenance metadata |
| < 0.5 | Dropped |

Any failure — call error, timeout, botched JSON, too-short summary — degrades
to the old regex summary with identical prune indices, so compaction can never
break a turn. The summary lands as a durable `log/replace` event; the pruned
messages stay in the event log. Knobs: `--no-llm-compact`,
`PHREN_AGENT_LLM_COMPACT=0`, `PHREN_AGENT_COMPACT_THRESHOLD`,
`PHREN_AGENT_COMPACT_MIN_TOKENS` (skip the LLM below this pruned-range size,
default 8k tokens).

## Governance: the review-queue triage loop

High-confidence knowledge never enters the queue (promotion above), so what
does land there is genuinely uncertain — and the agent makes sure it gets
looked at instead of silting up:

- **Session start (interactive):** items older than 14 days are auto-rejected
  (`PHREN_AGENT_QUEUE_EXPIRE_DAYS`, `0` = never; undated items never expire),
  then a banner shows the pending count and top 3 items. One-shot runs print a
  count only and never mutate the queue.
- **`/review`** lists pending items. **`/review go`** is a per-item keypress
  loop (approve / reject / edit / skip) over exact `review.md` lines.
  **`/review auto`** asks the model to propose a verdict + one-line reason per
  item, then applies the batch on one confirm — or preloads the proposals as
  defaults in the interactive loop.
- The warm-start context includes a clearly-labeled section (count + top 3,
  "do NOT treat as truth") so the model knows candidate knowledge exists
  without it leaking as fact. Notes and queue content are excluded from the
  automatic injection path entirely; explicit `phren_search` results tag them.

---

## Security

**Permission modes** control what the agent can do without asking:
- `suggest` (default): every tool call requires approval
- `auto-confirm`: safe tools (read, glob, grep) auto-approved; destructive tools need confirmation
- `full-auto` (`--yolo`): everything runs without confirmation

**Additional protections:**
- Path sandboxing limits file operations to the project directory
- Sensitive file patterns (`.env`, credentials) are protected
- Shell commands have safety checks and timeouts
- Environment variables are scrubbed before sending to LLM providers

### Kernel sandbox (Linux, bubblewrap)

With `--sandbox auto` (the default), shell commands are wrapped in `bwrap` so
the filesystem is **read-only outside the workspace** — enforced by the
kernel, which covers every child process, not just what in-process checks can
see. Writable roots derive from the same permission config as the in-process
path sandbox (project root + allowed paths + tmp), so the two layers cannot
drift apart. When a sandboxed write is blocked, the tool result gets a
`[sandbox]` annotation so the model redirects instead of retrying.

| Mode | Behavior |
|---|---|
| `auto` (default) | Confine when a functional `bwrap` probe passes; otherwise run unconfined with a one-time notice (non-Linux included) |
| `require` | Fail closed: no working bwrap ⇒ every shell call errors |
| `off` | Never wrap |

### web_fetch SSRF guard

`web_fetch` rejects URLs that are — or resolve via DNS to — private,
loopback, link-local (cloud metadata!), or CGNAT addresses, and follows
redirects manually so each hop is re-checked. Override with
`PHREN_AGENT_ALLOW_PRIVATE_FETCH=1` if your docs genuinely live on your LAN.

---

## Replay testing (keyless)

Every run records a session event log — and any recording can be replayed as
a scripted provider with **zero API cost and no credentials**:

```bash
PHREN_AGENT_REPLAY=path/to/session-<id>.events.jsonl phren-agent --yolo "same task"
```

Each recorded `assistant/message` replays as one response, in order; the loop
errors loudly if the conversation diverges past the script. This turns any
interesting real session into a deterministic regression test — CI runs the
built binary against a committed fixture (scripted tool call → real shell
execution → scripted final answer) on every push.

## Live smoke test

`scripts/agent-smoke.sh` runs one short real session per provider that has
credentials configured (skips the rest): a real tool call plus the final
answer check. Use it before releases or after provider-layer changes:

```bash
pnpm build && ./experimental/agent/scripts/agent-smoke.sh            # all configured
./experimental/agent/scripts/agent-smoke.sh anthropic                # just one
```

## Skills

The warm-start context lists enabled skills (name + description from the
phren skill registry, project scope honored) so the model knows what exists
instead of guessing `run_skill` names. In the REPL/TUI, an unknown slash
input that matches a skill — by name, frontmatter `command`, or alias — is
rewritten into a `run_skill` task: `/commit fix typo` runs your `commit`
skill with those args. Built-in commands always win.

---

## Session event log

Session history is an append-only event log at
`<phrenPath>/.runtime/sessions/session-<id>.events.jsonl` — one JSON line per
event (`user/message`, `assistant/message`, `tool/results`, `log/replace`).
The message array the model sees is derived from the log, and an invariant
asserts before every request that the projection still reconstructs from it
(disable with `PHREN_AGENT_NO_INVARIANT=1`). Context pruning appends a
`log/replace` event instead of deleting: the model sees a summary, the log
keeps everything for replay and resume. `--resume` prefers the newest event
log (forking it into the new run's own file, with `parentSession` lineage)
and falls back to legacy v1 message snapshots, which are still written once
at session end.

## Reasoning models

Reasoning/thinking output round-trips per provider: Codex re-sends encrypted
reasoning items so multi-turn tool use keeps the model's chain of thought;
Anthropic gets a `thinking` budget derived from `--reasoning` and replays
signed thinking blocks; OpenAI-compatible endpoints and Ollama surface
`reasoning_content`/`thinking` for display. Reasoning from a different
provider is stripped on send, so `--resume` under a new model never replays
another model's private state. The TUI shows a dim live thinking tail;
one-shot `--verbose` streams it to stderr.

## Images

`read_image` (registered only for vision-capable models) reads png/jpeg/webp/
gif up to 5MB into the conversation. On a text-only model, image content in
resumed history degrades to an explicit `[image omitted]` marker rather than
an unsendable request.

## Loop hygiene

Consecutive identical tool calls (same tool, same canonicalized arguments)
get escalating reminders at runs of 3/5/8 appended to the tool result;
identical calls within one assistant message execute once and share the
result. Every tool runs under a declarative per-tool timeout (default 120s)
with a real AbortSignal — shell commands are cancellable and no longer block
the event loop.

## Subagents in one-shot mode

`spawn_agent`, `send_message_to_agent`, and `list_agents` are available in
one-shot runs (not just the TUI); disable with `--no-subagents`. In `suggest`
permission mode, spawning asks first — a child runs with auto-confirm
permissions. Headless children auto-deny any tool that would need an
interactive approval.

