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
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,6 @@ wheels/
graphify_output/
.understand-anything/
graphify-out/

# Project-local Bach task artifacts (the repo dogfoods itself)
.bach/
12 changes: 9 additions & 3 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,17 @@ Follow the same constraints as `CLAUDE.md`. The short version:
- `bach sessions adopt <session> <task> [--force]` — link a discovered session ID to a Bach task artifact
- `bach sessions resume <session>` — resume a session in iTerm (prints fallback command if iTerm unavailable)
- `bach sessions watch <task-or-session>` — run the foreground session observer (polls transcript, calls judge, applies status via observer authority)
- `bach hooks install claude-code [--project-dir PATH]` — idempotent Claude Code hook install (SessionEnd + Stop + PostToolUse → `bach internal hook-event` / `bach internal session-stop`)
- `bach hooks install codex [--codex-home PATH]` — idempotent Codex hook install (sessionStop → `bach internal hook-event`)
- `bach hooks status [--project-dir PATH]` — show which runtimes have Bach hooks installed
- `bach hooks install claude-code [--project <name>] [--project-dir PATH]` — idempotent Claude Code hook install (SessionEnd + Stop + PostToolUse → `bach internal hook-event` / `bach internal session-stop`); `--project` resolves a registered project name to its filesystem path via ProjectRegistry
- `bach hooks install codex [--project <name>] [--codex-home PATH]` — idempotent Codex hook install (sessionStop → `bach internal hook-event`); `--project` resolves a registered project name
- `bach hooks status [--project <name>] [--project-dir PATH]` — show which runtimes have Bach hooks installed; `--project` resolves a registered project name
- `bach afk run <task>` — headless multi-turn task runner; claims `afk_queue` → `executing`, drives turns, never sets `done`

## PRD#8 Commands (Board × Radar fusion — observer enablement)

- `bach config set <key> <value>` — set an observer knob in `~/.bach/config.yaml`; allowlisted keys (hyphenated): `watch-on-launch`, `observer-moves`, `judge-confidence-threshold`, `judge-interval-seconds`, `liveness-running-seconds`, `liveness-idle-minutes`, `afk-max-turns`, `afk-time-budget-minutes`; bools accept `on`/`off`/`true`/`false`/`yes`/`no`
- Board `/board` (REPL) — kanban cards in `executing`/`qa` columns now show liveness dot prefix (● running, ◐ idle, ✓ ended), truncated observer summary line, and `⚑ needs you` marker when observer flags needs_human
- Show `/show <tN>` (REPL) — task detail appends an `── Observer ──` section (summary, confidence %, needs_human, session state) when the observer has produced a verdict

<!-- gitnexus:start -->
# GitNexus — Code Intelligence

Expand Down
11 changes: 8 additions & 3 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,16 @@ Bach is a CLI-first personal agentic development session launcher that turns dai
- `uv run bach sessions adopt <session> <task> [--force]` - Link a discovered session to a task artifact
- `uv run bach sessions resume <session>` - Resume a session via iTerm (fallback command printed on failure)
- `uv run bach sessions watch <task-or-session>` - Run the foreground session watcher (observer mode)
- `uv run bach hooks install claude-code [--project-dir PATH]` - Idempotent install of Claude Code hooks (SessionEnd/Stop/PostToolUse)
- `uv run bach hooks install codex [--codex-home PATH]` - Idempotent install of Codex hooks (sessionStop)
- `uv run bach hooks status [--project-dir PATH]` - Report which runtimes have Bach hooks installed
- `uv run bach hooks install claude-code [--project <name>] [--project-dir PATH]` - Idempotent install of Claude Code hooks (SessionEnd/Stop/PostToolUse); `--project` resolves a registered project name to its path
- `uv run bach hooks install codex [--project <name>] [--codex-home PATH]` - Idempotent install of Codex hooks (sessionStop); `--project` resolves a registered project name
- `uv run bach hooks status [--project <name>] [--project-dir PATH]` - Report which runtimes have Bach hooks installed; `--project` resolves a registered project name
- `uv run bach afk run <task>` - Run a task headlessly (AFK mode) from `afk_queue`

### PRD#8 — Board × Radar fusion (observer enablement)
- `uv run bach config set <key> <value>` - Set an observer knob (e.g. `watch-on-launch on`); allowlisted keys: `watch-on-launch`, `observer-moves`, `judge-confidence-threshold`, `judge-interval-seconds`, `liveness-running-seconds`, `liveness-idle-minutes`, `afk-max-turns`, `afk-time-budget-minutes`
- `/board` (REPL) - Kanban board now shows liveness dots (●/◐/✓) and observer summary lines on executing/qa cards; `⚑ needs you` marker when observer flags needs_human
- `/show <tN>` (REPL) - Task detail now includes an Observer section (summary, confidence, needs_human, session state) when the observer has data

## File References
- `docs/internal/DISCOVERY.md` - real goal and scope
- `docs/internal/PRD.md` - requirements
Expand Down
180 changes: 180 additions & 0 deletions docs/how-to/enable-observer.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
# Enable the Observer (Board × Radar fusion)

Set up Bach's session observer in about 2 minutes: hooks for both runtimes,
one config knob, and you'll see live liveness dots on the `/board` kanban.

---

## Prerequisites

- Bach installed and `uv run bach --help` works.
- At least one project registered (`bach project add <name> <path>`).
- Claude Code and/or Codex installed.

---

## Step-by-step

### 1. Install hooks for both runtimes

Run one command per runtime, targeting your project by name:

```bash
# Claude Code — installs SessionEnd + Stop + PostToolUse hooks
# into <project>/.claude/settings.json
bach hooks install claude-code --project <name>

# Codex — installs sessionStop hook into ~/.codex/hooks.json
bach hooks install codex --project <name>
```

The `--project <name>` flag resolves to the project's registered path via
`ProjectRegistry`. Without the flag, the current working directory is used
(same as before Phase 17 — backward compatible).

Both installs are **idempotent** — re-running never duplicates entries and
never removes unrelated user hooks. Every change made is printed to stdout.

Verify the install:

```bash
bach hooks status --project <name>
```

You should see a `✓` next to both `claude-code` and `codex`.

---

### 2. Enable watch-on-launch

Tell Bach to automatically spawn a background session watcher every time you
launch a task:

```bash
bach config set watch-on-launch on
```

Confirm the setting was written:

```bash
bach config show
```

Look for `Watch on launch: True` in the output.

**Other observer knobs you can tune** (all via `bach config set <key> <value>`):

| Key | Default | Accepts | Effect |
|-----|---------|---------|--------|
| `watch-on-launch` | `off` | `on`/`off` | Auto-spawn watcher on task launch |
| `observer-moves` | `on` | `on`/`off` | Let observer write status changes (off = log-only) |
| `judge-confidence-threshold` | `0.7` | float 0–1 | Minimum confidence before a verdict is applied |
| `judge-interval-seconds` | `300` | positive int | Minimum gap between mid-session judge calls |
| `liveness-running-seconds` | `60` | positive int | Transcript mtime within N seconds → running |
| `liveness-idle-minutes` | `5` | positive int | Process alive but transcript stale N min → idle |
| `afk-max-turns` | `10` | positive int | AFK hard turn budget |
| `afk-time-budget-minutes` | `60` | positive int | AFK hard time budget |

---

### 3. Launch a task

```bash
# From the REPL — stable-ID reference (e.g. t12)
bach
bach > /open t12

# Or directly from the CLI
bach task launch <artifact-path>
```

When `watch-on-launch` is on, Bach prints a confirmation line immediately
after spawning the watcher:

```
Observer watching this session (watch_on_launch=on).
```

If the watcher fails to spawn (process table issue, bad PATH, etc.) Bach logs
a WARNING but the task launch continues — the watcher is bonus, not blocking.

---

### 4. What you'll see on the board

Open the kanban board:

```bash
bach
bach > /board
# or
bach > /b
```

Each task card in the `executing` and `qa` columns gains a **liveness dot**
in the top-left corner:

| Dot | Color | Meaning |
|-----|-------|---------|
| `●` | green | Session is **running** (transcript updated recently) |
| `◐` | yellow | Session is **idle** (process alive but transcript stale) |
| `✓` | dim | Session has **ended** |
| _(none)_ | — | No session linked yet |

When the session watcher has produced an observer verdict, a truncated
**summary line** appears below the task title in the card (executing and qa
columns only).

If the observer flagged `needs_human`, the card shows:

```
⚑ needs you
```

in red — meaning the agent is blocked and needs your input.

---

### 5. Task detail (Observer section)

Open a task's detail view in the REPL:

```bash
bach > /show t12
```

When the observer has data, a dedicated `── Observer ──` section appears
after the artifact text:

```
── Observer ──
Summary: Tests passing, waiting on type errors in module X
Confidence: 82%
⚑ needs you
Session: idle
```

This section only appears when the observer has written at least one verdict
(`observer_suggestion` or `observer_status_written` event in the artifact log).

---

## Troubleshooting

| Symptom | Check |
|---------|-------|
| No dots on the board | Run `bach hooks status --project <name>` — both hooks must show `✓` |
| No "Observer watching" line after launch | Confirm `Watch on launch: True` in `bach config show` |
| `✓` dot but no summary line | The watcher ran but found no observer events yet; wait for the next judge cycle |
| Observer moved status unexpectedly | Set `observer-moves off` to make the observer log-only while you audit |
| No `⚑ needs you` even when agent is blocked | The judge confidence was below threshold — lower `judge-confidence-threshold` |

---

## Related docs

- `wire-up-session-hook.md` — full hook reference, manual setup, loop-gate
- `configure-user-settings.md` — all user config knobs
- `use-the-repl.md` — board view, /show, /open, /done commands
- `../adr/015-session-observability-codex-parity.md` — observer architecture
- `../adr/016-loop-control.md` — loop gate and AFK bounds
13 changes: 13 additions & 0 deletions docs/internal/progress.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,19 @@

- Repo scaffolded; core docs created; Python CLI skeleton; basic
project/task services in place.
- **PRD#8 (Board × Radar fusion) complete** (board liveness join:
`board_liveness.py` with `TaskLiveness` frozen dataclass + `LivenessMap`
+ TTL cache + stale-on-failure fallback; observer knob config (`bach
config set <key> <value>`) with 8 allowlisted keys in `settings.py`;
`--project <name>` flag on `hooks install` and `hooks status` resolves
registered project names to paths via `ProjectRegistry`; board
rendering gains liveness dot prefix ●/◐/✓ per `SessionLiveness`,
truncated observer summary line, `⚑ needs you` marker; `/show` task
detail appends Observer section; watcher spawn confirmation line when
`watch_on_launch=on`; board legend updated in `help_content.py`;
`docs/how-to/enable-observer.md` documents the 2-minute enablement
path; CLAUDE.md + AGENTS.md command lists updated; 902 baseline
tests preserved).
- **Phase 16 complete** (session observability & loop control: `DiscoveredSession`,
`SessionEvent`, `JudgeVerdict` domain types; `session_discovery.py` scans
Claude Code + Codex transcript directories with mtime-based liveness;
Expand Down
45 changes: 44 additions & 1 deletion src/bach/cli/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,12 @@
from bach.cli.sessions import sessions_app
from bach.config.paths import bach_home
from bach.config.projects import ProjectRegistry
from bach.config.settings import is_valid_user_name, load_config, write_config_field
from bach.config.settings import (
is_valid_user_name,
load_config,
set_observer_key,
write_config_field,
)
from bach.config.themes import THEMES, ThemePalette, get_theme, list_themes
from bach.domain.models import AgentRuntime, SessionMode
from bach.logging_setup import configure_logging
Expand Down Expand Up @@ -449,6 +454,15 @@ def config_show() -> None:
console.print(f"[bold]Normalize model:[/bold] {config.normalize_model}")
console.print(f"[bold]Theme:[/bold] {config.theme}")
console.print(f"[bold]User name:[/bold] {config.user_name}")
# Phase 17 — observer knobs (8 allowlisted keys from PRD#8)
console.print(f"[bold]Watch on launch:[/bold] {config.watch_on_launch}")
console.print(f"[bold]Observer moves:[/bold] {config.observer_moves}")
console.print(f"[bold]Judge confidence:[/bold] {config.judge_confidence_threshold}")
console.print(f"[bold]Judge interval s:[/bold] {config.judge_interval_seconds}")
console.print(f"[bold]Liveness run s:[/bold] {config.liveness_running_seconds}")
console.print(f"[bold]Liveness idle m:[/bold] {config.liveness_idle_minutes}")
console.print(f"[bold]AFK max turns:[/bold] {config.afk_max_turns}")
console.print(f"[bold]AFK budget min:[/bold] {config.afk_time_budget_minutes}")

# Soft-warn if the configured model is not in the current codex catalog.
# We never raise here — stale config must not block `bach config show`,
Expand Down Expand Up @@ -567,6 +581,35 @@ def config_set_name(
console.print(f"[{t.success}]Set user_name = {stripped}[/{t.success}]")


@config_app.command("set")
def config_set(
key: Annotated[
str,
typer.Argument(help="Observer knob to set (e.g. watch-on-launch)."),
],
value: Annotated[
str,
typer.Argument(help="Value to assign. Bools: on/off/true/false/yes/no."),
],
) -> None:
"""Set an observer knob in ~/.bach/config.yaml.

Allowlisted keys (hyphenated CLI spelling):
watch-on-launch, observer-moves, judge-confidence-threshold,
judge-interval-seconds, liveness-running-seconds,
liveness-idle-minutes, afk-max-turns, afk-time-budget-minutes

Use `bach config show` to inspect current values.
"""
t = _cli_theme()
try:
path = set_observer_key(key, value)
except ValueError as exc:
console.print(f"[{t.error}]{exc}[/{t.error}]")
raise typer.Exit(code=1) from None
console.print(f"[{t.success}]Set {key} = {value}[/{t.success}] ({path})")


@config_app.command("list-themes")
def config_list_themes() -> None:
"""List all built-in color themes.
Expand Down
Loading
Loading