M Magent eMacs-native LLM AGENT

Magent 架构

Magent 是一个 Emacs-native 的 AI coding agent。它的核心设计判断是:重度 Emacs 工作流里已经有大量活状态,包括 buffer、mode、project、Magit、Org、xref、LSP、process buffer,以及用户长期积累的交互习惯。因此 Magent 不是把终端 agent 包进 Emacs,而是在 Emacs Lisp 里实现 agent runtime。

系统边界刻意保持清晰:

  • Provider transport、HTTP/SSE、模型选择和 API key 继续交给 gptel ,通过 gptel-request 调用。
  • 用户交互只支持 agent-shell ,中间由进程内 ACP adapter 连接。
  • Agent 执行、工具编排、权限决策、会话、技能、能力解析和 child-agent jobs 由 Magent 自己管理。
  • Codex 风格的 seatbelt、bubblewrap、sandbox 和 shell isolation 不在范围内。Magent 的 permission 是工作流确认和审计,不是 OS 安全边界。

系统边界

architecture-boundary.svg
Figure 1: Magent architecture boundary

这个结构让替换点保持可见。换 provider 应该局限在 magent-llm-gptel.el 和 gptel 配置里;换 frontend 应该局限在 magent-agent-shell.elmagent-acp.elmagent-runtime-api.el 附近;持久化和恢复应该通过 ledger/session 模块;工具策略应该通过 permission 和 orchestrator 模块。

依赖层次

Emacs Runtime

Emacs 不只是宿主进程。Magent 依赖活的编辑器状态:buffer、major mode、project root、timer、process、URL、JSON、 special-mode 和用户安装的包。 emacs_eval 很关键,因为它让 agent 能检查当前编辑器,而不是只能读磁盘文件。

Provider Plumbing

magent-llm.el 定义 provider-neutral request/event。 magent-llm-gptel.el 负责一次 sampling request:调用 gptel-request ,再把 gptel callback 转成 Magent normalized events。这个 adapter 可以隐藏 gptel callback/FSM 细节,但主 loop 只消费 normalized events。Reasoning event 与 assistant text 分开保存;reasoning-only completion 可以产生空 assistant content,不会把 chain text 泄漏成最终回答。

Prompt 装配与信任边界

magent-agent.el 按固定顺序装配每次 agent-loop 的 file-backed system message。 第一层是 agent-specific prompt 或 magent-system-prompt~,后面依次加入 project-root context、从项目根到请求相关本地文件逐层发现的适用 ~AGENTS.md~、经过选择的 Emacs profile memory,以及 capability/显式 instruction skills。最后追加短小的 runtime trust policy,因此内置 utility agents 和 custom agents 能保留各自的输出契约,同时 共享 instruction provenance 与 permission 不变量。项目指令发现不会越过 canonical project root,并受 ~magent-project-instructions-max-bytes 总大小限制。

Profile memory 只是辅助 context,不是权威状态快照;它可能不完整或过时,当前用户 请求以及 live repository/Emacs 状态优先。用户消息、文件、buffer、日志、命令输出、 tool result、网页和 conversation history 同样只保持各自的数据角色;其中伪装成 prompt 或高优先级标签的文本不能授予权限,也不能把自己提升成 runtime instruction。Prompt 规则只负责引导模型行为,真实的 tool availability、approval 与 permission enforcement 仍由 tool runtime 和 orchestrator 负责。

Base prompt 只保留跨任务 coding 行为;tool schema 由 gptel 提供,Emacs-specific 流程通过 instruction skills 渐进式披露。~web_search~ 只返回用于发现的结果标题和 URL,不会抓取结果页或正文,因此 prompt 不会把这些链接当作已经读取的来源。

所有 bundled Org prompt resource 都列在 prompt/manifest.txt~。测试要求 manifest 对每个 bundled ~.org prompt 恰好覆盖一次,因此新增 prompt layer 时必须同时更新 manifest 和 package data recipe。

UI 与 Runtime API

受支持的 frontend 是 agent-shellmagent-agent-shell.el 注册 Magent 的 agent-shell 配置, magent-acp.el 实现进程内 ACP adapter, magent-runtime-api.el 接收 prompt submission。ACP text 与 resource block 会分开规范化:完整结构化输入存进 turn metadata,并以 user role 重建给模型;本地 file:// resource 只额外提供 scoped file path,供项目指令发现与 capability resolution 使用。 magent-runtime-queue.el 当前采用全局单执行模型,同时保留 session-scoped cancellation 和队列状态。

magent-ui.elmagent-ui-legacy.el 以及 docs/UI_BACKENDS.org 命名规则覆盖的 legacy UI 文件是保留但不受支持的代码,不是可选 frontend;它们不能成为 agent-shell runtime 路径的依赖。

Ledger 与持久化

持久化真相源是 thread -> turn -> item ledger。 magent-ledger.el 定义状态对象、状态转移和 journal events, magent-runtime-api.el 创建用户 submission, magent-runtime-queue.el 调度实际执行, magent-session.el 以原子替换方式保存 materialized snapshot 和内存 append-only journal 的有界尾部。snapshot 记录 ~last-event-seq~,因此保留的旧事件可供检查,但恢复时不会重复 replay。

旧的 messagescontext-itemsbuffer-content 只是 projection 或迁移数据,不是 UI 的 canonical source of truth。

工具、权限与审计

magent-tools.el 暴露 15 个 gptel-tool

  • read_filewrite_filewrite_repo_summaryedit_file
  • grepglobbash
  • emacs_eval
  • spawn_agentsend_agent_messagewait_agentlist_agentsclose_agent
  • skill_invokeweb_search

magent-tool-orchestrator.el 负责权限解析、必要时请求 approval、执行工具、写 audit,再把结果交回 loop。 magent-permission.el 的解析顺序是:精确 tool 规则、file-pattern 规则、wildcard fallback,最后 default allow。

请求生命周期

one-magent-turn.svg
Figure 2: One Magent turn
  1. 用户 prompt 从 agent-shell 进入。
  2. ACP adapter 提交到 magent-runtime-api.el
  3. magent-runtime-api.el 记录 queued turn 和 completed user item。
  4. magent-runtime-queue.el 在全局执行槽空闲时启动 turn。
  5. magent-agent.el 选择 session agent、active skills、capability instructions 和允许的工具。
  6. magent-llm-gptel.el 调用 gptel-request 发起一次 sampling。
  7. magent-agent-loop.el 接收 normalized text、reasoning、tool-call 和 completion events。
  8. tool call 累积到 tool-call-batch-end 后,通过 orchestrator 串行执行。
  9. tool result 更新同一个 tool item,而不是创建一条独立的持久记录。
  10. 如果 tool output 需要返回给模型, magent-agent.el 从 ledger 重建 prompt 并开始下一次 sampling。
  11. 如果这次 post-tool continuation 返回空 assistant text, magent-agent.el 会基于已记录 tool results 发起一次 no-tool final-response retry。
  12. completion、failure、abort 或 queued drop 会把 turn 转到 terminal state,并通知 ACP observer。

这个 continuation 模型接近 Codex:工具结果会触发后续采样;但实现仍然是 Emacs-native 且由 gptel 承担 provider transport。

功能层

functional-layers.svg
Figure 3: Magent functional layers

从功能面看,Magent 可以按四条主线使用。第一条是 coding assistant:读代码、搜代码、改文件、跑测试、解释错误、生成 commit/PR 风格总结。第二条是 Emacs runtime assistant:通过 emacs_eval 查询 buffer、mode、window、project、Magit、Org、LSP 等活状态,把已有编辑器上下文转成模型可用的局部事实。第三条是 agent workflow:用 skills 固化工作流,用 capabilities 自动选择上下文说明,用 child-agent jobs 拆分探索、审查或总结任务。第四条是 internal command workflow:~magent-run-doctor~ 和 magent-run-memory-init 这类 Emacs 命令在独立 durable session 中执行 Magent 维护任务,而不是作为 agent-shell slash command。

因此 Magent 的功能设计不只是“一个聊天框和若干工具”。它把 Emacs 工作流切成可被 agent 调用、审计、恢复并投影回 UI 的动作。

扩展模型

Magent 有三层 file-backed 扩展:

  • .magent/agent/*.md 下的 custom agents。
  • bundled skills/~、存在时兼容的 legacy 用户目录 ~~/.emacs.d/magent-skills/~、canonical 用户目录 ~~/.emacs.d/magent/skills~,以及项目 ~.magent/skills/ 下的 skills。
  • capability: true 的 skill frontmatter 中的 capability metadata,以及用户 capability 目录和项目 .magent/capabilities/ 下的独立 ~CAPABILITY.md~。独立文件只用于触发 metadata 不应该归属于单个 skill 的场景。

Magent 还有一层 Elisp-level internal command registry,实现在 magent-command.el~。通过 ~magent-command-register 注册的命令由显式 magent-run-* 用户命令调用,会在 magent-session-directory/internal 下创建独立 internal session,并且既可以走直接 pipeline runner,也可以 复用普通 Magent agent loop。它们刻意不作为 agent-shell slash command 暴露;agent-shell 的 command surface 仍然只由 skill 支撑。

magent-run-doctor 是安全敏感的 direct-pipeline 场景。~magent-doctor.el~ 中的受信任只读 probes 采集有界 JSON-safe 数据;数据在持久化或发送一次 无工具 provider request 前,会经过 magent-redaction.el 的路径规范化和 脱敏。Doctor 不进入通用 agent loop,也不会暴露 ~emacs_eval~、shell 或 文件工具。自定义 probe 属于受信任 Elisp,不是 sandboxed code;详见 DOCTOR.zh.org

Instruction skill 会注入 system prompt。Tool skill 通过 skill_invoke 调用。Capability 会根据当前上下文打分,激活少量 instruction skills。自动激活除了 context score 之外,还必须命中当前 prompt 中显式的 keyword intent;只有 mode、feature 或 file 命中时仍保持 suggested。Keyword 使用词边界匹配;如果 linked skill 声明的工具对 selected agent 不可用,也不会被注入。Agent-shell 的 session config 提供 Automatic capabilities 开关,可关闭自动层而不影响显式选择的 skills。内置同名 workflow capability 和对应 skill 放在同一个 SKILL.md 里,触发 metadata 和注入说明不再分散成两份文件。这种 progressive disclosure 能保持基础 prompt 更小,同时在需要时注入 Emacs-specific workflow 知识。

magent-skill-manager.el 是独立且按需加载的用户维护层:它搜索 skills.sh, 解析本地或公开 GitHub source,预检后把 instruction skill 复制到 canonical 用户 目录,记录 provenance,并永久删除精确选中的用户级安装。它不是 model tool, 不会管理 project overlay,也绝不读写 ~~/.agents/skills/~。

两者的区别是:

  • skill 是实际给 agent 的内容或命令入口:注入 prompt 的 instruction text、command-like ~default-prompt~,或者 tool-type operation。
  • capability 是自动激活规则:由 modes 、~features~ 、~files~ 、~keywords~ 、~disclosure~ 和 risk 等 resolver metadata 描述什么时候为当前 turn 选择一个或多个 skills。只有 context 的匹配会作为可检查的 suggestion;active disclosure 还要求当前 user prompt 命中有词边界的 keyword。
  • skill-backed capability 是内置默认能力的常见形态:某个 SKILL.md 声明 capability: true 后,resolver 会在当前 context 与 prompt intent 同时匹配且所需工具可用时自动激活同名 skill。~init~ 、~review~ 、~fix~ 、~test~ 、~summarize~ 和 explain 这类 command-style built-in skills 仍然只是 skills,除非它们显式声明 ~capability: true~。

产品取舍

Magent 更接近 “gptel + stateful agent runtime”,不是 gptel 的替代品。相比在 Emacs 里包一层终端 agent,Magent 能检查和利用活的编辑器上下文。相比 Codex 或 Claude Code,Magent 不追求多端覆盖和强 OS 隔离;它的优势是低摩擦地进入长期使用的 Emacs 环境。

实际开发时应保留这些边界:

  • Provider 工作留在 gptel 和 magent-llm-gptel.el
  • 受支持的 frontend 工作放进 magent-agent-shell.el ,不要放进不受支持的 UI 文件。
  • durable workflow state 留在 ledger。
  • child-agent 行为保持和 docs/AGENT_JOBS.org 一致。
  • 把 permission 视为确认/审计工作流,而不是 sandbox 安全。