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
72 changes: 72 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
# DARE Framework

`deterministic-agent-runtime-engine` 是当前仓库的发行包名,安装后在 Python 代码里使用的导入名是 `dare_framework`。

除非特别说明,下面的命令都默认在仓库根目录、且已激活目标 Python 虚拟环境后执行。

## 本地安装

### 方式 1:可编辑安装(联调推荐)

```bash
python -m pip install -e .
```

如果你只想安装入口而暂时跳过依赖解析:

```bash
python -m pip install -e . --no-deps
```

### 方式 2:先打本地 wheel,再安装(冻结版本推荐)

```bash
python -m pip wheel . -w dist --no-deps
python -m pip install dist/deterministic_agent_runtime_engine-0.1.0-py3-none-any.whl
```

## 在另一个项目里引用当前仓库

如果下游项目和当前仓库在同一台机器上,不需要发布到公共镜像仓库。

### 直接从源码路径安装

```bash
python -m pip install -e /abs/path/to/Deterministic-Agent-Runtime-Engine
```

如果两个项目是同级目录,也可以用相对路径:

```bash
python -m pip install -e ../Deterministic-Agent-Runtime-Engine
```

### 从本地 wheel 安装

先在当前仓库构建 wheel:

```bash
python -m pip wheel . -w dist --no-deps
```

再在下游项目环境里安装:

```bash
python -m pip install /abs/path/to/Deterministic-Agent-Runtime-Engine/dist/deterministic_agent_runtime_engine-0.1.0-py3-none-any.whl
```

## 导入方式

安装成功后,下游项目直接导入 `dare_framework`:

```python
from dare_framework.agent import BaseAgent
from dare_framework.model import OpenAIModelAdapter
```

如果下游项目可以接受修改 import,推荐直接迁到 `dare_framework.*`,不要额外做 `agentscope` 兼容包。

## 相关文档

- `client/README.md`:CLI 使用方式与运行参数
- `docs/README.md`:设计、治理和模块文档导航
45 changes: 44 additions & 1 deletion _local_backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,40 @@ def _sdist_filename(project: dict) -> str:
return f"{_sdist_root_dir(project)}.tar.gz"


def _infer_readme_content_type(path: Path) -> str:
if path.suffix.lower() == ".md":
return "text/markdown"
if path.suffix.lower() == ".rst":
return "text/x-rst"
return "text/plain"


def _readme_payload(project: dict) -> tuple[str, str] | None:
readme = project.get("readme")
if isinstance(readme, str):
path = _project_root() / readme
return path.read_text(encoding="utf-8"), _infer_readme_content_type(path)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Handle missing readme file when building from sdist

_metadata_text now always loads project.readme, and this line unconditionally reads README.md from disk, but build_sdist still does not include the repository root README.md in the tarball. As a result, pip wheel <generated-sdist> (or any metadata hook run from the unpacked sdist) fails with FileNotFoundError during prepare_metadata_for_build_wheel, so source-distribution installs are broken in that environment.

Useful? React with 👍 / 👎.

if isinstance(readme, dict):
content_type = str(readme.get("content-type") or "text/plain")
if "file" in readme:
path = _project_root() / str(readme["file"])
if "content-type" not in readme:
content_type = _infer_readme_content_type(path)
return path.read_text(encoding="utf-8"), content_type
if "text" in readme:
return str(readme["text"]), content_type
return None


def _readme_file(project: dict) -> Path | None:
readme = project.get("readme")
if isinstance(readme, str):
return _project_root() / readme
if isinstance(readme, dict) and "file" in readme:
return _project_root() / str(readme["file"])
return None


def _metadata_text(project: dict) -> str:
lines = [
"Metadata-Version: 2.1",
Expand All @@ -75,7 +109,12 @@ def _metadata_text(project: dict) -> str:
lines.append(f"Requires-Python: {requires_python}")
for dep in project.get("dependencies", []):
lines.append(f"Requires-Dist: {dep}")
return "\n".join(lines) + "\n"
readme_payload = _readme_payload(project)
if readme_payload is None:
return "\n".join(lines) + "\n"
readme_text, content_type = readme_payload
lines.append(f"Description-Content-Type: {content_type}")
return "\n".join(lines) + "\n\n" + readme_text.rstrip() + "\n"


def _wheel_text() -> str:
Expand Down Expand Up @@ -129,6 +168,10 @@ def _yield(path: Path) -> Iterable[Path]:
):
yield from _yield(candidate)

readme_file = _readme_file(_load_project_metadata())
if readme_file is not None:
yield from _yield(readme_file)

for file_path in _package_files():
if file_path not in seen:
seen.add(file_path)
Expand Down
66 changes: 34 additions & 32 deletions client/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,69 +2,71 @@

统一对外 CLI 入口,面向 `dare_framework` 的任务执行与运行时控制。

除非特别说明,以下命令默认在仓库根目录、且已激活目标 Python 虚拟环境后执行。若你使用显式虚拟环境路径,可将 `python` 替换为 `<venv>/bin/python`。

## 运行方式

```bash
# 仓库根目录
.venv/bin/python -m client --help
python -m client --help

# 可编辑安装后使用 console script
.venv/bin/pip install -e .
.venv/bin/dare --help
python -m pip install -e .
dare --help
```

如果是在离线或受限网络环境,可跳过依赖安装,仅安装 CLI 入口:

```bash
.venv/bin/pip install -e . --no-deps
python -m pip install -e . --no-deps
```

## 常用命令

```bash
# 交互模式
.venv/bin/python -m client chat
python -m client chat
# 恢复最近一次会话
.venv/bin/python -m client chat --resume
python -m client chat --resume
# 恢复指定会话
.venv/bin/python -m client chat --resume <session-id>
python -m client chat --resume <session-id>
# 兼容入口:恢复指定会话
.venv/bin/python -m client chat --session-id <session-id>
python -m client chat --session-id <session-id>
# 列出当前 workspace 可恢复会话
.venv/bin/python -m client sessions list
python -m client sessions list

# 一次性执行
.venv/bin/python -m client run --task "读取 README 并总结"
python -m client run --task "读取 README 并总结"
# 在已有会话历史上继续执行一次任务
.venv/bin/python -m client run --resume latest --task "继续上一轮,补充测试计划"
python -m client run --resume latest --task "继续上一轮,补充测试计划"
# 兼容入口:基于指定 session 继续执行
.venv/bin/python -m client run --session-id <session-id> --task "继续上一轮"
python -m client run --session-id <session-id> --task "继续上一轮"
# 一次性执行(审批等待超时,默认 120s)
.venv/bin/python -m client run --task "读取 README 并总结" --approval-timeout-seconds 120
python -m client run --task "读取 README 并总结" --approval-timeout-seconds 120
# 一次性执行(自动审批指定工具,例如 run_command)
.venv/bin/python -m client run --task "读取 README 并总结" --auto-approve-tool run_command
python -m client run --task "读取 README 并总结" --auto-approve-tool run_command

# 脚本模式
.venv/bin/python -m client script --file /abs/path/to/demo.txt
python -m client script --file /abs/path/to/demo.txt
# 仓库内示例脚本
.venv/bin/python -m client chat --script client/examples/basic.script.txt
python -m client chat --script client/examples/basic.script.txt
# 在已有会话上继续跑脚本
.venv/bin/python -m client script --resume latest --file /abs/path/to/demo.txt
python -m client script --resume latest --file /abs/path/to/demo.txt
# 兼容入口:在指定会话上继续跑脚本
.venv/bin/python -m client script --session-id <session-id> --file /abs/path/to/demo.txt
python -m client script --session-id <session-id> --file /abs/path/to/demo.txt

# 审批控制
.venv/bin/python -m client approvals list
.venv/bin/python -m client approvals poll --timeout-ms 30000
.venv/bin/python -m client approvals grant <request_id> --scope workspace --matcher exact_params [--session-id session-id]
python -m client approvals list
python -m client approvals poll --timeout-ms 30000
python -m client approvals grant <request_id> --scope workspace --matcher exact_params [--session-id session-id]

# MCP 控制
.venv/bin/python -m client mcp list
.venv/bin/python -m client mcp inspect
.venv/bin/python -m client mcp reload
python -m client mcp list
python -m client mcp inspect
python -m client mcp reload

# 诊断(不要求模型可执行)
.venv/bin/python -m client doctor
python -m client doctor
```

## 会话持久化与 Resume
Expand Down Expand Up @@ -346,7 +348,7 @@ OpenAI-compatible / 自建模型网关:
临时切模型或切换 provider 时,可以直接用 CLI flags 覆盖文件配置:

```bash
.venv/bin/python -m client \
python -m client \
--adapter openrouter \
--model qwen/qwen3-coder:free \
--api-key "$OPENROUTER_API_KEY" \
Expand All @@ -356,15 +358,15 @@ OpenAI-compatible / 自建模型网关:
或者只临时改 endpoint:

```bash
.venv/bin/python -m client \
python -m client \
--endpoint http://127.0.0.1:8000/v1 \
run --task "读取 README 并总结"
```

临时覆盖 system prompt(完整替换):

```bash
.venv/bin/python -m client \
python -m client \
--system-prompt-mode replace \
--system-prompt-file .dare/prompts/strict_system.txt \
run --task "读取 README 并总结"
Expand All @@ -373,7 +375,7 @@ OpenAI-compatible / 自建模型网关:
临时覆盖 system prompt(在默认提示词后追加):

```bash
.venv/bin/python -m client \
python -m client \
--system-prompt-mode append \
--system-prompt-text "Always answer in Chinese unless user explicitly asks otherwise." \
chat
Expand All @@ -390,13 +392,13 @@ OpenAI-compatible / 自建模型网关:

```bash
# 查看最终生效的合并配置
.venv/bin/python -m client config show
python -m client config show

# 查看当前 runtime 选中的模型信息
.venv/bin/python -m client model show
python -m client model show

# 做环境与依赖诊断
.venv/bin/python -m client doctor
python -m client doctor
```

这三个命令分别用于:
Expand Down
20 changes: 12 additions & 8 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,35 +9,38 @@
### 快速上手(最终架构设计以权威文档为准)

```text
1. 项目概览
1. 项目入口与本地安装
└── /README.md (发行包名、导入名、本地 editable/wheel 安装、下游项目引用方式)

2. 项目概览
└── /openspec/project.md (项目上下文、技术栈、架构概览)

2. 核心架构(权威设计)
3. 核心架构(权威设计)
└── design/Architecture.md

3. 接口设计(权威设计)
4. 接口设计(权威设计)
└── design/Interfaces.md

4. 设计对齐与证据(可选但推荐)
5. 设计对齐与证据(可选但推荐)
├── design/DARE_alignment.md
└── design/DARE_evidence.yaml

5. 代码实现(以权威设计文档为准)
6. 代码实现(以权威设计文档为准)
└── dare_framework/(当前实现;若与权威设计不一致,需先修订设计并执行 gap 分析)

6. 示例实现
7. 示例实现
├── /examples/04-dare-coding-agent/ (五层循环示例 Agent)
├── /examples/06-dare-coding-agent-mcp/ (Config 驱动 MCP + 动态重载示例)
├── /examples/07-tool-approval-memory/ (工具审批记忆与自动放行示例)
└── /examples/08-hook-governance/ (Hook 治理:patch + block 示例)

7. CLI 使用与配置
8. CLI 使用与配置
├── /client/README.md (命令入口、`.dare/config.json`、LLM 配置说明)
├── /.dare/config.json.example (OpenAI 最小配置)
├── /.dare/config.openrouter.example.json (OpenRouter 最小配置)
└── /.dare/config.advanced.example.json (进阶配置示例)

8. 开发规范
9. 开发规范
├── /CONTRIBUTING_AI.md (AI Agent 协作规范)
├── guides/Development_Constraints.md (开发约束清单)
├── guides/Documentation_First_Development_SOP.md (文档先行 SOP,Bug/Feature/Refactor 必走)
Expand Down Expand Up @@ -78,6 +81,7 @@

| 文档/目录 | 作用 | 状态 |
|---|---|---|
| `/README.md` | 项目入口与本地安装说明,面向“如何在本机或下游项目中引用 `dare_framework`” | ✅ 仓库入口 |
| `dare_framework/` | 框架实现主目录(目标收敛到单一架构;详见权威设计) | ✅ 实现入口 |
| `client/README.md` | DARE Client CLI 用法与配置入口,含 `.dare/config.json` 和 LLM 配置说明 | ✅ CLI 入口 |
| `.dare/config.json.example` | OpenAI 最小配置示例,可作为 workspace `.dare/config.json` 起点 | ✅ 配置示例 |
Expand Down
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ backend-path = ["."]
name = "deterministic-agent-runtime-engine"
version = "0.1.0"
description = "DARE Framework and external CLI runtime"
readme = "README.md"
requires-python = ">=3.12"
dependencies = [
"anthropic",
Expand Down
17 changes: 17 additions & 0 deletions tests/unit/test_local_backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,21 @@ def test_local_backend_build_wheel_includes_cli_sources(tmp_path: Path) -> None:
assert any(name.endswith(".dist-info/RECORD") for name in names)


def test_local_backend_wheel_metadata_includes_markdown_readme(tmp_path: Path) -> None:
backend = importlib.import_module("_local_backend")
wheel_name = backend.build_wheel(str(tmp_path))
wheel_path = tmp_path / wheel_name

with zipfile.ZipFile(wheel_path, "r") as archive:
metadata_name = next(
name for name in archive.namelist() if name.endswith(".dist-info/METADATA")
)
metadata = archive.read(metadata_name).decode("utf-8")

assert "Description-Content-Type: text/markdown" in metadata
assert "## 本地安装" in metadata


def test_local_backend_build_sdist_includes_core_sources(tmp_path: Path) -> None:
backend = importlib.import_module("_local_backend")
sdist_name = backend.build_sdist(str(tmp_path))
Expand All @@ -44,11 +59,13 @@ def test_local_backend_build_sdist_includes_core_sources(tmp_path: Path) -> None

with tarfile.open(sdist_path, "r:gz") as archive:
names = set(archive.getnames())
root_dir = next(name.split("/", 1)[0] for name in names if name.endswith("/PKG-INFO"))
assert any(name.endswith("/pyproject.toml") for name in names)
assert any(name.endswith("/_local_backend.py") for name in names)
assert any(name.endswith("/client/main.py") for name in names)
assert any(name.endswith("/dare_framework/__init__.py") for name in names)
assert any(name.endswith("/PKG-INFO") for name in names)
assert f"{root_dir}/README.md" in names


def test_local_backend_reports_no_extra_requirements_for_build_sdist() -> None:
Expand Down
Loading