Skip to content

feat: 引入可选的双层架构(Steering Loop + AgentMessage)#1224

Closed
xzq-xu wants to merge 4 commits intoHKUDS:nightlyfrom
xzq-xu:feature/dual-layer-architecture
Closed

feat: 引入可选的双层架构(Steering Loop + AgentMessage)#1224
xzq-xu wants to merge 4 commits intoHKUDS:nightlyfrom
xzq-xu:feature/dual-layer-architecture

Conversation

@xzq-xu
Copy link
Copy Markdown
Contributor

@xzq-xu xzq-xu commented Feb 26, 2026

概述

实现 #1181 提出的可选双层架构,为 Agent Runtime 增加两项核心能力。所有新功能均为 opt-in,默认配置下行为与现有 main 分支完全一致。

A. Steering Loop(动态任务中断)

开启 enable_steering: true 后,用户在 Agent 执行工具链期间发送的新消息会合并到当前对话,而非排队等待。

中断检查点(两处):

  1. 每轮 LLM 调用前:若有待处理中断,先注入再调用 LLM
  2. 每个工具执行前:若有中断到达,立即取消剩余工具(为每个未执行的工具填充 CANCELLED: User interrupted 结果以维持 OpenAI 消息格式),然后注入新消息

中断 vs 新任务的区分:

  • 工具执行阶段(Agent 正在工作)→ 中断合并,模型继续处理(单回复)
  • Final response 之后(任务已结束)→ 不注入,残留消息通过 bus.publish_inbound() 重新排队为新任务(独立处理)

实现细节:

  • 每个 session 独立的 InterruptionChecker(异步队列),跨会话零干扰
  • _dispatch()finally 块负责 checker 生命周期管理和孤儿消息回收
  • 注入内容为带上下文提示的 user message,由 LLM 自主决定继续、切换或同时处理
  • 借鉴了 feat: add async event injection mechanism for user interruption #1233 的逐工具中断 + CANCELLED 填充策略

B. AgentMessage 模型(结构化上下文钩子)

  • AgentMessage 数据类在标准 LLM dict 之上封装应用层元数据:msg_typetagsartifactmetadata
  • AgentLoop 新增两个可选钩子:
    • transform_context:LLM 调用前的语义裁剪(如"保留最近 3 个 artifact")
    • convert_to_llm:自定义 AgentMessage → LLM dict 转换
  • 未配置钩子时,LLM 接收的仍然是原始 list[dict],零开销

变更文件

文件 变更内容
nanobot/agent/messages.py 新增AgentMessageArtifactInfoMessageType 数据模型及查询方法
nanobot/agent/steering.py 新增InterruptionChecker 异步信号/排空队列
nanobot/agent/loop.py run() 中的中断路由、_dispatch() 中的 checker 生命周期管理与孤儿消息回收、_run_agent_loop() 中的逐工具中断检查与上下文钩子;已适配上游 chat_with_retry 重试机制
nanobot/config/schema.py AgentDefaults 新增 enable_steering: bool = False
nanobot/cli/commands.py 2 处 AgentLoop 实例化传入 enable_steering

向后兼容性

所有新代码路径均由 if self.enable_steeringif self._transform_context 守卫。默认配置下:

  • 无任何新分支被执行,无任何新对象被创建
  • provider.chat_with_retry() 接收的 messages 与现有主分支完全一致

测试验证

  • enable_steering=false:gateway 正常启动,消息顺序处理,无回归
  • enable_steering=true:发送多工具任务,执行期间发送中断消息——中断在工具执行前被捕获,剩余工具以 CANCELLED 结果填充,LLM 动态响应合并后的上下文
  • Final response 后的新消息不会注入到已完成的循环,而是作为新任务独立处理

与 PR #1233 的关系

本 PR 借鉴了 #1233 的逐工具中断检查和 CANCELLED 填充策略,同时保留了以下差异设计:

  • 队列在 agent 层InterruptionChecker),而非 bus 层,保持关注点分离
  • 注入格式为 user message(带上下文提示),而非 <SYS_EVENT> system message,让 LLM 获得更多决策信息
  • 额外提供 AgentMessage 模型 + 上下文钩子,覆盖 Issue 中的"问题二:结构化语义记忆"

待后续完善

本 PR 完成了双层架构的核心框架和接口层,以下能力已预留数据模型和方法,但尚未端到端打通:

  • Artifact 自动填充ArtifactInfo 模型已定义,但工具执行结果未自动标记为 artifact
  • 内置语义裁剪策略get_latest_artifacts(n)filter_by_type() 等查询方法已就绪,但未提供开箱即用的 transform_context 实现
  • 外部事件监控:当前仅支持用户新消息中断,其他事件源可通过扩展 InterruptionChecker 实现

Closes #1181

@xzq-xu xzq-xu force-pushed the feature/dual-layer-architecture branch 2 times, most recently from 3ba3a28 to ed21299 Compare March 3, 2026 07:32
@xzq-xu xzq-xu force-pushed the feature/dual-layer-architecture branch 6 times, most recently from 2a1b08d to 276a59f Compare March 7, 2026 16:18
@xzq-xu xzq-xu force-pushed the feature/dual-layer-architecture branch 3 times, most recently from 2922f6d to e59e17f Compare March 10, 2026 12:11
@xzq-xu xzq-xu force-pushed the feature/dual-layer-architecture branch 3 times, most recently from 2ba9355 to 9e48703 Compare March 17, 2026 02:13
@xzq-xu xzq-xu force-pushed the feature/dual-layer-architecture branch 2 times, most recently from 53ec63e to 7ba54bc Compare March 18, 2026 09:45
@xzq-xu xzq-xu changed the base branch from main to nightly March 18, 2026 09:45
@xzq-xu xzq-xu force-pushed the feature/dual-layer-architecture branch from 7ba54bc to df77ee4 Compare March 19, 2026 14:30
@xzq-xu xzq-xu force-pushed the feature/dual-layer-architecture branch 3 times, most recently from 50ca97d to 8222022 Compare March 25, 2026 11:04
@xzq-xu xzq-xu closed this Mar 30, 2026
@xzq-xu xzq-xu force-pushed the feature/dual-layer-architecture branch from 8222022 to 80403d3 Compare March 30, 2026 02:43
@xzq-xu xzq-xu reopened this Mar 30, 2026
xzq-xu added a commit to xzq-xu/nanobot that referenced this pull request Mar 30, 2026
Reimplement PR HKUDS#1224 Level-1 steering (inject user messages before LLM
call via _LoopHook.before_iteration) and PR HKUDS#2219 incremental session
save (persist after each tool-using iteration via after_iteration hook)
on the new AgentRunner/AgentHook architecture.

Made-with: Cursor
@xzq-xu xzq-xu force-pushed the feature/dual-layer-architecture branch from e9b8c11 to 1a16b43 Compare March 31, 2026 05:41
xzq-xu added a commit to xzq-xu/nanobot that referenced this pull request Mar 31, 2026
- steering.py: add SteeringHook(AgentHook) with before_iteration drain
- loop.py: add extra_hooks param to _run_agent_loop/_process_message/process_direct
- _dispatch: create per-session InterruptionChecker + SteeringHook
- run(): route interruptions to active session checkers
- messages.py: AgentMessage data model for dual-layer architecture
- config: add context_budget_tokens setting

Made-with: Cursor
@xzq-xu xzq-xu force-pushed the feature/dual-layer-architecture branch from 341d810 to 99ab041 Compare April 1, 2026 04:03
xzq-xu added a commit to xzq-xu/nanobot that referenced this pull request Apr 1, 2026
Sync nanobot with upstream/nightly while preserving nobot-specific features:

Upstream features merged:
- Dream memory system (Consolidator + Dream two-stage architecture)
- cached_tokens tracking (Anthropic / OpenAI-compat providers)
- iMessage / Discord channel support
- /dream, /dream-log slash commands
- StreamRenderer improvements (force_terminal, stop_for_input)
- /status cache hit rate display

Preserved nobot features (by PR):
- HKUDS#1224: Steering two-layer architecture (SteeringHook, InterruptionChecker, AgentMessage)
- HKUDS#2219: Incremental session saving
- HKUDS#2205: Tool definitions cache
- HKUDS#2262: Cron tz+at support
- HKUDS#2667: Feishu _resuming fix
- HKUDS#1838: Cron run history tracking

Preserved DeskClaw platform adaptation:
- _is_win_gui_command (Windows shell support)

Made-with: Cursor
@xzq-xu xzq-xu force-pushed the feature/dual-layer-architecture branch 2 times, most recently from ec3b3af to 5af0c60 Compare April 3, 2026 03:16
@xzq-xu xzq-xu force-pushed the feature/dual-layer-architecture branch 2 times, most recently from 88792b0 to fdc1dd5 Compare April 7, 2026 03:04
xzq-xu added a commit to xzq-xu/nanobot that referenced this pull request Apr 7, 2026
Major upstream features:
- Jinja2 template system for agent prompts
- OpenAI Responses API support
- Built-in grep/glob search tools
- Microsoft Teams channel
- Runtime hardening (checkpoints, retry, empty response handling)
- GPT-5, GitHub Copilot, Xiaomi MiMo, Qianfan providers
- GitStore moved to utils/gitstore.py
- Retry-after mechanism, SSRF whitelist
- Tool class refactor (@tool_parameters decorator)
- Various channel improvements (telegram, weixin, whatsapp, discord)
- New dependencies: jinja2, msteams (PyJWT, cryptography)

Nobot features preserved via rebased upstream PRs:
- PR HKUDS#1224: Steering dual-layer architecture
- PR HKUDS#2205: Tool definitions cache
- PR HKUDS#2243: Fast preflight trim (replaces on_iteration_complete)
- PR HKUDS#2706: Feishu streaming tool hints + _resuming

Additional nobot features manually preserved:
- Cron tz+at timezone support
- Cron job history API
- Windows GUI command exit code normalization
- Version suffix +nobot

Made-with: Cursor
@xzq-xu xzq-xu force-pushed the feature/dual-layer-architecture branch 2 times, most recently from 6190ec0 to 0d6f3ba Compare April 8, 2026 02:59
xzq-xu added a commit to xzq-xu/nanobot that referenced this pull request Apr 8, 2026
Upstream changes (80+ commits):
- feat: tool hints extracted to utils/tool_hints.py with path abbreviation
- feat: shell sandbox (bwrap), bash -l -c, minimal env, _kill_process
- feat: feishu bot open_id, _resolve_mentions, done_emoji (HKUDS#2899)
- feat: dream enhancement with date/char context (HKUDS#2887)
- feat: cron job name parameter (HKUDS#2680)
- feat: ${VAR} env interpolation in config secrets
- feat: voice transcription unification (OpenAI/Groq Whisper)
- fix: runner empty-response retry (_MAX_EMPTY_RETRIES=2)
- fix: shell CancelledError subprocess cleanup
- security: bind api port to localhost, prevent env leak
- refactor: hook CompositeHook._for_each_hook_safe DRY
- Various channel fixes (matrix, telegram, whatsapp, email)

Preserved nobot features (5 upstream PRs):
- PR HKUDS#1224: Steering Loop (InterruptionChecker, SteeringHook, extra_hooks)
- PR HKUDS#2205: ToolRegistry definitions cache (_definitions_cache)
- PR HKUDS#2219: Incremental session save (on_turn_saved) — re-applied
- PR HKUDS#2243: Fast preflight trim (fast_trim_if_needed, archive_trimmed_chunk)
- PR HKUDS#2706: Feishu streaming resuming + inline tool hints (_resuming, tool_hint_len)

Other nobot features preserved:
- _is_win_gui_command (shell.py)
- context_budget_tokens (schema.py)
- cron tz+at timezone fix

Gateway: no changes needed (registry_patch.py interfaces unchanged).
Made-with: Cursor
@xzq-xu xzq-xu force-pushed the feature/dual-layer-architecture branch 2 times, most recently from 8130cd9 to 6eed049 Compare April 10, 2026 03:31
xzq-xu added a commit to xzq-xu/nanobot that referenced this pull request Apr 10, 2026
Upstream changes:
- feat(channels): add WebSocket server channel
- feat(session): add unified_session config
- feat(exec): support allowed_env_keys
- fix(agent): deliver LLM errors to streaming channels
- fix(providers): enforce role alternation for non-Claude
- feat(channel): add proxy support for Discord
- Remove msteams channel

Merge resolution:
- Keep local HKUDS#1224 (steering/extra_hooks) and HKUDS#2219 (incremental save)
- Integrate upstream unified_session alongside local steering logic
- Update _run_agent_loop to return 4-tuple (add stop_reason)

Made-with: Cursor
xzq-xu added 4 commits April 13, 2026 10:44
…age)

Adapted for the new AgentHook/runner architecture in nightly.

- Steering Loop: when enable_steering is true, interruptions arriving
  during tool execution are injected via after_iteration hook instead
  of inline loop logic.
- AgentMessage model: rich message wrapper with application-layer
  metadata (artifacts, tags) and optional context transform hooks.
- InterruptionChecker: thread-safe signaling between dispatch and
  running agent tasks.

Both layers are fully opt-in. Default behavior is identical to the
original loop with zero overhead.

Made-with: Cursor
- Module docstrings → one-liners matching project style
- Remove unused InterruptionChecker.check() method
- Remove section comments in messages.py

Made-with: Cursor
…n only

Remove before_tool_call hook and _execute_tools_with_hook since the
upstream runner uses asyncio.gather for concurrent tool execution,
making sequential per-tool interruption checks impractical.

Steering now only injects user messages before each LLM call via
before_iteration, which is sufficient for dynamic direction changes.

Made-with: Cursor
Move interruption injection logic from _LoopHook into a standalone
SteeringHook(AgentHook) that plugs into the hooks system. Steering is
now always active — InterruptionChecker is created per-dispatch and
SteeringHook is passed as an extra hook. When no interruptions arrive,
drain_all() returns empty and the hook is a no-op.

Removes enable_steering from AgentDefaults config, CLI wiring, and
AgentLoop.__init__. Removes transform_context/convert_to_llm params
that were unused after the per-tool cancellation drop.

Made-with: Cursor
@xzq-xu xzq-xu force-pushed the feature/dual-layer-architecture branch from 6eed049 to 455734c Compare April 13, 2026 02:50
xzq-xu added a commit to xzq-xu/nanobot that referenced this pull request Apr 13, 2026
- steering.py: InterruptionChecker + SteeringHook for mid-conversation
  user interruptions (per-tool checking with CANCELLED fill)
- messages.py: AgentMessage structured model for dual-layer architecture
- loop.py: _process_message and _run_agent_loop accept per-call
  extra_hooks (merged with instance-level hooks via CompositeHook)

DeskClaw gateway passes SteeringHook as extra_hooks for WebSocket
sessions.

Ref: upstream PR HKUDS#1224
Made-with: Cursor
@xzq-xu xzq-xu closed this Apr 14, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

建议引入可选的双层架构(Steering Loop + AgentMessage),以提升 Agent Runtime 的自主性与智能水平

1 participant