Skip to content
Open
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
32 changes: 17 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -131,13 +131,13 @@ You can open your oh-my-openagent config file (typically ~/.config/opencode/oh-m

### Structured Trace

FM-Agent always writes structured execution traces under `fm_agent/trace/`:
FM-Agent writes structured execution traces under each run's `trace/` directory (`fm_agent/runs/<run-id>/trace/`):

| Path | Content |
|---|---|
| `fm_agent/trace/events.jsonl` | Structured events for OpenCode calls and verification LLM calls |
| `fm_agent/trace/payloads/` | Event payloads such as OpenCode stdout and selected LLM messages |
| `fm_agent/trace/opencode/` | Optional raw OpenCode LLM request/response JSONL files |
| `trace/events.jsonl` | Structured events for OpenCode calls and verification LLM calls |
| `trace/payloads/` | Event payloads such as OpenCode stdout and selected LLM messages |
| `trace/opencode/` | Optional raw OpenCode LLM request/response JSONL files |

To capture raw OpenCode LLM traffic, install the OpenCode trace plugin manually by adding it to `~/.config/opencode/opencode.json`:

Expand All @@ -148,7 +148,7 @@ To capture raw OpenCode LLM traffic, install the OpenCode trace plugin manually
}
```

FM-Agent automatically passes `TRACE_DIR` and `TRACE_FILENAME` to each OpenCode process. The plugin writes `fm_agent/trace/opencode/<event_id>.jsonl`, where `<event_id>` matches the corresponding `opencode_call` event in `events.jsonl`.
FM-Agent automatically passes `TRACE_DIR` and `TRACE_FILENAME` to each OpenCode process. The plugin writes `trace/opencode/<event_id>.jsonl` inside the selected run, where `<event_id>` matches the corresponding `opencode_call` event in `events.jsonl`.
OpenCode may cache the `@latest` package; to force a refresh, remove `~/.cache/opencode/packages/@lucentia/opencode-trace@latest`.


Expand Down Expand Up @@ -177,7 +177,7 @@ To provide project-specific domain knowledge without editing FM-Agent's built-in
uv run python main.py <proj_dir> --domain-knowledge docs/invariants.md docs/protocol.md
```

FM-Agent stages these files under `fm_agent/spec_prompts/domain_context/user_knowledge/` for the current run. You can also set `FM_AGENT_DOMAIN_KNOWLEDGE` to an `os.pathsep`-separated list of Markdown files.
FM-Agent stages these files under `fm_agent/runs/<run-id>/spec_prompts/domain_context/user_knowledge/` for the current run. You can also set `FM_AGENT_DOMAIN_KNOWLEDGE` to an `os.pathsep`-separated list of Markdown files.

Use `--submodule` to limit a full or incremental run to selected project subdirectories:

Expand All @@ -188,7 +188,9 @@ uv run python main.py <proj_dir> --incremental intent.md --submodule src/core sr

`--submodule` paths must point to directories inside `proj_dir`. The option can be combined with `--resume`, `--isolate`, and `--incremental`, but not with `--entry-func`.

By default, every invocation wipes the existing `fm_agent/` directory and restarts from scratch, so an interrupted run loses all prior progress. Pass `--resume` (or set the environment variable `FM_AGENT_RESUME=1`) to continue where the previous run left off. In resume mode FM-Agent keeps the existing `fm_agent/` directory and only does the remaining work.
Each new invocation writes to a timestamped directory such as `fm_agent/runs/20260717-143000/`; FM-Agent never deletes the entire `fm_agent/` root automatically. `fm_agent/current_run.json` identifies the active run. When results already exist, an interactive terminal offers four choices: resume the current run, archive it by leaving it under `runs/` and create a new run, overwrite only the current run, or exit without changes. Pass `--resume` (or set `FM_AGENT_RESUME=1`) to select the current run directly. In a non-interactive terminal, existing results are preserved and FM-Agent exits unless `--resume` is explicit.

Older flat `fm_agent/` workspaces are migrated to `fm_agent/runs/legacy-<timestamp>/` when resumed or archived.

Use `--only-spec` to stop after generating behavioral specs, skipping the reasoning and bug validation stages. This produces the `[SPEC]` blocks for each function without spending time on verification, which is useful when you only want the specs or want to review them before running the full analysis. It cannot be combined with `--incremental`, which is inherently a reasoning/bug-validation flow.

Expand Down Expand Up @@ -225,33 +227,33 @@ Extra-edge field rules:

### Incremental Mode

In incremental mode, FM-Agent reuses the results of a previous run and only re-checks what changed. It diffs the current code against the commit recorded by the previous run in `fm_agent/version.log`. Each run records the processed commit id to that file, so a subsequent `--incremental` run automatically picks it up:
In incremental mode, FM-Agent reuses the selected run and only re-checks what changed. It diffs the current code against the commit recorded in `fm_agent/runs/<run-id>/version.log`.

```bash
python3 main.py <proj_dir> --incremental <intent_file>
```

If `fm_agent/version.log` does not exist (no previous run to compare against), FM-Agent falls back to a full run.
If the selected run has no `version.log`, FM-Agent falls back to a full run.

### Live Dashboard

FM-Agent ships a standalone real-time TUI dashboard ([dashboard.py](dashboard.py)) that visualizes a run as it progresses: per-stage progress, token usage and cost, prompt-cache hit rate, and bug-validation verdicts. It reads the trace files FM-Agent writes under `fm_agent/`, so run it in a second terminal while `main.py` is going:
FM-Agent ships a standalone real-time TUI dashboard ([dashboard.py](dashboard.py)) that visualizes a run as it progresses: per-stage progress, token usage and cost, prompt-cache hit rate, and bug-validation verdicts. Given a project root, it follows `current_run.json`; you can also pass a specific run directory.

```bash
uv run python dashboard.py <proj_dir>
```

| Argument | Description |
| ----------- | ----------------------------------------------------------- |
| `proj_dir` | Same codebase directory passed to `main.py` (monitors `<proj_dir>/fm_agent/`). You can also point it directly at any workspace directory containing a `trace/` subdir, e.g. an archived run |
| `proj_dir` | Same codebase directory passed to `main.py` (monitors the current run), or a specific `fm_agent/runs/<run-id>/` directory containing `trace/` |

Press `Ctrl-C` to exit the dashboard; it does not affect the running pipeline.

### Output

FM-Agent creates an `fm_agent/` directory under your codebase directory. The key outputs are:
FM-Agent creates each run under `fm_agent/runs/<run-id>/`. The key outputs below are relative to that run directory:

#### Bug Reports (`fm_agent/bug_validation/<bug_id>.md`)
#### Bug Reports (`bug_validation/<bug_id>.md`)

Each confirmed or investigated bug produces a Markdown report containing:

Expand All @@ -265,11 +267,11 @@ Each confirmed or investigated bug produces a Markdown report containing:
| Probe Script | The full test script used to confirm the bug |
| Probe Output | Raw stdout from executing the probe script |

A `summary.json` file in `fm_agent/bug_validation/` aggregates all bug results with counts of total reported, confirmed, not confirmed bugs.
A `summary.json` file in `bug_validation/` aggregates all bug results with counts of total reported, confirmed, not confirmed bugs.

## Important Notes

1. FM-Agent will create an `fm_agent/` directory under your codebase directory. Make sure there is no name conflict.
1. FM-Agent keeps independent run directories under `fm_agent/runs/`; use `current_run.json` or the startup prompt to select the active run.
2. The markdown files under `md/` provide general instructions that guide the agent's reasoning process. Prefer `--domain-knowledge` for project-specific context such as invariants, protocols, encoding rules, and domain terminology. For reusable framework behavior, customize the built-in prompts; for example, if you are reasoning about a compiler, modify `md/bug_validator.md` to instruct the agent to compare outputs against a reference implementation (e.g., GCC).
3. **Supported languages**: Rust, C, C++, Python, Java, Go, CUDA, JavaScript, TypeScript, ArkTS, Erlang. Erlang function extraction and call graphs require ELP; if ELP is unavailable, Erlang files are skipped with a warning.

Expand Down
24 changes: 13 additions & 11 deletions README_zh.md
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ uv run python main.py <proj_dir> [--resume] [--domain-knowledge FILE ...] [--sub
uv run python main.py <proj_dir> --domain-knowledge docs/invariants.md docs/protocol.md
```

FM-Agent 会将这些文件暂存到 `fm_agent/spec_prompts/domain_context/user_knowledge/`,并在本次运行中让相关 Agent 读取。也可以通过 `FM_AGENT_DOMAIN_KNOWLEDGE` 提供使用 `os.pathsep` 分隔的 Markdown 文件列表。
FM-Agent 会将这些文件暂存到 `fm_agent/runs/<run-id>/spec_prompts/domain_context/user_knowledge/`,并在本次运行中让相关 Agent 读取。也可以通过 `FM_AGENT_DOMAIN_KNOWLEDGE` 提供使用 `os.pathsep` 分隔的 Markdown 文件列表。

使用 `--submodule` 可以把完整运行或增量运行限制到指定项目子目录:

Expand All @@ -167,7 +167,9 @@ uv run python main.py <proj_dir> --incremental intent.md --submodule src/core sr

`--submodule` 路径必须是 `proj_dir` 内部目录。该参数可与 `--resume`、`--isolate` 和 `--incremental` 一起使用,但不能与 `--entry-func` 一起使用。

默认情况下,每次运行都会清空已有的 `fm_agent/` 目录并从头开始,因此一旦运行中断,之前的所有进度都会丢失。可通过 `--resume` 参数(或设置环境变量 `FM_AGENT_RESUME=1`)从上一次中断处继续。在续跑模式下,FM-Agent 会保留已有的 `fm_agent/` 目录,只执行剩余的工作。
每次新任务默认写入带时间戳的独立目录,例如 `fm_agent/runs/20260717-143000/`;FM-Agent 不会再自动删除整个 `fm_agent/` 根目录。`fm_agent/current_run.json` 指向当前 Run。检测到已有结果时,交互式终端会提供四个选项:续跑当前 Run、保留归档后创建新 Run、仅覆盖当前 Run、退出且不做修改。使用 `--resume`(或设置 `FM_AGENT_RESUME=1`)可直接续跑当前 Run。非交互终端检测到旧结果时会安全退出,除非显式传入 `--resume`。

旧版扁平的 `fm_agent/` 工作区在续跑或归档时会迁移至 `fm_agent/runs/legacy-<timestamp>/`。

使用 `--only-spec` 可以在生成行为规约后即停止,跳过推理与 Bug 验证阶段。它会为每个函数生成 `[SPEC]` 块,而不在验证上花费时间,适用于只需要规约、或希望先审阅规约再运行完整分析的场景。该参数不能与 `--incremental` 一起使用,因为增量模式本质上是一个推理/Bug 验证流程。

Expand Down Expand Up @@ -205,33 +207,33 @@ Extra-edge 字段规则:

### 增量模式

增量模式会复用上一次运行的结果,仅重新检测发生变化的部分。它将当前代码与上一次运行记录在 `fm_agent/version.log` 中的提交进行 diff。每次运行都会把所处理的提交 id 写入该文件,因此后续的 `--incremental` 运行会自动读取它
增量模式会复用所选 Run 的结果,仅重新检测发生变化的部分。它将当前代码与 `fm_agent/runs/<run-id>/version.log` 中记录的提交进行 diff:

```bash
python3 main.py <proj_dir> --incremental <intent_file>
```

如果 `fm_agent/version.log` 不存在(没有可供比较的历史运行),FM-Agent 会回退为完整运行。
如果所选 Run 中不存在 `version.log`,FM-Agent 会回退为完整运行。

### 实时监控面板

FM-Agent 自带一个独立的实时 TUI 监控面板([dashboard.py](dashboard.py)),用于在运行过程中可视化展示:各阶段进度、Token 用量与花费、prompt 缓存命中率,以及 Bug 验证结果。它读取 FM-Agent 写入 `fm_agent/` 目录下的 trace 文件,因此可在 `main.py` 运行期间于另一个终端中启动:
FM-Agent 自带一个独立的实时 TUI 监控面板([dashboard.py](dashboard.py)),用于在运行过程中可视化展示:各阶段进度、Token 用量与花费、prompt 缓存命中率,以及 Bug 验证结果。传入项目根目录时,面板会根据 `current_run.json` 监控当前 Run;也可以直接传入指定 Run 目录。

```bash
uv run python dashboard.py <proj_dir>
```

| 参数 | 描述 |
|---|---|
| `proj_dir` | 与 `main.py` 相同的代码库目录(监控 `<proj_dir>/fm_agent/`)。也可直接指向任意包含 `trace/` 子目录的工作区目录,例如已归档的运行 |
| `proj_dir` | 与 `main.py` 相同的代码库目录(监控当前 Run),或包含 `trace/` 的指定 `fm_agent/runs/<run-id>/` 目录 |

按 `Ctrl-C` 退出监控面板,不会影响正在运行的流水线。

### 输出说明

FM-Agent 会在代码库目录下创建 `fm_agent/` 目录,主要输出内容如下
FM-Agent 会把每次运行创建在 `fm_agent/runs/<run-id>/` 下。以下路径均相对于该 Run 目录

#### Bug 报告(`fm_agent/bug_validation/<bug_id>.md`)
#### Bug 报告(`bug_validation/<bug_id>.md`)

每个已确认或经过排查的 Bug 都会生成一份 Markdown 报告,包含以下内容:

Expand All @@ -245,15 +247,15 @@ FM-Agent 会在代码库目录下创建 `fm_agent/` 目录,主要输出内容
| Probe Script | 用于触发 Bug 的完整测试脚本 |
| Probe Output | 执行测试脚本的输出 |

`fm_agent/bug_validation/` 目录下的 `summary.json` 文件汇总了所有 Bug 结果,包括报告的Bug总数、已确认Bug数、未确认Bug数。
`bug_validation/` 目录下的 `summary.json` 文件汇总了所有 Bug 结果,包括报告的Bug总数、已确认Bug数、未确认Bug数。

#### 日志文件(`fm_agent/fm_agent.log`)
#### 日志文件(`fm_agent.log`)

单一日志文件记录完整的流水线执行过程,包括文件提取进度、推理任务的提交与完成情况、网络错误与重试,以及最终的推理统计摘要。日志级别为 `INFO`,格式为 `%(asctime)s [%(levelname)s] %(message)s`。

## 注意事项

1. FM-Agent 会在代码库目录下创建 `fm_agent/` 目录,请确保不存在命名冲突
1. FM-Agent 将独立运行保存在 `fm_agent/runs/` 下,可通过 `current_run.json` 或启动时的交互选项选择当前 Run
2. `md/` 目录下的 Markdown 文件提供了引导 Agent 推理过程的通用说明。针对项目特定的上下文(如不变量、协议、编码规则、领域术语),优先使用 `--domain-knowledge`。对于可复用的框架行为,可定制内置提示词;例如,若正在推理编译器的正确性,可修改 `md/bug_validator.md`,指示 Agent 将输出与参考实现(如 GCC)进行对比。
3. **支持的编程语言**:Rust、C、C++、Python、Java、Go、CUDA、JavaScript、TypeScript、ArkTS、Erlang。Erlang 的函数抽取与调用图需要 ELP;ELP 不可用时会给出警告并跳过 Erlang 文件。

Expand Down
23 changes: 19 additions & 4 deletions dashboard.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"""Real-time TUI dashboard for an FM-Agent run.

Usage:
uv run python dashboard.py <proj_dir> # live: <proj_dir>/fm_agent/
uv run python dashboard.py <proj_dir> # live: current run
uv run python dashboard.py <proj_dir>/fm_agent.archived_xx # any workspace dir (auto-detected by trace/ subdir)
uv run python dashboard.py <proj_dir> --refresh 1.0 # refresh every 1.0s

Expand Down Expand Up @@ -178,14 +178,29 @@ def _locate_workdir(proj_dir):
"""Resolve which fm_agent workdir to monitor.

Accepts either:
- A project root: dashboard looks for <root>/fm_agent/ (the live workspace).
- A project root: dashboard follows <root>/fm_agent/current_run.json.
- A workspace directly (any name like fm_agent.opus_partial_*): detected
by the presence of a `trace/` subdir, used as-is.
"""
p = Path(proj_dir).resolve()
if (p / "trace").is_dir():
return p
return p / "fm_agent"
root = p / "fm_agent"
marker = root / "current_run.json"
try:
run_id = json.loads(marker.read_text()).get("run_id", "")
except (OSError, ValueError, AttributeError):
run_id = ""
if run_id and Path(run_id).name == run_id:
current = root / "runs" / run_id
if current.is_dir():
return current
runs_dir = root / "runs"
if runs_dir.is_dir():
runs = [candidate for candidate in runs_dir.iterdir() if candidate.is_dir()]
if runs:
return max(runs, key=lambda candidate: candidate.stat().st_mtime)
return root


class State:
Expand Down Expand Up @@ -740,7 +755,7 @@ def build_layout(state):
def main():
ap = argparse.ArgumentParser(description=__doc__)
ap.add_argument("proj_dir",
help=("Either a target codebase (monitors <proj_dir>/fm_agent/) "
help=("Either a target codebase (monitors its current fm_agent run) "
"or a workspace directly (any dir containing a trace/ subdir)"))
ap.add_argument("--refresh", type=float, default=1.5, help="Refresh seconds (default 1.5)")
args = ap.parse_args()
Expand Down
Loading