Most signals don’t need a custom script. Start with conditions or a judge, and drop to a script when you hit their limits.
The shape of a script
Your code runs as the body of an async function, so you can useawait at the top level. It must return a score built with Score, Passed, or Failed.
Passed() and Failed() are shorthand for a full match (1) and no match (0). Use Score(value, feedback) when you want a strength in between, or a note explaining the verdict.
Returning a verdict
A script returns a strength between0 and 1, not a pass or fail. Latitude compares that strength against a threshold (0.5 by default): at or above it, the behavior counts as present and the session joins the signal.
feedback is optional free text. Latitude stores it on the score and uses it when it groups and displays matches, so a short reason pays off later.
The session object
Every script receives one global,session: a read-only snapshot of the conversation being checked. It is frozen, so you can read it but not change it.
Numbers are in raw units: durations are nanoseconds, costs are microcents (one US cent is 1,000,000 microcents), and token counts are integers. Most scripts read
conversation, tags, metadata, and the tool data rather than the cost and token fields.
Messages
session.conversation is the deduplicated transcript for the whole session. Each entry is { role, content }.
[role] content lines, one per message, which is handy for prompts:
Traces and tools
session.traces breaks the session down by trace. Each trace carries its own rollups plus the models, providers, finish reasons, and tool calls it used.
Each tool call has this shape:
Built-in functions
Alongside the score helpers, a few functions are always available, and two more appear only when your script calls them.semanticSimilarity(query)
Returns the highest cosine similarity betweenquery and any message in the session, from 0 to 1 (0 when the session has no messages). It is async.
llm(prompt, options)
Sends a prompt to an LLM and returns a structured object matchingschema. Latitude manages the model and the system prompt. schema is required and must be built with z (below). It is async.
z
A schema builder for shapingllm() output and parse() input. It mirrors a subset of Zod: z.string(), z.number(), z.boolean(), z.literal(), z.enum(), z.array(), z.object(), and z.union(), with .optional(), .nullable(), .describe(), .min(), .max(), and .int().
parse(value, schema)
Validates a value against az schema and returns it, or throws if it doesn’t match. Useful for checking JSON your agent produced.
Call
llm and semanticSimilarity by name in your source. Latitude reads those names to allocate the right resources and to make the functions available, so building the call dynamically won’t work.What isn’t available
The sandbox is deliberately small. There is no network access, nofetch, no timers, no Date.now or Math.random, and no Node or browser APIs. The only import allowed is zod. A script’s verdict depends only on the session and any llm() call, which keeps results reproducible.
Where scripts run and their limits
Detectors run in the background, never inside your app’s request path. When a session finishes, Latitude runs the matching detectors on it. A preview runs the same way, over recent sessions, on demand. Latitude picks a resource budget from what your script uses:
Every script gets 64 MiB of memory. These limits are enforced automatically and are generous for typical detectors.
Errors are handled at two points:
- Invalid JavaScript is caught when you save, so a script that won’t compile can’t be stored.
- A script that throws at runtime, or exceeds its time or memory budget, scores that session as
errored. Errored sessions show up in the preview. A detector that errors often is flagged as unhealthy.
Editing and detaching
If you build a detector with conditions or an LLM judge, the Custom script tab shows the exact script Latitude compiled from your settings, read-only. Choose “Edit as custom script” to take it over by hand. This clears the conditions or criteria form and switches the detector to your script. It is a one-way move. Once the script is the source of truth, the settings form is gone. Switching between the conditions and judge tabs before you detach keeps both drafts, so detach only when you’re ready to hand-write.Related pages
- Detection methods: conditions and LLM judges, the other two ways to define a detector
- Create a signal: the full creation flow
- Scores: what a detector produces when it matches
- Sessions: what a session is