Complex models
An executor can route to several targets, enrich a request, and preserve both streaming and non-streaming behavior. Keep this logic in the model source, not in client applications.
import { prependSystemPrompt } from "@neutrome/lil-engine";import type { Executor } from "@neutrome/lilsdk";import { fallback, retry } from "@neutrome/lilsdk/loops";
const upstream = fallback([ retry("openai/gpt-5", { attempts: 2 }), "openai/gpt-5-mini",]);
const executor: Executor = { async execute(request, ctx) { return ctx.invoke( upstream, prependSystemPrompt(request, "Answer clearly and cite uncertainty."), ); },
async *stream(request, ctx) { yield* ctx.invokeStream( upstream, prependSystemPrompt(request, "Answer clearly and cite uncertainty."), ); },};
export default executor;prependSystemPrompt() returns a new program. It does not mutate the client
request. retry() retries only before a stream emits content; fallback()
tries the next target only when the prior target fails before emitting content.
Composition guidelines
Section titled “Composition guidelines”- Use provider-qualified targets such as
openai/gpt-5. - Use another workspace model ID when composing your own models.
- Keep
executeandstreambehavior equivalent unless the model is intentionally streaming-only. - Attach tools in the dashboard; a deployed attachment owns the tool loop.
Use the loops reference and engine reference when you need the complete option contracts.