724-office-ai-agent
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
Chinese7/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-officebash
git clone https://github.com/wangziqi06/724-office.git
cd 724-officeOnly 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
undefinedcp config.example.json config.json
undefinedConfiguration (config.json
)
config.json配置(config.json
)
config.jsonjson
{
"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
undefinedbash
undefinedStart 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/
undefinedundefinedFile 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/context724-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 decorator in :
@tooltools.pypython
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@toolpython
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 during a conversation to write and load a new Python tool without restarting:
create_toolUser: "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 and hot-loaded immediately.
workspace/custom_tools/md_to_html.pyAgent可在对话过程中调用来编写并加载新的Python工具,无需重启:
create_tool用户:"创建一个将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.pyConnecting an MCP Server
连接MCP服务器
Edit to add MCP servers (stdio or HTTP):
config.jsonjson
{
"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 (double underscore). Reload without restart:
servername__toolnameUser: "reload MCP servers"编辑添加MCP服务器(标准输入输出或HTTP方式):
config.jsonjson
{
"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()
undefinedundefinedScheduling Tasks
任务调度
The agent uses tool internally, but you can also call the scheduler API directly:
schedulepython
from scheduler import Scheduler
import json
sched = Scheduler(jobs_file="workspace/jobs.json", timezone="Asia/Shanghai")Agent内部使用工具,但你也可以直接调用调度器API:
schedulepython
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:
```pythonresults = 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流水线会在每次调用前自动注入检索到的记忆:
```pythonSimplified 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}"
undefinedrelevant = 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}"
undefinedPersonality Files
人格定义文件
Create these in to shape agent behavior:
workspace/workspace/SOUL.mdmarkdown
undefined在目录下创建以下文件来塑造Agent行为:
workspace/workspace/SOUL.mdmarkdown
undefinedAgent 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:
```markdownYou 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`** — 操作流程规范:
```markdownOperational Guide
Operational Guide
On Error
On Error
- Check logs in workspace/logs/
- Run self_check() tool
- Notify owner if critical
- Check logs in workspace/logs/
- Run self_check() tool
- 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`** — 用户上下文:
```markdownUser 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
undefinedTool-Use Loop (Core LLM Flow)
工具调用循环(核心LLM流程)
python
undefinedpython
undefinedSimplified 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 appendedundefinedasync 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)
})
# 循环继续,附加工具结果undefinedSelf-Repair and Diagnostics
自我修复与诊断
python
undefinedpython
undefinedThe 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.pypython
undefinedrouter.pypython
undefinedrouter.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>
undefinedpython3 router.py # 监听80端口,路由到每个用户的8080+N端口
使用Docker标签进行服务发现:724office.user_id=<user_id>
724office.port=<assigned_port>
undefinedCommon Patterns
常用模式
Send a proactive message from a scheduled job
从调度任务发送主动消息
python
undefinedpython
undefinedIn 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"]
}
undefinedSearch memory semantically
语义化搜索记忆
python
undefinedpython
undefinedVia agent tool call:
通过Agent工具调用:
results = tools.execute("search_memory", {
"query": "what did the user say about the Q1 budget?",
"top_k": 3
})
undefinedresults = tools.execute("search_memory", {
"query": "what did the user say about the Q1 budget?",
"top_k": 3
})
undefinedExecute arbitrary Python in the agent's process
在Agent进程中执行任意Python代码
python
undefinedpython
undefinedexec tool (use carefully — runs in-process)
exec工具(谨慎使用 — 进程内运行)
tools.execute("exec", {
"code": "import psutil; return psutil.virtual_memory().percent"
})
undefinedtools.execute("exec", {
"code": "import psutil; return psutil.virtual_memory().percent"
})
undefinedList 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
故障排除
| Symptom | Cause | Fix |
|---|---|---|
| Missing dependency | |
| Memory retrieval empty | LanceDB not initialized | Ensure |
| MCP tool not found | Server not connected | Check |
| Scheduler not firing | Timezone mismatch | Set |
| Tool loop hits 20 iterations | Runaway tool chain | Add guardrails in |
| WeChat webhook 403 | Token mismatch | Verify |
| High RAM on Jetson | LanceDB index size | Reduce |
| Wrong workspace path | Confirm |
| 症状 | 原因 | 解决方法 |
|---|---|---|
| 缺少依赖 | |
| 记忆检索结果为空 | LanceDB未初始化 | 确保 |
| MCP工具未找到 | 服务器未连接 | 检查 |
| 调度任务未触发 | 时区不匹配 | 在配置中设置 |
| 工具循环达到20次迭代 | 工具调用链失控 | 在 |
| 微信Webhook返回403 | Token不匹配 | 验证 |
| Jetson设备内存占用过高 | LanceDB索引过大 | 减小 |
| 工作区路径错误 | 确认 |
Edge Deployment (Jetson Orin Nano)
边缘部署(Jetson Orin Nano)
bash
undefinedbash
undefinedARM64-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服务器
undefinedundefined