Skip to content

feat(agent): steering#1517

Merged
yinwm merged 4 commits intosipeed:refactor/agentfrom
afjcjsbx:feat/agent-steering
Mar 15, 2026
Merged

feat(agent): steering#1517
yinwm merged 4 commits intosipeed:refactor/agentfrom
afjcjsbx:feat/agent-steering

Conversation

@afjcjsbx
Copy link
Collaborator

@afjcjsbx afjcjsbx commented Mar 13, 2026

📝 Description

This PR introduces the Agent Steering feature.

Currently, once the agent starts executing a chain of tool calls, the user has no way to redirect it mid-run. They must wait for the entire turn to complete — watching the agent waste time (and cause side effects) on actions that are no longer relevant. This feature introduces a message injection mechanism that lets external callers push messages into a running agent loop, which are picked up at well-defined checkpoints between tool executions.

Key changes:

  • Added pkg/agent/steering.go — thread-safe steeringQueue (FIFO, max 10 messages) with two dequeue modes: "one-at-a-time" (default) and "all".
  • Added public API on AgentLoop: Steer(), SteeringMode(), SetSteeringMode(), and Continue().
  • Changed tool execution in runLLMIteration from parallel (sync.WaitGroup) to sequential, enabling steering polls between individual tool completions.
  • Steering is polled at two checkpoints: at loop start (before first LLM call) and after every tool completes. When a steering message is found, remaining tools in the batch receive "Skipped due to queued user message." as their result and the LLM is called again with the updated context.
  • Added drainBusToSteering goroutine in Run(): while the agent is busy processing, new inbound bus messages (from Telegram, Discord, Slack, etc.) are automatically redirected into the steering queue via Steer(). (audio transcription applied before steering).
  • Added Continue() method for resuming an idle agent using pending steering messages (uses SkipInitialSteeringPoll to prevent double-dequeuing).
  • Added steering_mode field to AgentDefaults config, configurable via PICOCLAW_AGENTS_DEFAULTS_STEERING_MODE.
  • Added comprehensive documentation in docs/steering.md and a detailed implementation spec in docs/design/steering-spec.md.
  • Added pkg/agent/steering_test.go with full test coverage of the queue, modes, polling checkpoints, skip logic, and Continue().

Intent: This PR is not intended to be merged immediately into main. It serves as the reference branch for the steering implementation, to be cited in issue #1316 (Agent Loop Refactor). The full refactor described in that issue supersedes this implementation architecturally — this branch documents and validates the steering concept (queue semantics, polling points, skip behavior, bus drain) that the refactor will incorporate in a redesigned form.

🗣️ Type of Change

  • 🐞 Bug fix (non-breaking change which fixes an issue)
  • ✨ New feature (non-breaking change which adds functionality)
  • 📖 Documentation update
  • ⚡ Code refactoring (no functional changes, no api changes)

🤖 AI Code Generation

  • 🤖 Fully AI-generated (100% AI, 0% Human)
  • 🛠️ Mostly AI-generated (AI draft, Human verified/modified)
  • 👨‍💻 Mostly Human-written (Human lead, AI assisted or none)

🔗 Related Issue

Referenced by #1316Agent Loop Refactor: the refactor issue describes a full redesign of the agent loop as an event-driven, hookable, interruptible, and appendable system. This branch provides the working reference implementation for the steering and potentially interrupt mechanisms that the refactor will incorporate.

📚 Technical Context (Skip for Docs)

🧪 Test Environment

  • Hardware: All
  • OS: All
  • Model/Provider: All
  • Channels: All

📸 Evidence (Optional)

Click to view flow example
1. User: "search for info on X, write a file, and send me a message"

2. LLM responds with 3 tool calls: [web_search, write_file, message]

3. web_search is executed → result saved

4. [polling] → User sent "no, search for Y instead" → enqueued via Steer()

5. write_file → "Skipped due to queued user message."
   message   → "Skipped due to queued user message."

6. Steering message "search for Y instead" injected into context

7. LLM receives full updated context and responds accordingly

Steering mode behavior:

Mode Behavior
"one-at-a-time" (default) One message dequeued per polling cycle — model reacts to each individually
"all" Entire queue drained in a single poll — all messages injected together

@sipeed-bot sipeed-bot bot added type: enhancement New feature or request domain: agent domain: config go Pull requests that update go code labels Mar 13, 2026
Copy link
Collaborator

@yinwm yinwm left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@yinwm yinwm merged commit 021aa7d into sipeed:refactor/agent Mar 15, 2026
4 checks passed
lppp04808 pushed a commit to lppp04808/picoclaw_team that referenced this pull request Mar 16, 2026
* feat(agent): steering

* fix loop

* fix lint

* fix lint
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

domain: agent domain: config go Pull requests that update go code type: enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants