两个数字员工,一个组织:企业数字员工开发最佳实践

Two Digital Employees, One Org — A Reference Architecture for Building AI Workforce

上个月我在钉钉群里发了一句话:「给数字员工加一个查闲忙的能力。」

十分钟后,我收到了三条消息。第一条来自 Coding Agent:「✅ 已创建 Issue #12:日程能力——支持查询闲忙。」第二条还是它:「✅ 开发完成,15/15 测试通过,已 push,CI 运行中。」第三条来自数字员工本身:「🤖 v1.3.1 已上线,日程插件已更新,随时 @我 开始工作。」

从一句话到生产环境上线,我没有打开电脑,没有写一行代码,没有手动跑一次部署。

这不是 Demo。这是我过去三周每天在用的工作方式。

两个数字员工一个组织:Coding Agent 开发、CI/CD 交付、数字员工上线

一个组织里的两个数字员工

我的钉钉组织里有两个数字员工,它们的主管都是我。

Coding Agent数字员工
职责开发、测试、部署数字员工服务组织里的真实用户
技术栈Claude Code + dws event consumeOpenCode + dws event consume
运行环境本机Docker 容器(远端服务器)
入口管理群消息(事件流)用户群消息(消息消费)
产出git commit → CI/CD → Docker 镜像用户问题的回答、任务执行

一个造,一个用。造的那个通过 GitHub CI/CD 把新的自己部署到服务器上,用的那个上线后主动向我报告状态。

这个架构不是设计出来的,是长出来的。我在 两个 Agent 的钉钉对话 里写过异构 Agent 通过钉钉消息总线协作的模式,在 让钉钉机器人自己开发自己 里写过 Coding Agent 在钉钉上修自己的 Bug。但那些都是单点突破——一个 Agent 做一件事。

这篇文章要解决的是一个完整的工程问题: 怎么让「开发数字员工」这件事本身也变成数字员工的工作?

为什么不用人写代码部署 AI

先回答一个看似显而易见的问题:为什么不让工程师写代码、跑 CI、手动部署?

因为数字员工的迭代节奏和传统软件完全不同。

传统软件是 项目制——需求评审、开发、测试、上线、验收,一个周期几周。数字员工是 运营制——用户在群里说一句「这个功能不好用」,你最好当天就改好。一个 FDE 在客户现场发现数字员工不会处理图片,他不可能提一个 Jira 等两周排期。

我在 三步上线一个数字员工 里把上线门槛降到了三分钟。但上线只是开始, 持续迭代才是数字员工的生命线。如果每次迭代都要人介入——人写需求、人改代码、人跑测试、人点部署——那数字员工的迭代速度就被人的响应速度锁死了。

解法不是「让人更快」,而是 让迭代本身也自动化。谁最了解数字员工怎么改?是另一个数字员工。

架构全景

架构全景:钉钉组织 → 两个数字员工 → GitHub CI/CD

四个组件,各司其职:

  • 钉钉:唯一的人机界面。需求从钉钉来,结果回钉钉去。
  • GitHub Issues:唯一的任务记录。每个需求、每个 Bug 都有编号、有状态、有验收标准。
  • GitHub CI/CD:唯一的交付流水线。push 触发 build → test → docker → deploy,有审计日志,可回滚。
  • Docker:唯一的运行环境。标准镜像,环境变量传参,换环境只改 docker run -e

任务流:从一句话到生产环境

第一步:钉钉下发需求

我在管理群里说:

@Coding Agent 给数字员工加一个查闲忙的功能,要能查指定人未来 7 天的日程。

Coding Agent 通过 dws event consume 收到这条消息。它做的第一件事不是写代码,而是 创建 Issue

import subprocess
import json
from dataclasses import dataclass


@dataclass
class TaskRequest:
    """从钉钉消息解析出的任务请求。"""
    title: str
    body: str
    labels: list[str]
    sender_id: str
    conversation_id: str


def create_issue(task: TaskRequest) -> int:
    """创建 GitHub Issue 并返回编号。"""
    result = subprocess.run(
        [
            "gh", "issue", "create",
            "--title", task.title,
            "--body", task.body,
            "--label", ",".join(task.labels),
        ],
        capture_output=True,
        text=True,
        check=True,
    )
    # gh issue create 输出格式: https://github.com/.../issues/12
    issue_number = int(result.stdout.strip().rsplit("/", 1)[-1])
    return issue_number
# generated by hugo AI

Issue 不是形式主义。它是 Coding Agent 的开发规格——标题是目标,body 是验收标准,label 是优先级。三个月后回头看,每个功能的「为什么做、怎么做的、什么时候上的」全在一条链上。

第二步:开发

Coding Agent 读取 Issue 内容,在 dingtalk-opencode-tag 仓库里开发。项目结构是 core/custom 分层:

dingtalk-opencode-tag/
├── core/                  # 基础设施(不动)
│   ├── dws_consumer.py    # dws consume 长连接
│   ├── message_router.py  # 消息路由
│   └── state_manager.py   # 状态管理
├── custom/                # 业务能力插件(改这里)
│   ├── calendar/
│   │   ├── freebusy.py    # ← 新增:闲忙查询
│   │   └── test_freebusy.py
│   ├── todo/
│   ├── approval/
│   └── ...
├── Dockerfile
├── docker-compose.yml
└── .github/workflows/deploy.yml

关键约束: 改 custom/ 不需要动 core/。每个插件是独立的,有自己的测试。Coding Agent 只需要在 custom/calendar/ 下加文件,跑测试,确认通过。

第三步:Commit + Push → CI/CD 自动触发

import subprocess
from pathlib import Path


def commit_and_push(issue_number: int, message: str) -> None:
    """提交代码并推送,触发 CI/CD。"""
    repo = Path("~/Projects/dingtalk-opencode-tag").expanduser()

    subprocess.run(["git", "add", "-A"], cwd=repo, check=True)
    subprocess.run(
        ["git", "commit", "-m", f"feat(calendar): 闲忙查询 Closes #{issue_number}"],
        cwd=repo,
        check=True,
    )
    subprocess.run(
        ["git", "push", "origin", "main"],
        cwd=repo,
        check=True,
        env={
            **__import__("os").environ,
            "GIT_SSH_COMMAND": "ssh -i ~/.ssh/id_ed25519",
        },
    )
# generated by hugo AI

Push 之后,GitHub Actions 自动接管:

# .github/workflows/deploy.yml
name: Build & Deploy Digital Employee

on:
  push:
    branches: [main]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: "3.13"
      - run: pip install -r requirements.txt
      - run: python -m pytest custom/ -v

  build-and-deploy:
    needs: test
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Build Docker image
        run: |
          docker build -t ${{ secrets.REGISTRY }}/digital-employee:${{ github.sha }} .

      - name: Push image
        run: |
          echo "${{ secrets.REGISTRY_PASSWORD }}" | docker login -u ${{ secrets.REGISTRY_USER }} --password-stdin
          docker push ${{ secrets.REGISTRY }}/digital-employee:${{ github.sha }}

      - name: Deploy to server
        uses: appleboy/ssh-action@v1
        with:
          host: ${{ secrets.DEPLOY_HOST }}
          username: deploy
          key: ${{ secrets.DEPLOY_SSH_KEY }}
          script: |
            docker pull ${{ secrets.REGISTRY }}/digital-employee:${{ github.sha }}
            docker stop digital-employee || true
            docker rm digital-employee || true
            docker run -d \
              --name digital-employee \
              --restart=always \
              -e DWS_TOKEN=*** \
              -e OPENCODE_MODEL=*** \
              -e DINGTALK_CORP_ID=*** \
              -e SUPERVISOR_USER_ID=*** \
              -e IMAGE_TAG=${{ github.sha }} \
              ${{ secrets.REGISTRY }}/digital-employee:${{ github.sha }}
# generated by hugo AI

镜像不含任何密钥。 所有敏感参数通过环境变量注入。同一个镜像可以跑在任何环境——开发、测试、生产——只需要换 docker run -e 的值。

第四步:数字员工上线,主动打招呼

容器启动后,start.py 做的第一件事不是等用户消息,而是 向主管报告状态

import os
import asyncio
import subprocess
import json
from datetime import datetime


async def report_startup() -> None:
    """数字员工上线后主动向主管打招呼。"""
    supervisor_id = os.environ["SUPERVISOR_USER_ID"]
    image_tag = os.environ.get("IMAGE_TAG", "unknown")
    start_time = datetime.now()

    # 自检:加载所有插件,检查状态
    plugins = load_plugins()
    plugin_status = []
    for name, plugin in plugins.items():
        try:
            await plugin.health_check()
            plugin_status.append(f"{name} ✅")
        except Exception as e:
            plugin_status.append(f"{name} ❌ ({e})")

    startup_seconds = (datetime.now() - start_time).total_seconds()

    message = (
        f"🤖 数字员工 {image_tag[:7]} 已上线\n"
        f"━━━━━━━━━━━━━━━━\n"
        f"状态:{'✅ 正常' if all('✅' in s for s in plugin_status) else '⚠️ 部分异常'}\n"
        f"插件:{' | '.join(plugin_status)}\n"
        f"启动耗时:{startup_seconds:.1f}s\n"
        f"━━━━━━━━━━━━━━━━\n"
        f"随时 @我 开始工作。"
    )

    # 通过 dws 发送单聊消息给主管
    subprocess.run(
        [
            "dws", "chat", "send",
            "--user", supervisor_id,
            "--content", message,
        ],
        check=True,
    )
# generated by hugo AI

我在钉钉里收到的消息长这样:

🤖 数字员工 abc1234 已上线 ━━━━━━━━━━━━━━━━ 状态:✅ 正常 插件:calendar ✅ | todo ✅ | approval ✅ | contact ✅ | chat ✅ | doc ✅ 启动耗时:3.2s ━━━━━━━━━━━━━━━━ 随时 @我 开始工作。

不是被动等查询,是主动报告。 我不用 ssh 到服务器看日志,钉钉里就知道「它活了、它健康、它准备好了」。

钉钉是唯一的控制面

这个架构里最反直觉的设计是: 没有 Dashboard,没有管理后台,没有 Web UI。所有管理操作都在钉钉里完成。

操作怎么做
下发需求管理群 @Coding Agent「加一个 XX 功能」
修 Bug管理群 @Coding Agent「修一下 #15」
查进度管理群 @Coding Agent「status」
回滚管理群 @Coding Agent「rollback abc1234」
看数字员工状态等它主动报告,或直接 @数字员工 问

为什么不用 Jira + Jenkins + Grafana 那套?因为 数字员工的管理者不应该需要学一套新工具。钉钉是组织里每个人每天都在用的东西。把管理面放在钉钉里,意味着任何有权限的人都能管理数字员工——不需要会看 Kubernetes Dashboard,不需要会读 CI 日志。

这也是我在 面向 Agent 开发 里说的「可运维」的延伸:不只是 Agent 要能运维自己, 运维界面本身也要在用户已经在的地方

环境变量:标准镜像的契约

Docker 镜像是通用的,环境差异全部通过环境变量解决:

变量用途示例
DWS_TOKENdws CLI 认证令牌***
OPENCODE_MODELOpenCode 使用的模型qwen3-8-max
DINGTALK_CORP_ID组织 IDding...
SUPERVISOR_USER_ID主管 userId(打招呼用)161095
IMAGE_TAG当前镜像版本(CI 注入)abc1234
NODE_ENV运行环境production

这意味着:

  • 换模型:改一个环境变量,不用重新 build
  • 换组织:改 corp_id + token,同一个镜像服务不同客户
  • 换主管:改 supervisor_user_id,上线报告发给不同的人
  • 回滚docker run 旧 tag 的镜像,秒级恢复

FDE 到客户现场,不需要带代码、不需要装环境。一条 docker run 命令,数字员工就上线了。

安全阀:人在回路

让一个数字员工开发另一个数字员工,最大的担忧是失控。这个架构里有三道安全阀:

第一道:CI 测试

每次 push 必须过 pytest。测试不过,CI 失败,不会 build 镜像,不会部署。Coding Agent 不能绕过测试直接上线。

第二道:GitHub branch protection

main 分支开启保护规则:必须过 CI 才能 merge。即使 Coding Agent 有 push 权限,失败的 CI 会阻止部署。

第三道:主管确认(可选)

对于高风险变更(改 core/、改部署配置),可以配置 CI 在 deploy 步骤前发一张钉钉审批卡片:

📦 发布请求 变更:core/dws_consumer.py 断线重连逻辑 测试:✅ 15/15 passed 影响范围:基础设施层 [确认发布] [查看 diff] [拒绝]

我点「确认发布」才真正触发 deploy。日常改 custom/ 插件不需要审批,改 core/ 需要。 风险等级决定审批力度。

自举闭环

把整个链路串起来:

用户在群里说「这个功能不好用」
我看到 → 在管理群 @Coding Agent「修一下 #15」
Coding Agent: dws event consume 收到
       ├─ gh issue view 15(读取需求)
       ├─ 开发、测试
       ├─ git commit + push
GitHub CI/CD: build → test → docker → deploy
数字员工重启 → 自检 → 向我报告「✅ v1.3.2 已上线」
用户在群里 @数字员工,发现功能修好了
新的反馈 → 新的迭代

这是一个 自举闭环:数字员工服务用户 → 用户反馈 → 主管下发需求 → Coding Agent 开发 → CI/CD 部署 → 数字员工更新 → 更好地服务用户。

每一轮迭代都不需要人写代码、不需要人跑部署。人只做两件事: 判断该不该改确认改得对不对

写在最后

回到开头那个场景。我说了一句话,十分钟后数字员工就上线了新功能。这中间没有「提需求 → 排期 → 开发 → 测试 → 部署」的五步流程,只有一句话和三条回执消息。

这不是因为 Coding Agent 有多强——Claude Code 写代码的能力大家都见过。关键是把 钉钉做控制面、GitHub Issues 做任务记录、CI/CD 做交付流水线、Docker 做运行环境 这四件事串成了一条自动化链路。每个组件都是成熟的、标准的、可替换的。

我在 三步上线一个数字员工 里说,脚手架的意义是「把生产环境的脏活封装成可复制的 Harness」。这篇文章是它的下一步: 把数字员工的开发本身也变成可复制的 Harness

上线一个数字员工需要三步。持续迭代一个数字员工,只需要一句话。

你的组织里有数字员工了吗?它是怎么迭代的?欢迎留言聊聊。


See also