Problem
ScriptedAlchemy/cargo-hauler had to move its ubiquitous shell hooks out of routed events and into config-declared fast-path handlers because every non-cargo shell command paid the full rendered event-route startup cost.
Measured in cargo-hauler #90:
- routed
tool/before: ~0.10–0.11 s, ~64 MB RSS;
- routed
tool/after: ~0.09–0.10 s, ~64 MB RSS;
- the route loaded the full rendering runtime before it could decide that
ls, git, etc. were irrelevant.
The current workaround in cargo-hauler/agent-bundle.config.ts uses hooks.beforeTool.handler / hooks.afterTool.handler pointing at src/hooks/fast-path/*. Those entries are deliberately tiny, avoid React/Effect, and only load the real rewrite/telemetry path when the command contains cargo/hauler tokens.
This solves the performance problem but creates exactly the architectural split #592 is trying to remove: semantic lifecycle events live in src/events/**, while high-frequency hooks need a second handler system because the normal execution kernel cannot short-circuit before expensive route/runtime/provider initialization.
Direction
Add a framework-owned preflight/gate phase to the common execution kernel.
Conceptually:
native host event
↓
envelope decode + canonicalization
↓
cheap preflight gate <-- no RSC/React/providers unless requested
├─ continue immediately
├─ deny immediately
└─ execute route
↓
providers/layout/rendering
The gate is part of the same event route/application graph, not another host-hook registration model.
Possible authoring forms:
export const preflight = ({ canonical }) => {
const command = commandFrom(canonical.payload);
return mentionsCargo(command) ? 'execute' : { outcome: 'continue' };
};
export default async function BeforeTool(props) {
// full route only runs for relevant events
}
or a convention such as a statically compiled gate export. Exact API is open.
Lazy context/providers
The same issue appears with providers. The framework currently constructs provider values per request before route execution. A high-frequency event that does not need a daemon probe, state store, lineage lookup, etc. should not pay for those values.
Support one or both of:
- lazy provider resolution on first access;
- route/gate declarations of required providers;
- a preflight context that contains only canonical event data, signal, terminal/host capability metadata, and explicitly cheap values.
Preserve deterministic provider ordering once providers are actually materialized.
Compiler/runtime requirement
The emitted hook entry must be physically cheap, not merely logically early inside the full bundle. The compiler should be able to emit a small preflight shell that imports the heavier route/runtime only after the gate says execute.
This is similar to middleware edge-gating in a web framework: the optimization has to exist in the entry graph, not after the application bundle is already loaded.
Relationship to config-declared hooks
Do not remove config handlers until routed events can meet the same latency/runtime characteristics. After this lands, config-declared handlers can remain an escape hatch for genuinely raw/native behavior, but should not be required for ordinary semantic events that need a cheap rejection path.
Acceptance
Consumer evidence
Problem
ScriptedAlchemy/cargo-haulerhad to move its ubiquitous shell hooks out of routed events and into config-declared fast-path handlers because every non-cargo shell command paid the full rendered event-route startup cost.Measured in cargo-hauler #90:
tool/before: ~0.10–0.11 s, ~64 MB RSS;tool/after: ~0.09–0.10 s, ~64 MB RSS;ls,git, etc. were irrelevant.The current workaround in
cargo-hauler/agent-bundle.config.tsuseshooks.beforeTool.handler/hooks.afterTool.handlerpointing atsrc/hooks/fast-path/*. Those entries are deliberately tiny, avoid React/Effect, and only load the real rewrite/telemetry path when the command contains cargo/hauler tokens.This solves the performance problem but creates exactly the architectural split #592 is trying to remove: semantic lifecycle events live in
src/events/**, while high-frequency hooks need a second handler system because the normal execution kernel cannot short-circuit before expensive route/runtime/provider initialization.Direction
Add a framework-owned preflight/gate phase to the common execution kernel.
Conceptually:
The gate is part of the same event route/application graph, not another host-hook registration model.
Possible authoring forms:
or a convention such as a statically compiled
gateexport. Exact API is open.Lazy context/providers
The same issue appears with providers. The framework currently constructs provider values per request before route execution. A high-frequency event that does not need a daemon probe, state store, lineage lookup, etc. should not pay for those values.
Support one or both of:
Preserve deterministic provider ordering once providers are actually materialized.
Compiler/runtime requirement
The emitted hook entry must be physically cheap, not merely logically early inside the full bundle. The compiler should be able to emit a small preflight shell that imports the heavier route/runtime only after the gate says
execute.This is similar to middleware edge-gating in a web framework: the optimization has to exist in the entry graph, not after the application bundle is already loaded.
Relationship to config-declared hooks
Do not remove config handlers until routed events can meet the same latency/runtime characteristics. After this lands, config-declared handlers can remain an escape hatch for genuinely raw/native behavior, but should not be required for ordinary semantic events that need a cheap rejection path.
Acceptance
src/events/tool/beforeroute can reject/continue a non-matching event before loading the rendered route runtime.src/hooks/fast-path/shell-before.ts/shell-after.tsback under the canonical event-route model without regressing perf(test): drop rstest maxWorkers pins, isolate per RSTEST_WORKER_ID #90.Consumer evidence
ScriptedAlchemy/cargo-hauler/issues/90ScriptedAlchemy/cargo-hauler/agent-bundle.config.ts