Skip to content

Instantly share code, notes, and snippets.

@jeremychone
Last active April 8, 2026 16:25
Show Gist options
  • Select an option

  • Save jeremychone/c8a4f3987b68112da064a76fe123759f to your computer and use it in GitHub Desktop.

Select an option

Save jeremychone/c8a4f3987b68112da064a76fe123759f to your computer and use it in GitHub Desktop.
# 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