nanobot My Tool 深度指南:让 AI Agent 具备运行时自我感知与动态调优能力
【免费下载链接】nanobotUltra-lightweight, open-source, self-hosted personal AI agent framework in Python with WebUI, tools, memory, MCP, multi-agent workflows, automation, and chat apps项目地址: https://gitcode.com/gh_mirrors/nanob/nanobot
本篇指南系统讲解 nanobot 内置的
my自省工具(Self-Awareness Skill):它让 Agent 能够检查自己正在运行的模型、上下文窗口、工作区与工具配置,诊断"为什么某个功能不可用",在任务过程中动态切换模型预设、调整迭代上限,并通过 scratchpad 在会话内跨轮次记住偏好。读完本文,你将掌握my工具的完整配置方式、check/set两种动作的全部关键参数、安全边界设计,以及从源码实现到测试用例的完整证据链。
为什么 Agent 需要"自我感知"
常规工具让 Agent 作用于外部世界——读写文件、搜索代码、执行命令。但 Agent 对自己的运行状态几乎一无所知:它不知道自己在跑哪个模型、能访问哪个工作区、每轮对话的迭代上限是多少。
nanobot 的my工具(Skill 定义)填补了这个空白。在 官方文档 中,它的定位被形象地描述为"像问同事'你现在忙不忙?能换个大点的显示器吗?'"。借助该工具,Agent 可以:
- 认识自己:我用的什么模型?我的工作区在哪?我的每轮迭代上限是多少?
- 动态适应:任务复杂就扩大上下文窗口;简单问答就切换更快的模型。
- 跨轮次记忆:把临时偏好写进 scratchpad,在下一轮对话中继续读取。
my工具的实现位于 nanobot/agent/tools/self.py,其背后的运行时状态边界由 nanobot/agent/tools/runtime_control.py 提供,并配有完整的单元测试 tests/agent/tools/test_self_tool.py。
一、基础用法:两种动作与三个参数
my工具暴露了极简的接口——两个动作(action)、一个键(key)、一个值(value):
| 参数 | 类型 | 说明 |
|---|---|---|
action | string(必填) | check(检查)或set(设置),枚举值见 self.py 的参数定义 |
key | string | 点路径(dot-path)。例如max_iterations、web_config.enable、request.channel;留空表示查看全部配置概览 |
value | any | 仅set时需要,类型必须与目标匹配(max_iterations/context_window_tokens为 int,model/model_preset为 str) |
Skill 文档 SKILL.md 给出的推荐使用流程是四步:
- 从下方分类中识别当前场景;
- 以合适的
action调用my工具; - 若涉及
set,在改动模型或运行时上限这类影响较大的设置前,先警告用户; - 需要详细示例时,查阅 references/examples.md。
二、配置:默认只读,可一键放开写权限
my工具默认启用,但处于只读模式(Agent 可以检查状态但不能修改)。配置项在 MyToolConfig 定义 中,对应 YAML 配置为:
tools: my: enable: true # 默认 true:启用工具 allow_set: false # 默认 false:只读模式将allow_set设为true后,Agent 才可以修改运行时配置(切换模型、调整参数等)。工具是否可用由 MyTool.enabled 决定:需要ctx.runtime_control存在且ctx.config.my.enable为真;create时若没有 runtime control 能力会直接抛错。
旧配置键自动迁移:早期版本使用扁平键tools.myEnabled/tools.mySet,加载时会被自动迁移为tools.my.enable/tools.my.allowSet,下次运行nanobot onboard刷新配置时会原地重写。迁移逻辑见 nanobot/config/loader.py。
持久化边界:除model_preset外,绝大多数修改只在内存中生效;model_preset会保存到当前会话,从而在重启后依然生效(细节见下文"约束"一节)。
三、check:查看"我"的当前状态
3.1 无 key:一键获取关键配置概览
不带key参数时返回核心配置概览,内容来自 RuntimeSnapshot.as_mapping 的白名单字段:
my(action="check") # → max_iterations: 40 # context_window_tokens: 200000 # model: 'anthropic/claude-sonnet-4-6' # workspace: PosixPath('/tmp/workspace') # provider_retry_mode: 'standard' # max_tool_result_chars: 16000 # _last_usage: {'prompt_tokens': 45000, 'completion_tokens': 8000}概览的渲染顺序与内容在 _inspect_all 中定义:依次输出受保护参数、model_preset、workspace、provider_retry_mode、max_tool_result_chars、web_config、exec_config、subagents,若 scratchpad 非空还会追加展示。注意prompt_tokens是跨所有轮次的累计值,不是当前上下文窗口的占用率。
3.2 带 key:点路径精准下钻
my(action="check", key="model") # 当前运行的模型 my(action="check", key="model_preset") # 当前生效的模型预设 my(action="check", key="max_iterations") # 每轮迭代上限 my(action="check", key="workspace") # 工作目录 my(action="check", key="web_config.enable") # 网页搜索是否启用 my(action="check", key="subagents") # 子 Agent 运行状态点路径解析由 _resolve_path 实现:按.切分逐级下钻,同时每一级都会经过_DENIED_ATTRS(Python 内省属性)、BLOCKED(核心子系统)和敏感字段名的三重拦截。若 key 位于模型运行时字段(model/model_preset/context_window_tokens),则优先读取当前请求的实时运行时值(见 _current_runtime_value),保证返回的是本次会话真正生效的配置。
3.3 请求路由元数据(request.*)
request是只读的当前消息路由元数据,支持三个子字段(对应 RequestContext 测试):
my(action="check", key="request.channel") # → 'feishu'(渠道) my(action="check", key="request.chat_id") # → 'oc_abc123'(聊天 ID) my(action="check", key="request.sender_id") # → 'ou_user456'(发送者 ID)该元数据只在显式查询时返回——my(action="check")的概览输出中不会包含chat_id、sender_id,避免把敏感路由信息写进上下文。
四、set:运行时动态调优
set允许在不重启进程的情况下调整运行时状态。修改生效时机分两类(见 docs/my-tool.md):
model_preset:保存到当前会话,作用于该会话的下一轮对话;- 其他可写参数:立即生效。
my(action="set", key="max_iterations", value=80) # → Bump iteration limit from 40 to 80 my(action="set", key="model_preset", value="fast") # → 为当前会话的下一轮切换配置好的模型预设4.1 受保护参数:类型与范围双重校验
以下参数在 RESTRICTED 定义 中声明了类型和取值范围,非法值一律拒绝:
| 参数 | 类型 | 范围 | 用途 |
|---|---|---|---|
max_iterations | int | 1–100 | 每轮对话的最大工具调用次数 |
context_window_tokens | int | 4,096–1,000,000 | 实例默认上下文窗口;会话内应通过 preset 切换 |
model | str | 非空 | 实例默认模型;会话内应通过 preset 切换 |
model_preset | str | 已配置的 preset 名称 | 当前会话下一轮使用的预设 |
校验逻辑在 _modify_restricted:先校验类型(bool 不会被当作 int 接受,字符串"80"会被尝试强转成 int),再校验min/max/min_len范围。测试 test_self_tool.py 覆盖了越界、类型错误、bool 冒充 int、None 值等全部拒绝路径。
会话内禁止直接改model/context_window_tokens:这两个 setter 修改的是共享的实例默认值,在活跃会话中会被拒绝(返回错误并提示"use a configured model_preset")。这是刻意的安全设计——实例级变更会影响其他会话,会话级需求应通过model_preset表达。
4.2 可自由设置的参数
workspace、provider_retry_mode、max_tool_result_chars等参数可以随意设置(值必须 JSON-safe)。workspace的修改尤其特殊:_modify_runtime_setting 调用set_workspace_display,只更新展示用的工作区路径,不会改变文件工具的沙箱边界——这也是 SKILL.md 中"Don't set workspace"反模式的源码依据。
4.3 Scratchpad:会话内的临时记忆
set一个未知的新 key时,值会被写入 scratchpad(内存中的 JSON 安全字典),跨轮次保留、重启后消失:
my(action="set", key="current_project", value="nanobot") my(action="set", key="user_style_preference", value="concise") my(action="set", key="task_complexity", value="high")scratchpad 的实现约束(见 _modify_scratchpad 与_validate_json_safe):
- 最多64 个 key(
_MAX_RUNTIME_KEYS),写满后新增会被拒绝,更新已有 key 不受影响(测试见 test_self_tool.py); - 值必须是 JSON 安全的(str/int/float/bool/None/list/dict),拒绝 callable、Path 等复杂对象,嵌套深度上限 10 层,dict 的 key 必须是字符串;
- 敏感字段名(
api_key、password、secret、token等)在任何路径层级都被拦截,防止凭据泄入上下文。
五、何时用、何时不用:Skill 内置的使用规则
SKILL.md 把使用时机总结为可操作的规则:
先诊断再解释(Diagnose before explaining):当某个功能不工作时,先检查自身状态再向用户解释。例如用户问"为什么你不能搜网页?",Agent 应先执行check("web_config.enable")确认开关状态。
复杂任务前先查预算(Check budget before complex tasks):接受大型任务前先确认自己的上下文窗口与迭代上限,避免承诺后失败。
跨轮次回忆(Recall across turns):把偏好写进 scratchpad,后续轮次再读回来。
只在收益明确且用户知情时 set(Only set when benefit is clear and user is informed):切换模型前必须警告用户。权衡原则是偏向稳定(bias toward stability)——只在默认值确实不够用时才动手。
反模式(Anti-patterns)
- 不要每轮都 check:check 也是一次工具调用,有成本。需要信息时才用,不要形成反射式调用。
- 不要在 scratchpad 存敏感数据:API key、密码、token 一律不得写入。
- 不要 set workspace:如前所述,它不更新文件工具边界,改了也没用。
六、约束与持久化语义
model_preset保存到当前会话,重启后依然生效;其他修改仅存于内存。- 活跃会话中直接写
model和context_window_tokens会被拒绝(会改变共享实例默认值),请改用配置好的model_preset。 - 受保护参数有类型/范围校验:
max_iterations(1–100)、context_window_tokens(4096–1M)、model(非空字符串)。 - 若
tools.my.allow_set为 false,则只能 check、不能 set——此时set会返回"Error: set is disabled (tools.my.allow_set is false)"(见 execute 分支 与 只读模式测试)。
与其他记忆机制的取舍
| 需求 | 使用 | 持久性 |
|---|---|---|
| 会话内临时状态 | my(action="set", key="...", value=...) | 否(重启丢失) |
| 长期事实 | Memory skill(MEMORY.md、USER.md) | 是 |
| 永久配置变更 | 编辑配置文件 | 是 |
经验法则(Rule of thumb):明天还要用 → Memory;只在本次轮次有用 → My。
七、实用场景示例
以下示例均来自 references/examples.md,可直接作为 Agent 行为模式的参考。
7.1 诊断类
# "为什么你不能搜网页?" → my(action="check", key="web_config.enable") → False → "Web search is disabled. Add web.enable: true to your config to enable it." # "你为什么停了?" → my(action="check", key="max_iterations") → 40 → "I hit the iteration limit (40). The task was complex. I can ask the user if they want to increase it." # "你现在跑的是什么模型?" → my(action="check", key="model") → 'anthropic/claude-sonnet-4-6' → my(action="check", key="model_preset") → 'deep'7.2 自适应行为类
# 大型代码库分析:先查容量,再切换到 deep 预设 → my(action="check") → context_window_tokens: 200000 → my(action="set", key="model_preset", value="deep") → "Set model_preset = 'deep' for the next turn; context_window_tokens will be 262144" # 简单问题:切换到 fast 预设省算力 → my(action="set", key="model_preset", value="fast") → "Set model_preset = 'fast' for the next turn; model will be 'openai/gpt-4.1-mini'"7.3 跨轮次记忆类
# 第 1 轮:用户说"说简洁点" → my(action="set", key="user_style", value="concise") → "Set scratchpad.user_style = 'concise'" # 第 3 轮:换了新话题 → my(action="check", key="user_style") → 'concise' (据此调整回答风格) # 跟踪项目上下文 → my(action="set", key="active_branch", value="feat/auth") → my(action="set", key="test_framework", value="pytest") → my(action="set", key="has_docker", value=true)7.4 子 Agent 监控
subagents是只读字段,但提供了丰富的监控信息(含阶段、迭代次数、耗时、最近 5 个工具事件和用量),格式化逻辑见 _format_status:
→ my(action="check", key="subagents") # → 2 subagent(s): # [task-1] 'Code review' # phase: running, iteration: 5, elapsed: 12.3s # tools: read(✓), grep(✓) # usage: {'prompt_tokens': 8000, 'completion_tokens': 1200} # [task-2] 'Write tests' # phase: pending, iteration: 0, elapsed: 0.2s # tools: none八、安全机制:工具绝不改写 config.json
my工具的核心设计原则是:它不重写配置文件。实例级变更只存在于内存,model_preset仅作为当前会话的选择器持久化。在此基础上,访问控制分为三个层次(定义见 self.py,均有对应测试验证):
BLOCKED——完全隐藏(check 和 set 均拒绝)
| 类别 | 属性 | 原因 |
|---|---|---|
| 核心基础设施 | bus、provider、runtime_resolver、_running、tools | 改动会直接搞崩系统或让 Agent 移走自己的工具 |
| 配置管理 | _runtime_vars | — |
| 子系统 | runner、sessions、consolidator、dream、auto_compact、context、commands | 影响其他用户/会话 |
| 敏感运行时状态 | _pending_queues、_session_locks、_active_tasks、_background_tasks | 含凭据与消息路由信息 |
| 安全边界 | restrict_to_workspace、channels_config | 绕过会破坏隔离 |
| Python 内省 | __class__、__dict__、__globals__、__wrapped__、__closure__等_DENIED_ATTRS | 防止沙箱逃逸 |
注意tools与subagents的差异:subagents属于READ_ONLY(可观察但不可替换),tools属于BLOCKED(连检查都拒绝,因为工具注册表自身也是工具之一)。
READ_ONLY——只读(check 允许,set 拒绝)
| 属性 | 说明 |
|---|---|
subagents | 可观察,但替换会破坏系统 |
tool_names | 工具清单 |
exec_config | 可查看沙箱/启用状态,不可修改 |
web_config | 可查看启用状态,不可修改 |
model_presets | 配置派生的预设目录,修改需重新加载配置 |
workspace_sandbox | 工作区强制级别的只读视图 |
request | 当前消息路由元数据 |
敏感字段保护
子字段名若命中敏感名称集合(api_key、secret、password、token、credential、private_key、access_token、refresh_token、auth),无论父路径是什么,check 和 set 都会被拦截——这防止了通过点路径遍历泄密(例如web_config.search.api_key),测试见 test_self_tool.py。同时web_config快照在生成时就会对代理 URL 做脱敏处理(只显示<configured>),见 _snapshot_web_config。
此外,所有set操作都会经 _audit 写入结构化日志(动作、详情、会话标识),便于事后审计;被 BLOCKED/READ_ONLY/敏感字段拒绝的写操作同样会记录审计日志。
九、源码导读:从调用到落地的完整链路
如果希望深入理解my工具,推荐按以下路径阅读:
- nanobot/skills/my/SKILL.md:Skill 使用规则本体(本指南的骨架来源);
- nanobot/skills/my/references/examples.md:配套实战示例;
- nanobot/agent/tools/self.py:
MyTool完整实现——参数 schema、路径解析、拦截清单、格式化与动作分发; - nanobot/agent/tools/runtime_control.py:
RuntimeSnapshot白名单数据契约与AgentRuntimeControl适配器——它定义了 Agent 能"看见"和"改动"的边界; - docs/my-tool.md:面向使用者的官方文档;
- tests/agent/tools/test_self_tool.py:1139 行单元测试,几乎覆盖了上文提到的每一个拦截规则与格式化分支,是理解安全边界的"规范说明书"。
结语
my工具是 nanobot 赋予 Agent 的"元认知能力":通过一个统一的白名单快照(RuntimeSnapshot)把模型、上下文、工作区、工具、子 Agent 状态安全地暴露给 Agent 自己,再用严格的三层拦截(BLOCKED / READ_ONLY / 敏感字段)把修改能力限制在安全范围内。理解它,既能帮助你在配置中正确开启tools.my.allow_set,也能让你在设计自己的 Agent 系统时借鉴这种"可自省、可自调、有边界"的运行时控制模式。
【免费下载链接】nanobotUltra-lightweight, open-source, self-hosted personal AI agent framework in Python with WebUI, tools, memory, MCP, multi-agent workflows, automation, and chat apps项目地址: https://gitcode.com/gh_mirrors/nanob/nanobot
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考