Macro routines 解析:让 Agent 为自己排期的定时任务(Routine)AI 工具链

发布时间:2026/10/31 1:37:30
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.

项目地址:https://gitcode.com/GitHub_Trending/macro3/macro
点击查看免费下载

本文基于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 引擎与持久化的调度存储 —— 这些属于本 crateoutbound所调用的 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)

变体字段约束
onceat(RFC3339,含 UTC 偏移)必须严格晚于当前时间,否则报错"Choose a one-off time in the future."
cronexpression(六字段 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 选择
scheduleOnce或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)
CreateRoutineconfiguration: RoutineConfigurationRoutineInfoadditive
ListRoutinesquery?(匹配 name/instructions)、enabled?RoutineListread_only
ReadRoutineroutine_idRoutineDetailsread_only
UpdateRoutineroutine_id+ 恰好一个RoutineChangeRoutineInfodestructive
RunRoutineroutine_idRoutineRunStartedadditive
DeleteRoutineroutine_idDeletedRoutinedestructive + 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 / 404routine 或目标对该用户不可用
402用户的 AI 额度已用尽,routine 当前无法运行
409routine 正在运行或已被改动;重新读取后再编辑
其他服务不可用;重试创建前先查ListRoutines

4.4 与调度器服务的 Wire 契约

RoutineService各方法对应的端点(均相对 base,即 scheduled-action 服务):

能力HTTP 请求
createPOST scheduled-actions(body 附加"enabled": true)
listGET scheduled-actions?include_events=true(结果按最近优先反转)
readGET scheduled-actions/{id}+GET scheduled-actions/{id}/history(take(50))
updatePUT scheduled-actions/{id}
set_enabledPUT scheduled-actions/{id}/enabled,body 仅{"enabled": bool}
deleteDELETE scheduled-actions/{id}
run_nowPOST 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)")。结合现有代码,落地清单为:

  1. domain.rs:定义/扩展命令与返回 schema(#[derive(JsonSchema)]生成工具参数;带标签枚举用rename_all保持 snake_case 线格式),并把新操作加进RoutineServicetrait;
  2. outbound.rs:在RoutineService for RoutineClient中实现 HTTP 调用,复用request::<T>以获得鉴权头、体积上限与统一错误转述;若引入新 wire 结构,仿照WireRoutine/WireExecution写From转换;
  3. 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.

项目地址:https://gitcode.com/GitHub_Trending/macro3/macro
点击查看免费下载

相关推荐

上一篇:libwebsockets 深入解析:用 minimal-mqtt-client-multi 构建多路并发 MQTT 客户端
下一篇:RuoYi-Vue-Plus 多租户后台管理系统:把租户隔离、幂等与分布式调度写进声明式配置

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考