Debugging policies
Trace a decision from loaded source to frame result, execution check, and actual order outcome.
Debug a policy by locating the first stage where the observed result differs from the expected result. A missing frame, a missing proposal, a rejected request, and an unfilled order have different causes.
Trace the quickstart
- Source loaded: confirm the L3 source is saved and enabled. Check for compilation or runtime errors.
- Frame hook ran: inspect
context.l3State.quantityTutorial.policies["quantity-limit"].framesandlastFrameResult. - Resolution completed: inspect the stack's
lastResolution. The quantity-only example should have no selected action. - A request reached L3: inspect
lastIntentCheckand thequantity-policylog. - Execution outcome: inspect the actual orders, positions, and trades. An allowed check is not a fill receipt.
The quickstart logs checks, not every frame. No check log can be normal when there are no execution requests or an earlier automation control has already blocked the request.
Common symptoms
| Symptom | Likely cause and next check |
|---|---|
| No policy state | Wrong source field, disabled L3, compile error, or no processed frames |
| Framework unavailable | The script is not receiving an L3 scope; check source placement and runtime support |
Policy remains waiting | Inspect configured model IDs, data readiness, and timestamps |
A blocked status still permits an order | Status is descriptive; implement an onIntent check and export the aggregate gate |
| A frame block does not stop SJS | Frame resolution and execution gating are separate; inspect the intent hook |
| A large-priority entry loses to an exit | Kind rank is evaluated before numeric priority |
| A place proposal disappears | The resolver saw an existing first position with an action |
A selected action returns false | Inspect required order/position payloads and downstream execution controls |
| Intent checks are missing for a policy action | The executor uses source l3, which bypasses the L3 intent hook |
| A manual request proceeds after a rejection | The normal runtime permits manual override; compare allowed and finalAllowed |
| Two instances share a counter | They use the same stack key and policy ID |
| An old result looks current | A hook returned nothing or no new execution occurred; inspect timestamps |
Read the runtime decision record
The normal L3 execution gate records recent decisions in context.l3State.decisions and the latest one in lastDecision. Useful fields include source, kind, allowed, finalAllowed, reasonCodes, latencyMs, and any error.
These runtime records are separate from your stack's policy-specific checks. The stack can explain which policy object rejected the request; the runtime can explain the final gate outcome. L3-source requests bypass this intent path.
Frame errors populate l3State.lastError in the normal runtime. Agent Lab propagates L3 compile, frame, and intent failures so the historical run can report them. Investigate the first error before interpreting later absence of trades as a strategy decision.
Reproduce with fixed inputs
Use a small historical window and retain the source revision, instrument, dataset, and execution settings. For the quantity guard, test the limit itself, a value above it, missing and invalid quantities, and explicit close/cancel requests.
For a stateful policy, repeat the same frame, advance the market timestamp, remove a dependency, and start a fresh state object. For an action policy, test both an accepted and a rejected context API result.
Keep the explanatory logs small. Record the policy ID, checkpoint, relevant input, and reason instead of serializing the full session.
Continue with Examples or return to Your first policy.