给 Coding Agent 一段自然语言需求,它可以迅速生成文件、接口、测试,甚至一套看起来完整的系统。但对于已经有明确架构判断的开发者,困难有时并不是如何让模型写出更多代码,而是如何阻止它替自己决定那些本来已经确定的事情。
例如,开发者可能已经知道模块之间的边界、接口类型、必须保持的不变量,以及哪些实现细节仍然没有答案。把这些内容全部改写成文字提示,不仅费力,也会使原本可以被编译器和工具检查的结构退化成供模型解释的描述。
我想探索一种不同的输入方式:开发者提交的不是完整需求文档,也不是等待执行的完整程序,而是一份带有明确空缺的程序。 确定的部分用真实代码表达,尚未确定的部分用注解、结构化注释和待实现位置表达。Agent 先确认关键语义,再在这些边界内补全实现。
这里暂时称它为 Intent-as-Code。重点不在用代码包装 Prompt,而在于把部分工程决策直接保留在程序结构中,让模型负责处理剩余的不确定性。
程序可以是未完成的,但结构已经有意义
考虑一个消费支付成功事件并创建发货单的函数。开发者知道消息可能重复投递,也知道同一订单不应该重复发货,却还没有决定如何处理外部发货接口的失败和重试。
一种示意性的 Python 写法是:
# 以下为拟议语法,不是现成框架的可运行 API。
@goal("收到支付成功事件后创建发货单")
@assume("事件可能重复投递")
@invariant("同一 order_id 不得产生重复发货")
@hole("补齐幂等与失败恢复;不得改变公开函数签名")
def on_paid(
event: PaymentSucceeded,
shipments: ShipmentStore,
carrier: Carrier,
) -> None:
raise NotImplementedError
这里至少有三类不同的信息。函数签名约束了组件可以接受哪些依赖,以及它对外暴露的形状;注解表达目标、前提和行为要求;函数体中的空缺则把实现权有意识地交给 Agent。
这不是让模型从零猜测整个系统,而是要求它针对一个局部问题工作。开发者仍然使用熟悉的 Python、Java 或 Go 来表达已有设计,不必把接口、数据流和模块关系重新翻译成一篇自然语言说明。
但这些信息的约束强度并不相同。函数签名能接受类型检查;“不得重复发货”如果只是一行自然语言注解,仍然需要进一步定义可观察行为、失败模型与验证手段。给一句话加上 invariant 标记,并不会让它自动成为可以形式化证明的不变量。
Agent 最重要的下一步,可能不是写代码
面对上面的程序,一个直接补洞的 Agent 很容易选择“查一下数据库,没有记录就调用发货接口,再写入记录”。这在正常路径上可能工作,但如果外部接口已经成功、数据库写入却失败,消息重试仍然可能产生第二次发货。
因此,在选择实现之前,Agent 应该先问一个影响架构的问题:
Carrier 是否支持以 order_id 为键的幂等请求?如果不支持,外部调用成功但本地状态未提交时,系统允许怎样恢复?
回答可能直接决定设计边界。若 Carrier 支持可靠的幂等键,可以围绕该能力设计重试。若它不支持,而业务又要求严格避免重复副作用,仅靠本地去重表和重试逻辑通常无法给出所需保证;可能必须改变外部协议,或引入状态对账与人工处理的策略。
这类问题适合在生成代码前确认,因为选错以后需要改变的不是某一行条件判断,而是整个失败处理模型。
反过来,Agent 没必要对所有命名、局部辅助函数或实现风格都发起询问。澄清应集中在会改变组件契约、跨系统副作用、安全边界或验收条件的分歧上。能够从现有类型、测试和仓库约定中直接确定的事项,可以先提出有依据的默认方案。
确认后的答案也不应该只留在对话历史里。它需要回写为源码中的约束、独立的决策记录,或其他可被后续执行重新读取的结构。否则下一轮生成仍然需要重新猜测同一个问题。
从源代码提取约束,而不是把源码当成长 Prompt
一个可行的实现不一定要先发明全新的 DSL。初期可以利用宿主语言已有的语法树、类型信息、装饰器或注释标记,抽取出 Agent 能使用的中间表示(IR)。
其中需要保留的不只是目标描述,还包括它来自哪里,以及它约束哪个范围。例如,“不得修改函数签名”是开发者显式给出的限制;“Carrier 可能支持幂等键”只是 Agent 的待确认假设;“项目使用特定错误类型”可能来自已有代码库的惯例。这三者不能被混为同等权威的指令。
提取过程本身也需要谨慎。Python 装饰器可能携带运行时行为,Go 的注释则没有同样的语言语义。一个跨语言框架可以在静态分析阶段识别特定标记,但不应为了理解意图而直接执行不可信源码。普通解释性注释也不应自动升级为硬约束,否则历史备注、临时 TODO 和已经过时的设计讨论会同时获得不合理的权重。
更重要的是,Agent 对约束的理解必须允许被质疑。当两个标记冲突、注解无法转换为可检验条件,或仓库现状与书面意图不一致时,系统应暴露冲突,而不是生成一份表面符合所有要求的代码。
有了这些边界,补全流程可以保持相对简单:
部分程序 + 仓库上下文
|
v
提取确定结构、意图、假设与 holes
|
v
发现冲突或关键歧义 -> 向开发者确认
|
v
按组件契约生成候选补丁
|
v
类型检查、测试、契约检查与人工 Review
|
v
保留证据,或将失败反馈到相应的 hole
这更接近一次受约束的程序合成,而不是把整个代码库和一长段 Prompt 交给模型重新规划。
局部补全不等于永远只能局部修改
我对现有部分 Agent 工作流的一个顾虑,是设计决策经常存在于一次性的规划文本或对话上下文中。系统先给出完整计划,然后大量生成文件。后续如果开发者纠正了早期假设,模型可能需要跨越多个文件重新解释原来的结构。
但这不是 top-down 规划天然错误,也不能据此断言“第一次生成一定最好”。真正的问题是:哪些设计决定已经被确认,哪些仍可改变,以及一次修改应该使哪些实现失效,没有稳定的机器可读边界。
部分程序可以改善这一点。开发者先固定某些接口与组件关系,Agent 对明确的 hole 生成补丁,并说明它依赖了哪些前提。若一项新需求只改变局部算法,可以在同一组件内重新求解;若它要求改变上游契约,系统就应报告受影响的调用方,并请求扩大修改范围,而不是悄悄越过原有约束。
局部性是一项需要检测和维护的性质,不是标注 hole 以后自动得到的保证。某些合理的需求变化本来就必须触及系统整体。在这种情况下,强行保留原来的程序骨架只会把错误设计固定得更牢。
这也解释了为什么组件化和规划并不冲突。组件图规定接口与依赖;规划决定先处理哪些不确定性;补全则针对图中仍开放的节点展开。它们解决的是不同层面的问题。
这与已有的程序合成工作是什么关系?
不完整程序与自动补洞不是新的发明。Program Sketching 已经研究过让程序员保留高层实现结构,把部分低层细节留给合成器。2023 年的 ANPL 更直接地使用精确代码表示程序骨架,用自然语言描述 holes,并支持通过交互式分解局部修订程序。它与本文设想有实质性的重合,不能把“代码骨架 + 自然语言空缺 + 交互修订”本身当作原创机制。
其他工作则从不同角度把结构引入 LLM 使用过程。DSPy 用签名、模块和评估指标组织可组合的语言模型程序;LMQL 通过模板、变量和约束控制语言模型交互。它们说明“用程序结构代替自由文本 Prompt”已经有多条实践路径,但主要处理的对象与这里讨论的“由 Coding Agent 补全宿主语言中的真实软件组件”并不完全相同。
本文真正想继续追问的是工程接口问题:当开发者已经掌握部分架构知识时,能否让这些知识成为长期保留的、带来源和作用域的生成约束?当尚未确定的语义会影响架构时,Agent 能否在写代码之前识别它、向人确认,并把确认结果带进后续局部修改?
这可以作为对已有研究方向的延伸,而不是一个已经证明更优的新编程范式。
名称也要避免误导。“In-Context Prompt”容易被理解为一般的上下文提示,甚至与 In-Context Learning 混淆;它没有准确指出这里的交互对象是部分程序。因此,Intent-as-Code 比较适合作为这一设想的工作名称。它强调意图被放进代码结构中,但并不意味着所有意图都能被代码充分表达。
更强的结构也可能带来更强的误导
这套方式主要面向已经能够判断接口、数据模型和系统边界的开发者。对于探索性很强的项目,提前写下精细骨架可能比自然语言 Prompt 更危险:开发者把未经验证的假设放进类型和注解,Agent 又把这些内容当作固定条件,最终得到一个实现质量不错、方向却错误的系统。
结构化输入还有成本。开发者需要维护意图与实现之间的一致性,Agent 需要追踪约束的来源和生效范围,验证器则需要区分“测试通过”“满足某些形式化性质”与“符合完整业务意图”。这些不能用一个统一的“验证成功”状态遮盖。
并非每个项目都值得引入 DSL。若任务只是修改一个明确的函数,现有 IDE、类型系统、单元测试和简短提示可能已经足够。只有当反复发生的误解确实集中在架构边界、跨组件依赖与隐含设计决策上时,额外的意图层才有可能抵消维护成本。
什么实验能判断它是否有用?
可以从一种宿主语言、一个成熟代码库里的有限任务集开始,而不是直接实现跨语言编译器。挑选约二十个含有真实设计分歧的修改任务,分别使用常规自然语言需求与带 holes、约束标记的部分程序驱动同一 Coding Agent。再增加一个“有部分程序但不主动澄清”的对照,区分结构化输入与提问机制各自带来的作用。
评估不能只看代码生成的 pass rate。还需要统计开发者准备输入的时间、澄清轮数、后续返工、越过既定接口的修改次数、受影响文件范围,以及人工 Review 是否真正更容易确认设计意图。测试通过只代表已覆盖的行为;架构层面的判断仍需要独立审查。模型、任务难度和可用上下文也应尽量保持可比。
如果带注解的程序只是让开发者花更多时间把需求重写一遍,却没有减少返工和错误假设,这种方式就没有明显优势。如果大部分问题只有执行原型以后才会暴露,那么生成前的反复确认还可能延误真正有价值的反馈。
这个设想的关键不在于让程序取代自然语言,更不在于让 Agent 自动理解所有注释。它是在尝试重新分配编程过程中的决定权:人负责明确自己已经知道、并愿意承担责任的结构;模型负责探索仍然开放的实现空间;工具提供可以观察的验证证据。
真正需要验证的,是这种分工能否在不提前冻结错误设计的情况下,让开发者的工程判断在多轮 Agent 工作中持续有效。