| 1 | # Extensions |
| 2 | |
| 3 | ## Source Anchors |
| 4 | |
| 5 | - Extension base and discovery: `/a0/helpers/extension.py` |
| 6 | - Backend extension DOX: `/a0/extensions/AGENTS.md`, `/a0/extensions/python/AGENTS.md` |
| 7 | - WebUI extension DOX: `/a0/extensions/webui/AGENTS.md` |
| 8 | - Plugin extension contract: `/a0/plugins/AGENTS.md` |
| 9 | - Example profile note: `/a0/agents/_example/AGENTS.md` |
| 10 | |
| 11 | ## Python Extension Contract |
| 12 | |
| 13 | Backend extensions derive from `helpers.extension.Extension`: |
| 14 | |
| 15 | ```python |
| 16 | from helpers.extension import Extension |
| 17 | |
| 18 | class MyExtension(Extension): |
| 19 | async def execute(self, **kwargs): |
| 20 | ... |
| 21 | ``` |
| 22 | |
| 23 | `self.agent` may be `None` for startup or non-agent hooks. Match the arguments supplied by the hook point. Keep imports light because many extensions run in hot paths. |
| 24 | |
| 25 | ## Python Discovery Layout |
| 26 | |
| 27 | `helpers.extension._get_extension_classes(...)` discovers backend extension classes through: |
| 28 | |
| 29 | ```text |
| 30 | extensions/python/<extension_point>/ |
| 31 | ``` |
| 32 | |
| 33 | using `helpers.subagents.get_paths(agent, "extensions/python", extension_point)`. |
| 34 | |
| 35 | Common locations: |
| 36 | |
| 37 | | Location | Use | |
| 38 | |---|---| |
| 39 | | `extensions/python/<point>/` | Built-in framework extension. | |
| 40 | | `plugins/<plugin>/extensions/python/<point>/` | Bundled plugin extension. | |
| 41 | | `usr/plugins/<plugin>/extensions/python/<point>/` | User plugin extension. | |
| 42 | | `usr/extensions/python/<point>/` | Standalone user extension. Prefer plugin packaging for durable features. | |
| 43 | | Project/profile roots resolved by `helpers.subagents.get_paths(...)` | Scope-specific extension overrides when discovery supports the path. Verify before copying examples. | |
| 44 | |
| 45 | The checked-in `_example` profile contains an older-looking `agents/_example/extensions/agent_init/...` sample. Current discovery code expects `extensions/python/<point>`. Treat source code and DOX as authority before copying profile extension layout. |
| 46 | |
| 47 | ## Ordering And Overrides |
| 48 | |
| 49 | - Files are sorted by filename. Numeric prefixes like `_10_`, `_20_`, `_50_` control order. |
| 50 | - Use gaps between prefixes so future extensions can fit between them. |
| 51 | - Discovery de-duplicates by module filename, preserving the first occurrence by search priority. |
| 52 | - Do not bypass secret masking, auth, persistence, or cleanup extensions for convenience. |
| 53 | |
| 54 | ## Implicit `@extensible` Hooks |
| 55 | |
| 56 | Functions decorated with `@extensible` emit two implicit hook points: |
| 57 | |
| 58 | ```text |
| 59 | _functions/<module>/<qualname>/start |
| 60 | _functions/<module>/<qualname>/end |
| 61 | ``` |
| 62 | |
| 63 | The path preserves every module segment and nested qualname segment. Example: |
| 64 | |
| 65 | ```text |
| 66 | helpers.something.Outer.Inner.__init__ |
| 67 | ``` |
| 68 | |
| 69 | becomes: |
| 70 | |
| 71 | ```text |
| 72 | _functions/helpers/something/Outer/Inner/__init__/start |
| 73 | _functions/helpers/something/Outer/Inner/__init__/end |
| 74 | ``` |
| 75 | |
| 76 | Extensions receive a mutable `data` dict with `args`, `kwargs`, `result`, and `exception`. They may mutate inputs, short-circuit by setting `result`, or force/clear an exception. |
| 77 | |
| 78 | Do not use retired flattened `_functions` folder names. |
| 79 | |
| 80 | ## Current Built-In Python Hook Directories |
| 81 | |
| 82 | Directory-backed built-in hook points currently include: |
| 83 | |
| 84 | ```text |
| 85 | agent_init |
| 86 | banners |
| 87 | before_main_llm_call |
| 88 | error_format |
| 89 | hist_add_before |
| 90 | hist_add_tool_result |
| 91 | job_loop |
| 92 | message_loop_end |
| 93 | message_loop_prompts_after |
| 94 | message_loop_prompts_before |
| 95 | message_loop_start |
| 96 | monologue_end |
| 97 | monologue_start |
| 98 | process_chain_end |
| 99 | reasoning_stream |
| 100 | reasoning_stream_chunk |
| 101 | reasoning_stream_end |
| 102 | response_stream |
| 103 | response_stream_chunk |
| 104 | response_stream_end |
| 105 | startup_migration |
| 106 | system_prompt |
| 107 | tool_execute_after |
| 108 | tool_execute_before |
| 109 | user_message_ui |
| 110 | util_model_call_before |
| 111 | webui_ws_connect |
| 112 | webui_ws_disconnect |
| 113 | webui_ws_event |
| 114 | ``` |
| 115 | |
| 116 | This list comes from the current `extensions/python/` tree. Re-check the tree before claiming the complete current set. |
| 117 | |
| 118 | ## WebUI Extension Points |
| 119 | |
| 120 | Frontend extension files live under: |
| 121 | |
| 122 | ```text |
| 123 | extensions/webui/<point>/ |
| 124 | plugins/<plugin>/extensions/webui/<point>/ |
| 125 | usr/plugins/<plugin>/extensions/webui/<point>/ |
| 126 | ``` |
| 127 | |
| 128 | Current built-in WebUI extension directories include: |
| 129 | |
| 130 | ```text |
| 131 | fetch_api_call_after |
| 132 | fetch_api_call_before |
| 133 | get_message_handler |
| 134 | initFw_end |
| 135 | json_api_call_after |
| 136 | json_api_call_before |
| 137 | right-canvas-panels |
| 138 | right_canvas_register_surfaces |
| 139 | set_messages_after_loop |
| 140 | set_messages_before_loop |
| 141 | webui_ws_push |
| 142 | ``` |
| 143 | |
| 144 | Plugin frontend HTML extensions should include a root Alpine scope and use `x-move-*` directives when targeting static breakpoints. JS extensions export a default function. |
| 145 | |
| 146 | ## Verification |
| 147 | |
| 148 | - Run targeted lifecycle, prompt, stream, WebSocket, or WebUI extension tests for changed hook points. |
| 149 | - Smoke-test startup for `agent_init`, `startup_migration`, and `system_prompt` changes when practical. |
| 150 | - Check the exact directory path used by `helpers.extension` before adding profile or plugin extension files. |