Macro routines 解析:让 Agent 为自己排期的定时任务(Routine)AI 工具链
- 后端
- 前端
- 协同办公
- 即时通讯
- 企业应用
- CRM
- AI Agent
- 人工智能
【免费下载链接】macro
Macro is a unified workspace for teams: email, chat, docs, tasks, agents, calls, and CRM — @-linked together with shared AI memory.
本文基于routinescrate 及其 AGENTS.md 指南,拆解 Macro 中"agent 定时任务(routine)"功能的完整技术链路:如何定义并校验RoutineTarget/RoutineSchedule等命令模型,如何把六个 AI 工具注册进 MCP 与嵌入式 agent 的工具集,以及RoutineClient如何以内部鉴权方式调用 scheduled-action 服务完成 CRUD 与即时执行。读完你将掌握该 crate 的职责边界、校验规则、HTTP 协议契约,以及向其中新增一个 routine 工具的标准流程。
1. 定位:routines 在 Macro 中的角色
routinescrate(crates/routines/AGENTS.md)是 Macro "agent sessions" 功能的一部分。它提供经过校验的 routine(scheduled-action)命令以及供 agent 使用的 AI 工具,让 agent 能够为"自己"或"另一个 persona/模型"排定稍后运行的工作。
AGENTS.md 对职责边界(Scope)给出了明确切分:
- 本 crate 拥有:routine 命令的校验(validation),以及 agent 侧创建/管理 routine 的工具;
- 本 crate 不拥有:cron 引擎与持久化的调度存储 —— 这些属于本 crate
outbound所调用的 scheduled-action 服务(对应 crates/scheduled_action crate,它拥有 scheduled-action 与 event-trigger 的 CRUD、dispatch、运行历史,实际宿主进程在services/agent_harness_service的 scheduling 模块中)。
依赖与使用方在 Cargo.toml 与 AGENTS.md 的 Boundaries 一节中一致:依赖ai_toolset、macro_authorization(axumfeature)、macro_user_id,以及chrono、chrono-tz、cron、reqwest、schemars、serde、uuid等 workspace 依赖;使用方是ai_tools。crate 的模块地图见 lib.rs:domain(命令与RoutineService能力接口)、inbound(AI 工具)、outbound(HTTP 实现)。
2. 领域模型:RoutineTarget 与 RoutineSchedule
核心命令形状定义在 domain.rs 中,两者都实现了Serialize/Deserialize/JsonSchema(用于生成工具参数 schema),并采用#[serde(tag = "type", rename_all = "snake_case")]的带标签枚举编码。
2.1 RoutineTarget:谁来执行
/// Who executes the routine. Agent IDs refer to personas, never conversation IDs. pub enum RoutineTarget { /// A model running as Macro. Model { /// Runtime model ID. model: String, }, /// An agent with its configured runtime, tools and default model. Agent { /// Persona/bot UUID (`bot.botId`) from ListAgents. #[serde(rename = "agentId")] agent_id: Uuid, }, }(domain.rs#L14-L28)
要点:
Model变体只接受一个运行时模型 ID,routine 以 "Macro 身份" 运行该模型;Agent变体使用 persona/bot 的 UUID(来自ListAgents的bot.botId),文档注释明确警告agent ID 指的是 persona,绝不是会话 ID;"想给自己排期就把自己的 persona ID 作为 agent target"。
2.2 RoutineSchedule:一次性与周期性
pub enum RoutineSchedule { /// Execute once at a future timestamp, even if the creating session has ended. Once { /// RFC3339 timestamp including UTC offset. at: DateTime<Utc>, }, /// Repeat using a six-field cron expression and an IANA time zone. Cron { /// Seconds, minutes, hours, day-of-month, month, weekday (optional year). expression: String, /// IANA time zone, e.g. America/New_York. timezone: String, }, }(domain.rs#L30-L45)
| 变体 | 字段 | 约束 |
|---|---|---|
once | at(RFC3339,含 UTC 偏移) | 必须严格晚于当前时间,否则报错"Choose a one-off time in the future." |
cron | expression(六字段 cron)、timezone(IANA 时区) | cron 必须可解析(秒 分 时 日 月 周,可选年份);时区必须是合法 IANA 名称;且cron.after(now)在目标时区下必须还有未来触发点,否则报"This schedule has no future runs." |
文件头注释点明了设计决策:"Commands use the scheduler's existing cron engine, including single-year one-off schedules."—— 一次性任务不是单独机制,而是复用调度器现有的 cron 引擎,把Once转写成"单年份"的六字段 cron。RoutineSchedule::cron(now)(domain.rs#L46-L86)完成这一翻译,返回(schedule, timezone)元组:
Once { at }:断言at > now后,格式化为"{sec} {min} {hour} {day} {month} * {year}"(年份放在第 6 字段),时区统一归一为"UTC";Cron:先解析 cron 表达式,再解析chrono_tz::Tz,最后确认在now.with_timezone(&zone)之后仍有触发点。
domain/test.rs 用两个测试锁定了这些语义:
once_has_exactly_one_future_firing_and_normalizes_offsets:输入"2026-10-03T09:15:00-04:00",断言时区归一为 UTC、从now起恰好只有一次触发(2026-10-03T13:15:00Z),且对已过去的时刻再调cron()返回错误;recurring_schedules_validate_timezone_and_future_runs:"0 0 9 * * MON-FRI"+America/New_York合法;非法时区"imaginary"报错;带过期单年份的"0 0 9 1 1 * 2000"因无未来运行而报错。
2.3 RoutineConfiguration 与校验
RoutineConfiguration(domain.rs#L88-L128)是"完整、用户可编辑的配置",与执行簿记解耦,字段为 camelCase:
| 字段 | 说明 |
|---|---|
name | 简短描述性名称,必填非空 |
instructions | 每次运行传给模型/agent 的指令,必填非空 |
target | 显式的模型或 agent 选择 |
schedule | Once或Cron |
required_apps | 每次运行需要的 Pipedream 应用列表(app_slug+ 展示名),在目标自身配置之外叠加;用户必须已连接这些应用,更新语义是整体替换(想保留必须重新列出全部) |
validate(now)的校验链是:名称非空 → 指令非空 → 若为Model目标则模型 ID 非空 → 委托schedule.cron(now)完成调度校验。这个校验在两个时机生效:outbound构造请求体之前(见第 4 节),以及服务端的最终权限检查(AGENTS.md 注明target accessibility is checked server-side)。
2.4 返回形状与 RoutineService 能力接口
domain 层还定义了返回结构:RoutineInfo(id/name/enabled/trigger/task/next_run_at,其中trigger与task按调度器存储形态透传为serde_json::Value)、RoutineList(有界列表 + 总匹配数)、RoutineDetails(配置 + 至多 50 条最近运行记录)、RoutineRunStarted(即时运行启动确认,含运行写入的 chat/agent session 引用)。
抽象能力由RoutineServicetrait 表达(domain.rs#L173-L203),注释强调"authorization remains in the scheduler, never the tools":
| 方法 | 语义 |
|---|---|
create(user, config) | 为认证用户创建;目标可访问性在服务端检查 |
list(user) | 列出用户的 routine,含指派给 agent 的 |
read(user, id) | 读取可访问 routine 及其历史 |
update(user, id, config) | 整体替换配置;激活状态(enabled)保留 |
set_enabled(user, id, enabled) | 独立于配置的暂停/恢复 |
delete(user, id) | 永久删除 routine 与其调度 |
run_now(user, id) | 立即启动一次计划外运行 |
3. inbound:暴露给 MCP 与嵌入式 agent 的六个 AI 工具
inbound.rs 用routine_toolset()注册全部工具(inbound.rs#L38-L46),并定义了工具宿主注入的上下文:
/// Routine capability injected by each tool host; default is explicitly unavailable for test hosts. pub struct RoutineToolContext { /// Owning-service client. pub service: Option<Arc<dyn RoutineService>>, }service()在缺失时统一报错"Routine tools are unavailable in this host."—— 默认(测试)宿主显式不可用,身份(request.user_id)由宿主提供,绝不来自模型参数。
| 工具 | 参数 | 输出 | 注解(ToolAnnotations) |
|---|---|---|---|
CreateRoutine | configuration: RoutineConfiguration | RoutineInfo | additive |
ListRoutines | query?(匹配 name/instructions)、enabled? | RoutineList | read_only |
ReadRoutine | routine_id | RoutineDetails | read_only |
UpdateRoutine | routine_id+ 恰好一个RoutineChange | RoutineInfo | destructive |
RunRoutine | routine_id | RoutineRunStarted | additive |
DeleteRoutine | routine_id | DeletedRoutine | destructive + idempotent |
注解对应 MCP 的readOnlyHint/destructiveHint/idempotentHint,其语义定义在 annotations.rs:ReadOnly客户端可免逐次确认;Additive只增不改不删;Destructive覆盖或删除数据,客户端必须先向用户提示。
3.1 CreateRoutine:工具描述即行为规范
CreateRoutine的 schemars 描述(inbound.rs#L47-L74)本身就是一段写给模型的行为规范,值得逐条理解:
- 排期给谁:想排给自己,用自己的 persona/bot ID 作为 agent target;想委派,从
ListAgents中挑选可访问的 agent,用其bot.botId; - 以谁的身份运行:"Routines run as the authenticated user after this session ends"—— 会话结束后仍以认证用户身份运行,使用所选 agent 的工具与配置;
- 选哪种调度:单次运行用
Once+ 未来 RFC3339 时间戳,重复用Cron; - 回复中如何引用(这也是 AGENTS.md 特别点出的一条):在 Macro 应用内回复中,把新建的 routine 写为
<m-document-mention>{"documentId":"ID","documentName":"NAME","blockName":"routine"}</m-document-mention>,ID/NAME 替换为返回的 ID 与名称;外部 MCP 回复则遵循 MCP item-linking 说明; - 反滥用约束:不要用 reminder 去做本该执行的工作;不要在某 routine 每次运行时自动再创建新 routine。
3.2 ListRoutines:客户端侧过滤 + 有界返回
ListRoutines(inbound.rs#L91-L118)拉取全部匹配项后在工具内做过滤:enabled精确匹配 + 小写化子串匹配 name 与 task,随后take(50)截断,同时通过RoutineList.total返回截断前的完整匹配数,描述中要求"若被截断请收窄查询"。历史细节则引导模型转用ReadRoutine。
3.3 UpdateRoutine:一次恰好一个变更意图
为避免"配置与激活状态被部分应用",UpdateRoutine用带标签枚举RoutineChange(inbound.rs#L148-L162)强制单次调用表达恰好一个意图:
Enabled { enabled }:不重写配置地暂停/恢复,走service.set_enabled;Configuration { configuration }:整体替换 name/instructions/target/schedule,走service.update,激活状态保留;描述要求"先 ReadRoutine 再替换",且*"运行中的 routine 可以暂停,但在结束前不能重新配置"*。
DeleteRoutine标记为 destructive 且幂等,描述限定"仅当用户明确要求删除 routine 时使用";临时停止应改用UpdateRoutine暂停。
4. outbound:调用 scheduled-action 服务的受控 HTTP 客户端
outbound.rs 实现了RoutineService的 HTTP 版本RoutineClient,其设计在注释中概括为"Authenticated, bounded HTTP calls to the scheduled-action service":
4.1 构造期约束
RoutineClient::new(base, key)(outbound.rs#L25-L49):
- base URL 必须是
http/https、有 host,且不含用户名/密码/query/fragment,否则报"Invalid routine service URL"; - 内部密钥不能为空,且被设为 sensitive header(不出现在日志中);
- 传输层约束:连接超时 5s、总超时 30s、禁用重定向、禁用自动重试(
retry(never()))—— 对写操作不盲目重试,重试语义交由上层(错误信息提示Check ListRoutines before retrying a creation; the write may have succeeded)。
4.2 鉴权头与响应边界
每个请求携带两个内部头(常量定义见 macro_authorization):
x-internal-auth-key:内部 API 密钥,来自应用配置而非模型参数;x-internal-macro-user-id:认证用户的MacroUserIdStr,同样由宿主注入。
响应侧做了双重体积防护(outbound.rs#L76-L94):先检查Content-Length,再在流式读取中累计,超过 2 MiB 即报"Routine response is too large."。
4.3 状态码到可操作错误信息的映射
request_bytes将调度器状态码翻译成面向 agent 的可操作建议(outbound.rs#L95-L107):
| 状态码 | 转述给 agent 的信息 |
|---|---|
| 200–299 | 正常解析(WireRoutine等) |
| 400 | 配置无效或模型/agent 不可用;检查 schedule,用ListAgents的bot.botId选可访问 agent |
| 422 | 配置无效,或requiredApps中的某个应用未连接/对所选 agent 不可用;请用户用DiscoverConnectors给的链接连接,或移除该项 |
| 401 / 403 / 404 | routine 或目标对该用户不可用 |
| 402 | 用户的 AI 额度已用尽,routine 当前无法运行 |
| 409 | routine 正在运行或已被改动;重新读取后再编辑 |
| 其他 | 服务不可用;重试创建前先查ListRoutines |
4.4 与调度器服务的 Wire 契约
RoutineService各方法对应的端点(均相对 base,即 scheduled-action 服务):
| 能力 | HTTP 请求 |
|---|---|
create | POST scheduled-actions(body 附加"enabled": true) |
list | GET scheduled-actions?include_events=true(结果按最近优先反转) |
read | GET scheduled-actions/{id}+GET scheduled-actions/{id}/history(take(50)) |
update | PUT scheduled-actions/{id} |
set_enabled | PUT scheduled-actions/{id}/enabled,body 仅{"enabled": bool} |
delete | DELETE scheduled-actions/{id} |
run_now | POST scheduled-actions/{id}/execute,返回{action_id, resource?} |
真正的"翻译器"是configuration_body(outbound.rs#L241-L263):先config.validate(now)再schedule.cron(now),然后组装调度器契约 ——kind: "Agent"、trigger: { type: "cron", schedule, timezone },以及task:
{ "prompt": "", "user_prompt": "<instructions>", "mcp_servers": [{ "app_slug": "linear", "server_name": "Linear" }], "model": "<model id>" // Model 目标时 // 或 "agent": { "bot_id": "<uuid>" } // Agent 目标时 }注意required_apps被映射为task.mcp_servers—— 这与 scheduled_action 的 AGENTS.md 中 Gotchas 一节互相印证:"A task'smcp_serversare apps the routine requires",保存时由TargetValidation检查、每次运行由RoutineSessions::prepare复查,并只允许 agent 使用所选应用。
outbound/test.rs 用真实 TCP socket 上的伪服务器验证了这些契约:
agent_tasks_do_not_silently_become_model_tasks_or_override_defaults:agent 目标的请求体只出现task.agent.bot_id与mcp_servers,不含model,也不夹带enabled/owner字段;requests_forward_authenticated_identity_and_preserve_target_and_activation:断言每个请求都携带x-internal-auth-key与x-internal-macro-user-id头,创建请求以POST .../scheduled-actions开头且 body 含enabled: true,更新不重发enabled,set_enabled的 body 恰为{"enabled": false};delete_and_run_now_call_the_owner_routes/run_now_explains_an_exhausted_ai_allowance:DELETE 204 成功、/execute返回的resource透传、402 时错误信息含 "allowance"。
5. 接入 ai_tools:工具如何进入各个宿主
routinescrate 本身不决定宿主,接线由ai_tools完成:
- 工具集注册:ai_tools/src/lib.rs#L294 在
tools_for_inner中通过.add_subtoolset::<RoutineToolContext>(routines::inbound::routine_toolset())把 routine 工具加入基础工具集,因此Chat、AgentSession、ChannelBot、Mcp 四类宿主都能拿到这六个工具(与 AGENTS.md 所称the AI toolset exposed to MCP and embedded agents一致); - 客户端构建:tool_context.rs#L1961-L1976 中
build_routine_tool_context()用macro_service_urls::ScheduledActionServiceUrl::new()?(按环境解析的服务 URL,见 macro_service_urls)与macro_auth::InternalApiKey::new()?构造RoutineClient; - 测试宿主:
no_op_schedule_context()返回RoutineToolContext::default()(service: None),工具调用会得到"本宿主不可用"的明确错误,而不是 panic。
从源码结构看,这套RoutineToolContext { service: Option<Arc<dyn RoutineService>> }的注入式设计,使得同一份inbound工具实现可以面向不同的RoutineService实现(生产用 HTTPRoutineClient,测试用内存实现),鉴权与凭据始终留在宿主与服务端,工具参数中不存在任何秘密。
6. 向 routines 新增一个工具:AGENTS.md 的标准配方
AGENTS.md 的 Recipes 一节给出了三步流程("Add a routine tool:src/domain.rs(command/schema) →src/outbound.rs(HTTP call) →src/inbound.rs(tool registration)")。结合现有代码,落地清单为:
- domain.rs:定义/扩展命令与返回 schema(
#[derive(JsonSchema)]生成工具参数;带标签枚举用rename_all保持 snake_case 线格式),并把新操作加进RoutineServicetrait; - outbound.rs:在
RoutineService for RoutineClient中实现 HTTP 调用,复用request::<T>以获得鉴权头、体积上限与统一错误转述;若引入新 wire 结构,仿照WireRoutine/WireExecution写From转换; - inbound.rs:定义工具结构体(
Deserialize + JsonSchema+#[schemars(title, description)]),实现ToolAnnotated(按实际影响选择read_only/additive/destructive)与AsyncTool,并在routine_toolset()中.add_tool::<X, RoutineToolContext>()注册。
验证命令为cargo test -p routines(AGENTS.md 要求SQLX_OFFLINE未设置)。相关规则文档指向create-ai-tool技能与 Rust 架构规则。
7. 小结:这套设计的关键取舍
- 校验前置、授权后置:
domain层保证"用户意图"合法(时间在未来、cron 可解析、时区合法),而身份与目标可访问性检查全部留在调度器服务(trait 注释"authorization remains in the scheduler, never the tools"); - 复用而非新造调度机制:
Once被归一为单年份六字段 cron,直接骑在现有 cron 引擎上,避免双套调度语义; - 面向 agent 的错误信息:每个 HTTP 状态码都被翻译成下一步行动建议(查
ListRoutines再重试创建、用DiscoverConnectors连接应用、402 时说明额度问题),让模型能自主纠错; - 传输安全默认值:禁重定向、禁自动重试、5s/30s 超时、2 MiB 响应上限、sensitive 内部密钥头、身份由宿主注入 —— 这些约束集中体现在
RoutineClient::new与request_bytes中,并有 socket 级集成测试逐条锁死。
对希望深入实现的读者,建议按 crates/routines/AGENTS.md 的 "Start here" 顺序阅读:domain.rs(命令形状与校验)→ inbound.rs(工具注册)→ outbound.rs(HTTP 契约),再对照 domain/test.rs 与 outbound/test.rs 理解每条约束背后的测试证据;调度器侧则继续读 crates/scheduled_action/AGENTS.md。
- 后端
- 前端
- 协同办公
- 即时通讯
- 企业应用
- CRM
- AI Agent
- 人工智能
【免费下载链接】macro
Macro is a unified workspace for teams: email, chat, docs, tasks, agents, calls, and CRM — @-linked together with shared AI memory.
相关推荐
OpenBot定时任务Routines详解:如何让你的AI同事自动按时工作
OpenBot定时任务Routines详解:如何让你的AI同事自动按时工作 OpenBot 是一个开源的 AI 同事(AI Coworkers)平台,每个 AI
Clawe自动化定时任务全指南:Routines让AI小队7x24小时自动开工
Clawe自动化定时任务全指南:Routines让AI小队7x24小时自动开工 Clawe 是一个多 Agent 协调系统,就像给 OpenClaw 智能体用的
AI Agent多智能体人工智能AI 应用任务调度前端后端ADK Long Horizon Harness 定时任务指南:用 routine 工具编排无人值守的隔离沙箱任务
ADK Long Horizon Harness 定时任务指南:用 routine 工具编排无人值守的隔离沙箱任务 本篇技术指南聚焦 ADK(Agent Dev
示例工程人工智能AI Agent
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考