# 把现有 Agent 接入 FluxBench Docker

本指引适用于你已有的私人 Agent，也适用于 GitHub 开源 Agent 项目。平台运行你提供的镜像，使用你在当前网站账号下选择的模型 API。自动把项目转换成镜像的功能尚未提供；本阶段需要你完成适配和构建。

## 1. 先确定项目如何执行一次任务

找出项目中“接收任务 → 调用模型 → 使用工具 → 写出结果”的入口。CLI、Python/Node 程序、LangGraph 等都可以。依赖浏览器、交互式终端、人工确认或常驻 Web 服务的项目，需要增加一个能自动执行一次任务并退出的入口。

镜像必须包含 Agent 源码和运行依赖。它不需要包含 DC、Innovus、PrimeTime、License、工艺库或 API Key。用户上传的是 Agent 镜像，同时单独选择或填写模型 API；两者不应打包在一起。私人仓库的拉取凭据只用于构建，不应保留在镜像层、构建上下文或日志中。

## 2. 环境变量与任务 Prompt 透明注入

平台保留镜像的 ENTRYPOINT、CMD、WORKDIR、USER，不需要在镜像内编写专门解析私有 JSON 文件的代码。平台在容器启动时会自动注入标准环境变量并写入任务 Prompt：

- **标准模型环境变量**：
  - `OPENAI_API_KEY`, `OPENAI_BASE_URL`（当选择 OpenAI 协议时注入）
  - `ANTHROPIC_API_KEY`, `ANTHROPIC_BASE_URL`（当选择 Anthropic 协议时注入）
  - `MODEL_NAME`（用户在网页端选择的模型名称）
- **任务路径与 Prompt**：
  - `TASK_WORK_DIR`：任务工作目录路径，综合与物理设计产物在此目录下生成。
  - `TASK_PROMPT_PATH`：Prompt 文件绝对路径。
  - `$TASK_WORK_DIR/prompt.md`：平台自动生成的 Markdown 格式 benchmark 任务说明。

Python 读取示例：

```python
import os
from pathlib import Path

api_key = os.environ.get("OPENAI_API_KEY") or os.environ.get("ANTHROPIC_API_KEY")
base_url = os.environ.get("OPENAI_BASE_URL") or os.environ.get("ANTHROPIC_BASE_URL")
model = os.environ.get("MODEL_NAME")

prompt_path = os.environ.get("TASK_PROMPT_PATH") or "prompt.md"
prompt = Path(prompt_path).read_text(encoding="utf-8")
```

若框架原生支持读取标准环境变量，则无需任何定制包装。不要把 Key 写进镜像、prompt、Tcl、日志或输出文件。平台不向容器注入共享账号的 Claude/Codex 配置。

## 3. 把 EDA 工具接到平台接口

所有 benchmark 输入都放在只读的 `$FLUXBENCH_WORK_ROOT/picorv32_input`，包括 RTL、SDC、工艺库和阶段检查脚本；生成文件放在可写的 work 目录。容器和宿主 EDA 看到的**绝对路径完全一致**，Tcl 内相对引用、`source`、输出路径不需要转换。不要硬编码某个任务的路径。

平台把 `dc_shell`、`innovus`、`pt_shell` 命令加入 PATH，确切路径也记录在 `task.tools`。命令入口自动调用 Worker 对应工具，转发日志并返回退出码。使用这些命令要求镜像的 PATH 中有 **Python 3**，无需额外 pip 依赖。启动子 shell 时保留注入的 PATH；建议用 `bash -c`，登录 shell 可能重置 PATH。

```bash
cd "$FLUXBENCH_WORK_ROOT/01_dc/run"
dc_shell -f dc.tcl
# 在 Innovus 的 run* 目录下：
innovus -nowin -init route.tcl
# 在 PrimeTime 工作目录下：
pt_shell -multi -f eco.tcl
```

支持 `-f SCRIPT` 或 `-init SCRIPT`，PrimeTime 的 `-multi`、Innovus 的 `-nowin`。脚本和 cwd 需要已存在且位于任务 work 内。这是批处理命令入口；不提供交互式 EDA 终端。未支持的参数会返回明确错误。缺文件或工具非零退出时，适配层应把错误交回 Agent，让它修正后重试。

普通文件操作和阶段检查在容器内执行；真正的 EDA 二进制使用宿主机统一环境 `/data/jydeng/eda-unified/v1/env.sh`，与 Preset 使用相同配置。镜像不需要挂载宿主软件安装目录或 Docker socket。

| 容器命令（`task.tools` 中的路径） | Worker 统一工具入口 |
|---|---|
| `dc_shell` | `/data/jydeng/eda-unified/v1/bin/dc_shell` |
| `innovus` | `/data/jydeng/eda-unified/v1/bin/innovus` |
| `pt_shell` | `/data/jydeng/eda-unified/v1/bin/pt_shell` |

框架也可以直接使用下面的 HTTP 接口，这种方式不要求 Python。

`task.eda` 包含 `socket`、`credential_file` 和 `operations_path`。从 credential_file 读取本任务 token，通过 HTTP over Unix socket 调用：

```python
import httpx, uuid
endpoint = task["eda"]
token = json.loads(Path(endpoint["credential_file"]).read_text())["token"]
client = httpx.Client(
    transport=httpx.HTTPTransport(uds=endpoint["socket"]),
    base_url="http://localhost", trust_env=False,
    headers={"Authorization": "Bearer " + token},
)
response = client.post(endpoint["operations_path"],
    headers={"Idempotency-Key": uuid.uuid4().hex},
    json={"tool": "dc_shell", "script": "synth.tcl", "cwd": "01_dc"})
response.raise_for_status()
operation = response.json()
```

先把脚本写到 `paths.work/01_dc/synth.tcl`。cwd 相对 `paths.work`，script 相对 cwd；也支持任务内的绝对路径。内部 `..` 和链接会正常解析，但不允许越出任务目录。兼容旧客户端：cwd 下找不到相对脚本时再尝试 work 根目录。`PATH_NOT_FOUND` 会返回实际解析出的缺失路径。

- 工具：`dc_shell`、`innovus`、`pt_shell`。
- PrimeTime DMSA：请求额外带 `"multi": true`，相当于 `pt_shell -multi`；其他工具不支持。
- 查询：`GET /v1/operations/{id}`。
- 日志：`GET /v1/operations/{id}/logs?after=0`，用返回的 `next_cursor` 继续读取。
- 取消：`POST /v1/operations/{id}/cancel`，JSON 为 `{}`。

等待 operation 到终态且 `cleanup_complete=true` 后再启动下一个操作；一次只有一个宿主机 EDA 操作。退出码 0 不证明设计成功，Agent 仍须阅读工具日志、检查产物并修复错误。结束 Tcl 时显式 `exit`。

## 4. 保留任务要求和产物

PicoRV32 按 DC → Innovus → PrimeTime/ECO 顺序运行。正式 prompt 由平台提供，频率、die size、目录及交付物都以它为准。

每阶段运行平台提供的对应脚本：

```bash
bash "$FLUXBENCH_INPUT_ROOT/checklist/check_dc_outputs.sh"
```

平台在容器和宿主 EDA 中注入相同的 `FLUXBENCH_*_ROOT` 变量；Tcl 可用 `$::env(FLUXBENCH_INPUT_ROOT)` 获取输入根目录。Innovus/PT 分别使用 `check_innovus_outputs.sh`、`check_pt_outputs.sh`。这些脚本即使退出 0，也可能打印 `MISSING:`，必须修复后重跑。

stdout/stderr 用于进度日志；进程成功退出 0，失败非 0；处理 SIGTERM 并取消尚未结束的操作。正常模式应完成一次任务后退出，不要留一个永久监听服务。

平台启用自动验收的 Worker 会在容器和宿主机操作清理后，独立执行现有 artifact acceptance 和 scoring。阶段检查通过、Agent 退出 0、兼容性预检通过，都不等于最终验收通过。Agent 不得自行执行平台评分。产物的内部文件链接可以在回收时物化；越界、断链和循环链接会被拒绝。

### 页面阶段进度

平台从已有 Agent stdout 日志读取阶段进度，不需要增加控制接口。LangGraph 示例已输出 `eda_submitted` 和 `stage_check`，可直接显示。其他框架可在真正开始阶段时打印一行 JSON，并在阶段检查通过后打印 END：

```json
{"kind":"stage_progress","stage":"dc","event":"START"}
{"kind":"stage_progress","stage":"dc","event":"END"}
```

stage 使用 `dc`、`innovus`、`pt`；失败且准备修复时可输出 `FAILED`，之后继续 START。每条记录独占一行并及时 flush。EDA 命令退出 0 不会自动将阶段标为完成；END 应以阶段检查通过为前提。界面的阶段状态用于展示 Agent 进度，最终验收和分数仍由平台独立计算。

## 5. 构建与导出

当前要求 Linux amd64、默认启动入口、数字 UID/GID（或 root），不能声明 VOLUME。推荐 `USER 1000:1000`。所有依赖在构建时安装并固定版本。

```dockerfile
FROM python:3.11-slim
WORKDIR /opt/agent
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY src/ ./src/
COPY fluxbench_entry.py .
USER 1000:1000
ENTRYPOINT ["python", "-u", "/opt/agent/fluxbench_entry.py"]
```

上述 `fluxbench_entry.py` 是你完成步骤 2–4 的适配入口，不是一个平台自动提供的通用转换器。Node 等项目可替换基础镜像和依赖安装方式。

`.dockerignore` 至少排除 `.git`、`.env*`、API 配置、SSH Key、构建缓存和历史产物。不要把本机整个 HOME 复制到镜像中。

```bash
docker build --platform linux/amd64 -t my-agent:fluxbench .
docker save my-agent:fluxbench | gzip > my-agent.tar.gz
```

每个包一个镜像、一个平台，不能用 `docker export` 的容器文件系统替代。当前上传上限 1 GiB，外层归档展开检查默认上限 4 GiB。平台最终绑定导入后的不可变 image ID。

仓库里的 `examples/langgraph-agent/` 是可运行的完整适配示例，支持上述三种模型协议。旧的 DC 小测试保留在 `dc_smoke_agent.py`，它不是全流程入口。

新版 LangGraph 示例会把 EDA 日志中的明确错误视为失败，即使退出码为 0。相同脚本内容和错误连续失败三次时，Agent 会终止任务，避免无效重试；修改脚本或错误变化后重新计数，执行成功后清零。请重新构建并上传镜像以启用此保护，已上传的旧镜像不会随网站更新。

## 6. 镜像兼容性测试 (沙箱冒烟测试)

外部 Agent 镜像无需在内部实现特定的 SHA256 握手或输出特定的预检报告。平台提供 4 项沙箱冒烟体检：

1. **归档格式与平台**：tar/tar.gz 归档完整，架构为 linux/amd64，且定义了默认 ENTRYPOINT 或 CMD。
2. **容器正常启动**：容器可成功运行，无动态链接库缺失或架构不兼容报错。
3. **EDA 桥接工具可用**：容器内可调用挂载的 EDA CLI 脚本（执行 `dc_shell --fluxbench-probe` 返回退出码 0）。
4. **工作区可写与干净退出**：容器对工作区具备写入权限，并在接收退出信号后正常退出。

提交前可以使用命令行在本地进行验证：

```bash
python -m fluxbench_backend.image_check /path/to/my-agent.tar.gz \
  --runtime --root /path/to/check-dir \
  --docker /path/to/docker
```

检查结果会写入 `report.json`；若失败会在 `diagnostics.stderr_tail` 中输出容器退出前的 stderr 日志。

## 7. 网站提交

登录账号 → 选择 Customer worker → 选中 `.tar` 或 `.tar.gz` 镜像文件。

1. **上传即体检**：前端在选择镜像文件后会自动上传并异步触发沙箱启动体检。根据镜像大小和 Worker 负载，可能需要几分钟；页面会持续显示检查进度。
2. **体检结果反馈**：
   - 体检通过：显示 `Verified: AMD64 / Entrypoint / EDA bridge ready` 并激活提交按钮。
   - 体检失败：显示 `Verification Failed` 并展开诊断面板，列出未通过的检查项与容器 stderr 错误输出。
3. **正式提交评测**：选择模型 provider、主频（350 或 700 MHz）及目标 Worker，确认后排队开始评测。

常见问题：

| 现象 | 应对 |
|---|---|
| UNSUPPORTED_IMAGE_USER | 改为数字 USER，如 1000:1000 |
| UNSUPPORTED_IMAGE_VOLUMES | 删除当前及基础镜像继承的 VOLUME，必要时换基础镜像 |
| INCOMPATIBLE_PLATFORM | 使用 linux/amd64 重新构建 |
| EDA bridge probe failed | 确保容器内已安装 Python 3，以支持运行挂载的 EDA CLI 包装脚本 |
| 401 / model not found | 检查当前用户的 Key、API 地址、协议、真实模型 ID |
| INVALID_PATH / PATH_NOT_FOUND | 路径必须在 work 内，script 相对 cwd，先创建目录和脚本 |
| MISSING: | 补全该阶段产物，不能当作检查通过 |
| Agent 成功但无分数 | 查看是否启用平台验收、验收错误和指标缺失情况 |
