724-office-ai-agent

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

7/24 Office AI Agent System

7×24办公AI Agent系统

Skill by ara.so — Daily 2026 Skills collection.
A 24/7 production AI agent in ~3,500 lines of pure Python with no framework dependencies. Features 26 built-in tools, three-layer memory (session + compressed + vector), MCP/plugin support, runtime tool creation, self-repair diagnostics, and cron scheduling.
技能来自 ara.so — 2026每日技能合集。
这是一个基于约3500行纯Python代码开发的7×24小时可用的生产级AI Agent,无框架依赖。具备26种内置工具、三层记忆机制(会话记忆+压缩记忆+向量记忆)、MCP/插件支持、运行时工具创建、自我修复诊断以及cron任务调度功能。

Installation

安装

bash
git clone https://github.com/wangziqi06/724-office.git
cd 724-office
bash
git clone https://github.com/wangziqi06/724-office.git
cd 724-office

Only 3 runtime dependencies

仅需3个运行时依赖

pip install croniter lancedb websocket-client
pip install croniter lancedb websocket-client

Optional: WeChat silk audio decoding

可选:微信 silk 音频解码

pip install pilk
pip install pilk

Set up directories

创建目录

mkdir -p workspace/memory workspace/files
mkdir -p workspace/memory workspace/files

Configure

配置

cp config.example.json config.json
undefined
cp config.example.json config.json
undefined

Configuration (
config.json
)

配置(
config.json

json
{
  "models": {
    "default": {
      "api_base": "https://api.openai.com/v1",
      "api_key": "${OPENAI_API_KEY}",
      "model": "gpt-4o",
      "max_tokens": 4096
    },
    "embedding": {
      "api_base": "https://api.openai.com/v1",
      "api_key": "${OPENAI_API_KEY}",
      "model": "text-embedding-3-small"
    }
  },
  "messaging": {
    "platform": "wxwork",
    "corp_id": "${WXWORK_CORP_ID}",
    "corp_secret": "${WXWORK_CORP_SECRET}",
    "agent_id": "${WXWORK_AGENT_ID}",
    "token": "${WXWORK_TOKEN}",
    "encoding_aes_key": "${WXWORK_AES_KEY}"
  },
  "memory": {
    "session_max_messages": 40,
    "compression_overlap": 5,
    "dedup_threshold": 0.92,
    "retrieval_top_k": 5,
    "lancedb_path": "workspace/memory"
  },
  "asr": {
    "api_base": "https://api.openai.com/v1",
    "api_key": "${OPENAI_API_KEY}",
    "model": "whisper-1"
  },
  "scheduler": {
    "jobs_file": "workspace/jobs.json",
    "timezone": "Asia/Shanghai"
  },
  "server": {
    "host": "0.0.0.0",
    "port": 8080
  },
  "workspace": "workspace",
  "mcp_servers": {}
}
Set environment variables rather than hardcoding secrets:
bash
export OPENAI_API_KEY="sk-..."
export WXWORK_CORP_ID="..."
export WXWORK_CORP_SECRET="..."
json
{
  "models": {
    "default": {
      "api_base": "https://api.openai.com/v1",
      "api_key": "${OPENAI_API_KEY}",
      "model": "gpt-4o",
      "max_tokens": 4096
    },
    "embedding": {
      "api_base": "https://api.openai.com/v1",
      "api_key": "${OPENAI_API_KEY}",
      "model": "text-embedding-3-small"
    }
  },
  "messaging": {
    "platform": "wxwork",
    "corp_id": "${WXWORK_CORP_ID}",
    "corp_secret": "${WXWORK_CORP_SECRET}",
    "agent_id": "${WXWORK_AGENT_ID}",
    "token": "${WXWORK_TOKEN}",
    "encoding_aes_key": "${WXWORK_AES_KEY}"
  },
  "memory": {
    "session_max_messages": 40,
    "compression_overlap": 5,
    "dedup_threshold": 0.92,
    "retrieval_top_k": 5,
    "lancedb_path": "workspace/memory"
  },
  "asr": {
    "api_base": "https://api.openai.com/v1",
    "api_key": "${OPENAI_API_KEY}",
    "model": "whisper-1"
  },
  "scheduler": {
    "jobs_file": "workspace/jobs.json",
    "timezone": "Asia/Shanghai"
  },
  "server": {
    "host": "0.0.0.0",
    "port": 8080
  },
  "workspace": "workspace",
  "mcp_servers": {}
}
请设置环境变量而非硬编码敏感信息:
bash
export OPENAI_API_KEY="sk-..."
export WXWORK_CORP_ID="..."
export WXWORK_CORP_SECRET="..."

Running the Agent

运行Agent

bash
undefined
bash
undefined

Start the HTTP server (listens on :8080 by default)

启动HTTP服务器(默认监听8080端口)

python3 xiaowang.py
python3 xiaowang.py

Point your messaging platform webhook to:

将你的消息平台Webhook指向:

http://YOUR_SERVER_IP:8080/

http://YOUR_SERVER_IP:8080/

undefined
undefined

File Structure

文件结构

724-office/
├── xiaowang.py      # Entry point: HTTP server, debounce, ASR, media download
├── llm.py           # Tool-use loop, session management, memory injection
├── tools.py         # 26 built-in tools + @tool decorator + plugin loader
├── memory.py        # Three-layer memory pipeline
├── scheduler.py     # Cron + one-shot scheduling, jobs.json persistence
├── mcp_client.py    # JSON-RPC MCP client (stdio + HTTP)
├── router.py        # Multi-tenant Docker routing
├── config.py        # Config loading and env interpolation
└── workspace/
    ├── memory/      # LanceDB vector store
    ├── files/       # Agent file storage
    ├── SOUL.md      # Agent personality
    ├── AGENT.md     # Operational procedures
    └── USER.md      # User preferences/context
724-office/
├── xiaowang.py      # 入口文件:HTTP服务器、防抖、ASR、媒体下载
├── llm.py           # 工具调用循环、会话管理、记忆注入
├── tools.py         # 26种内置工具 + @tool装饰器 + 插件加载器
├── memory.py        # 三层记忆处理流程
├── scheduler.py     # Cron+一次性任务调度、jobs.json持久化
├── mcp_client.py    # JSON-RPC MCP客户端(标准输入输出+HTTP)
├── router.py        # 多租户Docker路由
├── config.py        # 配置加载与环境变量解析
└── workspace/
    ├── memory/      # LanceDB向量存储
    ├── files/       # Agent文件存储
    ├── SOUL.md      # Agent人格定义
    ├── AGENT.md     # 操作流程规范
    └── USER.md      # 用户偏好/上下文

Adding a Built-in Tool

添加内置工具

Tools are registered with the
@tool
decorator in
tools.py
:
python
from tools import tool

@tool(
    name="fetch_weather",
    description="Get current weather for a city.",
    parameters={
        "type": "object",
        "properties": {
            "city": {
                "type": "string",
                "description": "City name, e.g. 'Beijing'"
            },
            "units": {
                "type": "string",
                "enum": ["metric", "imperial"],
                "default": "metric"
            }
        },
        "required": ["city"]
    }
)
def fetch_weather(city: str, units: str = "metric") -> str:
    import urllib.request, json
    api_key = os.environ["OPENWEATHER_API_KEY"]
    url = f"https://api.openweathermap.org/data/2.5/weather?q={city}&units={units}&appid={api_key}"
    with urllib.request.urlopen(url) as r:
        data = json.loads(r.read())
    temp = data["main"]["temp"]
    desc = data["weather"][0]["description"]
    return f"{city}: {temp}°, {desc}"
The tool is automatically available to the LLM in the next tool-use loop iteration.
tools.py
中使用
@tool
装饰器注册工具:
python
from tools import tool

@tool(
    name="fetch_weather",
    description="Get current weather for a city.",
    parameters={
        "type": "object",
        "properties": {
            "city": {
                "type": "string",
                "description": "City name, e.g. 'Beijing'"
            },
            "units": {
                "type": "string",
                "enum": ["metric", "imperial"],
                "default": "metric"
            }
        },
        "required": ["city"]
    }
)
def fetch_weather(city: str, units: str = "metric") -> str:
    import urllib.request, json
    api_key = os.environ["OPENWEATHER_API_KEY"]
    url = f"https://api.openweathermap.org/data/2.5/weather?q={city}&units={units}&appid={api_key}"
    with urllib.request.urlopen(url) as r:
        data = json.loads(r.read())
    temp = data["main"]["temp"]
    desc = data["weather"][0]["description"]
    return f"{city}: {temp}°, {desc}"
该工具会自动在下次工具调用循环中对LLM可用。

Runtime Tool Creation (Agent Creates Its Own Tools)

运行时创建工具(Agent自主创建工具)

The agent can call
create_tool
during a conversation to write and load a new Python tool without restarting:
User: "Create a tool that converts Markdown to HTML."

Agent calls: create_tool({
  "name": "md_to_html",
  "description": "Convert a Markdown string to HTML.",
  "parameters": { ... },
  "code": "import markdown\ndef md_to_html(text): return markdown.markdown(text)"
})
The tool is saved to
workspace/custom_tools/md_to_html.py
and hot-loaded immediately.
Agent可在对话过程中调用
create_tool
来编写并加载新的Python工具,无需重启:
用户:"创建一个将Markdown转换为HTML的工具。"

Agent调用:create_tool({
  "name": "md_to_html",
  "description": "Convert a Markdown string to HTML.",
  "parameters": { ... },
  "code": "import markdown\ndef md_to_html(text): return markdown.markdown(text)"
})
该工具会保存到
workspace/custom_tools/md_to_html.py
并立即热加载。

Connecting an MCP Server

连接MCP服务器

Edit
config.json
to add MCP servers (stdio or HTTP):
json
{
  "mcp_servers": {
    "filesystem": {
      "transport": "stdio",
      "command": "npx",
      "args": ["-y", "@modelcontextprotocol/server-filesystem", "/data"]
    },
    "myapi": {
      "transport": "http",
      "url": "http://localhost:3000/mcp"
    }
  }
}
MCP tools are namespaced as
servername__toolname
(double underscore). Reload without restart:
User: "reload MCP servers"
编辑
config.json
添加MCP服务器(标准输入输出或HTTP方式):
json
{
  "mcp_servers": {
    "filesystem": {
      "transport": "stdio",
      "command": "npx",
      "args": ["-y", "@modelcontextprotocol/server-filesystem", "/data"]
    },
    "myapi": {
      "transport": "http",
      "url": "http://localhost:3000/mcp"
    }
  }
}
MCP工具命名空间为
servername__toolname
(双下划线)。无需重启即可重载:
用户:"重载MCP服务器"

Agent calls: reload_mcp()

Agent调用:reload_mcp()

undefined
undefined

Scheduling Tasks

任务调度

The agent uses
schedule
tool internally, but you can also call the scheduler API directly:
python
from scheduler import Scheduler
import json

sched = Scheduler(jobs_file="workspace/jobs.json", timezone="Asia/Shanghai")
Agent内部使用
schedule
工具,但你也可以直接调用调度器API:
python
from scheduler import Scheduler
import json

sched = Scheduler(jobs_file="workspace/jobs.json", timezone="Asia/Shanghai")

One-shot task (ISO 8601)

一次性任务(ISO 8601格式)

sched.add_job( job_id="morning_brief", trigger="2026-04-01T09:00:00", action={"type": "message", "content": "Good morning! Here's your daily brief."}, user_id="user_001" )
sched.add_job( job_id="morning_brief", trigger="2026-04-01T09:00:00", action={"type": "message", "content": "Good morning! Here's your daily brief."}, user_id="user_001" )

Recurring cron task

周期性cron任务

sched.add_job( job_id="weekly_report", trigger="0 9 * * MON", # Every Monday 09:00 action={"type": "llm_task", "prompt": "Generate weekly summary"}, user_id="user_001" )
sched.start()

Jobs persist in `workspace/jobs.json` across restarts.
sched.add_job( job_id="weekly_report", trigger="0 9 * * MON", # 每周一09:00 action={"type": "llm_task", "prompt": "Generate weekly summary"}, user_id="user_001" )
sched.start()

任务会持久化到`workspace/jobs.json`,重启后依然保留。

Three-Layer Memory System

三层记忆系统

python
from memory import MemoryManager

mem = MemoryManager(config["memory"])
python
from memory import MemoryManager

mem = MemoryManager(config["memory"])

Layer 1 — session history (auto-managed, last 40 msgs)

第一层 — 会话历史(自动管理,最近40条消息)

mem.append_session(user_id="u1", session_id="s1", role="user", content="Hello!")
mem.append_session(user_id="u1", session_id="s1", role="user", content="Hello!")

Layer 2 — long-term compressed (triggered on session overflow)

第二层 — 长期压缩记忆(会话消息溢出时触发)

LLM extracts structured facts; deduped at cosine similarity 0.92

LLM提取结构化事实;余弦相似度0.92时去重

mem.compress_and_store(user_id="u1", messages=evicted_messages)
mem.compress_and_store(user_id="u1", messages=evicted_messages)

Layer 3 — vector retrieval (injected into system prompt automatically)

第三层 — 向量检索(自动注入到系统提示词)

results = mem.retrieve(user_id="u1", query="user's dietary preferences", top_k=5) for r in results: print(r["content"], r["score"])

The LLM pipeline in `llm.py` injects retrieved memories automatically before each call:

```python
results = mem.retrieve(user_id="u1", query="user's dietary preferences", top_k=5) for r in results: print(r["content"], r["score"])

`llm.py`中的LLM流水线会在每次调用前自动注入检索到的记忆:

```python

Simplified from llm.py

简化自llm.py

relevant = memory.retrieve(user_id, query=user_message, top_k=5) memory_block = "\n".join(f"- {m['content']}" for m in relevant) system_prompt = base_prompt + f"\n\n## Relevant Memory\n{memory_block}"
undefined
relevant = memory.retrieve(user_id, query=user_message, top_k=5) memory_block = "\n".join(f"- {m['content']}" for m in relevant) system_prompt = base_prompt + f"\n\n## Relevant Memory\n{memory_block}"
undefined

Personality Files

人格定义文件

Create these in
workspace/
to shape agent behavior:
workspace/SOUL.md
— Personality and values:
markdown
undefined
workspace/
目录下创建以下文件来塑造Agent行为:
workspace/SOUL.md
— 人格与价值观:
markdown
undefined

Agent Soul

Agent Soul

You are Xiao Wang, a diligent 24/7 office assistant.
  • Always respond in the user's language
  • Be concise but thorough
  • Proactively suggest next steps

**`workspace/AGENT.md`** — Operational procedures:
```markdown
You are Xiao Wang, a diligent 24/7 office assistant.
  • Always respond in the user's language
  • Be concise but thorough
  • Proactively suggest next steps

**`workspace/AGENT.md`** — 操作流程规范:
```markdown

Operational Guide

Operational Guide

On Error

On Error

  1. Check logs in workspace/logs/
  2. Run self_check() tool
  3. Notify owner if critical
  1. Check logs in workspace/logs/
  2. Run self_check() tool
  3. Notify owner if critical

Daily Routine

Daily Routine

  • 09:00 Morning brief
  • 17:00 EOD summary

**`workspace/USER.md`** — User context:
```markdown
  • 09:00 Morning brief
  • 17:00 EOD summary

**`workspace/USER.md`** — 用户上下文:
```markdown

User Profile

User Profile

  • Name: Alice
  • Timezone: UTC+8
  • Prefers bullet-point summaries
  • Primary language: English
undefined
  • Name: Alice
  • Timezone: UTC+8
  • Prefers bullet-point summaries
  • Primary language: English
undefined

Tool-Use Loop (Core LLM Flow)

工具调用循环(核心LLM流程)

python
undefined
python
undefined

Simplified representation of llm.py's main loop

llm.py主循环的简化表示

async def run(user_id, session_id, user_message, media=None): messages = memory.get_session(user_id, session_id) messages.append({"role": "user", "content": user_message})
for iteration in range(20):          # max 20 tool iterations
    response = await llm_call(
        model=config["models"]["default"],
        messages=inject_memory(messages, user_id, user_message),
        tools=tools.get_schema(),    # all 26 + plugins + MCP
    )

    if response.finish_reason == "stop":
        # Final text reply — send to user
        return response.content

    if response.finish_reason == "tool_calls":
        for call in response.tool_calls:
            result = await tools.execute(call.name, call.arguments)
            messages.append({
                "role": "tool",
                "tool_call_id": call.id,
                "content": str(result)
            })
        # Loop continues with tool results appended
undefined
async def run(user_id, session_id, user_message, media=None): messages = memory.get_session(user_id, session_id) messages.append({"role": "user", "content": user_message})
for iteration in range(20):          # 最多20次工具迭代
    response = await llm_call(
        model=config["models"]["default"],
        messages=inject_memory(messages, user_id, user_message),
        tools=tools.get_schema(),    # 所有26种工具+插件+MCP
    )

    if response.finish_reason == "stop":
        # 最终文本回复 — 发送给用户
        return response.content

    if response.finish_reason == "tool_calls":
        for call in response.tool_calls:
            result = await tools.execute(call.name, call.arguments)
            messages.append({
                "role": "tool",
                "tool_call_id": call.id,
                "content": str(result)
            })
        # 循环继续,附加工具结果
undefined

Self-Repair and Diagnostics

自我修复与诊断

python
undefined
python
undefined

The agent runs self_check() daily via scheduler

Agent会通过调度器每日运行self_check()

Or you can trigger it manually:

也可以手动触发:

Via chat: "run self-check"

通过聊天:"运行自我检查"

Agent calls: self_check()

Agent调用:self_check()

Via chat: "diagnose the last session"

通过聊天:"诊断上一个会话"

Agent calls: diagnose(session_id="s_20260322_001")

Agent调用:diagnose(session_id="s_20260322_001")


`self_check` scans:
- Error logs for exception patterns
- Session health (response times, tool failures)
- Memory store integrity
- Scheduled job status

Sends notification via the configured messaging platform if issues are found.

`self_check`会扫描:
- 错误日志中的异常模式
- 会话健康状态(响应时间、工具失败情况)
- 记忆存储完整性
- 调度任务状态

如果发现问题,会通过配置的消息平台发送通知。

Multi-Tenant Docker Routing

多租户Docker路由

router.py
provisions one container per user automatically:
python
undefined
router.py
会自动为每个用户分配一个容器:
python
undefined

router.py handles:

router.py负责:

POST / with user_id header -> route to user's container

携带user_id头的POST请求 -> 路由到对应用户的容器

If container missing -> docker run 724-office:latest with user env

如果容器不存在 -> 运行724-office:latest镜像并配置用户环境变量

Health-check every 30s -> restart unhealthy containers

每30秒健康检查 -> 重启不健康容器

Deploy the router separately:

单独部署路由:

python3 router.py # listens on :80, routes to per-user :8080+N

Docker labels used for discovery:
724office.user_id=<user_id> 724office.port=<assigned_port>
undefined
python3 router.py # 监听80端口,路由到每个用户的8080+N端口

使用Docker标签进行服务发现:
724office.user_id=<user_id> 724office.port=<assigned_port>
undefined

Common Patterns

常用模式

Send a proactive message from a scheduled job

从调度任务发送主动消息

python
undefined
python
undefined

In a scheduled job action, "type": "message" sends directly to user

在调度任务的action中,"type": "message"会直接发送给用户

{ "type": "message", "content": "Your weekly report is ready!", "attachments": ["workspace/files/report.pdf"] }
undefined
{ "type": "message", "content": "Your weekly report is ready!", "attachments": ["workspace/files/report.pdf"] }
undefined

Search memory semantically

语义化搜索记忆

python
undefined
python
undefined

Via agent tool call:

通过Agent工具调用:

results = tools.execute("search_memory", { "query": "what did the user say about the Q1 budget?", "top_k": 3 })
undefined
results = tools.execute("search_memory", { "query": "what did the user say about the Q1 budget?", "top_k": 3 })
undefined

Execute arbitrary Python in the agent's process

在Agent进程中执行任意Python代码

python
undefined
python
undefined

exec tool (use carefully — runs in-process)

exec工具(谨慎使用 — 进程内运行)

tools.execute("exec", { "code": "import psutil; return psutil.virtual_memory().percent" })
undefined
tools.execute("exec", { "code": "import psutil; return psutil.virtual_memory().percent" })
undefined

List and manage schedules

列出和管理调度任务

User: "list all scheduled tasks"
Agent calls: list_schedules()

User: "cancel the weekly_report job"
Agent calls: remove_schedule({"job_id": "weekly_report"})
用户:"列出所有调度任务"
Agent调用:list_schedules()

用户:"取消weekly_report任务"
Agent调用:remove_schedule({"job_id": "weekly_report"})

Troubleshooting

故障排除

SymptomCauseFix
ImportError: lancedb
Missing dependency
pip install lancedb
Memory retrieval emptyLanceDB not initializedEnsure
workspace/memory/
exists; send a few messages first
MCP tool not foundServer not connectedCheck
config.json
mcp_servers; call
reload_mcp
Scheduler not firingTimezone mismatchSet
scheduler.timezone
in config to your local TZ
Tool loop hits 20 iterationsRunaway tool chainAdd guardrails in
AGENT.md
; check for circular tool calls
WeChat webhook 403Token mismatchVerify
WXWORK_TOKEN
and
WXWORK_AES_KEY
env vars
High RAM on JetsonLanceDB index sizeReduce
retrieval_top_k
; use local embedding model
create_tool
not persisting
Wrong workspace pathConfirm
workspace/custom_tools/
directory exists and is writable
症状原因解决方法
ImportError: lancedb
缺少依赖
pip install lancedb
记忆检索结果为空LanceDB未初始化确保
workspace/memory/
目录存在;先发送几条消息
MCP工具未找到服务器未连接检查
config.json
中的mcp_servers配置;调用
reload_mcp
调度任务未触发时区不匹配在配置中设置
scheduler.timezone
为本地时区
工具循环达到20次迭代工具调用链失控
AGENT.md
中添加防护机制;检查是否存在循环工具调用
微信Webhook返回403Token不匹配验证
WXWORK_TOKEN
WXWORK_AES_KEY
环境变量
Jetson设备内存占用过高LanceDB索引过大减小
retrieval_top_k
;使用本地嵌入模型
create_tool
创建的工具未持久化
工作区路径错误确认
workspace/custom_tools/
目录存在且可写

Edge Deployment (Jetson Orin Nano)

边缘部署(Jetson Orin Nano)

bash
undefined
bash
undefined

ARM64-compatible — no GPU required for core agent

兼容ARM64架构 — 核心Agent无需GPU

Use a local embedding model to avoid cloud latency:

使用本地嵌入模型避免云延迟:

pip install sentence-transformers
pip install sentence-transformers

In config.json, point embedding to local model:

在config.json中,将嵌入模型指向本地模型:

{ "models": { "embedding": { "type": "local", "model": "BAAI/bge-small-en-v1.5" } } }
{ "models": { "embedding": { "type": "local", "model": "BAAI/bge-small-en-v1.5" } } }

Keep RAM under 2GB budget:

将内存占用控制在2GB以内:

- session_max_messages: 20 (reduce from 40)

- session_max_messages: 20(从40减少)

- retrieval_top_k: 3 (reduce from 5)

- retrieval_top_k: 3(从5减少)

- Avoid loading large MCP servers

- 避免加载大型MCP服务器

undefined
undefined