Conversation
Introduce a complete sub-agent orchestration module that enables the LLM to autonomously decompose complex tasks by spawning specialized child agents (planner, backend, frontend, researcher, reviewer, devops, tester). New modules: - spoon_bot/subagent/: core package with manager, models, catalog, registry, persistence, and the "spawn" tool - Role-based agent catalog with per-role system prompts, tool profiles, and iteration limits Integration points: - agent/loop.py: SubagentManager lifecycle, spawn tool registration, orchestration prompt injection, auto-routing logic - config.py: SubagentLimitsConfig for depth/children/total limits - channels/manager.py: bus wiring for wake-continuation messages - channels/telegram/commands.py: /subagents command suite (list/spawn/resume/cancel/steer/info) - channels/telegram/constants.py: register bot commands - channels/discord/channel.py: subagent lifecycle event notifications Tests and verification: - tests/test_subagent_orchestration.py: unit tests - verify_orchestration.py: end-to-end verification script
- Restore `attachments` parameter on process(), process_with_thinking(), and stream() to avoid TypeError from websocket handler callers - Forward `media` payload to self._agent.run() in the non-thinking process() path so multimodal requests are not silently downgraded - Skip persistent-agent cap check when reusing an existing profile (allow_existing_profile_name=True) so resume/dispatch still works after the cap is reached
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: e485de3b10
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| run_kwargs: dict[str, Any] = {} | ||
| if media: | ||
| run_kwargs["media"] = media | ||
| result = await self._agent.run(message, **run_kwargs) |
There was a problem hiding this comment.
Forward attachments through the main run path
process() now accepts attachments but only forwards media into self._agent.run(...), so file-upload context is silently dropped for this turn. This breaks attachment-driven requests (especially attachment-only prompts), because the model never receives the attached file references unless users manually restate paths in text; it also regresses prior behavior where attachment context was injected into runtime messages.
Useful? React with 👍 / 👎.
| spawn_tool.set_spawner_context( | ||
| session_key=record.session_key, | ||
| channel=record.spawner_channel, | ||
| ) |
There was a problem hiding this comment.
Keep nested subagent wakes off the user channel
When a sub-agent run initializes its own spawn tool, it binds channel=record.spawner_channel, which is the outer user channel. If that sub-agent spawns descendants, their completion wakes are published back onto the public channel and then handled as normal inbound messages, creating user-visible replies from internal sub-agent sessions. This leaks orchestration-internal traffic and can generate confusing extra responses whenever depth>1 delegation occurs.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 20f5cccf10
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
spoon_bot/agent/loop.py
Outdated
| nonlocal run_result_text | ||
| try: | ||
| result = await self._agent.run() | ||
| result = await self._agent.run(request=message) |
There was a problem hiding this comment.
Pass media and attachments into streaming run call
In stream(), the background runner invokes self._agent.run(request=message) without forwarding media/attachments, and this refactor also removed the earlier runtime-message injection path that encoded those inputs. As a result, streaming requests from REST/WS that include images or file attachments are processed as text-only, so users get materially different (and often incorrect) behavior versus non-streaming calls for the same prompt.
Useful? React with 👍 / 👎.
| return await self.spawn( | ||
| task=task, | ||
| label=task[:60], | ||
| config=profile.to_subagent_config(), |
There was a problem hiding this comment.
Preserve parent linkage when resuming from profile
When resume_agent() falls back to spawning from a persisted profile (no runtime record found), it drops the caller’s parent_id by not passing it into spawn(...). In nested orchestration after a restart, resumed agents are therefore re-created as top-level children, which breaks depth/descendant accounting and cascade behaviors that rely on correct parent-child linkage.
Useful? React with 👍 / 👎.
| name=chosen_name, | ||
| config=cfg, | ||
| ) | ||
| self._save_persistent_profile(persistent_profile) |
There was a problem hiding this comment.
Enforce persistent-agent cap in profile creation path
create_persistent_subagent() writes a new persistent profile directly without checking self.max_persistent_agents, even though spawn() enforces that limit. This lets natural-language/profile-only creation bypass the configured cap and can grow persistent agents beyond operator-defined limits.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: c174febfcd
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| run_kwargs: dict[str, Any] = {"thinking": True} | ||
| if media: | ||
| run_kwargs["media"] = media | ||
| result = await self._agent.run(message, **run_kwargs) |
There was a problem hiding this comment.
Forward attachments in thinking-mode run path
process_with_thinking() accepts an attachments argument (and the REST chat handler passes it when options.thinking=true), but this call only forwards media to self._agent.run(...). Any file-attachment context is therefore dropped for thinking-mode requests, so upload-driven prompts can fail or return inconsistent results compared with non-thinking mode.
Useful? React with 👍 / 👎.
| try: | ||
| await self._agent.add_message(role, content) | ||
| injected_count += 1 |
There was a problem hiding this comment.
Rehydrate multimodal history, not just text content
History replay now injects only plain content strings into runtime memory and ignores persisted multimodal fields (e.g., media/attachments). Because _prepare_request_context() clears runtime memory on every request, earlier image/file turns lose their actual payload after one roundtrip, so follow-up questions that rely on prior uploads can no longer be answered correctly.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 458c450da0
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| spawner_channel | ||
| if spawner_channel is not None | ||
| else self._current_spawner_channel, |
There was a problem hiding this comment.
Preserve explicit null channel in spawner context resolution
_resolve_spawner_context() treats spawner_channel=None as “use previous manager default”, so requests that intentionally clear channel context (e.g. REST/WS calls after a Telegram/Discord turn) can inherit a stale channel and publish sub-agent wake messages to the wrong user/channel. This is a cross-session routing leak because the caller explicitly passes no channel but the code reuses self._current_spawner_channel instead of preserving None.
Useful? React with 👍 / 👎.
| if parent_id and parent_id in self._records: | ||
| new_parent = self._records[parent_id] | ||
| if agent_id not in new_parent.children: | ||
| new_parent.children.append(agent_id) | ||
| record.depth = new_parent.depth + 1 |
There was a problem hiding this comment.
Reject cyclic reparenting during resume
prepare_for_resume() appends the resumed agent to the new parent’s children without validating ancestry, so parent_id values that point to the same agent (or a descendant) create a cycle in the tree. Once that happens, descendant traversals in this method and other registry walkers can loop indefinitely, causing resume/cancel/status operations to hang on malformed or accidental reparent requests.
Useful? React with 👍 / 👎.
Introduce a complete sub-agent orchestration module that enables the LLM to autonomously decompose complex tasks by spawning specialized child agents (planner, backend, frontend, researcher, reviewer, devops, tester).
New modules
spoon_bot/subagent/: core package with manager, models, catalog, registry, persistence, and the spawn toolIntegration points
agent/loop.py: SubagentManager lifecycle, spawn tool registration, orchestration prompt injection, auto-routing logicconfig.py: SubagentLimitsConfig for depth/children/total limitschannels/manager.py: bus wiring for wake-continuation messageschannels/telegram/commands.py: /subagents command suite (list/spawn/resume/cancel/steer/info)channels/telegram/constants.py: register bot commandschannels/discord/channel.py: subagent lifecycle event notificationsTests
tests/test_subagent_orchestration.py: unit testsverify_orchestration.py: end-to-end verification script