.agents/skills/agent-signal/references/handlers.md
Use the middleware helpers in apps/server/src/services/agentSignal/runtime/middleware.ts.
They provide:
defineSourceHandler(...)defineSignalHandler(...)defineActionHandler(...)defineAgentSignalHandlers(...)These helpers do two jobs:
listen points at concrete source, signal, or action typesEach handler receives:
RuntimeProcessorContextThe context gives you:
scopeKeynow()runtimeState.getGuardState(lane)runtimeState.touchGuardState(lane, now?)Read:
apps/server/src/services/agentSignal/runtime/context.tsReturn one of these shapes:
void: no fan-out, stop at this handler{ status: 'dispatch', signals?, actions? }: continue the chain{ status: 'wait', pending? }: pause for later host coordination{ status: 'schedule', nextHop }: schedule another hop{ status: 'conclude', concluded? }: stop with a terminal runtime resultExecutorResult: only for action handlers that performed a concrete side effectRead:
packages/agent-signal/src/base/types.tsapps/server/src/services/agentSignal/runtime/AgentSignalScheduler.tsUse defineAgentSignalHandlers([...]) to bundle related handlers into one policy.
Example from analyzeIntent:
return defineAgentSignalHandlers([
...(options.procedure ? [createToolOutcomeSourceHandler(options.procedure)] : []),
createFeedbackSatisfactionJudgeProcessor(...),
createFeedbackDomainJudgeSignalHandler(...),
createFeedbackActionPlannerSignalHandler(),
defineSkillManagementActionHandler(...),
createCompletionSkillSynthesisSourceHandler(...),
defineUserMemoryActionHandler(...),
]);
That bundle is later passed into the runtime via:
createDefaultAgentSignalPolicies(...)createAgentSignalRuntime({ policies })Read:
apps/server/src/services/agentSignal/policies/index.tsapps/server/src/services/agentSignal/policies/analyzeIntent/index.tsUse a source handler when you are interpreting a producer event into semantic signals.
Reference:
apps/server/src/services/agentSignal/policies/analyzeIntent/feedbackSatisfaction.tsPattern:
return defineSourceHandler(
AGENT_SIGNAL_SOURCE_TYPES.agentUserMessage,
'agent.user.message:my-handler',
async (source, ctx): Promise<RuntimeProcessorResult | void> => {
// interpret source payload
// optionally use ctx.runtimeState
return {
signals: [/* one or more semantic signals */],
status: 'dispatch',
};
},
);
Write source handlers when:
Use a signal handler when one semantic state should branch into more semantic states or planned actions.
References:
apps/server/src/services/agentSignal/policies/analyzeIntent/feedbackDomain.tsapps/server/src/services/agentSignal/policies/analyzeIntent/feedbackAction.tsPattern:
return defineSignalHandler(
MY_SIGNAL_TYPE,
'signal.my-policy-router',
async (signal): Promise<RuntimeProcessorResult | void> => {
return {
actions: [/* planned work */],
status: 'dispatch',
};
},
);
Use signal handlers for:
Use an action handler when the runtime should do actual work or enqueue the work that will run out-of-band.
References:
apps/server/src/services/agentSignal/policies/analyzeIntent/actions/userMemory.tsapps/server/src/services/agentSignal/policies/analyzeIntent/actions/skillManagement.tsPattern:
return defineActionHandler(
MY_ACTION_TYPE,
'action.my-policy-executor',
async (action, ctx): Promise<ExecutorResult> => {
// run service/tool/model side effect
// check idempotency if needed
return {
actionId: action.actionId,
attempt: {
completedAt: ctx.now(),
current: 1,
startedAt,
status: 'succeeded',
},
status: 'applied',
};
},
);
Keep these rules:
actionIderrorExecutorResult into built-in result signalsexecAgent actions, report the enqueue result here and project durable receipts from agent.execution.completedFor memory and skill self-iteration actions, the concrete side effect is
enqueueSelfIterationRun(...). The background run stamps an Agent Signal
operation marker, writes durable resources in the agent runtime, then exposes
mutation outcomes on the completion source's selfIteration payload. Do not add
a second synchronous receipt projection to the action handler.
Use this split:
packages/agent-signal/src/source/sourceTypes.tspackages/agent-signal/src/source/sourceEvent.ts
packages/agent-signal/src/source/scopeKey.tsapps/server/src/services/agentSignal/sources/**apps/server/src/services/agentSignal/policies/types.tspackages/agent-signal/src/base/types.tsDo not put app-specific signal catalogs into packages/agent-signal. That package should stay generic and reusable.
Choose source when:
Choose signal when:
Choose action when:
If a handler both interprets meaning and performs side effects, split it. That keeps chains inspectable and testable.
Prefer focused tests near the touched code.
Useful references:
apps/server/src/services/agentSignal/runtime/__tests__/AgentSignalRuntime.test.tsapps/server/src/services/agentSignal/__tests__/index.integration.test.tsapps/server/src/services/agentSignal/policies/analyzeIntent/__tests__/*apps/server/src/services/agentSignal/policies/analyzeIntent/actions/__tests__/*apps/server/src/services/agentSignal/services/selfIteration/completion/__test__/*Test at the smallest level that proves the behavior: