Practical tutorials

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.

ResultNext check
No reviewConfirm the L3 code is saved, enabled, and SJS has produced a signal
Missing API keyConfigure provider credentials on the session runtime
Provider request failedCheck endpoint access, selected model, and provider response
Invalid reviewInspect 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.

On this page