EnginePolicies

State and lifecycle

Keep policy state isolated, distinguish market time from wall time, and record useful diagnostics.

The policy framework stores state below context.l3State. You choose a stack key, and each policy ID owns an entry inside that stack.

State ownership

The quickstart creates this structure:

context.l3State
└── quantityTutorial
    ├── policies
    │   └── quantity-limit
    │       ├── frames
    │       ├── lastSeenAt
    │       ├── status
    │       └── lastFrameResult
    ├── lastFrameResults
    ├── lastResolution
    ├── lastExecution       (only after an execution attempt)
    └── lastIntentCheck     (only after an intent check)

getPolicyStackState(context, key) initializes the stack and its policies map. getPolicyState(stack, policyId) initializes an individual policy state. Reusing the same key and ID reuses the same state in that session.

Use a new stack key when intentionally creating an independent stack. Use distinct policy IDs for independent instances. Changing configuration while retaining the same state identity can leave old state with new semantics, so define a reset or migration rule for stateful policies.

What the framework updates

After each policy's frame hook completes, runPolicyFrames increments its frames counter and sets lastSeenAt using wall-clock time. It does this even when a policy has no frame hook or returns no result.

When the hook returns a result, the runner also sets status and lastFrameResult. If the hook returns nothing, a previous status/result can remain. If it throws, that invocation does not reach the bookkeeping updates and later policies in the batch do not run.

The framework does not automatically write stack-level lastFrameResults, lastResolution, lastExecution, or lastIntentCheck. The quickstart's app and intent exports write those records explicitly. A no-action frame also does not clear a previous lastExecution; inspect its timestamp.

Use the right clock

Use the market timestamp, usually bar.date, for a trading cooldown, evidence freshness, or one-decision-per-bar rule. Use wall-clock timestamps for operational diagnostics such as how recently a worker checked a request.

The framework's lastSeenAt and the quickstart's diagnostic timestamps are wall-clock values. During replay they do not describe the historical market time. The frame observer shows how to record a separate market timestamp.

Prevent repeated actions deliberately

The runner evaluates policies again on later frames. A ready signal that remains true can keep proposing actions. Before adding entries, decide how to handle pending orders, repeated bars, rejected requests, and re-entry after a close.

A policy-level reservationKey is only metadata in the shared executor. A field named cooldown and the status cooldown also have no automatic timing behavior. Your policy must store and compare the relevant market timestamps or episode IDs.

Do not mark an action filled merely because it won resolution. The script can record an accepted submission separately, then inspect orders, positions, or trades to observe the eventual outcome.

Keep state inspectable

Prefer small serializable values: IDs, numeric counters, timestamps, reasons, and compact snapshots. Avoid placing session objects, provider clients, promises, or functions in l3State. The state helpers initialize in-memory state; they do not promise persistence across session restarts.

Agent Lab revisions preserve source and configuration for experiments. Do not treat a new run as a continuation of a previous run's policy memory.

Continue with Debugging.

On this page