Last active
April 8, 2026 16:25
-
-
Save jeremychone/c8a4f3987b68112da064a76fe123759f to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # Dev Chat | |
| Add a new `## Request: _user_ask_title_concise_` with the answer below (concise title). Use markdown sub-headings for sub sections. Keep this top instruction in this file. | |
| ## Request: sub-agent event model over pre-post stages | |
| ### Summary | |
| You want to replace the current boolean stage model for coder sub-agents: | |
| - `stage_pre` | |
| - `stage_post` | |
| with a more general event registration model, for example: | |
| ```yaml | |
| sub_agents: | |
| - name: test.aip | |
| on: {"start", "end"} | |
| ``` | |
| and then pass a normalized event identifier into sub-agent input, such as: | |
| ```yaml | |
| event: "coder::start" | |
| ``` | |
| You also want this to support future custom event emission by sub-agents or builtin agents, for example: | |
| - `coder::start` | |
| - `coder::end` | |
| - `auto-context::end` | |
| ### Assessment | |
| Yes, this is a better long-term option. | |
| ### Why this is better | |
| #### 1. It generalizes the current design cleanly | |
| The current `stage_pre` and `stage_post` model works for the initial use case, but it bakes a very specific lifecycle into the API. An event model keeps the same capability while making it extensible. | |
| Mapping is straightforward: | |
| - `stage_pre: true` becomes `on: ["coder::start"]` | |
| - `stage_post: true` becomes `on: ["coder::end"]` | |
| So the old model is basically a special case of the new one. | |
| #### 2. It allows builtin and custom lifecycle hooks | |
| With events, `pro@coder` is no longer limited to only two hardcoded moments. It can expose richer lifecycle points over time, for example: | |
| - `coder::start` | |
| - `coder::before-main-run` | |
| - `coder::after-task` | |
| - `coder::end` | |
| - `auto-context::start` | |
| - `auto-context::end` | |
| - `dev::end` | |
| That makes the pipeline more composable. | |
| #### 3. It enables agent-to-agent orchestration | |
| Your example is the real advantage. A sub-agent or builtin helper can emit a domain event such as `auto-context::end`, and other sub-agents can subscribe without coupling themselves to implementation details. | |
| That is much better than teaching the pipeline many one-off booleans. | |
| #### 4. It scales better than adding more flags | |
| If the system keeps growing, booleans like these become awkward fast: | |
| - `stage_pre` | |
| - `stage_post` | |
| - `stage_after_auto_context` | |
| - `stage_after_dev` | |
| - `stage_before_output` | |
| An `on` array avoids config sprawl. | |
| ### Recommended shape | |
| I would recommend using an array rather than a set-like inline object literal in docs, because it is more idiomatic and less ambiguous in YAML. | |
| Preferred: | |
| ```yaml | |
| sub_agents: | |
| - name: test.aip | |
| on: | |
| - coder::start | |
| - coder::end | |
| ``` | |
| Possible shorthand compatibility: | |
| ```yaml | |
| sub_agents: | |
| - name: test.aip | |
| on: coder::start | |
| ``` | |
| I would avoid documenting this: | |
| ```yaml | |
| on: {"start", "end"} | |
| ``` | |
| because it looks more like Lua syntax than normal YAML, and it leaves ambiguity around namespacing. | |
| ### Recommended input shape | |
| Passing a single normalized field like this is a good idea: | |
| ```yaml | |
| event: "coder::start" | |
| ``` | |
| I would keep `coder_stage` only as a temporary backward-compatibility field during migration, then deprecate it. | |
| Suggested event-oriented input: | |
| ```ts | |
| type SubAgentInput = { | |
| event: string, | |
| coder_prompt_dir: string, | |
| coder_params: table, | |
| coder_prompt: string, | |
| agent_config: table, | |
| sub_agents_prev?: SubAgentHistoryItem[], | |
| sub_agents_next?: table[], | |
| ... | |
| } | |
| ``` | |
| Examples: | |
| - `event: "coder::start"` | |
| - `event: "coder::end"` | |
| - `event: "auto-context::end"` | |
| ### Important design recommendation | |
| There are really two different concepts here: | |
| - subscription, meaning what events an agent listens to | |
| - emission, meaning what events the pipeline dispatches | |
| So I would define both clearly. | |
| #### Subscription | |
| ```yaml | |
| sub_agents: | |
| - name: my-agent | |
| on: | |
| - coder::start | |
| - auto-context::end | |
| ``` | |
| #### Emission | |
| Either: | |
| - builtin pipeline emits known events automatically | |
| - sub-agents may request follow-up events to emit | |
| For example, a future response contract could allow: | |
| ```ts | |
| type SubAgentOutput = { | |
| emit_events?: string[], | |
| ... | |
| } | |
| ``` | |
| That would make custom orchestration very powerful. | |
| ### Caution | |
| This is better, but only if event ownership and ordering stay explicit. | |
| Without clear rules, event systems can become hard to reason about. I would recommend: | |
| - reserve namespaced event names, like `coder::start` | |
| - define deterministic ordering per event | |
| - document whether emitted events are immediate, queued, or only dispatched at boundaries | |
| - prevent infinite loops with a max step cap, similar to the current protection | |
| - document whether an agent can subscribe to an event it emits itself | |
| ### Suggested migration strategy | |
| A safe rollout would be: | |
| - keep `stage_pre` and `stage_post` as legacy input | |
| - internally normalize them to `on` | |
| - introduce canonical events: | |
| - `coder::start` | |
| - `coder::end` | |
| - pass both during transition: | |
| - `event` | |
| - `coder_stage` for compatibility | |
| - update docs to mark `stage_pre` and `stage_post` as deprecated | |
| ### Bottom line | |
| Yes, this is a better option. | |
| It is a cleaner abstraction, more extensible, better for builtin lifecycle growth, and much better for cross-agent orchestration such as `auto-context::end`. | |
| The main thing to get right is not the syntax, it is the event execution semantics: | |
| - who emits | |
| - when emitted events run | |
| - ordering | |
| - loop prevention | |
| - backward compatibility | |
| ### Suggested next design direction | |
| A strong minimal v1 would be: | |
| - replace `stage_pre` and `stage_post` with `on` | |
| - support: | |
| - string | |
| - list of strings | |
| - normalize legacy stage flags into: | |
| - `coder::start` | |
| - `coder::end` | |
| - pass `event` in sub-agent input | |
| - do not add custom emitted events yet, but reserve the model for them | |
| That gives the cleaner API now, without overcomplicating the first implementation. | |
| ## Request: should coder events use start and end only | |
| ### Short answer | |
| Yes, I think `start` and `end` are better canonical event names than `coder::start` and `coder::end`, as long as event scope is still well-defined by the dispatcher or surrounding pipeline context. | |
| ### Recommendation | |
| I would prefer this: | |
| ```yaml | |
| sub_agents: | |
| - name: test.aip | |
| on: | |
| - start | |
| - end | |
| ``` | |
| and then pass: | |
| ```yaml | |
| event: "start" | |
| ``` | |
| or: | |
| ```yaml | |
| event: "end" | |
| ``` | |
| ### Why this is better | |
| #### Simpler API | |
| For the core coder lifecycle, `start` and `end` are easier to read, write, and remember. | |
| They are the most obvious lifecycle hooks in this system, so short names are appropriate. | |
| #### Less noise in config | |
| This: | |
| ```yaml | |
| on: | |
| - start | |
| - end | |
| ``` | |
| is cleaner than: | |
| ```yaml | |
| on: | |
| - coder::start | |
| - coder::end | |
| ``` | |
| If most users are wiring events only inside `pro@coder`, the `coder::` prefix adds verbosity without much value. | |
| #### Better ergonomics for common cases | |
| These events will likely be the most commonly used ones. Short names make authoring and reading sub-agent configs nicer. | |
| ### The main caveat | |
| The only real reason to keep `coder::start` and `coder::end` is namespacing. | |
| If you expect a larger shared event bus later, with many producers such as: | |
| - `auto-context::end` | |
| - `dev::end` | |
| - `code-map::updated` | |
| then fully namespaced event names are safer because they avoid ambiguity. | |
| So there are really two good directions. | |
| ### Option A, preferred for now | |
| Use simple root lifecycle events for the main coder pipeline: | |
| - `start` | |
| - `end` | |
| and keep namespaced custom or component events for secondary domains: | |
| - `auto-context::end` | |
| - `dev::end` | |
| This gives a nice balance: | |
| - the top-level lifecycle stays simple | |
| - specialized emitted events still remain explicit | |
| Example: | |
| ```yaml | |
| sub_agents: | |
| - name: prepare-agent.aip | |
| on: start | |
| - name: finalize-agent.aip | |
| on: | |
| - end | |
| - auto-context::end | |
| ``` | |
| ### Option B, fully namespaced everywhere | |
| Use namespaced events for everything: | |
| - `coder::start` | |
| - `coder::end` | |
| - `auto-context::end` | |
| This is more uniform, but a bit heavier for the most common hooks. | |
| ### My recommendation | |
| I would use: | |
| - `start` | |
| - `end` | |
| as the canonical built-in lifecycle events for `pro@coder`. | |
| Then reserve namespaced events for non-root or emitted component-specific events, for example: | |
| - `auto-context::end` | |
| - `dev::end` | |
| That feels like the cleanest developer experience. | |
| ### Suggested rule | |
| A practical rule could be: | |
| - root pipeline lifecycle events are short names | |
| - component or emitted events are namespaced | |
| So: | |
| - `start` | |
| - `end` | |
| - `auto-context::end` | |
| - `dev::end` | |
| ### Backward design note | |
| If you go with this, I would still normalize legacy fields like: | |
| - `stage_pre` | |
| - `stage_post` | |
| into: | |
| - `start` | |
| - `end` | |
| and pass both during migration if needed: | |
| ```ts | |
| type SubAgentInput = { | |
| event: "start" | "end" | string, | |
| coder_stage?: "pre" | "post", | |
| ... | |
| } | |
| ``` | |
| ### Bottom line | |
| Yes, I think `start` and `end` are better than `coder::start` and `coder::end` for the main coder lifecycle. | |
| They are simpler and nicer to use. | |
| I would only keep namespacing for secondary or cross-component events, where it adds real clarity. | |
| ## Request: finalize start-end event model and emitted events | |
| ### Summary | |
| You decided to finalize the sub-agent event model with these choices: | |
| - use `start` and `end` as the canonical root coder lifecycle events | |
| - support `emit_events` in sub-agent outputs | |
| - drop backward compatibility for: | |
| - `stage_pre` | |
| - `stage_post` | |
| - `coder_stage = "pre" | "post"` | |
| You want the dev chat to capture both the decision and the recommended execution semantics. | |
| ### Recommendation | |
| Yes, this is the right cleanup point. | |
| This gives you a much cleaner model than the old stage booleans, and it avoids carrying transitional API baggage that you do not need. | |
| ### Final recommended shape | |
| #### Sub-agent config | |
| Use `on` as the subscription field. | |
| ```yaml | |
| sub_agents: | |
| - name: prepare-context.aip | |
| on: start | |
| - name: finalize-run.aip | |
| on: | |
| - end | |
| - name: react-to-auto-context.aip | |
| on: | |
| - auto-context::end | |
| ``` | |
| Supported forms: | |
| - `on: start` | |
| - `on: end` | |
| - `on: auto-context::end` | |
| - `on: [start, end]` | |
| ### Canonical builtin root events | |
| For `pro@coder`, the root lifecycle events should be: | |
| - `start` | |
| - `end` | |
| Meaning: | |
| - `start`, before the main coder task execution begins | |
| - `end`, after all main coder task outputs are complete | |
| These replace the old concept of pre/post stages. | |
| ### Input shape | |
| Pass a normalized `event` field to sub-agents. | |
| ```ts | |
| type SubAgentInput = { | |
| event: string, | |
| coder_prompt_dir: string, | |
| coder_params: table, | |
| coder_prompt: string, | |
| agent_config: table, | |
| sub_agents_prev?: SubAgentHistoryItem[], | |
| sub_agents_next?: AgentConfig[], | |
| ... | |
| } | |
| ``` | |
| Do not keep `coder_stage`. | |
| That is the right choice if there is no external compatibility requirement. | |
| ### Output shape | |
| Add emitted events explicitly to the response contract. | |
| ```ts | |
| type SubAgentOutput = { | |
| coder_params?: table, | |
| coder_prompt?: string, | |
| agent_result?: any, | |
| sub_agents_next?: AgentConfig[], | |
| emit_events?: string[], | |
| success?: boolean, | |
| error_msg?: string, | |
| error_details?: string, | |
| } | |
| ``` | |
| Recommended shorthand behavior: | |
| - if `emit_events` is missing or empty, emit nothing | |
| - if present, preserve order | |
| - allow duplicates only if you want repeat dispatch semantics | |
| - otherwise dedupe before queueing | |
| My recommendation is: | |
| - preserve order | |
| - dedupe only adjacent accidental duplicates if needed | |
| - keep semantics simple and explicit | |
| ### Recommended execution model | |
| This is the important part. | |
| You now have two concepts: | |
| - subscriptions, via `on` | |
| - emissions, via `emit_events` | |
| The cleanest model is a queued event dispatcher. | |
| #### Suggested semantics | |
| - Seed the run with `start` | |
| - Execute all sub-agents subscribed to `start`, in declared order | |
| - As agents run, they may return: | |
| - state updates | |
| - `sub_agents_next` | |
| - `emit_events` | |
| - `emit_events` are appended to an event queue | |
| - After the main coder execution completes, enqueue `end` | |
| - Continue draining the queue until empty or safety cap reached | |
| ### Strong recommendation on queue behavior | |
| Use queued, not immediate recursive dispatch. | |
| That means if an agent handling `start` emits `auto-context::end`, that event is scheduled after the current event processing step, not executed re-entrantly in the middle of the current agent call stack. | |
| This gives you: | |
| - deterministic ordering | |
| - simpler reasoning | |
| - less fragile control flow | |
| - easier loop protection | |
| ### Recommended ordering rules | |
| A good minimal rule set would be: | |
| #### 1. Event queue is FIFO | |
| Events are processed in the order they are queued. | |
| #### 2. Agent order is declaration order | |
| For a given event, matching sub-agents run in the order they appear in `sub_agents`. | |
| #### 3. Emitted events are appended | |
| If a sub-agent emits: | |
| ```yaml | |
| emit_events: | |
| - auto-context::end | |
| - dev::end | |
| ``` | |
| those events are appended to the tail of the queue in that same order. | |
| #### 4. `sub_agents_next` only affects future dispatches | |
| If a sub-agent changes the pending sub-agent list, that updated list is used for later event dispatch, not retroactively for already-running dispatch. | |
| That keeps behavior consistent with your current dynamic tail replacement idea. | |
| ### Suggested event examples | |
| Root lifecycle: | |
| - `start` | |
| - `end` | |
| Component events: | |
| - `auto-context::start` | |
| - `auto-context::end` | |
| - `dev::start` | |
| - `dev::end` | |
| - `code-map::updated` | |
| This naming split is good: | |
| - short names for the root lifecycle | |
| - namespaced names for component-level emitted events | |
| ### Safety rules | |
| You should definitely keep explicit protections. | |
| Recommended minimum: | |
| - max total dispatch steps, similar to the current max sub-agent step cap | |
| - optional max emitted event count | |
| - optional max times the same event can appear consecutively | |
| - document that self-emitting loops are possible and will be stopped by the cap | |
| Example problematic loop: | |
| - agent A listens on `foo` | |
| - agent A emits `foo` | |
| That should not be forbidden by default, but it must be bounded. | |
| ### Suggested normalized config shape | |
| ```ts | |
| type AgentConfig = { | |
| name: string, | |
| enabled: boolean, | |
| on: string[], | |
| options?: table, | |
| [key: string]: any | |
| } | |
| ``` | |
| Normalization rules: | |
| - string entry: | |
| ```yaml | |
| - my-agent | |
| ``` | |
| becomes: | |
| ```yaml | |
| { name: "my-agent", enabled: true, on: ["start"] } | |
| ``` | |
| - table entry without `on`: | |
| default to: | |
| ```yaml | |
| on: ["start"] | |
| ``` | |
| That preserves the most natural default behavior for existing internal usage, without needing the old stage model. | |
| ### Recommended migration decision | |
| Since you explicitly do not need backward compatibility, I would do a clean replacement: | |
| - remove `stage_pre` | |
| - remove `stage_post` | |
| - remove `coder_stage` | |
| - document only `on`, `event`, and `emit_events` | |
| That is better than carrying deprecated fields that nobody needs. | |
| ### Suggested concise spec direction | |
| A solid minimal implementation target is: | |
| - sub-agents subscribe with `on` | |
| - root pipeline seeds `start` | |
| - root pipeline later enqueues `end` | |
| - sub-agents receive `event` | |
| - sub-agents may return `emit_events` | |
| - emitted events are processed through a FIFO queue | |
| - dynamic `sub_agents_next` remains supported | |
| - execution is bounded by a max step cap | |
| ### Bottom line | |
| Yes, this is the right final design direction. | |
| It is cleaner than stages, more extensible than booleans, and the addition of `emit_events` gives you a real event system without making the initial implementation too complex. | |
| The one thing to be strict about is execution semantics: | |
| - queued dispatch | |
| - FIFO ordering | |
| - declaration-order subscribers | |
| - bounded execution | |
| If you lock those rules early, the model should stay understandable as it grows. | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment