Skip to content

Commit 0c350e9

Browse files
committed
use task.steered event xml shape for steer injection
Replace the ad-hoc <priority>...</priority> wrap from the previous commit with the same <event name="..."> shape the TaskAgent already reads for task.progress, task.cancelled, etc. Body starts with "User has steered your task:" so the LLM has a clear directive to treat this as overriding its current plan. Before: <priority>Override your current plan with the instruction below.</priority> {text} After: <event name="task.steered"> User has steered your task: {text} </event> Test + CHANGELOG updated to match.
1 parent 22e94f6 commit 0c350e9

3 files changed

Lines changed: 14 additions & 13 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
99

1010
### Fixed
1111

12-
- `TaskAgent._inject_steering` now wraps incoming steer text with a short `<priority>` directive before injecting it into LLM context, so the steer outranks the original task instruction (which is itself a user message). Orchestrator side drops its now-redundant `"Steering instruction: "` text prefix.
12+
- `TaskAgent._inject_steering` now wraps incoming steer text in a `<event name="task.steered">User has steered your task: ...</event>` block before injecting into LLM context — same shape as task.progress / task.cancelled events the agent already reads — so the steer outranks the original task instruction (which is itself a plain user message). Orchestrator side drops its now-redundant `"Steering instruction: "` text prefix.
1313
- `start_task` and `steer_task` now pre-flight the target's finishing state. If the in-process TaskAgent has already called `finished` (or been cancelled), the steer would race the terminal turn and silently drop. `start_task` handles this transparently: the public entrypoint waits up to `TASK_STEER_CLOSING_WAIT_SECONDS` (default 5s) for the slot to free, then retries the start cleanly — the LLM never sees the intermediate state. Explicit `steer_task` returns `error: "task_closing"` for the LLM to chain `start_task` itself. Personal and corp in-process ships symmetric; BYOA skips the check.
1414
- Voice agent prompt: removed the contradictory "do NOT call start_task if all slots are occupied" line; added a short note that for steered instructions the agent should read the `task.completed` message and mention any unfulfilled intent to the commander.
1515

src/gradientbang/runtime/subagents/task_agent.py

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1715,15 +1715,14 @@ async def _inject_steering(self, text: str) -> None:
17151715
cleaned = text.strip()
17161716
if not cleaned:
17171717
return
1718-
# Wrap with a brief priority directive so the LLM treats this user
1719-
# message as outranking the original task instruction. The task
1720-
# description was itself a user message, so without this wrap the
1721-
# steer is just another peer instruction with no priority signal.
1722-
prioritized = (
1723-
"<priority>Override your current plan with the instruction below.</priority>\n"
1724-
f"{cleaned}"
1718+
# Wrap as a task.steered event so the LLM treats it as a system
1719+
# directive overriding the original task instruction. The task
1720+
# description was itself a plain user message, so without this
1721+
# framing the steer is just another peer instruction.
1722+
steered_xml = (
1723+
f'<event name="task.steered">\nUser has steered your task: {cleaned}\n</event>'
17251724
)
1726-
message = {"role": "user", "content": prioritized}
1725+
message = {"role": "user", "content": steered_xml}
17271726
if self._llm_context is not None:
17281727
self._llm_context.add_message(message)
17291728
self._output(cleaned, TaskOutputType.INPUT)

tests/unit/test_task_agent.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -967,13 +967,15 @@ async def test_steering_emits_replanning_message(self):
967967
]
968968
agent.queue_frame.assert_awaited_once()
969969

970-
# The steer is injected as a user message wrapped with a brief
971-
# <priority> directive so the LLM treats it as outranking the
972-
# original task instruction (which was itself a user message).
970+
# The steer is injected as a user message wrapped in a
971+
# task.steered event so the LLM treats it as a system directive
972+
# overriding the original task instruction (which was itself a
973+
# plain user message).
973974
agent._llm_context.add_message.assert_called_once()
974975
injected = agent._llm_context.add_message.call_args.args[0]
975976
assert injected["role"] == "user"
976-
assert injected["content"].startswith("<priority>")
977+
assert injected["content"].startswith('<event name="task.steered">')
978+
assert "User has steered your task" in injected["content"]
977979
assert "Change direction" in injected["content"]
978980

979981
async def test_duplicate_progress_message_is_suppressed_without_new_action_or_event(self):

0 commit comments

Comments
 (0)