codex-agent-framework 0.1.20__tar.gz → 0.1.21__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {codex_agent_framework-0.1.20 → codex_agent_framework-0.1.21}/CHANGELOG.md +23 -0
- {codex_agent_framework-0.1.20/codex_agent_framework.egg-info → codex_agent_framework-0.1.21}/PKG-INFO +22 -8
- {codex_agent_framework-0.1.20 → codex_agent_framework-0.1.21}/README.md +21 -7
- {codex_agent_framework-0.1.20 → codex_agent_framework-0.1.21}/codex_agent/__init__.py +4 -1
- {codex_agent_framework-0.1.20 → codex_agent_framework-0.1.21}/codex_agent/agent.py +29 -18
- {codex_agent_framework-0.1.20 → codex_agent_framework-0.1.21}/codex_agent/agent_runtime.py +1 -1
- {codex_agent_framework-0.1.20 → codex_agent_framework-0.1.21}/codex_agent/builtin_plugins/memory/__init__.py +2 -1
- codex_agent_framework-0.1.21/codex_agent/builtin_plugins/subagents/__init__.py +220 -0
- codex_agent_framework-0.1.21/codex_agent/builtin_plugins/subagents/profiles/explorer.json +12 -0
- {codex_agent_framework-0.1.20 → codex_agent_framework-0.1.21}/codex_agent/client.py +16 -2
- {codex_agent_framework-0.1.20 → codex_agent_framework-0.1.21}/codex_agent/command.py +98 -11
- {codex_agent_framework-0.1.20 → codex_agent_framework-0.1.21}/codex_agent/config.py +3 -2
- {codex_agent_framework-0.1.20 → codex_agent_framework-0.1.21}/codex_agent/event.py +56 -9
- {codex_agent_framework-0.1.20 → codex_agent_framework-0.1.21}/codex_agent/mainloop.py +72 -15
- {codex_agent_framework-0.1.20 → codex_agent_framework-0.1.21}/codex_agent/message.py +41 -5
- {codex_agent_framework-0.1.20 → codex_agent_framework-0.1.21}/codex_agent/plugin.py +12 -3
- {codex_agent_framework-0.1.20 → codex_agent_framework-0.1.21}/codex_agent/prompts/system_prompt.txt +11 -8
- {codex_agent_framework-0.1.20 → codex_agent_framework-0.1.21}/codex_agent/provider.py +1 -1
- {codex_agent_framework-0.1.20 → codex_agent_framework-0.1.21}/codex_agent/scheduler.py +2 -2
- {codex_agent_framework-0.1.20 → codex_agent_framework-0.1.21}/codex_agent/server/core.py +2 -2
- {codex_agent_framework-0.1.20 → codex_agent_framework-0.1.21}/codex_agent/sessions.py +307 -17
- {codex_agent_framework-0.1.20 → codex_agent_framework-0.1.21}/codex_agent/stream_utils.py +63 -37
- {codex_agent_framework-0.1.20 → codex_agent_framework-0.1.21}/codex_agent/tool.py +30 -4
- {codex_agent_framework-0.1.20 → codex_agent_framework-0.1.21/codex_agent_framework.egg-info}/PKG-INFO +22 -8
- {codex_agent_framework-0.1.20 → codex_agent_framework-0.1.21}/codex_agent_framework.egg-info/SOURCES.txt +2 -0
- {codex_agent_framework-0.1.20 → codex_agent_framework-0.1.21}/pyproject.toml +2 -2
- {codex_agent_framework-0.1.20 → codex_agent_framework-0.1.21}/tests/test_agent.py +726 -65
- {codex_agent_framework-0.1.20 → codex_agent_framework-0.1.21}/tests/test_chat.py +1 -1
- {codex_agent_framework-0.1.20 → codex_agent_framework-0.1.21}/tests/test_events.py +18 -0
- {codex_agent_framework-0.1.20 → codex_agent_framework-0.1.21}/tests/test_memory.py +10 -6
- {codex_agent_framework-0.1.20 → codex_agent_framework-0.1.21}/tests/test_messages.py +13 -1
- {codex_agent_framework-0.1.20 → codex_agent_framework-0.1.21}/tests/test_scheduler.py +5 -4
- {codex_agent_framework-0.1.20 → codex_agent_framework-0.1.21}/tests/test_server.py +14 -12
- {codex_agent_framework-0.1.20 → codex_agent_framework-0.1.21}/LICENSE +0 -0
- {codex_agent_framework-0.1.20 → codex_agent_framework-0.1.21}/MANIFEST.in +0 -0
- {codex_agent_framework-0.1.20 → codex_agent_framework-0.1.21}/codex_agent/__main__.py +0 -0
- {codex_agent_framework-0.1.20 → codex_agent_framework-0.1.21}/codex_agent/ai.py +0 -0
- {codex_agent_framework-0.1.20 → codex_agent_framework-0.1.21}/codex_agent/browser.py +0 -0
- {codex_agent_framework-0.1.20 → codex_agent_framework-0.1.21}/codex_agent/builtin_commands.py +0 -0
- {codex_agent_framework-0.1.20 → codex_agent_framework-0.1.21}/codex_agent/builtin_plugins/__init__.py +0 -0
- {codex_agent_framework-0.1.20 → codex_agent_framework-0.1.21}/codex_agent/builtin_plugins/bash/__init__.py +0 -0
- {codex_agent_framework-0.1.20 → codex_agent_framework-0.1.21}/codex_agent/builtin_plugins/browser/__init__.py +0 -0
- {codex_agent_framework-0.1.20 → codex_agent_framework-0.1.21}/codex_agent/builtin_plugins/content/__init__.py +0 -0
- {codex_agent_framework-0.1.20 → codex_agent_framework-0.1.21}/codex_agent/builtin_plugins/content/system.py +0 -0
- {codex_agent_framework-0.1.20 → codex_agent_framework-0.1.21}/codex_agent/builtin_plugins/content/vision.py +0 -0
- {codex_agent_framework-0.1.20 → codex_agent_framework-0.1.21}/codex_agent/builtin_plugins/desktop/__init__.py +0 -0
- {codex_agent_framework-0.1.20 → codex_agent_framework-0.1.21}/codex_agent/builtin_plugins/environment/__init__.py +0 -0
- {codex_agent_framework-0.1.20 → codex_agent_framework-0.1.21}/codex_agent/builtin_plugins/files/__init__.py +0 -0
- {codex_agent_framework-0.1.20 → codex_agent_framework-0.1.21}/codex_agent/builtin_plugins/files/tools.py +0 -0
- {codex_agent_framework-0.1.20 → codex_agent_framework-0.1.21}/codex_agent/builtin_plugins/interface/__init__.py +0 -0
- {codex_agent_framework-0.1.20 → codex_agent_framework-0.1.21}/codex_agent/builtin_plugins/planner/__init__.py +0 -0
- {codex_agent_framework-0.1.20 → codex_agent_framework-0.1.21}/codex_agent/builtin_plugins/python/__init__.py +0 -0
- {codex_agent_framework-0.1.20 → codex_agent_framework-0.1.21}/codex_agent/builtin_plugins/scheduler/__init__.py +0 -0
- {codex_agent_framework-0.1.20 → codex_agent_framework-0.1.21}/codex_agent/cli/__init__.py +0 -0
- {codex_agent_framework-0.1.20 → codex_agent_framework-0.1.21}/codex_agent/cli/headless.py +0 -0
- {codex_agent_framework-0.1.20 → codex_agent_framework-0.1.21}/codex_agent/cli/main.py +0 -0
- {codex_agent_framework-0.1.20 → codex_agent_framework-0.1.21}/codex_agent/cli/output.py +0 -0
- {codex_agent_framework-0.1.20 → codex_agent_framework-0.1.21}/codex_agent/cli/root.py +0 -0
- {codex_agent_framework-0.1.20 → codex_agent_framework-0.1.21}/codex_agent/cli/runner.py +0 -0
- {codex_agent_framework-0.1.20 → codex_agent_framework-0.1.21}/codex_agent/context.py +0 -0
- {codex_agent_framework-0.1.20 → codex_agent_framework-0.1.21}/codex_agent/desktop.py +0 -0
- {codex_agent_framework-0.1.20 → codex_agent_framework-0.1.21}/codex_agent/get_text/__init__.py +0 -0
- {codex_agent_framework-0.1.20 → codex_agent_framework-0.1.21}/codex_agent/get_text/default_gitignore +0 -0
- {codex_agent_framework-0.1.20 → codex_agent_framework-0.1.21}/codex_agent/get_text/get_text.py +0 -0
- {codex_agent_framework-0.1.20 → codex_agent_framework-0.1.21}/codex_agent/get_text/simpler_get_text.py +0 -0
- {codex_agent_framework-0.1.20 → codex_agent_framework-0.1.21}/codex_agent/hooks.py +0 -0
- {codex_agent_framework-0.1.20 → codex_agent_framework-0.1.21}/codex_agent/image.py +0 -0
- {codex_agent_framework-0.1.20 → codex_agent_framework-0.1.21}/codex_agent/latex.py +0 -0
- {codex_agent_framework-0.1.20 → codex_agent_framework-0.1.21}/codex_agent/prompts/image_generation_system_prompt.txt +0 -0
- {codex_agent_framework-0.1.20 → codex_agent_framework-0.1.21}/codex_agent/runtime.py +0 -0
- {codex_agent_framework-0.1.20 → codex_agent_framework-0.1.21}/codex_agent/scripts/install-system-dependencies.sh +0 -0
- {codex_agent_framework-0.1.20 → codex_agent_framework-0.1.21}/codex_agent/server/__init__.py +0 -0
- {codex_agent_framework-0.1.20 → codex_agent_framework-0.1.21}/codex_agent/server/app.py +0 -0
- {codex_agent_framework-0.1.20 → codex_agent_framework-0.1.21}/codex_agent/service.py +0 -0
- {codex_agent_framework-0.1.20 → codex_agent_framework-0.1.21}/codex_agent/status.py +0 -0
- {codex_agent_framework-0.1.20 → codex_agent_framework-0.1.21}/codex_agent/tray.py +0 -0
- {codex_agent_framework-0.1.20 → codex_agent_framework-0.1.21}/codex_agent/tui/__init__.py +0 -0
- {codex_agent_framework-0.1.20 → codex_agent_framework-0.1.21}/codex_agent/tui/chat.py +0 -0
- {codex_agent_framework-0.1.20 → codex_agent_framework-0.1.21}/codex_agent/tui/lifecycle.py +0 -0
- {codex_agent_framework-0.1.20 → codex_agent_framework-0.1.21}/codex_agent/utils.py +0 -0
- {codex_agent_framework-0.1.20 → codex_agent_framework-0.1.21}/codex_agent/voice.py +0 -0
- {codex_agent_framework-0.1.20 → codex_agent_framework-0.1.21}/codex_agent/worker.py +0 -0
- {codex_agent_framework-0.1.20 → codex_agent_framework-0.1.21}/codex_agent_framework.egg-info/dependency_links.txt +0 -0
- {codex_agent_framework-0.1.20 → codex_agent_framework-0.1.21}/codex_agent_framework.egg-info/entry_points.txt +0 -0
- {codex_agent_framework-0.1.20 → codex_agent_framework-0.1.21}/codex_agent_framework.egg-info/requires.txt +0 -0
- {codex_agent_framework-0.1.20 → codex_agent_framework-0.1.21}/codex_agent_framework.egg-info/top_level.txt +0 -0
- {codex_agent_framework-0.1.20 → codex_agent_framework-0.1.21}/dependencies.txt +0 -0
- {codex_agent_framework-0.1.20 → codex_agent_framework-0.1.21}/setup.cfg +0 -0
- {codex_agent_framework-0.1.20 → codex_agent_framework-0.1.21}/tests/test_agent_runtime.py +0 -0
- {codex_agent_framework-0.1.20 → codex_agent_framework-0.1.21}/tests/test_ai.py +0 -0
- {codex_agent_framework-0.1.20 → codex_agent_framework-0.1.21}/tests/test_browser.py +0 -0
- {codex_agent_framework-0.1.20 → codex_agent_framework-0.1.21}/tests/test_builtin_config.py +0 -0
- {codex_agent_framework-0.1.20 → codex_agent_framework-0.1.21}/tests/test_cli.py +0 -0
- {codex_agent_framework-0.1.20 → codex_agent_framework-0.1.21}/tests/test_cli_headless.py +0 -0
- {codex_agent_framework-0.1.20 → codex_agent_framework-0.1.21}/tests/test_cli_root.py +0 -0
- {codex_agent_framework-0.1.20 → codex_agent_framework-0.1.21}/tests/test_client.py +0 -0
- {codex_agent_framework-0.1.20 → codex_agent_framework-0.1.21}/tests/test_get_text_browser.py +0 -0
- {codex_agent_framework-0.1.20 → codex_agent_framework-0.1.21}/tests/test_hooks.py +0 -0
- {codex_agent_framework-0.1.20 → codex_agent_framework-0.1.21}/tests/test_image_message.py +0 -0
- {codex_agent_framework-0.1.20 → codex_agent_framework-0.1.21}/tests/test_local_desktop.py +0 -0
- {codex_agent_framework-0.1.20 → codex_agent_framework-0.1.21}/tests/test_planner.py +0 -0
- {codex_agent_framework-0.1.20 → codex_agent_framework-0.1.21}/tests/test_service.py +0 -0
- {codex_agent_framework-0.1.20 → codex_agent_framework-0.1.21}/tests/test_tray.py +0 -0
- {codex_agent_framework-0.1.20 → codex_agent_framework-0.1.21}/tests/test_tui.py +0 -0
- {codex_agent_framework-0.1.20 → codex_agent_framework-0.1.21}/tests/test_utils.py +0 -0
- {codex_agent_framework-0.1.20 → codex_agent_framework-0.1.21}/tests/test_worker.py +0 -0
|
@@ -4,6 +4,29 @@ All notable changes to this project will be documented in this file.
|
|
|
4
4
|
|
|
5
5
|
This project loosely follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and uses semantic versioning where practical.
|
|
6
6
|
|
|
7
|
+
## [0.1.21] - 2026-05-10
|
|
8
|
+
### Added
|
|
9
|
+
- Add the structured `AgentTurn` API: `agent(...)` and `AgentFuture.wait()` now return a turn object with `result`, `completed`, `reason`, `messages`, and `events`.
|
|
10
|
+
- Add resumable non-terminal turns for `auto_proceed=False`; turns that need another model step pause with `reason="requires_next_step"` and continue through `agent.resume_turn(turn)`.
|
|
11
|
+
- Add `agent.set_turn_result(data)` for tools/plugins that need to explicitly finish the current turn with a structured result.
|
|
12
|
+
- Add the built-in `subagents` plugin with profile-based child agents, compact parent results, child-local `set_result`, `SubagentEvent` relay, and a packaged read-only `explorer` profile.
|
|
13
|
+
- Add hierarchical `types` to messages and events while keeping `type` as the concrete computed type.
|
|
14
|
+
- Add configurable `exclude_tools` matching by public or canonical plugin tool names.
|
|
15
|
+
|
|
16
|
+
### Changed
|
|
17
|
+
- Treat scheduled and post-restart wakeups as internal `DeveloperMessage` entries instead of user-authored messages.
|
|
18
|
+
- Make `AssistantTurnEndEvent` expose a terminal `reason` rather than carrying a raw result payload.
|
|
19
|
+
- Capture turn-local events and messages on `AgentTurn` so embedded callers can inspect what happened without replaying the whole session.
|
|
20
|
+
- Include packaged subagent profile JSON files in distributions.
|
|
21
|
+
- Refresh the packaged default system prompt around agent identity, clarification, durable fixes, and concise final reporting.
|
|
22
|
+
|
|
23
|
+
### Fixed
|
|
24
|
+
- Preserve coherent turn state across suspended/resumed turns, including stable turn IDs, non-terminal suspension without a turn-end event, and terminal completion on resume.
|
|
25
|
+
|
|
26
|
+
### Tests
|
|
27
|
+
- Validate the full suite at 483 passing tests.
|
|
28
|
+
- Validate post-restart wakeups after a real server restart and smoke-test `auto_proceed=False` `AgentTurn` suspension/resume behavior.
|
|
29
|
+
|
|
7
30
|
## [0.1.20] - 2026-05-09
|
|
8
31
|
### Added
|
|
9
32
|
- Make runtime plugins the single user-facing extension unit for tools, providers, commands, prompt sections, hooks, and events.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: codex-agent-framework
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.21
|
|
4
4
|
Summary: A lightweight event-driven Codex agent runtime.
|
|
5
5
|
Author: Baptiste
|
|
6
6
|
License-Expression: MIT
|
|
@@ -286,10 +286,17 @@ agent = Agent(
|
|
|
286
286
|
voice_enabled=False,
|
|
287
287
|
)
|
|
288
288
|
|
|
289
|
-
|
|
290
|
-
|
|
289
|
+
turn = agent("Summarize this repository in three practical bullet points.")
|
|
290
|
+
|
|
291
|
+
print("Completed:", turn.completed)
|
|
292
|
+
print("Reason:", turn.reason)
|
|
293
|
+
print("Result:", turn.result)
|
|
294
|
+
for message in turn.messages:
|
|
295
|
+
print(message.as_string())
|
|
291
296
|
```
|
|
292
297
|
|
|
298
|
+
`agent(...)` returns an `AgentTurn`, not a raw string. `turn.result` is the useful final result, while `turn.messages` and `turn.events` contain the messages and events produced by that command. If `auto_proceed=False`, a tool-using turn can pause with `completed=False` and `reason="requires_next_step"`; call `agent.resume_turn(turn)` to continue the same turn object until it reaches a terminal reason such as `completed`, `result_set`, `interrupted`, or `error`.
|
|
299
|
+
|
|
293
300
|
Resume the latest session interactively:
|
|
294
301
|
|
|
295
302
|
```python
|
|
@@ -398,7 +405,7 @@ def repo():
|
|
|
398
405
|
|
|
399
406
|
agent = Agent(session="latest", voice_enabled=False)
|
|
400
407
|
agent.add_command(repo)
|
|
401
|
-
print(agent("/repo"))
|
|
408
|
+
print(agent("/repo").result)
|
|
402
409
|
```
|
|
403
410
|
|
|
404
411
|
### Add stateful plugins
|
|
@@ -593,8 +600,11 @@ The default agent exposes local tools and plugin tools to the model. Plugin tool
|
|
|
593
600
|
| Scheduler plugin | `scheduler_schedule`, `scheduler_cancel`, `scheduler_list`, `scheduler_restart_and_wakeup` | Future turns, periodic wakeups, post-restart continuation. |
|
|
594
601
|
| Browser plugin | `browser_open`, `browser_goto`, `browser_click`, `browser_fill`, `browser_press`, ... | Persistent Playwright/Chromium automation with active-tab context. |
|
|
595
602
|
| Desktop plugin | `desktop_start_session`, `desktop_run_commands`, `desktop_stop_session` | Screenshot-backed Linux desktop automation. |
|
|
603
|
+
| Subagents plugin | `subagents_run` | Run an ephemeral child agent from a predefined profile and return a compact structured result. |
|
|
596
604
|
|
|
597
|
-
Built-ins are intentionally packaged as plugins. Current built-in plugin modules include `files`, `content`, `bash`, `python`, `interface`, `environment`, `memory`, `planner`, `scheduler`, `browser`, and `
|
|
605
|
+
Built-ins are intentionally packaged as plugins. Current built-in plugin modules include `files`, `content`, `bash`, `python`, `interface`, `environment`, `memory`, `planner`, `scheduler`, `browser`, `desktop`, and `subagents`.
|
|
606
|
+
|
|
607
|
+
The `subagents` plugin ships profile JSON files inside the package, starting with `explorer`, a read-only codebase inspection agent. Parent agents choose an existing profile and task; child agents receive their own config, tools, prompt, and temporary session instead of inheriting the parent's runtime implicitly.
|
|
598
608
|
|
|
599
609
|
Built-in plugins are configurable. `None` means load all plugins, an empty list means load none, and an explicit list selects specific plugin module names:
|
|
600
610
|
|
|
@@ -703,8 +713,11 @@ Useful exported events include:
|
|
|
703
713
|
- `ToolCallStartEvent` / `ToolCallDoneEvent`
|
|
704
714
|
- `SessionLoadedEvent` / `SessionDeletedEvent`
|
|
705
715
|
- `AssistantInterruptedEvent`
|
|
716
|
+
- `SubagentEvent`
|
|
706
717
|
- `AudioPlaybackEvent`
|
|
707
718
|
|
|
719
|
+
Messages and events expose hierarchical `types` as well as the final computed `type`, so listeners can subscribe to broad classes such as all events or to precise concrete event types.
|
|
720
|
+
|
|
708
721
|
Plugins can also register event and hook handlers with decorators:
|
|
709
722
|
|
|
710
723
|
```python
|
|
@@ -833,13 +846,13 @@ The tests isolate `AGENT_RUNTIME_DIR` automatically, so they should not create o
|
|
|
833
846
|
The current tree validates at:
|
|
834
847
|
|
|
835
848
|
```text
|
|
836
|
-
|
|
849
|
+
483 passed
|
|
837
850
|
```
|
|
838
851
|
|
|
839
|
-
The published `0.1.
|
|
852
|
+
The published `0.1.21` release was validated at:
|
|
840
853
|
|
|
841
854
|
```text
|
|
842
|
-
|
|
855
|
+
483 passed
|
|
843
856
|
```
|
|
844
857
|
|
|
845
858
|
## Packaging
|
|
@@ -857,6 +870,7 @@ The distribution includes prompt text files, `codex_agent/get_text/default_gitig
|
|
|
857
870
|
|
|
858
871
|
## Recent changes
|
|
859
872
|
|
|
873
|
+
- `0.1.21`: add structured `AgentTurn` returns with messages/events, resumable non-terminal turns for `auto_proceed=False`, profile-based subagents, hierarchical message/event types, and internal wakeups as developer messages.
|
|
860
874
|
- `0.1.20`: make runtime plugins the single user-facing extension unit for tools/providers/commands, remove legacy direct runtime tool/provider loading, split tool/provider managers, and keep system-prompt templating on an explicit minimal namespace.
|
|
861
875
|
- `0.1.19`: prune compacted raw session history after compaction, keep only the latest compaction anchor plus the new compaction and any unfinished remainder, and drop older redundant compaction summaries from active session files.
|
|
862
876
|
- `0.1.18`: make `start server/tray` detach by default, move durable memory fully into the memory plugin, keep wakeups as a core runtime primitive, expose RAG memory timestamps/sources, and move browser/desktop backend controllers into core modules with plugins as facades.
|
|
@@ -237,10 +237,17 @@ agent = Agent(
|
|
|
237
237
|
voice_enabled=False,
|
|
238
238
|
)
|
|
239
239
|
|
|
240
|
-
|
|
241
|
-
|
|
240
|
+
turn = agent("Summarize this repository in three practical bullet points.")
|
|
241
|
+
|
|
242
|
+
print("Completed:", turn.completed)
|
|
243
|
+
print("Reason:", turn.reason)
|
|
244
|
+
print("Result:", turn.result)
|
|
245
|
+
for message in turn.messages:
|
|
246
|
+
print(message.as_string())
|
|
242
247
|
```
|
|
243
248
|
|
|
249
|
+
`agent(...)` returns an `AgentTurn`, not a raw string. `turn.result` is the useful final result, while `turn.messages` and `turn.events` contain the messages and events produced by that command. If `auto_proceed=False`, a tool-using turn can pause with `completed=False` and `reason="requires_next_step"`; call `agent.resume_turn(turn)` to continue the same turn object until it reaches a terminal reason such as `completed`, `result_set`, `interrupted`, or `error`.
|
|
250
|
+
|
|
244
251
|
Resume the latest session interactively:
|
|
245
252
|
|
|
246
253
|
```python
|
|
@@ -349,7 +356,7 @@ def repo():
|
|
|
349
356
|
|
|
350
357
|
agent = Agent(session="latest", voice_enabled=False)
|
|
351
358
|
agent.add_command(repo)
|
|
352
|
-
print(agent("/repo"))
|
|
359
|
+
print(agent("/repo").result)
|
|
353
360
|
```
|
|
354
361
|
|
|
355
362
|
### Add stateful plugins
|
|
@@ -544,8 +551,11 @@ The default agent exposes local tools and plugin tools to the model. Plugin tool
|
|
|
544
551
|
| Scheduler plugin | `scheduler_schedule`, `scheduler_cancel`, `scheduler_list`, `scheduler_restart_and_wakeup` | Future turns, periodic wakeups, post-restart continuation. |
|
|
545
552
|
| Browser plugin | `browser_open`, `browser_goto`, `browser_click`, `browser_fill`, `browser_press`, ... | Persistent Playwright/Chromium automation with active-tab context. |
|
|
546
553
|
| Desktop plugin | `desktop_start_session`, `desktop_run_commands`, `desktop_stop_session` | Screenshot-backed Linux desktop automation. |
|
|
554
|
+
| Subagents plugin | `subagents_run` | Run an ephemeral child agent from a predefined profile and return a compact structured result. |
|
|
547
555
|
|
|
548
|
-
Built-ins are intentionally packaged as plugins. Current built-in plugin modules include `files`, `content`, `bash`, `python`, `interface`, `environment`, `memory`, `planner`, `scheduler`, `browser`, and `
|
|
556
|
+
Built-ins are intentionally packaged as plugins. Current built-in plugin modules include `files`, `content`, `bash`, `python`, `interface`, `environment`, `memory`, `planner`, `scheduler`, `browser`, `desktop`, and `subagents`.
|
|
557
|
+
|
|
558
|
+
The `subagents` plugin ships profile JSON files inside the package, starting with `explorer`, a read-only codebase inspection agent. Parent agents choose an existing profile and task; child agents receive their own config, tools, prompt, and temporary session instead of inheriting the parent's runtime implicitly.
|
|
549
559
|
|
|
550
560
|
Built-in plugins are configurable. `None` means load all plugins, an empty list means load none, and an explicit list selects specific plugin module names:
|
|
551
561
|
|
|
@@ -654,8 +664,11 @@ Useful exported events include:
|
|
|
654
664
|
- `ToolCallStartEvent` / `ToolCallDoneEvent`
|
|
655
665
|
- `SessionLoadedEvent` / `SessionDeletedEvent`
|
|
656
666
|
- `AssistantInterruptedEvent`
|
|
667
|
+
- `SubagentEvent`
|
|
657
668
|
- `AudioPlaybackEvent`
|
|
658
669
|
|
|
670
|
+
Messages and events expose hierarchical `types` as well as the final computed `type`, so listeners can subscribe to broad classes such as all events or to precise concrete event types.
|
|
671
|
+
|
|
659
672
|
Plugins can also register event and hook handlers with decorators:
|
|
660
673
|
|
|
661
674
|
```python
|
|
@@ -784,13 +797,13 @@ The tests isolate `AGENT_RUNTIME_DIR` automatically, so they should not create o
|
|
|
784
797
|
The current tree validates at:
|
|
785
798
|
|
|
786
799
|
```text
|
|
787
|
-
|
|
800
|
+
483 passed
|
|
788
801
|
```
|
|
789
802
|
|
|
790
|
-
The published `0.1.
|
|
803
|
+
The published `0.1.21` release was validated at:
|
|
791
804
|
|
|
792
805
|
```text
|
|
793
|
-
|
|
806
|
+
483 passed
|
|
794
807
|
```
|
|
795
808
|
|
|
796
809
|
## Packaging
|
|
@@ -808,6 +821,7 @@ The distribution includes prompt text files, `codex_agent/get_text/default_gitig
|
|
|
808
821
|
|
|
809
822
|
## Recent changes
|
|
810
823
|
|
|
824
|
+
- `0.1.21`: add structured `AgentTurn` returns with messages/events, resumable non-terminal turns for `auto_proceed=False`, profile-based subagents, hierarchical message/event types, and internal wakeups as developer messages.
|
|
811
825
|
- `0.1.20`: make runtime plugins the single user-facing extension unit for tools/providers/commands, remove legacy direct runtime tool/provider loading, split tool/provider managers, and keep system-prompt templating on an explicit minimal namespace.
|
|
812
826
|
- `0.1.19`: prune compacted raw session history after compaction, keep only the latest compaction anchor plus the new compaction and any unfinished remainder, and drop older redundant compaction summaries from active session files.
|
|
813
827
|
- `0.1.18`: make `start server/tray` detach by default, move durable memory fully into the memory plugin, keep wakeups as a core runtime primitive, expose RAG memory timestamps/sources, and move browser/desktop backend controllers into core modules with plugins as facades.
|
|
@@ -27,6 +27,7 @@ from .event import (
|
|
|
27
27
|
ResponseToolCallsDeltaEvent,
|
|
28
28
|
ServerToolCallEvent,
|
|
29
29
|
SessionDeletedEvent,
|
|
30
|
+
SubagentEvent,
|
|
30
31
|
SessionLoadedEvent,
|
|
31
32
|
ToolCallDoneEvent,
|
|
32
33
|
ToolCallStartEvent,
|
|
@@ -48,7 +49,7 @@ from .message import (
|
|
|
48
49
|
from .image import ImageMessage
|
|
49
50
|
from .mainloop import MainLoopManager
|
|
50
51
|
from .plugin import Plugin, PluginsManager, event_handler, hook_handler
|
|
51
|
-
from .command import AgentCommand, AgentFuture, CommandManager, command
|
|
52
|
+
from .command import AgentCommand, AgentFuture, AgentTurn, CommandManager, command
|
|
52
53
|
from .provider import ProvidersManager, provider
|
|
53
54
|
from .runtime import RuntimeManager
|
|
54
55
|
from .sessions import AgentSession, SessionsManager
|
|
@@ -87,6 +88,7 @@ __all__ = [
|
|
|
87
88
|
"ResponseToolCallsDeltaEvent",
|
|
88
89
|
"ServerToolCallEvent",
|
|
89
90
|
"SessionDeletedEvent",
|
|
91
|
+
"SubagentEvent",
|
|
90
92
|
"SessionLoadedEvent",
|
|
91
93
|
"ToolCallDoneEvent",
|
|
92
94
|
"ToolCallStartEvent",
|
|
@@ -111,6 +113,7 @@ __all__ = [
|
|
|
111
113
|
"ProvidersManager",
|
|
112
114
|
"AgentCommand",
|
|
113
115
|
"AgentFuture",
|
|
116
|
+
"AgentTurn",
|
|
114
117
|
"CommandManager",
|
|
115
118
|
"command",
|
|
116
119
|
"provider",
|
|
@@ -14,7 +14,7 @@ from .scheduler import AgentScheduler
|
|
|
14
14
|
from .sessions import SessionsManager
|
|
15
15
|
from .status import StatusManager
|
|
16
16
|
from .tool import ImageGenerationTool, ToolsManager, reset_current_agent, set_current_agent
|
|
17
|
-
from .ai import
|
|
17
|
+
from .ai import AIClient
|
|
18
18
|
from .voice import VoiceProcessor
|
|
19
19
|
from .latex import LaTeXProcessor
|
|
20
20
|
from .event import AssistantInterruptRequestedEvent, AssistantInterrupted, AssistantInterruptedEvent, AudioPlaybackEvent, EventBus
|
|
@@ -71,6 +71,7 @@ class Agent:
|
|
|
71
71
|
self.plugins_manager.init_runtime()
|
|
72
72
|
self.command_manager.init_runtime()
|
|
73
73
|
self.tools_manager.init_server_tools()
|
|
74
|
+
self.tools_manager.apply_config_exclusions()
|
|
74
75
|
self.start_new_session() if session == "new" else self.load_session(session)
|
|
75
76
|
|
|
76
77
|
def on(self, event_type, handler=None):
|
|
@@ -82,7 +83,11 @@ class Agent:
|
|
|
82
83
|
return self.events.on(event_type, handler)
|
|
83
84
|
|
|
84
85
|
def emit(self, event_type, **data):
|
|
85
|
-
|
|
86
|
+
event = self.events.emit(event_type, **data)
|
|
87
|
+
mainloop_manager = getattr(self, "mainloop_manager", None)
|
|
88
|
+
if mainloop_manager is not None:
|
|
89
|
+
mainloop_manager.record_event(event)
|
|
90
|
+
return event
|
|
86
91
|
|
|
87
92
|
@property
|
|
88
93
|
def busy(self):
|
|
@@ -104,28 +109,35 @@ class Agent:
|
|
|
104
109
|
def start_turn(self):
|
|
105
110
|
return self.mainloop_manager.start_turn()
|
|
106
111
|
|
|
112
|
+
def resume_turn(self, turn):
|
|
113
|
+
future = self.mainloop_manager.resume_turn(turn)
|
|
114
|
+
return None if future is None else future.wait()
|
|
115
|
+
|
|
107
116
|
def submit_command(self, prompt):
|
|
108
117
|
return self.command_manager.submit(prompt)
|
|
109
118
|
|
|
110
119
|
def submit_message(self, message):
|
|
111
120
|
return self.mainloop_manager.submit_message(message)
|
|
112
121
|
|
|
122
|
+
def set_turn_result(self, data):
|
|
123
|
+
return self.mainloop_manager.set_turn_result(data)
|
|
124
|
+
|
|
113
125
|
def list_wakeups(self, include_done=True):
|
|
114
126
|
return self.scheduler.list(include_done=include_done)
|
|
115
127
|
|
|
116
128
|
def schedule_wakeup(self, **kwargs):
|
|
117
129
|
if self.mainloop_manager.should_queue_state_command():
|
|
118
|
-
return self.submit("schedule_wakeup", **kwargs).wait()
|
|
130
|
+
return self.submit("schedule_wakeup", **kwargs).wait().result
|
|
119
131
|
return self.scheduler.schedule(**kwargs)
|
|
120
132
|
|
|
121
133
|
def cancel_wakeup(self, job_id):
|
|
122
134
|
if self.mainloop_manager.should_queue_state_command():
|
|
123
|
-
return self.submit("cancel_wakeup", job_id=job_id).wait()
|
|
135
|
+
return self.submit("cancel_wakeup", job_id=job_id).wait().result
|
|
124
136
|
return self.scheduler.cancel(job_id)
|
|
125
137
|
|
|
126
138
|
def delete_wakeup(self, job_id):
|
|
127
139
|
if self.mainloop_manager.should_queue_state_command():
|
|
128
|
-
return self.submit("delete_wakeup", job_id=job_id).wait()
|
|
140
|
+
return self.submit("delete_wakeup", job_id=job_id).wait().result
|
|
129
141
|
return self.scheduler.delete(job_id)
|
|
130
142
|
|
|
131
143
|
def interrupt(self, reason="user"):
|
|
@@ -256,7 +268,7 @@ class Agent:
|
|
|
256
268
|
return decorator
|
|
257
269
|
|
|
258
270
|
if hasattr(self, "mainloop_manager") and self.mainloop_manager.should_queue_state_command():
|
|
259
|
-
return self.submit("add_hook", key=key, func=func).wait()
|
|
271
|
+
return self.submit("add_hook", key=key, func=func).wait().result
|
|
260
272
|
|
|
261
273
|
return self.hooks.add(key, func)
|
|
262
274
|
|
|
@@ -327,6 +339,10 @@ class Agent:
|
|
|
327
339
|
Returns:
|
|
328
340
|
Message: The aggregated assistant message with content and/or tool calls.
|
|
329
341
|
"""
|
|
342
|
+
if self.session and not self.session.is_sane():
|
|
343
|
+
repair = self.session.repair()
|
|
344
|
+
if repair.repaired:
|
|
345
|
+
self.status_manager.invalidate_context_cache()
|
|
330
346
|
messages = self.context_manager.get_context()
|
|
331
347
|
if self.context_manager.should_auto_compact(messages):
|
|
332
348
|
self.compact_session(
|
|
@@ -335,21 +351,16 @@ class Agent:
|
|
|
335
351
|
)
|
|
336
352
|
messages = self.context_manager.get_context()
|
|
337
353
|
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
)
|
|
345
|
-
except AIBadRequestError:
|
|
346
|
-
self.sessions_manager.restore_api_success_backup()
|
|
347
|
-
raise
|
|
354
|
+
message=self.ai.run(
|
|
355
|
+
messages=messages,
|
|
356
|
+
tools=self.tools_manager.get_response_tools(),
|
|
357
|
+
prompt_cache_key=self.current_session_id,
|
|
358
|
+
**self.config.extract('model','reasoning_effort','verbosity','parallel_tool_calls')
|
|
359
|
+
)
|
|
348
360
|
self.sessions_manager.append_message(message)
|
|
349
361
|
for item in message.output_items:
|
|
350
362
|
if self.tools_manager.server_tool_for_item(item):
|
|
351
363
|
self.sessions_manager.append_message(ServerToolResponseMessage(item=item))
|
|
352
|
-
self.sessions_manager.save_api_success_backup()
|
|
353
364
|
return message
|
|
354
365
|
|
|
355
366
|
def speak(self,text,**kwargs):
|
|
@@ -431,7 +442,7 @@ class Agent:
|
|
|
431
442
|
|
|
432
443
|
return dest_path
|
|
433
444
|
|
|
434
|
-
def __call__(self,prompt=None)
|
|
445
|
+
def __call__(self, prompt=None):
|
|
435
446
|
return self.mainloop_manager.call(prompt=prompt)
|
|
436
447
|
|
|
437
448
|
def interact(self):
|
|
@@ -429,7 +429,7 @@ class ProcessAgentRuntime(AgentRuntime):
|
|
|
429
429
|
payload = dict(values or {})
|
|
430
430
|
payload.update(kwargs)
|
|
431
431
|
result = self.call("update_config", {"values": payload, "save": save}, timeout=self.command_timeout)
|
|
432
|
-
self.cache
|
|
432
|
+
self.cache["config"] = result
|
|
433
433
|
return result
|
|
434
434
|
|
|
435
435
|
def session(self):
|
|
@@ -370,6 +370,7 @@ You can manage memory explicitly with the memory tools:
|
|
|
370
370
|
Use your memory tools judiciously and proactively to create durable records of important information that should persist beyond the current session.
|
|
371
371
|
Do not over-memorize routine exchanges, transient implementation details, or information that is only useful within the current turn.
|
|
372
372
|
Prefer durable facts, preferences, decisions, and important outcomes.
|
|
373
|
+
Memory entries should be rather short (one paragraph) and semantically well scoped for more efficient retrieval.
|
|
373
374
|
Keep your memory tidy and up to date by editing or deleting outdated or irrelevant entries.
|
|
374
375
|
When automatic memory archiving is enabled, completed turns are summarized into memory in the background when they contain something worth remembering.
|
|
375
376
|
"""
|
|
@@ -469,7 +470,7 @@ When automatic memory archiving is enabled, completed turns are summarized into
|
|
|
469
470
|
|
|
470
471
|
@event_handler(AssistantTurnEndEvent)
|
|
471
472
|
def archive_completed_turn(self, event):
|
|
472
|
-
if event.get("
|
|
473
|
+
if event.get("reason") != "completed":
|
|
473
474
|
return None
|
|
474
475
|
if not self.config.auto_archive:
|
|
475
476
|
return None
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
"""Subagents plugin: delegate focused tasks to ephemeral child agents."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import os
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
from modict import modict
|
|
8
|
+
|
|
9
|
+
from ...event import SubagentEvent
|
|
10
|
+
from ...plugin import Plugin
|
|
11
|
+
from ...tool import tool
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class SubagentResult(modict):
|
|
15
|
+
_config = modict.config(require_all="never")
|
|
16
|
+
|
|
17
|
+
type = "subagent_result"
|
|
18
|
+
profile = ""
|
|
19
|
+
name = ""
|
|
20
|
+
task = ""
|
|
21
|
+
status = ""
|
|
22
|
+
result = None
|
|
23
|
+
error = None
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class SubagentsPlugin(Plugin):
|
|
27
|
+
name = "subagents"
|
|
28
|
+
description = "Delegate focused tasks to ephemeral child agents with their own config and plugins."
|
|
29
|
+
profiles_dir = Path(__file__).parent / "profiles"
|
|
30
|
+
system_prompt = """
|
|
31
|
+
# Subagents
|
|
32
|
+
|
|
33
|
+
Use `subagents_run` to delegate a focused task to a subagent profile with its own predefined config, tools and plugins.
|
|
34
|
+
Choose one of the available profiles and provide a focused task. The child agent receives its own `set_result` tool to provide a structured result. The parent receives that result and should report it back clearly.
|
|
35
|
+
"""
|
|
36
|
+
child_system_prompt = """
|
|
37
|
+
You are a focused subagent running inside a parent agent.
|
|
38
|
+
|
|
39
|
+
Fundamentals:
|
|
40
|
+
- Work only on the delegated task.
|
|
41
|
+
- Use your available tools autonomously when useful.
|
|
42
|
+
- You may perform multiple reasoning/tool steps before finishing.
|
|
43
|
+
- Keep your work scoped to your role and available capabilities.
|
|
44
|
+
- When the task is complete, call the local `set_result` tool with the structured result to return to the parent agent.
|
|
45
|
+
- Do not ask the user for follow-up; report uncertainty or missing inputs in the structured result instead.
|
|
46
|
+
"""
|
|
47
|
+
|
|
48
|
+
def __init__(self, agent):
|
|
49
|
+
super().__init__(agent)
|
|
50
|
+
self.profiles = modict()
|
|
51
|
+
self.load_profiles()
|
|
52
|
+
|
|
53
|
+
def load_profiles(self):
|
|
54
|
+
profiles_dir = Path(self.profiles_dir)
|
|
55
|
+
if not profiles_dir.is_dir():
|
|
56
|
+
return self.profiles
|
|
57
|
+
for path in sorted(profiles_dir.glob("*.json")):
|
|
58
|
+
self.load_profile_file(path)
|
|
59
|
+
return self.profiles
|
|
60
|
+
|
|
61
|
+
def load_profile_file(self, path):
|
|
62
|
+
with open(path, encoding="utf-8") as f:
|
|
63
|
+
profile = json.load(f)
|
|
64
|
+
if not isinstance(profile, dict):
|
|
65
|
+
raise ValueError(f"Invalid subagent profile {path}: expected JSON object")
|
|
66
|
+
profile_name = profile.pop("id", None) or path.stem
|
|
67
|
+
self.register_profile(profile_name, **profile)
|
|
68
|
+
return profile_name
|
|
69
|
+
|
|
70
|
+
def register_profile(self, profile_name, **profile):
|
|
71
|
+
self.profiles[profile_name] = modict(profile)
|
|
72
|
+
return self.profiles[profile_name]
|
|
73
|
+
|
|
74
|
+
def get_system_prompt_sections(self):
|
|
75
|
+
if not self.profiles:
|
|
76
|
+
return [self.system_prompt.strip() + "\n\nNo subagent profiles are currently available."]
|
|
77
|
+
lines = [self.system_prompt.strip(), "", "Available subagent profiles:"]
|
|
78
|
+
for name, profile in self.profiles.items():
|
|
79
|
+
description = profile.get("description", "No description provided.")
|
|
80
|
+
lines.append(f"- `{name}`: {description}")
|
|
81
|
+
return ["\n".join(lines)]
|
|
82
|
+
|
|
83
|
+
def profile(self, name):
|
|
84
|
+
if not name:
|
|
85
|
+
raise ValueError("A subagent profile is required")
|
|
86
|
+
if name not in self.profiles:
|
|
87
|
+
raise ValueError(f"Unknown subagent profile: {name}")
|
|
88
|
+
return modict(self.profiles[name])
|
|
89
|
+
|
|
90
|
+
def profile_system_prompt(self, profile):
|
|
91
|
+
sections = [self.child_system_prompt.strip()]
|
|
92
|
+
structured = []
|
|
93
|
+
for key, title in (
|
|
94
|
+
("role", "Role"),
|
|
95
|
+
("goal", "Goal"),
|
|
96
|
+
("means", "Means"),
|
|
97
|
+
("objective", "Objective"),
|
|
98
|
+
):
|
|
99
|
+
value = profile.get(key)
|
|
100
|
+
if value:
|
|
101
|
+
structured.append(f"## {title}\n{value}")
|
|
102
|
+
if structured:
|
|
103
|
+
sections.append("\n\n".join(structured))
|
|
104
|
+
if profile.get("system_prompt"):
|
|
105
|
+
sections.append(str(profile.system_prompt).strip())
|
|
106
|
+
return "\n\n".join(section for section in sections if section)
|
|
107
|
+
|
|
108
|
+
def child_config(self, profile):
|
|
109
|
+
data = modict(profile)
|
|
110
|
+
config = modict(data.pop("config", {}) or {})
|
|
111
|
+
|
|
112
|
+
for key in ("name", "builtin_plugins", "custom_plugins", "exclude_tools", "model", "reasoning_effort", "verbosity"):
|
|
113
|
+
if key in data:
|
|
114
|
+
config[key] = data[key]
|
|
115
|
+
|
|
116
|
+
config.system = self.profile_system_prompt(data)
|
|
117
|
+
config.setdefault("name", data.get("name") or f"{self.agent.config.name} subagent")
|
|
118
|
+
config.setdefault("username", self.agent.config.get("username"))
|
|
119
|
+
config.setdefault("userage", self.agent.config.get("userage"))
|
|
120
|
+
config.setdefault("openai_api_key", self.agent.config.get("openai_api_key"))
|
|
121
|
+
config.setdefault("voice_enabled", False)
|
|
122
|
+
config.setdefault("builtin_plugins", [])
|
|
123
|
+
config.setdefault("custom_plugins", [])
|
|
124
|
+
config.setdefault("exclude_tools", [])
|
|
125
|
+
return config
|
|
126
|
+
|
|
127
|
+
def relay_child_event(self, subagent, child, event):
|
|
128
|
+
wrapped = SubagentEvent(
|
|
129
|
+
subagent=subagent,
|
|
130
|
+
event=event,
|
|
131
|
+
session_id=getattr(child, "current_session_id", ""),
|
|
132
|
+
)
|
|
133
|
+
self.agent.events.emit(wrapped)
|
|
134
|
+
mainloop_manager = getattr(self.agent, "mainloop_manager", None)
|
|
135
|
+
if mainloop_manager is not None:
|
|
136
|
+
mainloop_manager.record_event(wrapped)
|
|
137
|
+
return event
|
|
138
|
+
|
|
139
|
+
def install_child_tools(self, child):
|
|
140
|
+
def set_result(data):
|
|
141
|
+
"""
|
|
142
|
+
description: |
|
|
143
|
+
Set some data as this turn's result and finish the turn after the current step.
|
|
144
|
+
The result will be accessible by the caller (user or other Agent) as `AgentTurn.result`
|
|
145
|
+
Use this when your task is complete and you need to return structured data to the caller.
|
|
146
|
+
parameters:
|
|
147
|
+
data:
|
|
148
|
+
description: Structured result data to return for the current subagent turn.
|
|
149
|
+
required:
|
|
150
|
+
- data
|
|
151
|
+
"""
|
|
152
|
+
child.set_turn_result(data)
|
|
153
|
+
return "Subagent result set."
|
|
154
|
+
|
|
155
|
+
child.add_tool(set_result, name="set_result")
|
|
156
|
+
return child
|
|
157
|
+
|
|
158
|
+
def cleanup_session(self, child):
|
|
159
|
+
session = getattr(child, "session", None)
|
|
160
|
+
if session is None:
|
|
161
|
+
return
|
|
162
|
+
for path in (session.session_file, session.api_success_backup_file):
|
|
163
|
+
if path and os.path.exists(path):
|
|
164
|
+
os.remove(path)
|
|
165
|
+
|
|
166
|
+
def run_subagent(self, profile, task):
|
|
167
|
+
from ...agent import Agent
|
|
168
|
+
|
|
169
|
+
child = None
|
|
170
|
+
profile_data = None
|
|
171
|
+
try:
|
|
172
|
+
profile_data = self.profile(profile)
|
|
173
|
+
child = Agent(session="new", **self.child_config(profile_data))
|
|
174
|
+
child.on("*", lambda event: self.relay_child_event(profile, child, event))
|
|
175
|
+
self.install_child_tools(child)
|
|
176
|
+
prompt = (
|
|
177
|
+
f"{task}\n\n"
|
|
178
|
+
"When the delegated task is complete, call the `set_result` tool with the structured result to return to the parent agent."
|
|
179
|
+
)
|
|
180
|
+
turn = child(prompt)
|
|
181
|
+
return SubagentResult(
|
|
182
|
+
profile=profile,
|
|
183
|
+
name=child.config.name,
|
|
184
|
+
task=task,
|
|
185
|
+
status=turn.status,
|
|
186
|
+
result=turn.result,
|
|
187
|
+
)
|
|
188
|
+
except Exception as exc:
|
|
189
|
+
return SubagentResult(
|
|
190
|
+
profile=profile,
|
|
191
|
+
name=(profile_data or {}).get("name", profile),
|
|
192
|
+
task=task,
|
|
193
|
+
status="error",
|
|
194
|
+
error=str(exc),
|
|
195
|
+
)
|
|
196
|
+
finally:
|
|
197
|
+
if child is not None:
|
|
198
|
+
child.stop(timeout=1.0)
|
|
199
|
+
if not (profile_data or {}).get("keep_session", False):
|
|
200
|
+
self.cleanup_session(child)
|
|
201
|
+
|
|
202
|
+
@tool(name="run")
|
|
203
|
+
def run(self, profile: str, task: str):
|
|
204
|
+
"""
|
|
205
|
+
description: |
|
|
206
|
+
Run a focused task in an ephemeral child agent selected from predefined profiles.
|
|
207
|
+
The calling agent must only choose an existing profile and provide the specific task.
|
|
208
|
+
The child agent receives a local `set_result` tool to return structured data to the parent.
|
|
209
|
+
parameters:
|
|
210
|
+
profile:
|
|
211
|
+
description: Registered subagent profile to use.
|
|
212
|
+
type: string
|
|
213
|
+
task:
|
|
214
|
+
description: Specific task prompt to give to the selected subagent profile.
|
|
215
|
+
type: string
|
|
216
|
+
required:
|
|
217
|
+
- profile
|
|
218
|
+
- task
|
|
219
|
+
"""
|
|
220
|
+
return self.run_subagent(profile=profile, task=task)
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "explorer",
|
|
3
|
+
"name": "Codebase explorer",
|
|
4
|
+
"description": "Read-only project/codebase exploration subagent for mapping structure, inspecting files, and reporting findings.",
|
|
5
|
+
"role": "You are a read-only project and codebase exploration subagent.",
|
|
6
|
+
"goal": "Understand the relevant structure, files, code paths, and implementation details needed for the delegated task, then return a clear structured report.",
|
|
7
|
+
"means": "Use read-only inspection tools such as file reading and content extraction. Do not modify files, run destructive actions, or change project state.",
|
|
8
|
+
"objective": "Call `set_result` with a structured exploration report including key findings, relevant files/symbols, confidence, and any remaining uncertainties.",
|
|
9
|
+
"builtin_plugins": ["files", "content", "environment"],
|
|
10
|
+
"exclude_tools": ["files.write", "files.edit"],
|
|
11
|
+
"voice_enabled": false
|
|
12
|
+
}
|
|
@@ -45,8 +45,22 @@ class AgentClient:
|
|
|
45
45
|
def emit_local(self, event):
|
|
46
46
|
event = modict(event)
|
|
47
47
|
event_type = event.get("type")
|
|
48
|
-
|
|
49
|
-
|
|
48
|
+
event_types = list(event.get("types") or [event_type])
|
|
49
|
+
if event_type and event_type not in event_types:
|
|
50
|
+
event_types.append(event_type)
|
|
51
|
+
handlers = []
|
|
52
|
+
seen = set()
|
|
53
|
+
for current_type in event_types:
|
|
54
|
+
for handler in self._event_handlers.get(current_type, ()):
|
|
55
|
+
if handler in seen:
|
|
56
|
+
continue
|
|
57
|
+
seen.add(handler)
|
|
58
|
+
handlers.append(handler)
|
|
59
|
+
for handler in self._event_handlers.get("*", ()):
|
|
60
|
+
if handler in seen:
|
|
61
|
+
continue
|
|
62
|
+
seen.add(handler)
|
|
63
|
+
handlers.append(handler)
|
|
50
64
|
for handler in handlers:
|
|
51
65
|
try:
|
|
52
66
|
handler(event)
|