Skip to content

feat: add sub-agent orchestration system#42

Open
lalyeah wants to merge 7 commits intomasterfrom
feat/subagent-pr
Open

feat: add sub-agent orchestration system#42
lalyeah wants to merge 7 commits intomasterfrom
feat/subagent-pr

Conversation

@lalyeah
Copy link
Collaborator

@lalyeah lalyeah commented Mar 20, 2026

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

  • tests/test_subagent_orchestration.py: unit tests
  • verify_orchestration.py: end-to-end verification script

lalyeah added 2 commits March 20, 2026 23:45
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
Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 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".

Comment on lines +1234 to +1237
run_kwargs: dict[str, Any] = {}
if media:
run_kwargs["media"] = media
result = await self._agent.run(message, **run_kwargs)

Choose a reason for hiding this comment

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

P1 Badge 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 👍 / 👎.

Comment on lines +1575 to +1578
spawn_tool.set_spawner_context(
session_key=record.session_key,
channel=record.spawner_channel,
)

Choose a reason for hiding this comment

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

P1 Badge 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 👍 / 👎.

Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 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".

nonlocal run_result_text
try:
result = await self._agent.run()
result = await self._agent.run(request=message)

Choose a reason for hiding this comment

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

P1 Badge 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 👍 / 👎.

Comment on lines +1087 to +1090
return await self.spawn(
task=task,
label=task[:60],
config=profile.to_subagent_config(),

Choose a reason for hiding this comment

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

P2 Badge 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)

Choose a reason for hiding this comment

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

P2 Badge 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 👍 / 👎.

Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 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".

Comment on lines +2241 to +2244
run_kwargs: dict[str, Any] = {"thinking": True}
if media:
run_kwargs["media"] = media
result = await self._agent.run(message, **run_kwargs)

Choose a reason for hiding this comment

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

P1 Badge 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 👍 / 👎.

Comment on lines 1012 to 1014
try:
await self._agent.add_message(role, content)
injected_count += 1

Choose a reason for hiding this comment

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

P1 Badge 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 👍 / 👎.

Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 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".

Comment on lines +180 to +182
spawner_channel
if spawner_channel is not None
else self._current_spawner_channel,

Choose a reason for hiding this comment

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

P1 Badge 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 👍 / 👎.

Comment on lines +304 to +308
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

Choose a reason for hiding this comment

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

P1 Badge 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 👍 / 👎.

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.

1 participant