Ask L3 to review a session
Call an AI provider with session evidence and record a structured review.
Add a review to the Pine/SJS example. L3 will read the signal evidence, recent market data, and risk state, then store an assessment. This example does not approve or block orders.
1. Prepare the provider
The running session needs access to a configured AI provider. This example uses the runtime's openai provider. Configure OPENAI_API_KEY on the session runtime through your deployment's secret configuration; do not paste credentials into the app or a shared code example.
If you do not manage the runtime, ask its operator to confirm provider access first. A key available to the website build is not necessarily available to the process running your app. You can set model in the exported config to an available model supported by your provider; otherwise the runtime uses its default.
2. Add the L3 code
Open the app's L3 editor and paste:
export const config = {
enabled: true,
provider: "openai",
};
export async function app({ context, bar, tools, provider }) {
if (!bar) return;
const signal = context.sjsState?.tutorial?.signal;
if (!signal) return;
const state = context.l3State ??= {};
if (state.reviewPending || state.lastReviewedFrame === signal.frame) return;
state.reviewPending = true;
state.lastReviewedFrame = signal.frame;
state.aiReview = undefined;
state.lastError = undefined;
try {
const response = await provider.provider.complete({
purpose: "frame",
instructions: [
"Review the supplied session evidence. Do not place orders.",
'Return JSON: {"risk":"low|medium|high","reason":string}.',
].join("\n"),
input: {
signal,
market: await tools.get_market_data_window({ limit: 12, format: "vectors" }),
risk: await tools.get_risk_state(),
},
tools,
});
const decision = response.output;
if (!decision || !["low", "medium", "high"].includes(decision.risk)
|| typeof decision.reason !== "string") {
throw new Error("Provider returned an invalid review");
}
state.aiReview = { frame: signal.frame, risk: decision.risk, reason: decision.reason };
console.log("L3 review", state.aiReview);
} catch (error) {
state.lastError = String(error);
console.error("L3 review failed", state.lastError);
} finally {
state.reviewPending = false;
}
}This makes one request per new SJS signal frame, rather than one on every tick. It records failed attempts too; another tick will not repeatedly retry the same failed review. Start a new session or wait for the next signal after fixing a provider issue.
3. Save and run a short test
Save the app and use an authorized running simulated session that produces a signal. Real provider calls are disabled during historical backtesting and warmup. For deterministic historical testing, use the fake-provider example.
L3 runs before SJS in the shared frame. The review consumes the latest SJS evidence available when L3 runs, normally from an earlier invocation. Do not assume that a review arrives before an order is submitted.
4. Inspect the result
In Logs, look for L3 review or L3 review failed. A successful result is also available at context.l3State.aiReview, with the originating signal frame, risk, and reason.
| Result | Next check |
|---|---|
| No review | Confirm the L3 code is saved, enabled, and SJS has produced a signal |
| Missing API key | Configure provider credentials on the session runtime |
| Provider request failed | Check endpoint access, selected model, and provider response |
| Invalid review | Inspect the response format before using it in another policy |
Next
Keep this observational while learning. An execution gate requires a separate policy for stale reviews, provider failures, and exits; the example intentionally exports no intent gate.
Continue with Backtest, inspect, and launch.