Catalog PyPI
PACKAGE EXPLORERView in registry

aer-runtime

PyPI

AER - Agent Experience Runtime (SQLite lightweight edition)

agentagent-runtimeexperiencellmretrievalsqliteverification
Versions collected1
Direct dependencies8From release 0.6.1
Last observed20 Oct, 20:49 UTC

README

Version 0.6.1 documentation

AER — Agent Experience Runtime

智能体经验运行时

AER 是一个面向 Agent 系统的轻量级经验学习基础设施。

它的目标不是构建通用 Agent,也不是让模型实时修改自身权重,而是:

将 Agent 在真实任务中的执行、失败、恢复、验证和成功过程, 转化为可追踪、可验证、可检索、可复用的经验。

核心闭环:

Task → Agent Execution → Hook Capture → Trajectory → Verification
     → Experience Distillation → Experience Store → Retrieval → Next Agent Execution

第一阶段重点是 Experience Retrieval,而不是 Model Training。

deploy python license ruff

流水线徽章指向 deploy.yml:ci.yml 只在 PR 上运行,而 deploy.yml 在 main 上重跑同一套质量门禁,所以它是"当前主线是否绿"的那个信号。


安装

要求 Python >= 3.12。

pip install aer-runtime                 # 运行时 / 独立验证 / 经验提炼 / SQLite 存储
pip install "aer-runtime[knowledge]"    # 再加上检索(NeuG 知识索引)

neug 是可选依赖,而不是把必需的东西藏进 extra:它在 PyPI 上只有 macOS(arm64) 与 Linux 的 wheel,没有 Windows 分发,声明为必需会让 Windows 上整条 pip install 直接失败 (docs/DECISIONS.md D-065)。没有它时 import aer、AER()、验证与提炼全部正常, 只有引擎侧检索不可用——引擎在 aer/knowledge/neug.py 里是惰性导入的, 测试也钉住了这一点。

从源码(要改代码时):

git clone https://github.com/Wike-CHI/aer.git
cd aer
python -m venv .venv && . .venv/bin/activate     # Windows: .venv\Scripts\activate
pip install -e ".[dev,knowledge]"                # Windows 上写 -e ".[dev]"

发行版由 GitHub Release 触发 .github/workflows/publish.yml 发布到 PyPI(Trusted Publishing,仓库里没有任何凭据)。


当前状态

Milestone 1 — Runtime Core           ✅
Milestone 2 — SQLite Storage         ✅
Milestone 3 — Hook & Trace           ✅
Milestone 4 — Verification           ✅
Milestone 5 — Experience Core        ✅
Infrastructure — Repo / Docker / CI / CD / Backup  ✅

已实现:

  • 统一枚举:RunStatus / EventType / VerifierType(按信任度声明)/ ExperienceKind / ExperienceStatus / DistillationTrigger
  • 领域模型:Run / Event / ErrorRecord / RecoveryRecord / VerificationRecord / Experience / ExperienceSource
  • SQLite 存储:WAL + 外键 + synchronous=NORMAL + busy_timeout=5000
  • 八张表:runs / events / errors / recoveries / verifications / experiences / experience_sources(+ alembic_version)
  • Schema 由 Alembic 管理(alembic upgrade head,revision 0001–0004), 不再依赖 create_all
  • Repository 层:Run / Event / Error / Recovery / Verification / Experience / ExperienceSource
  • 嵌入式运行时:AER + RunContext
  • Hook 上下文管理器:run.tool() / run.recovery(),异常路径也保证写出结束事件
  • 统一 Error 管道:run.error(),结构化落库 + 脱敏后的 stack trace
  • 独立验证引擎:run.verify() → VERIFICATION 事件 + VerificationRecord
  • 四类验证器:DETERMINISTIC(HttpStatusVerifier / H1CountVerifier / JsonValidVerifier / PredicateVerifier)、ENVIRONMENT、HUMAN、LLM
  • verified_success:区分「Agent 说完成」与「独立确认完成」
  • 经验提炼:run.distill() → Experience + ExperienceSource, 三类经验 SUCCESS / RECOVERY / FAILURE,生命周期受状态机约束
  • Event sequence 严格递增,自动写入 TASK_START / TASK_END
  • 可部署制品:Dockerfile(非 root、可丢弃、OCI 标签带 commit)、 deploy/compose.yaml、scripts/{deploy,rollback,smoke_test}.sh、 scripts/{backup,restore}_sqlite.py、.github/workflows/{ci,deploy}.yml
  • 灾备可验证:备份/恢复工具、scripts/drill_{facts,compare,seed}.py 与已真实执行过的灾难恢复演练

尚未实现(后续 Milestone):Retrieval(FTS5 / Embedding)、Experience Injection、 Experience Usage、Workflow、Dataset、完整 Sanitizer、Artifact、CLI、FastAPI、 Dashboard、模型训练。

本轮刻意不做(见 docs/DEPLOYMENT.md 第 16 节): Kubernetes / Helm / Terraform / Ansible、Redis / Kafka / PostgreSQL、 日志聚合与指标系统、HTTP healthcheck、NeuG。


快速开始

from aer import AER, EventType

aer = AER("./data")          # 自动建目录、跑迁移、打开 SQLite,无需任何服务

run = aer.start_run(
    task="Fix WordPress product page H1",
    task_type="wordpress",
    agent_name="wp-agent",
)

# 1. 工具调用:进入写 TOOL_CALL,退出写 TOOL_RESULT
with run.tool("wordpress.update_page", input={"page_id": 123}) as tool:
    tool.set_result({"status": 200})

# 2. 失败会被自动记录为 ERROR + ErrorRecord,异常照常抛出
attempt = run.tool("wordpress.update_page", input={"page_id": 123})
try:
    with attempt:
        raise PermissionError("403 Forbidden")
except PermissionError:
    pass

# 3. 恢复:显式关联到刚才那个错误,成功后自动 resolve
error = attempt.error_record
assert error is not None

with run.recovery(reason="REST API returned 403", error_id=error.id) as recovery:
    recovery.set_result({"action": "granted_edit_posts"})

# 4. 重试成功
with run.tool("wordpress.update_page", input={"page_id": 123}) as tool:
    tool.set_result({"status": 200})

# 5. Agent 声明完成 —— 这只是 Agent 的自述,还不是验证结论
run.success(final_score=1.0)
aer.close()

产生的 Trace:

1  TASK_START
2  TOOL_CALL          wordpress.update_page
3  TOOL_RESULT        success
4  TOOL_CALL          wordpress.update_page
5  ERROR              builtins.PermissionError: 403 Forbidden
6  TOOL_RESULT        success=false
7  RECOVERY_START     reason="REST API returned 403"
8  RECOVERY_RESULT    success=true
9  TOOL_CALL
10 TOOL_RESULT        success
11 TASK_END

验证:Agent 说的成功 ≠ 验证的成功

from aer import H1CountVerifier, HttpStatusVerifier, VerificationContext

run = aer.start_run(task="Fix the H1", task_type="wordpress")
with run.tool("wordpress.update_page", input={"page_id": 123}) as tool:
    tool.set_result({"status": 200})
run.success()                       # Run.status = SUCCESS

# 验证通常发生在 Agent 结束之后,甚至可以由另一个进程按 run_id 补做
ctx = VerificationContext(
    run_id=run.run_id,
    payload={"actual_status": 200, "actual_count": 0},   # 真实观测到的证据
)

run.verify(HttpStatusVerifier(expected_status=200), context=ctx)   # PASS
run.verify(H1CountVerifier(expected_count=1),      context=ctx)   # FAIL

run.status                  # SUCCESS  ← 执行事实,不会被改写
run.get_verification_summary()
# VerificationSummary(total=2, passed=1, failed=1, pass_rate=0.5, all_passed=False, ...)
run.verified_success()      # False    ← 派生判断

需要同时保存的两个事实:

Agent execution:   SUCCESS     # Agent 做完了它认为该做的事
Verification:      FAILED      # 真实环境没有满足最终要求
Verified success:  FALSE

verified_success 的定义(唯一实现见 aer/verification/summary.py):

verified_success := Run.status is SUCCESS
                    and required_total > 0        # 「没有验证」不等于「全部通过」
                    and required_failed == 0

经验:从执行轨迹到可复用知识

from aer import AER, CallableDistillationProvider, ExperienceCandidate

provider = CallableDistillationProvider(
    name="my-provider",                 # 可以是规则函数,也可以是模型适配器
    func=lambda evidence: ExperienceCandidate(
        domain="wordpress",
        title="REST API 403 on page update",
        problem="Updating a page returns 403 and the H1 never changes",
        failed_attempts=("blind retry",),
        root_cause="missing edit_posts capability",
        solution="switch to a credential with edit_posts",
        avoid=("retrying without checking permissions",),
    ),
)

with AER("./data", distillation_provider=provider) as aer:
    # ... 上面那个 Run:失败 → 恢复 → 成功 → 验证通过 ...
    experience = run.distill()

    experience.kind              # RECOVERY  ← 由运行事实决定,不是 Provider 说了算
    experience.status            # VERIFIED  ← 轨迹已提炼,且结果有证据
    experience.failed_attempts   # ('blind retry', ...)  失败尝试与修复同样重要
    experience.solution
    experience.outcome_verified  # True
    aer.experience_sources.get_runs(experience.id)   # 这条知识来自哪个 Run

三类经验(ExperienceKind):

Kind何时产生它回答什么
SUCCESS任务完成并经独立确认,且没有修复过程这样做可行
RECOVERY出现失败 → 修复 → 最终仍确认成功踩了什么坑、怎么修的
FAILURERun 失败,或 Agent 说成功而验证否定历史上哪些做法没有解决问题

关键规则:

  • 失败也会被提炼。 verified_success == false 不是丢弃的理由 —— Agent 误判成功是最有价值的信号之一。
  • FAILURE 不承载 solution。 Provider 若给了方案,会被移入 metadata 作为 假设;失败记录绝不会把未验证的猜测当作答案。
  • kind 由系统按运行事实判定,Provider 的建议会被记录并忽略。
  • 提炼幂等。 同一 Run 重复 distill() 返回同一条经验;不同 Run 的相同 claim 会合并为一个来源。重复出现让知识更强,而不是让库更大。

生命周期(aer/runtime/lifecycle.py 是唯一规则来源):

RAW ──► DISTILLED ──► VERIFIED ──► REUSED ──► PROVEN ──► TRAINING_CANDIDATE ──► TRAINING_DATA
 │          │            │
 └──────────┴────────────┴──────────► DEPRECATED(终态)

当前只有前三个状态可达:
  REUSED 及以上需要 experience_usage 统计(M7),本轮不存在任何到达它们的转换

项目结构

aer/
├── exceptions.py             # 异常层次(Verification / Experience / 配置)
├── config.py                 # 部署配置:AER_* 环境变量(不引入 Settings 框架)
├── runtime/
│   ├── enums.py              # RunStatus / EventType / VerifierType / ExperienceKind / ...
│   ├── lifecycle.py          # Experience 状态转换表(唯一规则来源)
│   ├── models.py             # 领域模型(Pydantic;Experience 为 frozen)
│   ├── serialization.py      # JSON 类型契约 + 降级 marker
│   ├── sanitization.py       # 最小脱敏与截断
│   ├── hooks.py              # ToolContext / RecoveryContext
│   ├── run.py                # RunContext(状态机 / Error 管道 / verify / distill)
│   └── runtime.py            # AER 外观类(嵌入式入口 + 装配)
├── verification/             # M4:独立验证
│   ├── base.py / deterministic.py / environment.py / human.py / llm.py
│   ├── engine.py             # 唯一验证管道
│   └── summary.py            # 汇总 + verified_success(派生)
├── experience/               # M5:经验核心
│   ├── evidence.py           # RunEvidence + RunEvidenceBuilder
│   ├── classify.py           # 由运行事实判定 kind(纯函数)
│   ├── policy.py             # DistillationPolicy(值不值得提炼)
│   ├── candidate.py          # ExperienceCandidate + 结构校验
│   ├── dedup.py              # 确定性去重指纹
│   ├── provider.py           # DistillationProvider 协议(不绑厂商)
│   ├── distiller.py          # 提炼段(调用 Provider + 规范化)
│   └── service.py            # ExperienceService(唯一提炼管道)
├── storage/
│   ├── connection.py / database.py / migrations.py / converters.py
│   ├── models.py             # 七张业务表
│   └── repositories/         # 每张表的仓储
└── schemas/                  # 预留:未来 FastAPI 的传输层 Schema

deploy/
├── compose.yaml              # 单服务、四挂载;用 `run --rm` 执行一次性命令
└── env.example               # 环境变量模板(唯一入库的环境文件,不含任何凭据)

scripts/                      # 运维入口点(按文件执行,不是可导入包)
├── backup_sqlite.py          # SQLite 在线备份 API + 完整性校验 + sidecar 元数据
├── restore_sqlite.py         # 显式恢复(必须给 --source-backup/--target)
├── smoke_test.py             # 生产冒烟:只读校验 + 临时库写路径校验
├── smoke_test.sh             # 上面脚本的薄封装
├── deploy.sh                 # 备份 → 迁移 → 冒烟 → 记录 current.env
├── rollback.sh               # 只切镜像;不自动降级数据库
├── drill_facts.py            # 只读:事实/行数/完整性/代表性记录(演练用,不可能写入)
├── drill_compare.py          # 字节差异定位 + 逻辑等价比较;--shared-tables 用于跨版本
├── drill_seed.py             # 在演练沙箱内播种每类记录各一条(演练用)
├── drill_seed_revision.py    # 版本感知播种:旧 revision 的库绝不用当前 Runtime 写
└── drill_runtime_probe.py    # 当前 Runtime 读+写探测;默认拒绝打开非 head 的库

.github/workflows/
├── ci.yml                    # PR:质量门禁 + 全新库迁移 + 镜像构建 + 容器冒烟
├── deploy.yml                # main:重跑门禁 → 推送 sha 镜像 → SSH 部署
└── publish.yml               # GitHub Release → 构建并发布到 PyPI(Trusted Publishing / OIDC)

Dockerfile / .dockerignore    # 运行镜像;数据绝不进镜像
setup.py / MANIFEST.in        # 打包 shim:构建期把 alembic.ini + migrations/ 复制进 wheel(D-064)
LICENSE / CONTRIBUTING.md / SECURITY.md

migrations/versions/          # 唯一真源;wheel 内的 aer/_migrations/ 由 setup.py 在构建期复制
├── 0001_baseline_runs_and_events.py
├── 0002_errors_and_recoveries.py
├── 0003_verifications.py
└── 0004_experiences_and_sources.py

tests/
├── runtime/                  # 领域模型 / 生命周期 / 事件 / 三个 Hook
├── verification/             # 原语 / 协议 / 引擎 / 崩溃语义 / 终态守卫 / 汇总
├── experience/               # 模型 / 状态机 / 策略 / 证据 / 提炼 / 去重 / 来源幂等
├── storage/                  # 初始化 / 迁移 / 仓储 / 持久化
├── infra/                    # 部署配置 / 备份恢复 / compose / Dockerfile / workflow
└── integration/              # M3 失败-恢复 / M4 Agent-Verifier 冲突 / M5 三类经验

data/
└── aer.db                    # 默认数据库(不纳入版本控制)

分层原则:

Business Agent
     ↓
AER SDK (AER / RunContext / ToolContext / RecoveryContext)
     ↓
Experience (Evidence / Policy / Provider / Service)     Verification (Verifier / Engine)
     ↓                                                         ↓
Repository (Run / Event / Error / Recovery / Verification / Experience / ExperienceSource)
     ↓
SQLAlchemy ORM
     ↓
SQLite
  • aer.runtime 是领域层,不 import experience / verification / 任何存储细节;
  • AER 是装配根:它是唯一同时认识所有层的地方。因此 aer.runtime 不重导出 AER(那会让 import aer.runtime.enums 拖入整个 应用图并形成真实循环)——用 from aer import AER;
  • 业务代码不得直接执行 SQL,也不得直接使用 Session。

数据库迁移

Schema 由 Alembic 管理,Base.metadata 只是 autogenerate 的输入。

# 升级到最新
alembic upgrade head

# 指定数据库(默认 ./data/aer.db)
alembic -x db_path=/tmp/other.db upgrade head
AER_DB_PATH=/tmp/other.db alembic upgrade head

# 查看当前版本
alembic current

alembic 默认在当前目录找 alembic.ini,所以上面这些命令面向源码检出与 镜像内(/app 下 alembic.ini、migrations/、aer/ 三件套齐全)。

从 wheel 安装的包没有仓库根目录:迁移脚本在包内 aer/_migrations/(构建期由 setup.py 复制,见 docs/DECISIONS.md D-064)。两种等价写法:

# 1) 直接用包内配置
alembic -c "$(python -c 'import aer,pathlib; print(pathlib.Path(aer.__file__).parent/"_migrations/alembic.ini")')" upgrade head

# 2) 不用 CLI:AER(...) 在构造时就会把库迁移到 head
python -c "from aer import AER; AER('./data').close()"

规则:

  • 修改 Schema 必须新增 revision,禁止手工改库;
  • 新增自定义列类型时,需在 migrations/env.py 的 render_item 中补充映射, 保证 revision 不引用应用代码;
  • alembic revision --autogenerate 会自动执行 ruff format + ruff check --fix;
  • Schema 与 ORM 的一致性由 tests/storage/test_migrations.py 的 parity 测试固定。

开发

运行时依赖:Python 3.12+、pydantic 2.x、SQLAlchemy 2.x、Alembic 1.13+。 开发额外需要 pytest / ruff / mypy / pyyaml(最后一个用于实际解析 compose 与 workflow 文件,而不是对它们做文本匹配)。

pip install -e ".[dev,knowledge]"   # Windows 上写 -e ".[dev]"(neug 无 Windows 分发)

pytest
ruff check .
ruff format --check .
mypy aer/

knowledge extra 只在能装 neug 的平台上加。少了它,引擎相关的用例会被跳过而不是失败, 所以本机全绿不等于 CI 全绿——CI 用的是 .[dev,knowledge]。

基础设施部分的额外校验(有 Docker 时):

docker build -t aer:local .
docker run --rm aer:local                       # 打印版本后立即退出
docker compose -f deploy/compose.yaml config    # 需要 deploy/.env

提炼的 Provider 不配置时 distill_run 会大声报错;验证与提炼的流水线本身 完全离线可测,测试套件不访问网络。


检索:SQLite 是事实源,NeuG 只是它的投影

M6 引入了一个可检索的知识索引,用的是嵌入式图数据库 NeuG。一句话的规则:

SQLite  = 唯一事实源(runs / events / errors / recoveries / verifications
          / experiences / experience_sources)
NeuG    = 从 SQLite 推导出来的可重建投影,只用来检索

没有任何一行代码把图库里的东西写回 experience store。整个 /srv/aer/knowledge 删掉,代价只是重建一次(一千条约两秒),不是丢数据。

with AER("./data", knowledge_dir="./knowledge") as aer:
    aer.project_experiences()                     # SQLite → NeuG

    result = aer.retrieve("WordPress REST API 403", domain="wordpress")
    print([hit.label for hit in result.guidance])   # ['Verified Recovery']
    print([hit.label for hit in result.warnings])   # ['Known Failure']

    print(aer.experience_context("WordPress REST API 403"))   # 直接可注入的文本

三个角色,永远不混

标签含义出现在
[Verified Success]独立验证通过、一步到位guidance
[Verified Recovery]失败后修好、且结果被验证guidance
[Known Failure]确认不管用的做法warnings
[Unverified Recovery Observation]只有 Agent 自述,没有外部确认warnings(仅 DIAGNOSTIC)

kind=FAILURE 的记录只进 warnings,而且格式上不可能输出 Solution: —— 唯一比不检索更糟的结果,是让 Agent 把"试过、没用"当成方案去执行。

运维

python -m aer.knowledge status     # 可达性 / 投影版本 / 两个计数 / drift
python -m aer.knowledge project    # 增量补齐(把还没投影的经验补上)
python -m aer.knowledge rebuild    # 从 SQLite 重建并原子替换(万能的修法)

知识库丢了、坏了、版本不符——三种情况的修法都是 rebuild, 因为它的每一个字节都能从 SQLite 推导出来。

这一版不做什么

不做 embedding、不做 HNSW 向量检索、不记录 reuse_count、不做 Workflow / Dataset。 原因见 docs/DECISIONS.md D-056 与 D-060:目前没有真实检索数据证明 BM25 不够用, 也没有数据能定义"这次检索有用"。

部署

完整步骤见 docs/DEPLOYMENT.md。要点:

Commit → PR → CI → merge main → 构建不可变镜像 → 推送 GHCR
       → SSH 生产 → 备份 SQLite → Alembic 迁移 → 生产冒烟 → 记录 deployed SHA
# 服务器上(脚本随镜像一起发布;服务器不需要 Python 和构建工具)
cd /srv/aer/deploy
bash ./deploy.sh --image ghcr.io/<owner>/aer:sha-<commit> --git-sha <commit>
bash ./deploy.sh --image ... --git-sha ... --dry-run    # 只看会做什么
bash ./rollback.sh                                      # 回到上一版本镜像

# 排障:对生产库做只读冒烟
docker compose -f compose.yaml run --rm aer-runtime python /app/scripts/smoke_test.py

灾备不是承诺,是已执行过的事实:备份已在隔离目录被真实恢复、迁移,并由线上镜像冒烟 通过(6/6);恢复也可以直接从只读挂载的备份进行,那是灾难现场的常态。

跨版本恢复也已验证:一份 revision 0003 的备份,经当前镜像恢复后前向迁移到 head (0004),历史记录逐字段保全、新表为空、当前 Runtime 既能读旧数据也能继续写新数据。 记录见 docs/DEPLOYMENT.md 第 15、16 节。

三条必须知道的边界:

  • 数据不在镜像里。 SQLite 只存在于 bind mount 的持久化目录(/srv/aer/data), 镜像随时可删可重建。
  • 服务器不构建源码。 它只 docker pull 一个由 CI 构建、带 commit 标签的镜像。
  • 回滚不降级数据库。 镜像回滚与数据库恢复是两个独立决定;不兼容时显式从迁移前 备份恢复(见 docs/DECISIONS.md D-044)。

设计约束

  • SQLite First / Embedded First / Single Process First
  • 单机可运行,不依赖 Redis、Kafka、PostgreSQL、独立向量库
  • 同步优先,确定性代码优先
  • 耗时用单调时钟(perf_counter),时刻用 timezone-aware wall clock
  • 未知 payload 降级为显式 marker,不静默 str()
  • Hook 只负责观察 / 记录 / 关联 / 测量,不含业务知识
  • 验证器 / 提炼器崩溃 ≠ 结论:崩溃记录 ERROR,绝不伪造 passed=false 或半条 Experience
  • 终态 Run 后禁止 Agent 变更,但允许 System Observation(白名单)
  • 经验状态只能经 transition_to() / mark_verified() 变更(模型 frozen)
  • kind 与 status 由确定性代码决定,模型只能提供被记录的建议
  • 所有长期数据写入前必须经过 Sanitizer(后续 Milestone 扩展)
  • 禁止 Agent 自评替代 Verifier

完整开发边界与优先级见 agent.md 与 docs/TASKS.md, 架构决策记录见 docs/DECISIONS.md(D-029 起为本轮新增)。


许可证

Apache License 2.0 —— 全文见 LICENSE。

贡献

见 CONTRIBUTING.md。里面最硬的一条:已发布的 Alembic revision 不允许改写——schema 变更必须新增 revision,并且必须让历史数据库仍然能升到 head。

安全

见 SECURITY.md。

请不要用公开 issue 报告漏洞。 首选 GitHub 的 Private Vulnerability Reporting; 该功能在本仓库尚未开启,需要管理员在 Settings → Security 中打开。在它开启之前, 请用 SECURITY.md 里的降级流程:issue 里只问私密渠道,不带任何细节。

也请读一下那里写明的设计限制——尤其是这一条:Sanitizer 尚未完整实现, 调用方传入的结构化 payload(tool input/output、event payload、verification message) 是原样落库的。不要依赖 AER 替你过滤凭据或个人信息。

Downloads & popularity

Awaiting observations

No metric observations yet

Download counts and popularity will appear here as the collector receives them.

Versions 1

Collected releases
VersionPublishedDirect dependenciesOpen version
0.6.118 Sept 20268

Package advisories

0
No matching advisories collected

This describes our collected data; it is not a security assessment.

Repository

PENDING
wike-chi/aer

Shared repository data · GitHub

License detected in repository
Not collected

Metadata history

Meaningful changes
Current collected state Current

Observed since 20 Oct, 20:49 UTC

No superseded metadata states collected. Unchanged observations do not create new revisions.

Record identity

package:fc768e137b52c1be584bca26e43102e31edf7b63bf1d6319f0a94935d526d541