如果你已经把LangChain、LangGraph、AutoGen、LlamaIndex这类框架玩得很熟,那么开发一个Agent原型的难度并不高。然而从原型走向生产环境,会碰到一系列必须解决的问题:用户鉴权怎么实现、多种消息渠道如何统一管理、会话与记忆怎样持久保存、部署时是否得带上解释器和一大堆依赖。面对这些疑问,多数Python Agent框架把工作留给了开发者自己。
OpenClaw.NET选择了另一条路:它是一套用.NET构建的自托管AI Agent运行时加网关,把鉴权、策略、记忆、可观测性以及多渠道接入都做进了内部;借助NativeAOT还能编译出无需额外依赖的单文件原生二进制。运行时不带解释器、不需要虚拟环境,也不存在依赖冲突。项目已经开源,代码托管在github.com/clawdotnet/openclaw.net。
拿Python来打比方,它很像一个FastAPI应用:对外暴露HTTP、WebSocket以及各IM平台的webhook,对内则运转着一个能调用工具、读写记忆、跨渠道对话的AI Agent,区别只在于整个东西最终被编译成了一个二进制文件。本文会全程借用Python中的概念做对照,读完后你应当能够理解系统组成与消息流转、在本地把服务跑起来,并写出自己的第一个工具、技能、插件或渠道。
一、30秒认识OpenClaw.NET的组成
从外部接口看,它提供HTTP、WebSocket、浏览器UI(路径为/chat)、各IM平台webhook、兼容OpenAI的端点(/v1/*)以及MCP(/mcp)。内部核心负责推理循环、工具执行、记忆、会话、技能、策略、审批和断路器。渠道层覆盖WebSocket、Telegram、Slack、Discord、Teams、WhatsApp,另外还接入了飞书、钉钉与企业微信。
可以扩展的位置包括工具(Tool)、技能(Skill)、插件(Plugin)、渠道(Channel)和LLM Provider;前端形态则有浏览器UI、CLI、Avalonia桌面应用、Blazor WASM运维面板和TUI。开源仓库里的解决方案文件名为OpenClaw.Net.slnx,命名空间统一使用OpenClaw.*,与项目名称保持一致,查找代码时不容易迷路。
二、从Python到.NET的心智模型对照
这一部分建议优先阅读,理解之后,后面约九成的代码都能顺利看懂。两者在异步写法上概念几乎一致:async/await的用法与Python几乎相同。异步队列方面,asyncio.Queue对应System.Threading.Channels;取消机制上,Python用asyncio.Task.cancel()从外部取消,而.NET使用CancellationToken,需要在方法最后一个参数显式传递。
包管理方面,pip、poetry、uv加pyproject.toml这一套对应CLI、.csproj与NuGet;依赖锁定在工程文件里,因此不再需要venv之类的虚拟环境。Web框架上,FastAPI或Flask对应ASP.NET Core的Minimal API风格,路由写法和FastAPI很相似。依赖注入方面,dependency-injector或FastAPI的Depends对应内置的Microsoft.Extensions.DependencyInjection容器。
数据建模上,@dataclass或pydantic BaseModel对应C#的record加required;序列化方面,pydantic的序列化对应System.Text.Json加源生成器,后者在编译期完成,这一点是关键差异。类型系统上,Python的鸭子类型或typing.Protocol对应C# interface,必须显式声明实现;Optional[str]或str | None对应string?,但这里的检查发生在编译期。
资源管理上,with open(...) as f:对应await using,用于确定性释放;生成器对应IEnumerable<T>或IAsyncEnumerable<T>,流式token就靠后者实现。测试栈上,pytest加unittest.mock对应xUnit v3加NSubstitute。并发模型上,.NET的worker是真正并行执行的,没有GIL限制。
语法层面最容易卡住的三个点如下。第一,异步方法返回Task<string>,其效果约等于await llm.call(msg),同样会让出事件循环;CancellationToken类似asyncio的取消机制,但采用显式传参风格,务必逐层向下传递。第二,record加required约等于pydantic的BaseModel加必填字段,但校验发生在编译期,不提供就编译不过。第三,可空引用类型中string?可空、string不可空,编译器会强制检查,这好比整个项目一直运行在myypy --strict之下;并且本项目把警告视为错误,兜底时应使用??运算符,它比maybe or "default"更严格,不会吞掉空串。
依赖注入方面,Python靠FastAPI的Depends或第三方库实现,.NET则内置了容器。注册写法如AddSingleton<IMemoryStore, FileMemoryStore>,约等于dependency_overrides[IMemoryStore] = FileMemoryStore;解析用GetRequiredService<IMemoryStore>,找不到时直接抛异常。本项目几乎全部采用单例(Singleton),注册按职责拆成若干扩展方法,在Program.cs里顺序调用,这与你main.py里组装依赖的拓扑结构是一致的,只是搬进了容器。
有一个绕不开的硬性约束是NativeAOT与裁剪。项目需要编译成NativeAOT单文件二进制,并启用了激进裁剪(TrimMode=link)。用Python工程师的视角来理解:这是一个没有猴子补丁、没有importlib动态加载、没有运行时反射的世界。getattr(obj, name)按字符串取属性、__import__动态导入模块、靠内省自动生成schema这些常见操作都行不通,因为裁剪器会把看起来没人引用的代码删掉。
最直接的影响体现在JSON序列化上:不能依赖反射发现类型,必须使用源生成器,为类型声明JsonSerializerContext,由编译期生成序列化代码。例如需要序列化的类型通过[JsonSerializable]标注ProblemDetails、OperatorAccountService.StoreState等,统一挂在GatewayJsonContext上。pydantic平时靠反射在运行时构建校验器,而这里相当于强制所有模型走提前编译路线,类似把pydantic v2的预编译思路推到极致。新增DTO时,记得把它挂到某个JsonSerializerContext上。

回报也很实在:无解释器、无依赖、毫秒级冷启动,这正是PyInstaller或Nuitka想要、却难以完整提供的那类产物。裁剪还引出了贯穿全文的两条运行时车道:默认车道裁剪安全、低内存、不支持动态加载,生产Docker镜像走这条;完整车道使用完整.NET,支持反射和进程内动态加载插件,开发期默认走这条,动态性与你在Python中的体验接近。
理解整个系统的主线在于中枢OpenClaw.Gateway。它在启动时组合Agent运行时、消息管道、渠道适配器与插件宿主,统一路由所有流量。一条用户消息从进入系统到收到回复,共经历11个步骤,下面用Python概念逐一对照。
第一步,IChannelAdapter把入站消息写入MessagePipeline,约等于一个有界的asyncio.Queue。第二步,Worker取出消息,worker数量为1到4个,上限等于CPU核数,注意这里是真正的线程并行,不受GIL约束。第三步,获取该会话的SemaphoreSlim,约等于asyncio.Semaphore(1),保证同一会话不会并发执行两轮。第四步,进行限流和token预算检查,可短路拒绝,相当于FastAPI的中间件链。
第五步,进入Agent运行时,调用MafAgentRuntime.RunAsync(...)。第六步,载入或新建会话、裁剪历史并注入记忆召回。第七步,调用LLM,如需工具就执行,结果回灌后再调用LLM,直到产出文本,这与LangChain AgentExecutor的循环思路相同。第八步是工具调用完整链路:预设过滤、治理策略、Hook、人工审批、执行、审计。
第九步,LLM调用自带指数退避重试、超时、断路器与降级模型级联,相当于内置了tenacity和熔断器。第十步,会话写入IMemoryStore,开发环境默认用SQLite。第十一步,按ChannelId找到渠道适配器完成投递。
系统的骨架接口都位于src/OpenClaw.Core/Abstractions/目录,包括ITool、IChannelAdapter、IAgentRuntime、IMemoryStore、IToolHook等,看懂它们就等于看懂了全部可扩展面。这里和Python有一处习惯差异:Python使用鸭子类型,对象有相应方法就能用;C#是显式接口,必须写class MyTool : ITool声明实现了它。因此扩展方式就是声明实现某个接口,再注册进DI容器,与你继承BaseTool再注册的思路一致,只是编译器会全程检查。
还需要澄清一个源码大于文档的现状:README说编排器默认是native、MAF可选,但当前开源版本实际只运行MAF(Microsoft Agent Framework),native运行时已不在仓库中,选错orchestrator启动时会直接抛异常。把MAF加jit理解为唯一运行时即可。
三、本地启动OpenClaw.NET的前置条件与步骤
前置条件包括必须安装.NET 10 SDK,这类似安装一次Python解释器,之后所有工程共用;可选Node.js 20+,仅在运行TS/JS插件时需要;以及一个LLM API Key。启动命令方面,先执行校验配置,作用相当于启动前自检,命令为dotnet run src/OpenClaw.Gateway ... Release -- ...;再执行启动,约等于uvicorn main:app,但会自带编译。默认监听http://127.0.0.1:18789,用浏览器打开/chat即可对话。
最快的本地启动只需设置三个环境变量并执行一条命令:MODEL_PROVIDER_KEY设置为你的LLM key,OPENCLAW_WORKSPACE设为"$PWD/workspace",还要把"$OPENCLAW_WORKSPACE"配置到相应位置。配置体系与Python服务常见的配置文件加环境变量覆盖是同一套路:环境变量用双下划线映射层级,例如OpenClaw__Runtime__Mode对应配置树OpenClaw:Runtime:Mode,约等于pydantic-settings的env_nested_delimiter。敏感字段支持env:VAR_NAME引用写法,生产环境推荐使用。
有几个坑值得注意:必须使用.NET 10,多个SDK并存时用global.json固定版本,作用类似.python-version文件。出厂的appsettings.json里默认AuthToken和示例API key仅限本地回环使用,对外部署务必改成env:引用,不要把真实密钥提交进仓库。公网绑定会被安全硬化拦截,缺少鉴权token、危险工具或使用raw:密钥都会拒绝启动,这是有意设计。
四、动手扩展的四条路径:从轻量到重量级
写法最常用的是编写工具(Tool)。一个工具就是一个实现ITool接口的类,用Python视角看,这相当于继承BaseTool写工具类,但接口是显式声明的。工具类需要提供Description供LLM判断何时调用,提供ParameterSchema描述参数的JSON Schema,还要实现ValueTask<string> ExecuteAsync并接收argumentsJson。
一个最小可用示例是字符串反转工具ReverseTextTool,它在类声明上显式标注实现ITool,这是比鸭子类型更明确的地方。它的Name为"reverse_text",Description为"Reverse the characters of the given text.",ParameterSchema包含"text"字段、"description"为"Text to reverse",并把文本列入required。实现时用JsonDocument解析入参,通过RootElement.GetProperty取值,这种做法是AOT安全的,约等于json.loads后手动取字段。
写好之后把它加入内置工具列表,CreateBuiltInTools(...)就是一个集中实例化的组装函数;重启网关后对它说“reverse the text hello”,工具调用就会经过完整的审计、治理与审批链路。

第二种方式最轻量:编写技能(Skill),它纯粹是Markdown文本。技能并非代码,而是一份供Agent阅读的操作手册。工具是你用@tool装饰器写出的函数,而技能是一段精心设计的system prompt加Runbook,用来教Agent在某类任务里如何组合调用现有工具。其机制是渐进式披露:系统提示里只放技能索引以节省token;当Agent判断相关时再拉取完整正文;需要时再读取附属文件。
创建一个技能只需要一个文件夹加一个SKILL.md,举例来说,pr-reviewer的name字段是pr-reviewer,description写明“当用户要求审查一个Pull Request或diff时使用。”,正文中要求使用`read_file`(git diff)拿到改动,然后按正确性、边界、安全、可读性审查,输出分级意见:🔴必须改、🟡建议、🟢可选。
第二种方式(指插件)是编写插件(Plugin),用于打包一组能力,有两条路线。一是原生.NET动态插件,在进程内加载DLL,仅限jit车道;二是JS/TS桥接插件,运行Node.js子进程并通过JSON-RPC通信,两条车道都可行。用Python来类比:前者约等于用importlib把模块动态加载进同一个进程,灵活但住在一起;后者约等于起一个子进程用RPC通信,隔离性好且语言自由,连权衡方式都与你熟悉的那套一样。原生契约仅45行,用INativeDynamicPlugin和INativeDynamicPluginContext,包含RegisterTool等,还能RegisterChannel、RegisterHook、RegisterProvider。
第三种方式(指渠道)是接入渠道(Channel),接的是你自己的IM。契约是IChannelAdapter,负责收和发;入站流程为webhook → handler校验解析 → 管道入队;出站按ChannelId路由投递。可以照抄Twilio SMS的实现,它是最简单的参照,共6步:配置类、适配器、webhook handler、DI注册、挂适配器、映射端点。
webhook handler的核心形态,写FastAPI的人会一眼认出来,注释给出约等于@app.post("/webhook")的写法:先验签,再解析,然后白名单校验,最后入队。它返回ValueTask<WebhookResult>,方法为WebhookResult HandleAsync,接收Func<InboundMessage, CancellationToken, ValueTask>,其中InboundMessage等平台字段用于签名验证。[FromQuery]等?[param:]等等——原文此处仅示例ValidateSignature;IsValidSignature返回Unauthorized。接着是解析、白名单校验等。
每个渠道都应当具备这些安全面:签名校验使用恒定时间比较,约等于Python的hmac.compare_digest;发送者白名单;积上限?严格地说,是「体积上限」——即消息体大小限制;以及去重窗口。这些能力保证渠道在后文中稳妥落地,适应不同IM生态。
前文四次扩展用途总结为:让Agent能调用一个动作、教Agent处理某类任务、打包一组能力或复用TS生态。需要补充的是,若需接入多渠道,实现IChannelAdapter便可延伸社交平台一侧。
五、开发必须知道的三条红线
红线的第一条是严肃对待类型与警告:本项目设置了TreatWarningsAsErrors=true并启用可空性强制,这相当于把mypy --strict和ruff --select ALL全部勾选为不通过不准提交。第一次上手时会觉得很缠,但它拦截的正是你在Python里半夜被叫起来修的AttributeError: 'NoneType'那类错误。
第二条是JSON必须走源生成器,别指望动反射的序列化方式,也就是“json.dumps(obj.__dict__)的习惯在这里需改掉”,否则在AOT下运行时会直接崩溃。第三条是关于数据隔离与测试写入边界:记忆与会话默认落在./memory/下,严禁用清空整库、DROP或删目录的方式做测试隔离,只能删除自己创建的数据,或者切换到独立的throwaway路径,做法类似pytest的tmp_path fixture。
六、测试方法与学习曲线小结
测试栈是xUnit v3加NSubstitute,约等于pytest加unittest.mock。运行全部测试约等于pytest;也可以按条件过滤运行单个类,命令带有"FullyQualifiedName~ProcessToolTests";若要按类筛选用法则类似pytest -k。对Python工程师而言,这套系统的学习曲线比想象中平缓:async/await的写法几乎照搬、异步队列、信号量、中间件链、显式的依赖组装——你在asyncio加FastAPI里形成的那套直觉,在这里基本都是成立的。
真正的难点只有三件:第一,类型是编译期铁一般的契约,不再仅是提示,而是纪律;第二,接口需要显式声明;第三,NativeAOT禁止反射的约束。与之回报的是一个编译完成即为一个二进制的生产级Agent运行时:无需解释器、没有GIL、也不存在依赖困境。
继续深挖的话,最可靠的三个源码入口如下。src/OpenClaw.Gateway/Program.cs是启动主线,相当于main.py;src/OpenClaw.Agent/MafAgentRuntime.cs是Agent循环本体;src/OpenClaw.Core/Abstractions/是所有对扩展的切入点,约等于各项目中的base.py或abc.py。本文涉及的全部代码与结论均对应开源仓库clawdotnet/openclaw.net当前源,从当前源看,运行时等于MAF加jit。若发现与实际源码不符,请以源为准,也欢迎提PR。
小结:OpenClaw.NET面向Python工程师制造惊喜的并非陌生概念,而是熟练如法的体验:FastAPI般的原生体验让渠道调用与工具审计无缝生成生产接入,并凭借NativeAOT的单文件交付免除部署依赖,最终拿出一个真正能跑的二进制Agent运行时;动手路径最短的入口,也确实被集成进作为唯一原生后端微软Agent框架的统一路线——你要用到的都是最短的网络与调度与一整套按到齐技能。
