返回列表
🧠 阿头学 · 🪞 Uota学

用 AI 智能体构建自我进化的对外拓展系统

将销售策略转化为版本化代码,利用 AI 基于市场反馈自动提出优化建议并由人类审核,这套“自我进化”机制在工程上极具启发,但其在复杂销售场景中的有效性被严重夸大。
打开原文 ↗

阅读简报
双语对照
完整翻译
原文
讨论归档

核心观点

  • 策略代码化:将隐性的销售判断(如信号权重、话术模板)显性化为 YAML 配置文件,使其可版本控制、可审查、可回滚。
  • 构建元循环:不要只用 AI 发邮件(第一循环),而是让 AI 读取结果日志,自动修改配置文件并提交 PR(第二循环)。
  • 评估关卡(Eval Gate):在 AI 提交修改前,必须通过预设的测试夹具(fixtures),防止 AI 盲目追逐偶然数据导致过拟合。
  • 人类保留合并权:AI 只负责提出修改建议(PR),人类负责最终审核和合并,这是防止系统失控的底线。

跟我们的关联

  • 对 ATou 意味着什么:这套“AI 提 PR,人类点 Merge”的协作模式,是目前最务实的 Agent 落地范式,可直接用于自动化运维或数据分析。
  • 对 Neta 意味着什么:将业务规则(Policy as Code)显式化,是引入 AI 优化的前置条件;没有配置化,就没有算法化。
  • 对 Uota 意味着什么:警惕“低成本评估”的陷阱,销售回复率受极多不可控因素影响,用小样本数据驱动 AI 调参极易陷入伪相关。

讨论引子

  • 在长周期、高复杂度的 B2B 销售中,如何避免 AI 因为几次偶然的回复而过度修改策略?
  • 静态的测试夹具(fixtures)能否真正应对动态变化的市场环境?

今年早些时候,Andrej Karpathy (@karpathy) 将一个智能体(agent)指向他自己的训练代码,并让它运行了两天。它运行了 700 次实验,保留了 20 个击败基准(benchmark)的实验,并使模型训练速度提高了 11%。然后他说了一句非常有趣的话:任何你可以低成本评估的指标(metric),都可以交给智能体群(agent swarm)来处理。

回复率(Reply rate)就是一个可以低成本评估的指标。从那以后,我花了一些时间来弄清楚,如果将这个循环指向对外拓展(outbound),它会是什么样子。

我的构建:

Codex 读取上周的结果(outcomes),编辑对外拓展系统运行所依赖的评分(scoring)和策略(play)文件,运行测试,并开启一个拉取请求(pull request)。它提出对策略手册(playbook)的更改,并附上证据和分数,然后等待人类批准。发送和合并操作保持在循环之外。

我已经多次构建了第一个循环:感知市场,对账户进行评分,根据信号(signal)进行撰写,检查消息,记录结果,从回复中学习。本文讨论的是第二个循环,即编辑第一个循环的那个循环。

这就是该构建的核心:将进入市场策略(GTM)作为版本化代码(versioned code),并根据市场反馈不断改进。

Northwind Finance: predicted=human_review expected=human_review
Bluepeak Studio: predicted=ignore expected=ignore
KiteOps: predicted=draft expected=draft
Atlas Recruiting: predicted=ignore expected=ignore
score=1.00

仓库(repo)

从文件夹开始。结构很重要,因为 Codex 只能改进它能读取和编辑的内容。

Northwind Finance: predicted=human_review expected=human_review
Bluepeak Studio: predicted=ignore expected=ignore
KiteOps: predicted=ignore expected=draft
Atlas Recruiting: predicted=ignore expected=ignore
score=0.75

仓库故意保持简单。config/scoring.yaml 包含决定哪些信号重要的规则。prompts/ 包含编写消息的策略。memory/outcomes.jsonl 记录了市场的反应。evals/score.py 是一个关卡(gate),用于判断提议的更改是否有帮助。AGENTS.md 是 Codex 在触碰任何东西之前必须阅读的法则。

离线运行第一个版本。没有 CRM,没有数据丰富(enrichment),没有交付系统。改进循环在接近真正的对外拓展机器之前,应该先在本地文件上证明自己。

第 1 步:先编写法则

在编写评分文件之前,在编写提示词(prompt)文件之前,先编写 AGENTS.md。这个文件能让智能体保持实用并受到约束。

Changed:
- Raised implementation_page_visit from 4 to 6.

Why:
- KiteOps had implementation-page intent and replied with implementation timing.
- The previous score routed this account to ignore.

Before:
- eval score 0.75

After:
- eval score 1.00

Reviewer check:
- Make sure implementation intent is specific enough.
- Keep generic downloads low.
- Merge only if this matches actual sales judgment.

法则只有一个任务:缩小工作范围。如果没有它,Codex 会试图通过扩大范围来提供帮助。它会添加更多数据,触碰更多文件,调用更多工具,或者自动化一个本应由人类控制的步骤。在这里,任务要小得多:读取结果,提出一个文件更改,证明它有帮助,然后等待。

好的表现是什么样的。 你可以在批准 PR 之前阅读法则,并确切知道 Codex 被允许做什么。

哪里会出问题。 法则变成了一份合规文档。如果 AGENTS.md 需要一个目录,那它就已经太大了。保持它的可操作性。

第 2 步:将判断移入配置(config)

大多数对外拓展的判断都存在于某人的脑海中。然后团队购买了软件,并期望软件能改进一个它看不见的决策。

将判断移入文件中。

name: weekly-outbound-tune

on:
  schedule:
    - cron: "0 8 * * 1"
  workflow_dispatch:

jobs:
  tune:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: "3.11"
      - run: pip install -r requirements.txt
      - run: python3 evals/score.py || true
      - run: scripts/weekly_tune.sh

这个文件最初是一个可见的假设。如果一个通用的下载应该计为零分,团队可以指向确切的代码行并更改它。如果访问实施页面是一个比你想象中更强烈的信号,Codex 可以提出差异(diff),并展示证明其合理性的结果行。

不要将这种逻辑埋藏在 Python 函数中。如果规则是可见的,团队就可以审查它、讨论它并改进它,而无需将销售判断变成工程重构(engineering refactor)。

好的表现是什么样的。 文件小到足以进行讨论。五个信号是一个很好的初始版本。

哪里会出问题。 评分文件变成了一个杂物屉。二十个信号、六个阈值以及针对每个边缘情况(edge case)的例外规则,会使改进器过拟合(overfit)。从狭窄的范围开始,让结果告诉你下一个旋钮应该放在哪里。

第 3 步:将结果写为记忆(memory)

最重要的文件是 memory/outcomes.jsonl

每次接触(touch)占一行,在结果已知时写入:

{"date":"2026-07-01","account":"Northwind Finance","signal":"competitor_comparison","play":"migration_note","score":8,"outcome":"reply","reason":"asked for migration notes"}
{"date":"2026-07-01","account":"Bluepeak Studio","signal":"generic_download","play":"resource_followup","score":1,"outcome":"no_reply","reason":"content-only intent"}
{"date":"2026-07-02","account":"KiteOps","signal":"implementation_page_visit","play":"implementation_angle","score":6,"outcome":"reply","reason":"asked about implementation timeline"}
{"date":"2026-07-02","account":"Atlas Recruiting","signal":"job_repost","play":"hiring_angle","score":5,"outcome":"bad_fit","reason":"student research request"}

reason 字段是整个重点。no_reply 几乎什么也没告诉你。content-only intent 告诉下一次运行,这个信号可能不值得起草消息。bad_fit 只有在原因解释了为什么不合适时才有用。asked about implementation timeline 则是那种可以改变权重(weight)的细节。

在构建改进器之前,先构建验证器(validator):

You improve one outbound play.

Read:
- AGENTS.md
- config/plays.yaml
- memory/outcomes.jsonl
- the prompt file for the chosen play

Pick one play with at least 10 outcomes.

Find:
- lines or structures that appear in positive outcomes
- lines or structures that appear in no_reply or bad_fit outcomes
- any phrase that should be banned

Make one small edit to that play's prompt.

Rules:
- Do not change scoring.
- Do not create a new play.
- Do not add a new channel.
- Cite outcome rows.
- Write the before and after instruction.

Then run the copy eval if present.
If no copy eval exists, open the PR as review_required.

这就是复利(compounding)开始的地方。仪表板(dashboard)可以告诉你一个活动(campaign)表现不佳。而一个干净的结果日志可以告诉 Codex,在下一次运行之前,哪个信号、策略或短语应该被更改。

好的表现是什么样的。 一周后,一个陌生人可以阅读该文件,并分辨出哪些信号带来了回复,哪些策略导致了不匹配的对话,以及哪些内部偏好被市场忽视了。

哪里会出问题。 团队在周五凭记忆回填结果。成功的案例被保留下来,不匹配的原因变得模糊,系统从虚构中学习。在结果落地时就写入该行。

第 4 步:构建评估关卡(eval gate)

在 Codex 编辑任何东西之前,它需要一个它无法敷衍过去的测试。

创建 evals/fixtures.yaml

- implementation_page_visit: 4
+ implementation_page_visit: 6

然后创建 evals/score.py

You improve the outbound scoring system.

Read:
- AGENTS.md
- config/scoring.yaml
- memory/outcomes.jsonl
- evals/fixtures.yaml

Your job:
1. Find one scoring rule that should change.
2. The reason must cite memory/outcomes.jsonl.
3. Change only config/scoring.yaml.
4. Run python3 evals/score.py.
5. If the score improves, keep the change.
6. If the score stays flat or drops, revert your change and stop.

Output:
- the exact line changed
- the outcome rows that caused it
- before score
- after score
- whether the change should become a PR

Do not edit prompts.
Do not add new signals.
Do not touch delivery.

第一个关卡应该小到易于理解,并且敏锐到足以捕捉到真正的失误。在我的第一次运行中,基线(baseline)在一个案例上失败了:

0 8 * * MON cd ~/codex-self-improving-outbound && scripts/weekly_tune.sh

这很好。系统将实施意图(implementation intent)设定在起草阈值之下,因此它忽略了一个测试夹具(fixture)认为值得发送消息的账户。在测试中发现这一点,总比错过账户一个月后才发现要好。

好的表现是什么样的。 一个命令给出一个数字,每一个失败的案例都很容易检查。

哪里会出问题。 测试夹具只包含明显的成功案例。这样一来,每一个鲁莽的更改都会通过。把丑陋的案例放进关卡中:微弱的意图、不匹配、无回复、过时的信号,以及你希望系统跳过的账户。

第 5 步:让 Codex 提出一个评分更改

现在 Codex 可以进行编辑了。

创建 prompts/improve_scoring.md

http://yourmax.ai/

通过仓库包装器(repo wrapper)运行它:

我的改进器的第一个版本犯了一个有用的错误。它追逐了看起来最干净的回复信号。在微小的结果日志中,competitor_comparison 拥有最强的回复率,所以改进器想要增加那个权重。评估(eval)停留在 0.75,因此该更改被拒绝了。

这正是关卡存在的原因。一个较弱的系统会接受这个说法,因为它听起来很合理。而这个系统提出了一个更好的问题:这个更改修复了已知的失误吗?

第二次运行找到了有帮助的最小编辑:

git clone <repo>
cd codex-self-improving-outbound
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
python3 evals/score.py
python3 scripts/propose_improvement.py

评估通过了:

这就是循环变得有用的时刻。它因为一个原因更改了一条规则,并针对测试夹具证明了该更改。

Build scripts/append_outcome.py.

It accepts:
- date
- account
- signal
- play
- score
- outcome: reply | meeting | no_reply | bad_fit | bounced
- reason

It rejects:
- missing fields
- unknown outcomes
- empty reason
- dates in the future

Append valid rows to memory/outcomes.jsonl.
Print the appended row.

好的表现是什么样的。 提议的差异是枯燥且可追踪的:更改了一行,附带了一个基于结果的原因,改进了一个评估。

哪里会出问题。 Codex 一次更改了三个权重和两个提示词。现在没人能说清哪个更改起了作用。保持法则的严格性:每个提案只涉及一个概念。

第 6 步:单独改进提示词文件

评分只是系统的一半。消息模板也会衰退。

上个月有效的句子开始让人觉得耳熟。在一个细分市场(segment)中获得回复的问题在另一个细分市场中被忽略。一个在内部感觉很犀利的短语受到了市场的惩罚。将提示词改进作为一个单独的通道,这样 Codex 就不会在同一个 PR 中混合评分和文案(copy)。

创建 config/plays.yaml

scripts/run_codex_step.sh improve_scoring

然后创建 prompts/improve_prompt.md

#!/usr/bin/env bash
set -euo pipefail

cd "$(dirname "$0")/.."

python3 evals/score.py || true
scripts/run_codex_step.sh improve_scoring
python3 evals/score.py
scripts/run_codex_step.sh pr_summary > outputs/pr-summary.md
scripts/open_pr.sh

有些改进可以自动评分。其他改进仍然需要品味。如果没有文案评估,Codex 可以提出提示词编辑,但它应该将 PR 标记为需要审查,而不是假装该编辑已被证明有效。

好的表现是什么样的。 Codex 说:“这个短语出现在七个无回复的结果中,所以我把它添加到了 banned_lines 中”,或者“积极的回复在第一句中引用了实施细节,所以我收紧了策略以要求这样做。”

哪里会出问题。 改进器因为一条消息得到了回复就重写了整个语气(voice)。提示词的编辑应该比你的直觉更小。

第 7 步:将更改作为拉取请求(pull requests)发布

这是控制层。Codex 编辑文件,运行评估,并编写 PR 摘要。人类进行审查和合并。

cases:
  - account: Northwind Finance
    signals: [competitor_comparison, implementation_page_visit]
    expected: human_review
    note: "two strong signals on one account"

  - account: Bluepeak Studio
    signals: [generic_download]
    expected: ignore
    note: "content-only intent"

  - account: KiteOps
    signals: [implementation_page_visit]
    expected: draft
    note: "implementation intent should clear draft threshold"

  - account: Atlas Recruiting
    signals: [job_repost, student_research]
    expected: ignore
    note: "bad-fit marker cancels the signal"

创建 prompts/pr_summary.md

codex-self-improving-outbound/
  AGENTS.md
  README.md
  config/
    scoring.yaml
    plays.yaml
  prompts/
    improve_scoring.md
    improve_prompt.md
    pr_summary.md
  memory/
    outcomes.jsonl
  evals/
    fixtures.yaml
    score.py
  scripts/
    append_outcome.py
    run_codex_step.sh
    propose_improvement.py
    open_pr.sh
    weekly_tune.sh
  examples/
    outcomes.sample.jsonl
    weekly-pr.md

创建 scripts/open_pr.sh

Write a pull request summary for this outbound improvement.

Include:
1. What changed.
2. Why it changed, citing outcome rows.
3. Before score.
4. After score.
5. Files changed.
6. Risk.
7. What the human reviewer should check.

Keep it short.
Do not claim the change is live.

PR 读起来应该像是一个队友写的:

score=0.75
changed config/scoring.yaml
implementation_page_visit: 4 -> 6
score=1.00
open PR for human review

这就是安全系统。Codex 完成繁琐的工作。操作员(operator)保持标准。

好的表现是什么样的。 每周一个 PR,差异很小,原因清晰,评估通过。

哪里会出问题。 有人赋予 Codex 合并的权限,因为审查感觉像是一种摩擦。那一分钟区分了一个不断改进的系统和一个随波逐流的系统。

第 8 步:将其置于节奏(cadence)中

不要每次回复后都运行它。那样系统会对一个吵闹的账户产生过拟合。

让这一周过去,让结果积累,然后进行调整(tune)。

plays:
  migration_note:
    prompt_file: prompts/plays/migration_note.md
    use_when:
      - competitor_comparison
    banned_lines:
      - "thought this might be relevant"
      - "quick question"

  implementation_angle:
    prompt_file: prompts/plays/implementation_angle.md
    use_when:
      - implementation_page_visit
    banned_lines:
      - "checking out our solution"
      - "would love to chat"

创建 scripts/weekly_tune.sh

然后设置定时任务(cron):

如果你使用 GitHub Actions,保持相同的结构:

# Self-Improving Outbound Rules

You improve an outbound system from outcomes.

Hard rules:
- Never send messages.
- Never scrape or enrich real people.
- Never self-merge.
- Edit only files in this repo.
- Change one concept at a time.
- Cite outcomes from memory/outcomes.jsonl for every proposed change.
- Improve evals/score.py before a change can become a PR.
- If the eval does not improve, revert your edit and stop.

Allowed edits:
- config/scoring.yaml
- config/plays.yaml
- prompts/*.md

Required output:
- changed files
- reason for each change
- before score
- after score
- pull request summary

手动运行前两次调整。阅读每一个差异。观察当样本稀少时 Codex 试图更改什么。一旦提案变得枯燥,就把它放到日程表中。

好的表现是什么样的。 每周出现一个 PR,附带证据、差异和评估结果。你可以合并、编辑或关闭它。

哪里会出问题。 任务运行了,没人审查,PR 堆积如山。一个自我改进的系统仍然需要一个人类习惯:阅读差异。

克隆并运行(clone-and-run)版本

仓库应该附带四个命令:

#!/usr/bin/env bash
set -euo pipefail

branch="codex/weekly-tune-$(date +%Y-%m-%d)"

git checkout -b "$branch"
git add config prompts evals memory
git commit -m "Codex weekly outbound tune"

body="$(cat outputs/pr-summary.md)"

python3 scripts/create_pr.py \
  "$branch" \
  "Codex weekly outbound tune" \
  "$body"

预期的首次运行:

Build evals/score.py.

Read config/scoring.yaml and evals/fixtures.yaml.

For each case:
1. Sum weights for every signal.
2. Add negative signal penalties.
3. Route the account:
   - score >= thresholds.human_review => human_review
   - score >= thresholds.draft => draft
   - otherwise => ignore
4. Compare route to expected.

Print each prediction.
Print final accuracy as score=0.00 to score=1.00.
Exit 0 only when accuracy is 1.00.

离线演示证明了文件契约(file contracts)。Codex 运行证明了编辑循环。之后,用你自己的结果替换样本结果,重命名信号,添加你的策略,并构建一个测试夹具,以反映你希望系统以不同方式路由的账户。

不要从连接交付(delivery)开始。从证明改进循环开始。

完整版本:max

这个仓库是手动层。它从文件、公共信号和你的 Codex 计划中运行。它教授了结构,因为每条规则都是暴露的。

yourmax.ai 是同一个系统,只是隐藏了接缝。

max 不是一个你自己组装的仓库,而是你直接使用的智能体。它检测市场的动向,决定谁值得联系以及为什么是现在,起草跨越电子邮件和 LinkedIn 的拓展信息供你批准,并根据结果不断改进。

该仓库展示了大多数团队从未构建的自我调整层:结果变成提议的规则更改,提议的规则更改通过一个关卡,人类的合并决定了什么会生效。max 采用了相同的操作逻辑,并将其作为一个托管系统(managed system)运行。

如果你想要完整的仓库,可以告诉我,我会发给你。

Earlier this year, Andrej Karpathy (@karpathy) pointed an agent at his own training code and let it run for two days. It ran 700 experiments, kept the 20 that beat the benchmark, and made the model train 11% faster. Then he said something quite interesting: any metric you can evaluate cheaply can be handed to an agent swarm.

今年早些时候,Andrej Karpathy (@karpathy) 将一个智能体(agent)指向他自己的训练代码,并让它运行了两天。它运行了 700 次实验,保留了 20 个击败基准(benchmark)的实验,并使模型训练速度提高了 11%。然后他说了一句非常有趣的话:任何你可以低成本评估的指标(metric),都可以交给智能体群(agent swarm)来处理。

Reply rate is a metric you can evaluate cheaply. I have spent some time since working out what that loop looks like pointed at outbound.

回复率(Reply rate)就是一个可以低成本评估的指标。从那以后,我花了一些时间来弄清楚,如果将这个循环指向对外拓展(outbound),它会是什么样子。

My build:

我的构建:

Codex reads last week's outcomes, edits the scoring and play files the outbound system runs on, runs a test, and opens a pull request. It proposes a change to the playbook with the evidence and the score attached, then waits for a human to approve it. Sending and merging stay outside the loop.

Codex 读取上周的结果(outcomes),编辑对外拓展系统运行所依赖的评分(scoring)和策略(play)文件,运行测试,并开启一个拉取请求(pull request)。它提出对策略手册(playbook)的更改,并附上证据和分数,然后等待人类批准。发送和合并操作保持在循环之外。

I have built the first loop a few times: sense the market, score the account, write from the signal, check the message, log the outcome, learn from the reply. This article is about the second loop, the one that edits the first.

我已经多次构建了第一个循环:感知市场,对账户进行评分,根据信号(signal)进行撰写,检查消息,记录结果,从回复中学习。本文讨论的是第二个循环,即编辑第一个循环的那个循环。

That is the build: GTM as versioned code that improves from the market.

这就是该构建的核心:将进入市场策略(GTM)作为版本化代码(versioned code),并根据市场反馈不断改进。

Northwind Finance: predicted=human_review expected=human_review
Bluepeak Studio: predicted=ignore expected=ignore
KiteOps: predicted=draft expected=draft
Atlas Recruiting: predicted=ignore expected=ignore
score=1.00
Northwind Finance: predicted=human_review expected=human_review
Bluepeak Studio: predicted=ignore expected=ignore
KiteOps: predicted=draft expected=draft
Atlas Recruiting: predicted=ignore expected=ignore
score=1.00

The repo

仓库(repo)

Start with the folder. The shape matters because Codex can only improve what it can read and edit.

从文件夹开始。结构很重要,因为 Codex 只能改进它能读取和编辑的内容。

Northwind Finance: predicted=human_review expected=human_review
Bluepeak Studio: predicted=ignore expected=ignore
KiteOps: predicted=ignore expected=draft
Atlas Recruiting: predicted=ignore expected=ignore
score=0.75
Northwind Finance: predicted=human_review expected=human_review
Bluepeak Studio: predicted=ignore expected=ignore
KiteOps: predicted=ignore expected=draft
Atlas Recruiting: predicted=ignore expected=ignore
score=0.75

The repo is intentionally plain. config/scoring.yaml holds the rules that decide which signals matter. prompts/ holds the plays that write the messages. memory/outcomes.jsonl holds what the market did. evals/score.py is the gate that says whether a proposed change helped. AGENTS.md is the law Codex reads before it touches anything.

仓库故意保持简单。config/scoring.yaml 包含决定哪些信号重要的规则。prompts/ 包含编写消息的策略。memory/outcomes.jsonl 记录了市场的反应。evals/score.py 是一个关卡(gate),用于判断提议的更改是否有帮助。AGENTS.md 是 Codex 在触碰任何东西之前必须阅读的法则。

Run the first version offline. No CRM, no enrichment, no delivery system. The improvement loop should prove itself on local files before it gets anywhere near a real outbound machine.

离线运行第一个版本。没有 CRM,没有数据丰富(enrichment),没有交付系统。改进循环在接近真正的对外拓展机器之前,应该先在本地文件上证明自己。

Step 1. Write the law first

第 1 步:先编写法则

Before the scoring file, before the prompt files, write AGENTS.md. This is the file that keeps the agent useful and contained.

在编写评分文件之前,在编写提示词(prompt)文件之前,先编写 AGENTS.md。这个文件能让智能体保持实用并受到约束。

Changed:
- Raised implementation_page_visit from 4 to 6.

Why:
- KiteOps had implementation-page intent and replied with implementation timing.
- The previous score routed this account to ignore.

Before:
- eval score 0.75

After:
- eval score 1.00

Reviewer check:
- Make sure implementation intent is specific enough.
- Keep generic downloads low.
- Merge only if this matches actual sales judgment.
Changed:
- Raised implementation_page_visit from 4 to 6.

Why:
- KiteOps had implementation-page intent and replied with implementation timing.
- The previous score routed this account to ignore.

Before:
- eval score 0.75

After:
- eval score 1.00

Reviewer check:
- Make sure implementation intent is specific enough.
- Keep generic downloads low.
- Merge only if this matches actual sales judgment.

The law has one job: narrow the work. Without it, Codex will try to help by expanding scope. It will add more data, touch more files, call more tools, or automate a step that should stay under human control. Here the job is smaller: read outcomes, propose one file change, prove it helped, then wait.

法则只有一个任务:缩小工作范围。如果没有它,Codex 会试图通过扩大范围来提供帮助。它会添加更多数据,触碰更多文件,调用更多工具,或者自动化一个本应由人类控制的步骤。在这里,任务要小得多:读取结果,提出一个文件更改,证明它有帮助,然后等待。

What good looks like. You can read the law before approving a PR and know exactly what Codex was allowed to do.

好的表现是什么样的。 你可以在批准 PR 之前阅读法则,并确切知道 Codex 被允许做什么。

Where it breaks. The law becomes a compliance document. If AGENTS.md needs a table of contents, it is already too large. Keep it operational.

哪里会出问题。 法则变成了一份合规文档。如果 AGENTS.md 需要一个目录,那它就已经太大了。保持它的可操作性。

Step 2. Move judgment into config

第 2 步:将判断移入配置(config)

Most outbound judgment lives in someone's head. Then the team buys software and expects the software to improve a decision it cannot see.

大多数对外拓展的判断都存在于某人的脑海中。然后团队购买了软件,并期望软件能改进一个它看不见的决策。

Move the judgment into a file.

将判断移入文件中。

name: weekly-outbound-tune

on:
  schedule:
    - cron: "0 8 * * 1"
  workflow_dispatch:

jobs:
  tune:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: "3.11"
      - run: pip install -r requirements.txt
      - run: python3 evals/score.py || true
      - run: scripts/weekly_tune.sh
name: weekly-outbound-tune

on:
  schedule:
    - cron: "0 8 * * 1"
  workflow_dispatch:

jobs:
  tune:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: "3.11"
      - run: pip install -r requirements.txt
      - run: python3 evals/score.py || true
      - run: scripts/weekly_tune.sh

This file starts as a visible hypothesis. If a generic download should count for zero, the team can point to the exact line and change it. If an implementation page visit is a stronger signal than you thought, Codex can propose the diff and show the outcome rows that justify it.

这个文件最初是一个可见的假设。如果一个通用的下载应该计为零分,团队可以指向确切的代码行并更改它。如果访问实施页面是一个比你想象中更强烈的信号,Codex 可以提出差异(diff),并展示证明其合理性的结果行。

Do not bury this logic in a Python function. If the rule is visible, the team can review it, argue with it, and improve it without turning a sales judgment into an engineering refactor.

不要将这种逻辑埋藏在 Python 函数中。如果规则是可见的,团队就可以审查它、讨论它并改进它,而无需将销售判断变成工程重构(engineering refactor)。

What good looks like. The file is small enough to argue with. Five signals is a good first version.

好的表现是什么样的。 文件小到足以进行讨论。五个信号是一个很好的初始版本。

Where it breaks. The scoring file becomes a junk drawer. Twenty signals, six thresholds, and exception rules for every edge case will make the improver overfit. Start narrow and let the outcomes tell you where the next knob belongs.

哪里会出问题。 评分文件变成了一个杂物屉。二十个信号、六个阈值以及针对每个边缘情况(edge case)的例外规则,会使改进器过拟合(overfit)。从狭窄的范围开始,让结果告诉你下一个旋钮应该放在哪里。

Step 3. Write outcomes as memory

第 3 步:将结果写为记忆(memory)

The most important file is memory/outcomes.jsonl.

最重要的文件是 memory/outcomes.jsonl

One line per touch, written when the outcome is known:

每次接触(touch)占一行,在结果已知时写入:

{"date":"2026-07-01","account":"Northwind Finance","signal":"competitor_comparison","play":"migration_note","score":8,"outcome":"reply","reason":"asked for migration notes"}
{"date":"2026-07-01","account":"Bluepeak Studio","signal":"generic_download","play":"resource_followup","score":1,"outcome":"no_reply","reason":"content-only intent"}
{"date":"2026-07-02","account":"KiteOps","signal":"implementation_page_visit","play":"implementation_angle","score":6,"outcome":"reply","reason":"asked about implementation timeline"}
{"date":"2026-07-02","account":"Atlas Recruiting","signal":"job_repost","play":"hiring_angle","score":5,"outcome":"bad_fit","reason":"student research request"}
{"date":"2026-07-01","account":"Northwind Finance","signal":"competitor_comparison","play":"migration_note","score":8,"outcome":"reply","reason":"asked for migration notes"}
{"date":"2026-07-01","account":"Bluepeak Studio","signal":"generic_download","play":"resource_followup","score":1,"outcome":"no_reply","reason":"content-only intent"}
{"date":"2026-07-02","account":"KiteOps","signal":"implementation_page_visit","play":"implementation_angle","score":6,"outcome":"reply","reason":"asked about implementation timeline"}
{"date":"2026-07-02","account":"Atlas Recruiting","signal":"job_repost","play":"hiring_angle","score":5,"outcome":"bad_fit","reason":"student research request"}

The reason field is the whole point. no_reply tells you almost nothing. content-only intent tells the next run that this signal might not deserve a draft. bad_fit is useful only when the reason explains why. asked about implementation timeline is the kind of detail that can change a weight.

reason 字段是整个重点。no_reply 几乎什么也没告诉你。content-only intent 告诉下一次运行,这个信号可能不值得起草消息。bad_fit 只有在原因解释了为什么不合适时才有用。asked about implementation timeline 则是那种可以改变权重(weight)的细节。

Build the validator before you build the improver:

在构建改进器之前,先构建验证器(validator):

You improve one outbound play.

Read:
- AGENTS.md
- config/plays.yaml
- memory/outcomes.jsonl
- the prompt file for the chosen play

Pick one play with at least 10 outcomes.

Find:
- lines or structures that appear in positive outcomes
- lines or structures that appear in no_reply or bad_fit outcomes
- any phrase that should be banned

Make one small edit to that play's prompt.

Rules:
- Do not change scoring.
- Do not create a new play.
- Do not add a new channel.
- Cite outcome rows.
- Write the before and after instruction.

Then run the copy eval if present.
If no copy eval exists, open the PR as review_required.
You improve one outbound play.

Read:
- AGENTS.md
- config/plays.yaml
- memory/outcomes.jsonl
- the prompt file for the chosen play

Pick one play with at least 10 outcomes.

Find:
- lines or structures that appear in positive outcomes
- lines or structures that appear in no_reply or bad_fit outcomes
- any phrase that should be banned

Make one small edit to that play's prompt.

Rules:
- Do not change scoring.
- Do not create a new play.
- Do not add a new channel.
- Cite outcome rows.
- Write the before and after instruction.

Then run the copy eval if present.
If no copy eval exists, open the PR as review_required.

This is where the compounding starts. A dashboard can tell you a campaign is down. A clean outcome log can tell Codex which signal, play, or phrase should change before the next run.

这就是复利(compounding)开始的地方。仪表板(dashboard)可以告诉你一个活动(campaign)表现不佳。而一个干净的结果日志可以告诉 Codex,在下一次运行之前,哪个信号、策略或短语应该被更改。

What good looks like. After a week, a stranger can read the file and tell which signals created replies, which plays created bad-fit conversations, and which internal favorite the market ignored.

好的表现是什么样的。 一周后,一个陌生人可以阅读该文件,并分辨出哪些信号带来了回复,哪些策略导致了不匹配的对话,以及哪些内部偏好被市场忽视了。

Where it breaks. The team backfills outcomes on Friday from memory. The wins survive, the bad-fit reasons blur, and the system learns from fiction. Write the row when the outcome lands.

哪里会出问题。 团队在周五凭记忆回填结果。成功的案例被保留下来,不匹配的原因变得模糊,系统从虚构中学习。在结果落地时就写入该行。

Step 4. Build the eval gate

第 4 步:构建评估关卡(eval gate)

Before Codex edits anything, it needs a test it cannot explain away.

在 Codex 编辑任何东西之前,它需要一个它无法敷衍过去的测试。

Create evals/fixtures.yaml:

创建 evals/fixtures.yaml

- implementation_page_visit: 4
+ implementation_page_visit: 6
- implementation_page_visit: 4
+ implementation_page_visit: 6

Then create evals/score.py:

然后创建 evals/score.py

You improve the outbound scoring system.

Read:
- AGENTS.md
- config/scoring.yaml
- memory/outcomes.jsonl
- evals/fixtures.yaml

Your job:
1. Find one scoring rule that should change.
2. The reason must cite memory/outcomes.jsonl.
3. Change only config/scoring.yaml.
4. Run python3 evals/score.py.
5. If the score improves, keep the change.
6. If the score stays flat or drops, revert your change and stop.

Output:
- the exact line changed
- the outcome rows that caused it
- before score
- after score
- whether the change should become a PR

Do not edit prompts.
Do not add new signals.
Do not touch delivery.
You improve the outbound scoring system.

Read:
- AGENTS.md
- config/scoring.yaml
- memory/outcomes.jsonl
- evals/fixtures.yaml

Your job:
1. Find one scoring rule that should change.
2. The reason must cite memory/outcomes.jsonl.
3. Change only config/scoring.yaml.
4. Run python3 evals/score.py.
5. If the score improves, keep the change.
6. If the score stays flat or drops, revert your change and stop.

Output:
- the exact line changed
- the outcome rows that caused it
- before score
- after score
- whether the change should become a PR

Do not edit prompts.
Do not add new signals.
Do not touch delivery.

The first gate should be small enough to understand and sharp enough to catch a real miss. In my first run, the baseline failed one case:

第一个关卡应该小到易于理解,并且敏锐到足以捕捉到真正的失误。在我的第一次运行中,基线(baseline)在一个案例上失败了:

0 8 * * MON cd ~/codex-self-improving-outbound && scripts/weekly_tune.sh
0 8 * * MON cd ~/codex-self-improving-outbound && scripts/weekly_tune.sh

That was good. The system had implementation intent below the draft threshold, so it ignored an account the fixture said deserved a message. Better to catch that in a test than after a month of missed accounts.

这很好。系统将实施意图(implementation intent)设定在起草阈值之下,因此它忽略了一个测试夹具(fixture)认为值得发送消息的账户。在测试中发现这一点,总比错过账户一个月后才发现要好。

What good looks like. One command gives one number, and every failed case is easy to inspect.

好的表现是什么样的。 一个命令给出一个数字,每一个失败的案例都很容易检查。

Where it breaks. The fixture only includes obvious wins. Then every reckless change passes. Put ugly cases in the gate: weak intent, bad fit, no reply, stale signals, and the accounts you wish the system had skipped.

哪里会出问题。 测试夹具只包含明显的成功案例。这样一来,每一个鲁莽的更改都会通过。把丑陋的案例放进关卡中:微弱的意图、不匹配、无回复、过时的信号,以及你希望系统跳过的账户。

Step 5. Let Codex propose one scoring change

第 5 步:让 Codex 提出一个评分更改

Now Codex can edit.

现在 Codex 可以进行编辑了。

Create prompts/improve_scoring.md:

创建 prompts/improve_scoring.md

Run it through the repo wrapper:

通过仓库包装器(repo wrapper)运行它:

The first version of my improver made a useful mistake. It chased the cleanest-looking reply signal. competitor_comparison had the strongest reply rate in the tiny outcome log, so the improver wanted to increase that weight. The eval stayed at 0.75, so the change was rejected.

我的改进器的第一个版本犯了一个有用的错误。它追逐了看起来最干净的回复信号。在微小的结果日志中,competitor_comparison 拥有最强的回复率,所以改进器想要增加那个权重。评估(eval)停留在 0.75,因此该更改被拒绝了。

That is exactly why the gate exists. A weaker system would have accepted the story because it sounded reasonable. This one asked a better question: did the change fix the known miss?

这正是关卡存在的原因。一个较弱的系统会接受这个说法,因为它听起来很合理。而这个系统提出了一个更好的问题:这个更改修复了已知的失误吗?

The second pass found the smallest edit that helped:

第二次运行找到了有帮助的最小编辑:

git clone <repo>
cd codex-self-improving-outbound
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
python3 evals/score.py
python3 scripts/propose_improvement.py
git clone <repo>
cd codex-self-improving-outbound
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
python3 evals/score.py
python3 scripts/propose_improvement.py

The eval passed:

评估通过了:

That is the moment the loop becomes useful. It changed one rule, for one reason, and proved the change against a fixture.

这就是循环变得有用的时刻。它因为一个原因更改了一条规则,并针对测试夹具证明了该更改。

Build scripts/append_outcome.py.

It accepts:
- date
- account
- signal
- play
- score
- outcome: reply | meeting | no_reply | bad_fit | bounced
- reason

It rejects:
- missing fields
- unknown outcomes
- empty reason
- dates in the future

Append valid rows to memory/outcomes.jsonl.
Print the appended row.
Build scripts/append_outcome.py.

It accepts:
- date
- account
- signal
- play
- score
- outcome: reply | meeting | no_reply | bad_fit | bounced
- reason

It rejects:
- missing fields
- unknown outcomes
- empty reason
- dates in the future

Append valid rows to memory/outcomes.jsonl.
Print the appended row.

What good looks like. The proposed diff is boring and traceable: one line changed, one outcome-backed reason attached, one eval improved.

好的表现是什么样的。 提议的差异是枯燥且可追踪的:更改了一行,附带了一个基于结果的原因,改进了一个评估。

Where it breaks. Codex changes three weights and two prompts at once. Now nobody can tell which change helped. Keep the law strict: one concept per proposal.

哪里会出问题。 Codex 一次更改了三个权重和两个提示词。现在没人能说清哪个更改起了作用。保持法则的严格性:每个提案只涉及一个概念。

Step 6. Improve prompt files separately

第 6 步:单独改进提示词文件

Scoring is only half the system. The message templates decay too.

评分只是系统的一半。消息模板也会衰退。

A line that worked last month starts sounding familiar. A question that earns replies in one segment gets ignored in another. A phrase that feels sharp internally gets punished by the market. Treat prompt improvement as a separate lane so Codex does not mix scoring and copy in the same PR.

上个月有效的句子开始让人觉得耳熟。在一个细分市场(segment)中获得回复的问题在另一个细分市场中被忽略。一个在内部感觉很犀利的短语受到了市场的惩罚。将提示词改进作为一个单独的通道,这样 Codex 就不会在同一个 PR 中混合评分和文案(copy)。

Create config/plays.yaml:

创建 config/plays.yaml

scripts/run_codex_step.sh improve_scoring
scripts/run_codex_step.sh improve_scoring

Then create prompts/improve_prompt.md:

然后创建 prompts/improve_prompt.md

#!/usr/bin/env bash
set -euo pipefail

cd "$(dirname "$0")/.."

python3 evals/score.py || true
scripts/run_codex_step.sh improve_scoring
python3 evals/score.py
scripts/run_codex_step.sh pr_summary > outputs/pr-summary.md
scripts/open_pr.sh
#!/usr/bin/env bash
set -euo pipefail

cd "$(dirname "$0")/.."

python3 evals/score.py || true
scripts/run_codex_step.sh improve_scoring
python3 evals/score.py
scripts/run_codex_step.sh pr_summary > outputs/pr-summary.md
scripts/open_pr.sh

Some improvements can be scored automatically. Others still need taste. If there is no copy eval, Codex can propose the prompt edit, but it should mark the PR for review instead of pretending the edit is proven.

有些改进可以自动评分。其他改进仍然需要品味。如果没有文案评估,Codex 可以提出提示词编辑,但它应该将 PR 标记为需要审查,而不是假装该编辑已被证明有效。

What good looks like. Codex says, "This phrase appeared in seven no-reply outcomes, so I added it to banned_lines," or "positive replies quoted the implementation detail in sentence one, so I tightened the play to require that."

好的表现是什么样的。 Codex 说:“这个短语出现在七个无回复的结果中,所以我把它添加到了 banned_lines 中”,或者“积极的回复在第一句中引用了实施细节,所以我收紧了策略以要求这样做。”

Where it breaks. The improver rewrites the whole voice because one message got a reply. Prompt edits should be smaller than your instinct.

哪里会出问题。 改进器因为一条消息得到了回复就重写了整个语气(voice)。提示词的编辑应该比你的直觉更小。

Step 7. Ship changes as pull requests

第 7 步:将更改作为拉取请求(pull requests)发布

This is the control layer. Codex edits files, runs the eval, and writes the PR summary. A human reviews and merges.

这是控制层。Codex 编辑文件,运行评估,并编写 PR 摘要。人类进行审查和合并。

cases:
  - account: Northwind Finance
    signals: [competitor_comparison, implementation_page_visit]
    expected: human_review
    note: "two strong signals on one account"

  - account: Bluepeak Studio
    signals: [generic_download]
    expected: ignore
    note: "content-only intent"

  - account: KiteOps
    signals: [implementation_page_visit]
    expected: draft
    note: "implementation intent should clear draft threshold"

  - account: Atlas Recruiting
    signals: [job_repost, student_research]
    expected: ignore
    note: "bad-fit marker cancels the signal"
cases:
  - account: Northwind Finance
    signals: [competitor_comparison, implementation_page_visit]
    expected: human_review
    note: "two strong signals on one account"

  - account: Bluepeak Studio
    signals: [generic_download]
    expected: ignore
    note: "content-only intent"

  - account: KiteOps
    signals: [implementation_page_visit]
    expected: draft
    note: "implementation intent should clear draft threshold"

  - account: Atlas Recruiting
    signals: [job_repost, student_research]
    expected: ignore
    note: "bad-fit marker cancels the signal"

Create prompts/pr_summary.md:

创建 prompts/pr_summary.md

codex-self-improving-outbound/
  AGENTS.md
  README.md
  config/
    scoring.yaml
    plays.yaml
  prompts/
    improve_scoring.md
    improve_prompt.md
    pr_summary.md
  memory/
    outcomes.jsonl
  evals/
    fixtures.yaml
    score.py
  scripts/
    append_outcome.py
    run_codex_step.sh
    propose_improvement.py
    open_pr.sh
    weekly_tune.sh
  examples/
    outcomes.sample.jsonl
    weekly-pr.md
codex-self-improving-outbound/
  AGENTS.md
  README.md
  config/
    scoring.yaml
    plays.yaml
  prompts/
    improve_scoring.md
    improve_prompt.md
    pr_summary.md
  memory/
    outcomes.jsonl
  evals/
    fixtures.yaml
    score.py
  scripts/
    append_outcome.py
    run_codex_step.sh
    propose_improvement.py
    open_pr.sh
    weekly_tune.sh
  examples/
    outcomes.sample.jsonl
    weekly-pr.md

Create scripts/open_pr.sh:

创建 scripts/open_pr.sh

Write a pull request summary for this outbound improvement.

Include:
1. What changed.
2. Why it changed, citing outcome rows.
3. Before score.
4. After score.
5. Files changed.
6. Risk.
7. What the human reviewer should check.

Keep it short.
Do not claim the change is live.
Write a pull request summary for this outbound improvement.

Include:
1. What changed.
2. Why it changed, citing outcome rows.
3. Before score.
4. After score.
5. Files changed.
6. Risk.
7. What the human reviewer should check.

Keep it short.
Do not claim the change is live.

The PR should read like a teammate wrote it:

PR 读起来应该像是一个队友写的:

score=0.75
changed config/scoring.yaml
implementation_page_visit: 4 -> 6
score=1.00
open PR for human review
score=0.75
changed config/scoring.yaml
implementation_page_visit: 4 -> 6
score=1.00
open PR for human review

That is the safety system. Codex does the tedious work. The operator keeps the standard.

这就是安全系统。Codex 完成繁琐的工作。操作员(operator)保持标准。

What good looks like. One PR a week, small diff, clear reason, passing eval.

好的表现是什么样的。 每周一个 PR,差异很小,原因清晰,评估通过。

Where it breaks. Someone gives Codex permission to merge because review feels like friction. That minute separates a system that improves from a system that drifts.

哪里会出问题。 有人赋予 Codex 合并的权限,因为审查感觉像是一种摩擦。那一分钟区分了一个不断改进的系统和一个随波逐流的系统。

Step 8. Put it on a cadence

第 8 步:将其置于节奏(cadence)中

Do not run this after every reply. That is how a system overfits to one loud account.

不要每次回复后都运行它。那样系统会对一个吵闹的账户产生过拟合。

Let the week happen, let outcomes accumulate, then tune.

让这一周过去,让结果积累,然后进行调整(tune)。

plays:
  migration_note:
    prompt_file: prompts/plays/migration_note.md
    use_when:
      - competitor_comparison
    banned_lines:
      - "thought this might be relevant"
      - "quick question"

  implementation_angle:
    prompt_file: prompts/plays/implementation_angle.md
    use_when:
      - implementation_page_visit
    banned_lines:
      - "checking out our solution"
      - "would love to chat"
plays:
  migration_note:
    prompt_file: prompts/plays/migration_note.md
    use_when:
      - competitor_comparison
    banned_lines:
      - "thought this might be relevant"
      - "quick question"

  implementation_angle:
    prompt_file: prompts/plays/implementation_angle.md
    use_when:
      - implementation_page_visit
    banned_lines:
      - "checking out our solution"
      - "would love to chat"

Create scripts/weekly_tune.sh:

创建 scripts/weekly_tune.sh

Then cron:

然后设置定时任务(cron):

If you use GitHub Actions, keep the same shape:

如果你使用 GitHub Actions,保持相同的结构:

# Self-Improving Outbound Rules

You improve an outbound system from outcomes.

Hard rules:
- Never send messages.
- Never scrape or enrich real people.
- Never self-merge.
- Edit only files in this repo.
- Change one concept at a time.
- Cite outcomes from memory/outcomes.jsonl for every proposed change.
- Improve evals/score.py before a change can become a PR.
- If the eval does not improve, revert your edit and stop.

Allowed edits:
- config/scoring.yaml
- config/plays.yaml
- prompts/*.md

Required output:
- changed files
- reason for each change
- before score
- after score
- pull request summary
# Self-Improving Outbound Rules

You improve an outbound system from outcomes.

Hard rules:
- Never send messages.
- Never scrape or enrich real people.
- Never self-merge.
- Edit only files in this repo.
- Change one concept at a time.
- Cite outcomes from memory/outcomes.jsonl for every proposed change.
- Improve evals/score.py before a change can become a PR.
- If the eval does not improve, revert your edit and stop.

Allowed edits:
- config/scoring.yaml
- config/plays.yaml
- prompts/*.md

Required output:
- changed files
- reason for each change
- before score
- after score
- pull request summary

Run the first two tune-ups by hand. Read every diff. Watch what Codex tries to change when the sample is thin. Once the proposals are boring, put it on a schedule.

手动运行前两次调整。阅读每一个差异。观察当样本稀少时 Codex 试图更改什么。一旦提案变得枯燥,就把它放到日程表中。

What good looks like. A weekly PR appears with the evidence, the diff, and the eval result. You merge, edit, or close it.

好的表现是什么样的。 每周出现一个 PR,附带证据、差异和评估结果。你可以合并、编辑或关闭它。

Where it breaks. The job runs, nobody reviews, and PRs pile up. A self-improving system still has one human habit: read the diff.

哪里会出问题。 任务运行了,没人审查,PR 堆积如山。一个自我改进的系统仍然需要一个人类习惯:阅读差异。

The clone-and-run version

克隆并运行(clone-and-run)版本

The repo should ship with four commands:

仓库应该附带四个命令:

#!/usr/bin/env bash
set -euo pipefail

branch="codex/weekly-tune-$(date +%Y-%m-%d)"

git checkout -b "$branch"
git add config prompts evals memory
git commit -m "Codex weekly outbound tune"

body="$(cat outputs/pr-summary.md)"

python3 scripts/create_pr.py \
  "$branch" \
  "Codex weekly outbound tune" \
  "$body"
#!/usr/bin/env bash
set -euo pipefail

branch="codex/weekly-tune-$(date +%Y-%m-%d)"

git checkout -b "$branch"
git add config prompts evals memory
git commit -m "Codex weekly outbound tune"

body="$(cat outputs/pr-summary.md)"

python3 scripts/create_pr.py \
  "$branch" \
  "Codex weekly outbound tune" \
  "$body"

Expected first run:

预期的首次运行:

Build evals/score.py.

Read config/scoring.yaml and evals/fixtures.yaml.

For each case:
1. Sum weights for every signal.
2. Add negative signal penalties.
3. Route the account:
   - score >= thresholds.human_review => human_review
   - score >= thresholds.draft => draft
   - otherwise => ignore
4. Compare route to expected.

Print each prediction.
Print final accuracy as score=0.00 to score=1.00.
Exit 0 only when accuracy is 1.00.
Build evals/score.py.

Read config/scoring.yaml and evals/fixtures.yaml.

For each case:
1. Sum weights for every signal.
2. Add negative signal penalties.
3. Route the account:
   - score >= thresholds.human_review => human_review
   - score >= thresholds.draft => draft
   - otherwise => ignore
4. Compare route to expected.

Print each prediction.
Print final accuracy as score=0.00 to score=1.00.
Exit 0 only when accuracy is 1.00.

The offline demo proves the file contracts. The Codex run proves the editing loop. After that, replace the sample outcomes with your own, rename the signals, add your plays, and build a fixture that reflects the accounts you wish the system had routed differently.

离线演示证明了文件契约(file contracts)。Codex 运行证明了编辑循环。之后,用你自己的结果替换样本结果,重命名信号,添加你的策略,并构建一个测试夹具,以反映你希望系统以不同方式路由的账户。

Do not start by wiring delivery. Start by proving the improvement loop.

不要从连接交付(delivery)开始。从证明改进循环开始。

The full version: max

完整版本:max

This repo is the manual layer. It runs from files, public signals, and your Codex plan. It teaches the shape because every rule is exposed.

这个仓库是手动层。它从文件、公共信号和你的 Codex 计划中运行。它教授了结构,因为每条规则都是暴露的。

yourmax.ai is the same system with the seams hidden.

yourmax.ai 是同一个系统,只是隐藏了接缝。

Instead of a repo you wire together yourself, max is the agent you use directly. It detects movement in the market, decides who is worth contacting and why now, drafts the outreach across email and LinkedIn for your approval, and keeps improving from the outcomes.

max 不是一个你自己组装的仓库,而是你直接使用的智能体。它检测市场的动向,决定谁值得联系以及为什么是现在,起草跨越电子邮件和 LinkedIn 的拓展信息供你批准,并根据结果不断改进。

The repo shows the self-tuning layer most teams never build: outcomes become proposed rule changes, proposed rule changes run through a gate, and the human merge decides what becomes live. max takes that same operating logic and runs it as a managed system.

该仓库展示了大多数团队从未构建的自我调整层:结果变成提议的规则更改,提议的规则更改通过一个关卡,人类的合并决定了什么会生效。max 采用了相同的操作逻辑,并将其作为一个托管系统(managed system)运行。

If you want the full repo, you can let me know and I will send it your way.

如果你想要完整的仓库,可以告诉我,我会发给你。

Earlier this year, Andrej Karpathy (@karpathy) pointed an agent at his own training code and let it run for two days. It ran 700 experiments, kept the 20 that beat the benchmark, and made the model train 11% faster. Then he said something quite interesting: any metric you can evaluate cheaply can be handed to an agent swarm.

Reply rate is a metric you can evaluate cheaply. I have spent some time since working out what that loop looks like pointed at outbound.

My build:

Codex reads last week's outcomes, edits the scoring and play files the outbound system runs on, runs a test, and opens a pull request. It proposes a change to the playbook with the evidence and the score attached, then waits for a human to approve it. Sending and merging stay outside the loop.

I have built the first loop a few times: sense the market, score the account, write from the signal, check the message, log the outcome, learn from the reply. This article is about the second loop, the one that edits the first.

That is the build: GTM as versioned code that improves from the market.

Northwind Finance: predicted=human_review expected=human_review
Bluepeak Studio: predicted=ignore expected=ignore
KiteOps: predicted=draft expected=draft
Atlas Recruiting: predicted=ignore expected=ignore
score=1.00

The repo

Start with the folder. The shape matters because Codex can only improve what it can read and edit.

Northwind Finance: predicted=human_review expected=human_review
Bluepeak Studio: predicted=ignore expected=ignore
KiteOps: predicted=ignore expected=draft
Atlas Recruiting: predicted=ignore expected=ignore
score=0.75

The repo is intentionally plain. config/scoring.yaml holds the rules that decide which signals matter. prompts/ holds the plays that write the messages. memory/outcomes.jsonl holds what the market did. evals/score.py is the gate that says whether a proposed change helped. AGENTS.md is the law Codex reads before it touches anything.

Run the first version offline. No CRM, no enrichment, no delivery system. The improvement loop should prove itself on local files before it gets anywhere near a real outbound machine.

Step 1. Write the law first

Before the scoring file, before the prompt files, write AGENTS.md. This is the file that keeps the agent useful and contained.

Changed:
- Raised implementation_page_visit from 4 to 6.

Why:
- KiteOps had implementation-page intent and replied with implementation timing.
- The previous score routed this account to ignore.

Before:
- eval score 0.75

After:
- eval score 1.00

Reviewer check:
- Make sure implementation intent is specific enough.
- Keep generic downloads low.
- Merge only if this matches actual sales judgment.

The law has one job: narrow the work. Without it, Codex will try to help by expanding scope. It will add more data, touch more files, call more tools, or automate a step that should stay under human control. Here the job is smaller: read outcomes, propose one file change, prove it helped, then wait.

What good looks like. You can read the law before approving a PR and know exactly what Codex was allowed to do.

Where it breaks. The law becomes a compliance document. If AGENTS.md needs a table of contents, it is already too large. Keep it operational.

Step 2. Move judgment into config

Most outbound judgment lives in someone's head. Then the team buys software and expects the software to improve a decision it cannot see.

Move the judgment into a file.

name: weekly-outbound-tune

on:
  schedule:
    - cron: "0 8 * * 1"
  workflow_dispatch:

jobs:
  tune:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: "3.11"
      - run: pip install -r requirements.txt
      - run: python3 evals/score.py || true
      - run: scripts/weekly_tune.sh

This file starts as a visible hypothesis. If a generic download should count for zero, the team can point to the exact line and change it. If an implementation page visit is a stronger signal than you thought, Codex can propose the diff and show the outcome rows that justify it.

Do not bury this logic in a Python function. If the rule is visible, the team can review it, argue with it, and improve it without turning a sales judgment into an engineering refactor.

What good looks like. The file is small enough to argue with. Five signals is a good first version.

Where it breaks. The scoring file becomes a junk drawer. Twenty signals, six thresholds, and exception rules for every edge case will make the improver overfit. Start narrow and let the outcomes tell you where the next knob belongs.

Step 3. Write outcomes as memory

The most important file is memory/outcomes.jsonl.

One line per touch, written when the outcome is known:

{"date":"2026-07-01","account":"Northwind Finance","signal":"competitor_comparison","play":"migration_note","score":8,"outcome":"reply","reason":"asked for migration notes"}
{"date":"2026-07-01","account":"Bluepeak Studio","signal":"generic_download","play":"resource_followup","score":1,"outcome":"no_reply","reason":"content-only intent"}
{"date":"2026-07-02","account":"KiteOps","signal":"implementation_page_visit","play":"implementation_angle","score":6,"outcome":"reply","reason":"asked about implementation timeline"}
{"date":"2026-07-02","account":"Atlas Recruiting","signal":"job_repost","play":"hiring_angle","score":5,"outcome":"bad_fit","reason":"student research request"}

The reason field is the whole point. no_reply tells you almost nothing. content-only intent tells the next run that this signal might not deserve a draft. bad_fit is useful only when the reason explains why. asked about implementation timeline is the kind of detail that can change a weight.

Build the validator before you build the improver:

You improve one outbound play.

Read:
- AGENTS.md
- config/plays.yaml
- memory/outcomes.jsonl
- the prompt file for the chosen play

Pick one play with at least 10 outcomes.

Find:
- lines or structures that appear in positive outcomes
- lines or structures that appear in no_reply or bad_fit outcomes
- any phrase that should be banned

Make one small edit to that play's prompt.

Rules:
- Do not change scoring.
- Do not create a new play.
- Do not add a new channel.
- Cite outcome rows.
- Write the before and after instruction.

Then run the copy eval if present.
If no copy eval exists, open the PR as review_required.

This is where the compounding starts. A dashboard can tell you a campaign is down. A clean outcome log can tell Codex which signal, play, or phrase should change before the next run.

What good looks like. After a week, a stranger can read the file and tell which signals created replies, which plays created bad-fit conversations, and which internal favorite the market ignored.

Where it breaks. The team backfills outcomes on Friday from memory. The wins survive, the bad-fit reasons blur, and the system learns from fiction. Write the row when the outcome lands.

Step 4. Build the eval gate

Before Codex edits anything, it needs a test it cannot explain away.

Create evals/fixtures.yaml:

- implementation_page_visit: 4
+ implementation_page_visit: 6

Then create evals/score.py:

You improve the outbound scoring system.

Read:
- AGENTS.md
- config/scoring.yaml
- memory/outcomes.jsonl
- evals/fixtures.yaml

Your job:
1. Find one scoring rule that should change.
2. The reason must cite memory/outcomes.jsonl.
3. Change only config/scoring.yaml.
4. Run python3 evals/score.py.
5. If the score improves, keep the change.
6. If the score stays flat or drops, revert your change and stop.

Output:
- the exact line changed
- the outcome rows that caused it
- before score
- after score
- whether the change should become a PR

Do not edit prompts.
Do not add new signals.
Do not touch delivery.

The first gate should be small enough to understand and sharp enough to catch a real miss. In my first run, the baseline failed one case:

0 8 * * MON cd ~/codex-self-improving-outbound && scripts/weekly_tune.sh

That was good. The system had implementation intent below the draft threshold, so it ignored an account the fixture said deserved a message. Better to catch that in a test than after a month of missed accounts.

What good looks like. One command gives one number, and every failed case is easy to inspect.

Where it breaks. The fixture only includes obvious wins. Then every reckless change passes. Put ugly cases in the gate: weak intent, bad fit, no reply, stale signals, and the accounts you wish the system had skipped.

Step 5. Let Codex propose one scoring change

Now Codex can edit.

Create prompts/improve_scoring.md:

http://yourmax.ai/

Run it through the repo wrapper:

The first version of my improver made a useful mistake. It chased the cleanest-looking reply signal. competitor_comparison had the strongest reply rate in the tiny outcome log, so the improver wanted to increase that weight. The eval stayed at 0.75, so the change was rejected.

That is exactly why the gate exists. A weaker system would have accepted the story because it sounded reasonable. This one asked a better question: did the change fix the known miss?

The second pass found the smallest edit that helped:

git clone <repo>
cd codex-self-improving-outbound
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
python3 evals/score.py
python3 scripts/propose_improvement.py

The eval passed:

That is the moment the loop becomes useful. It changed one rule, for one reason, and proved the change against a fixture.

Build scripts/append_outcome.py.

It accepts:
- date
- account
- signal
- play
- score
- outcome: reply | meeting | no_reply | bad_fit | bounced
- reason

It rejects:
- missing fields
- unknown outcomes
- empty reason
- dates in the future

Append valid rows to memory/outcomes.jsonl.
Print the appended row.

What good looks like. The proposed diff is boring and traceable: one line changed, one outcome-backed reason attached, one eval improved.

Where it breaks. Codex changes three weights and two prompts at once. Now nobody can tell which change helped. Keep the law strict: one concept per proposal.

Step 6. Improve prompt files separately

Scoring is only half the system. The message templates decay too.

A line that worked last month starts sounding familiar. A question that earns replies in one segment gets ignored in another. A phrase that feels sharp internally gets punished by the market. Treat prompt improvement as a separate lane so Codex does not mix scoring and copy in the same PR.

Create config/plays.yaml:

scripts/run_codex_step.sh improve_scoring

Then create prompts/improve_prompt.md:

#!/usr/bin/env bash
set -euo pipefail

cd "$(dirname "$0")/.."

python3 evals/score.py || true
scripts/run_codex_step.sh improve_scoring
python3 evals/score.py
scripts/run_codex_step.sh pr_summary > outputs/pr-summary.md
scripts/open_pr.sh

Some improvements can be scored automatically. Others still need taste. If there is no copy eval, Codex can propose the prompt edit, but it should mark the PR for review instead of pretending the edit is proven.

What good looks like. Codex says, "This phrase appeared in seven no-reply outcomes, so I added it to banned_lines," or "positive replies quoted the implementation detail in sentence one, so I tightened the play to require that."

Where it breaks. The improver rewrites the whole voice because one message got a reply. Prompt edits should be smaller than your instinct.

Step 7. Ship changes as pull requests

This is the control layer. Codex edits files, runs the eval, and writes the PR summary. A human reviews and merges.

cases:
  - account: Northwind Finance
    signals: [competitor_comparison, implementation_page_visit]
    expected: human_review
    note: "two strong signals on one account"

  - account: Bluepeak Studio
    signals: [generic_download]
    expected: ignore
    note: "content-only intent"

  - account: KiteOps
    signals: [implementation_page_visit]
    expected: draft
    note: "implementation intent should clear draft threshold"

  - account: Atlas Recruiting
    signals: [job_repost, student_research]
    expected: ignore
    note: "bad-fit marker cancels the signal"

Create prompts/pr_summary.md:

codex-self-improving-outbound/
  AGENTS.md
  README.md
  config/
    scoring.yaml
    plays.yaml
  prompts/
    improve_scoring.md
    improve_prompt.md
    pr_summary.md
  memory/
    outcomes.jsonl
  evals/
    fixtures.yaml
    score.py
  scripts/
    append_outcome.py
    run_codex_step.sh
    propose_improvement.py
    open_pr.sh
    weekly_tune.sh
  examples/
    outcomes.sample.jsonl
    weekly-pr.md

Create scripts/open_pr.sh:

Write a pull request summary for this outbound improvement.

Include:
1. What changed.
2. Why it changed, citing outcome rows.
3. Before score.
4. After score.
5. Files changed.
6. Risk.
7. What the human reviewer should check.

Keep it short.
Do not claim the change is live.

The PR should read like a teammate wrote it:

score=0.75
changed config/scoring.yaml
implementation_page_visit: 4 -> 6
score=1.00
open PR for human review

That is the safety system. Codex does the tedious work. The operator keeps the standard.

What good looks like. One PR a week, small diff, clear reason, passing eval.

Where it breaks. Someone gives Codex permission to merge because review feels like friction. That minute separates a system that improves from a system that drifts.

Step 8. Put it on a cadence

Do not run this after every reply. That is how a system overfits to one loud account.

Let the week happen, let outcomes accumulate, then tune.

plays:
  migration_note:
    prompt_file: prompts/plays/migration_note.md
    use_when:
      - competitor_comparison
    banned_lines:
      - "thought this might be relevant"
      - "quick question"

  implementation_angle:
    prompt_file: prompts/plays/implementation_angle.md
    use_when:
      - implementation_page_visit
    banned_lines:
      - "checking out our solution"
      - "would love to chat"

Create scripts/weekly_tune.sh:

Then cron:

If you use GitHub Actions, keep the same shape:

# Self-Improving Outbound Rules

You improve an outbound system from outcomes.

Hard rules:
- Never send messages.
- Never scrape or enrich real people.
- Never self-merge.
- Edit only files in this repo.
- Change one concept at a time.
- Cite outcomes from memory/outcomes.jsonl for every proposed change.
- Improve evals/score.py before a change can become a PR.
- If the eval does not improve, revert your edit and stop.

Allowed edits:
- config/scoring.yaml
- config/plays.yaml
- prompts/*.md

Required output:
- changed files
- reason for each change
- before score
- after score
- pull request summary

Run the first two tune-ups by hand. Read every diff. Watch what Codex tries to change when the sample is thin. Once the proposals are boring, put it on a schedule.

What good looks like. A weekly PR appears with the evidence, the diff, and the eval result. You merge, edit, or close it.

Where it breaks. The job runs, nobody reviews, and PRs pile up. A self-improving system still has one human habit: read the diff.

The clone-and-run version

The repo should ship with four commands:

#!/usr/bin/env bash
set -euo pipefail

branch="codex/weekly-tune-$(date +%Y-%m-%d)"

git checkout -b "$branch"
git add config prompts evals memory
git commit -m "Codex weekly outbound tune"

body="$(cat outputs/pr-summary.md)"

python3 scripts/create_pr.py \
  "$branch" \
  "Codex weekly outbound tune" \
  "$body"

Expected first run:

Build evals/score.py.

Read config/scoring.yaml and evals/fixtures.yaml.

For each case:
1. Sum weights for every signal.
2. Add negative signal penalties.
3. Route the account:
   - score >= thresholds.human_review => human_review
   - score >= thresholds.draft => draft
   - otherwise => ignore
4. Compare route to expected.

Print each prediction.
Print final accuracy as score=0.00 to score=1.00.
Exit 0 only when accuracy is 1.00.

The offline demo proves the file contracts. The Codex run proves the editing loop. After that, replace the sample outcomes with your own, rename the signals, add your plays, and build a fixture that reflects the accounts you wish the system had routed differently.

Do not start by wiring delivery. Start by proving the improvement loop.

The full version: max

This repo is the manual layer. It runs from files, public signals, and your Codex plan. It teaches the shape because every rule is exposed.

yourmax.ai is the same system with the seams hidden.

Instead of a repo you wire together yourself, max is the agent you use directly. It detects movement in the market, decides who is worth contacting and why now, drafts the outreach across email and LinkedIn for your approval, and keeps improving from the outcomes.

The repo shows the self-tuning layer most teams never build: outcomes become proposed rule changes, proposed rule changes run through a gate, and the human merge decides what becomes live. max takes that same operating logic and runs it as a managed system.

If you want the full repo, you can let me know and I will send it your way.

📋 讨论归档

讨论进行中…