Defining a policy
Understand policy identity, hooks, scopes, and the results returned to the framework.
Define a policy when one decision deserves its own inputs, state, explanation, and tests. A quantity guard, a session-window check, or a position-exit rule can each be a policy.
Policy fields
| Field | Purpose |
|---|---|
id | Stable key for this policy's state within a stack |
description | Human-readable explanation of its responsibility |
intents | Array of declared policy intent kinds |
onFrame(scope) | Optional async hook returning a frame result or nothing |
onIntent(scope, request) | Optional async hook returning an allow/reject result or nothing |
Use unique IDs within a stack. Two policy instances with the same ID receive the same state object, even when their settings differ.
The intents list documents a policy's intended capabilities. The current runners do not use it to filter hook calls or validate results. Each onIntent must check the request kind itself.
Frame scope and results
The policy frame scope includes the ordinary L3 frame arguments plus policyId, policyStack, and policyState. Return policyId from the supplied scope so your result matches the instance being evaluated.
This observation-only policy can be added to the quickstart's policies array:
const frameObserver = {
id: "frame-observer",
description: "Record the latest market timestamp without requesting an action",
intents: ["none"],
async onFrame({ policyId, policyState, bar }) {
if (!bar?.date) {
return { policyId, status: "waiting", reason: "No market timestamp" };
}
const timestamp = new Date(bar.date).getTime();
if (!Number.isFinite(timestamp)) {
return { policyId, status: "waiting", reason: "Invalid market timestamp" };
}
policyState.marketTimestamp = timestamp;
return { policyId, status: "watching", reason: "Frame observed" };
},
};A frame result has policyId, status, and optional intent, reason, and confidence. Available statuses are idle, watching, waiting, triggered, cooldown, and blocked.
These statuses describe the policy; they do not implement a state machine. The resolver does not require status: "triggered" before selecting an intent. When you mean “wait,” omit the action intent rather than returning an actionable order with a waiting status.
Intent scope and results
An intent scope adds the execution request as scope.intent; the same request is also passed as the second argument. Return:
const allowResult = {
policyId: "quantity-limit",
allowed: true,
reason: "Quantity within limit",
audit: { quantity: 1, maximumQuantity: 2 },
};priority is also an optional intent-check result field, but the current check resolver does not use it. Any returned check with allowed: false rejects the request. An omitted result abstains; an empty check list permits execution.
Keep decisions separate from execution
For frame-driven actions, return a proposed intent and let the stack resolve competing proposals before executing one. Direct calls to context.placeOrder(...) inside a policy hook happen outside that selection step.
Keep explanations concrete: “quantity 3 exceeds maximum 2” or “waiting for regime fast” is more useful than “not ready.” Record enough inputs to reproduce the decision without storing an entire session in policy state.
Continue with Requirements and Intents.