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
37 changes: 27 additions & 10 deletions docs/local_agent_loop.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,22 +14,28 @@ The default coder is Claude and the default reviewer is Codex. Reverse the direc
## Architecture

The tool is a local orchestrator. It does not call model APIs directly; it shells
out to locally authenticated agent and GitHub CLIs from separate checkouts.
out to locally authenticated agent and GitHub CLIs from separate checkouts. The
only durable state it creates is local: agent logs in the active coder checkout
and optional advisory memory in a repo-scoped cache directory.

```mermaid
flowchart LR
User[Developer terminal] --> CLI[agent-loop CLI<br/>cli.py]
CLI --> Config[Config and workdir setup<br/>config.py / workdirs.py]
CLI --> Orchestrator[Issue, task, and PR loops<br/>orchestrator.py]
CLI --> Config[Config validation<br/>config.py]
Config --> Orchestrator[Issue, task, and PR loops<br/>orchestrator.py]

Config --> Workdirs[(Agent checkouts<br/>Claude / Codex / Gemini)]
Config --> Logs[(.agent-loop-logs)]
Orchestrator --> Workdirs[Workdir setup and validation<br/>workdirs.py / git / gh repo clone]
Orchestrator --> Memory[Agent memory preparation<br/>memory.py]

Workdirs --> AgentDirs[(Separate agent checkouts<br/>Claude / Codex / Gemini)]
Memory --> MemoryCache[(Repo-scoped memory cache<br/>summary / architecture / tests)]

Orchestrator --> Prompts[Prompt builders<br/>prompts.py]
Orchestrator --> Protocol[Marker parsing<br/>protocol.py]
Orchestrator --> Protocol[Marker and follow-up parsing<br/>protocol.py]
Orchestrator --> Registry[Agent registry<br/>agents/registry.py]
Orchestrator --> GitHubOps[GitHub operations<br/>github.py]
Orchestrator --> OptionalTests[Optional local test command]
Orchestrator --> OptionalTests[Optional local test command<br/>--test-command]
Orchestrator --> Followups[Approved follow-up handling<br/>summaries / issues / same-PR fixes]

Registry --> Claude[Claude backend<br/>claude]
Registry --> Codex[Codex backend<br/>codex exec]
Expand All @@ -44,10 +50,11 @@ flowchart LR
Runner --> AgentCLIs[Local agent CLIs]
Runner --> GhCLI[gh CLI]
Runner --> TestCmd[Local test process]
Runner --> Logs

AgentCLIs --> Workdirs
AgentCLIs --> Logs
AgentCLIs --> AgentDirs
GhCLI --> GitHub[(GitHub repo<br/>issues / PRs / comments / checks)]
Followups --> GitHubOps
```

At runtime, the orchestrator drives one of three entrypoints:
Expand All @@ -57,12 +64,15 @@ sequenceDiagram
participant User as Developer
participant CLI as agent-loop CLI
participant Orch as Orchestrator
participant Memory as Agent memory
participant Coder as Coder agent CLI
participant Reviewer as Reviewer agent CLI(s)
participant GH as GitHub via gh

User->>CLI: agent-loop issue | task | pr
CLI->>Orch: validated config and workdirs
CLI->>Orch: validated config
Orch->>Orch: ensure active agent workdirs
Orch->>Memory: prepare advisory repo memory
alt issue or task
Orch->>Coder: create or update PR
Coder-->>Orch: output with AGENT_PR marker
Expand All @@ -79,7 +89,14 @@ sequenceDiagram
Orch->>Coder: address combined feedback
Coder-->>Orch: AGENT_STATE blocking
Orch->>GH: post coder update
else approved review has same-PR follow-ups in a fix-and mode
Orch->>Coder: address same-PR follow-ups
Coder-->>Orch: AGENT_STATE blocking
Orch->>GH: post coder update
else all approved
opt future follow-ups requested
Orch->>GH: summarize follow-ups or create issues
end
Orch->>Orch: run optional local tests
opt auto-merge enabled
Orch->>GH: wait for configured check and merge
Expand Down
2 changes: 1 addition & 1 deletion src/coding_review_agent_loop/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,7 @@ def add_common(subparser: argparse.ArgumentParser) -> None:
"--no-agent-memory",
dest="agent_memory",
action="store_false",
help="Disable repo-local advisory agent memory.",
help="Disable repo-scoped advisory agent memory.",
)
subparser.add_argument(
"--refresh-agent-memory",
Expand Down
38 changes: 36 additions & 2 deletions src/coding_review_agent_loop/memory.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
"""Repo-local advisory memory for repeated agent loop runs."""
"""Repo-scoped advisory memory for repeated agent loop runs."""

from __future__ import annotations

Expand Down Expand Up @@ -232,10 +232,44 @@ def _write_architecture_map(path: Path, tracked_files: list[str]) -> None:
for file in tracked_files:
head = file.split("/", 1)[0]
buckets.setdefault(head, []).append(file)
lines = ["# Architecture Map", ""]
lines = [
"# Architecture Map",
"",
"## Top-level Layout",
"",
]
for head in sorted(buckets):
examples = ", ".join(buckets[head][:8])
lines.append(f"- `{head}`: {len(buckets[head])} tracked file(s); examples: {examples}")

python_files = [
file for file in tracked_files if file.startswith("src/") and file.endswith(".py")
]
if python_files:
lines.extend(["", "## Python Modules", ""])
module_buckets: dict[str, list[str]] = {}
for file in python_files:
parts = file.split("/")
if len(parts) >= 4:
module = "/".join(parts[:3])
else:
module = "/".join(parts[:-1]) or "src"
module_buckets.setdefault(module, []).append(file)
for module in sorted(module_buckets):
examples = ", ".join(module_buckets[module][:8])
lines.append(f"- `{module}`: {len(module_buckets[module])} file(s); examples: {examples}")

docs = [file for file in tracked_files if file == "README.md" or file.startswith("docs/")]
tests = [file for file in tracked_files if file.startswith("tests/")]
workflows = [file for file in tracked_files if file.startswith(".github/workflows/")]
if docs or tests or workflows:
lines.extend(["", "## Supporting Surfaces", ""])
if docs:
lines.append(f"- Docs: {', '.join(docs[:8])}")
if tests:
lines.append(f"- Tests: {', '.join(tests[:8])}")
if workflows:
lines.append(f"- GitHub Actions: {', '.join(workflows[:8])}")
path.write_text("\n".join(lines) + "\n", encoding="utf-8")


Expand Down
5 changes: 5 additions & 0 deletions tests/test_agent_loop.py
Original file line number Diff line number Diff line change
Expand Up @@ -704,6 +704,11 @@ def test_agent_memory_is_created_and_added_to_review_prompt(tmp_path):
assert (memory_dir / "test-profile.md").exists()
assert (memory_dir / "toolchain.json").exists()
assert (memory_dir / "last-analyzed-commit").read_text(encoding="utf-8") == "abc123\n"
architecture_map = (memory_dir / "architecture-map.md").read_text(encoding="utf-8")
assert "## Top-level Layout" in architecture_map
assert "## Python Modules" in architecture_map
assert "## Supporting Surfaces" in architecture_map
assert "`src/coding_review_agent_loop`" in architecture_map

prompt = next(cmd[-1] for cmd, _cwd in runner.commands if cmd[:2] == ["codex", "exec"])
assert "Agent memory context:" in prompt
Expand Down
Loading