Jadpo is experimental and not ready for production. These docs follow the compiler's main branch as of 30 Oct 2026. What's built · Install 0.1.0
jadpo
Reference: AI

AI

Typed output

ai.generate(prompt, as: T) never hands back a value that hasn't passed every one of T's rules. You write the type; Jadpo turns it into a schema, asks for an answer in that shape, and decodes the answer the way it decodes a request body. The steps, in order:

  1. The schema. The compiler writes T's JSON Schema when it builds your app. It is the same schema the API record and OpenAPI use (OpenAPI), with each field's /// description, so the model sees what each field means.
  2. Asking. Where the package's schema: mode is strict_subset, Jadpo sends the schema in the provider's structured-output mode, cut to the keywords the provider enforces: types, required fields, enums and nesting. Limits it doesn't enforce, such as max_length, pattern or a rule across fields, are checked in step 3. With json_only or none, Jadpo adds the schema to the instructions under a fixed heading and asks for JSON alone.
  3. Decoding. The answer goes through the same decoding as json.read(…, as: T), every constraint and named rule included. There is no second way in. A T built on Text asks for plain text and skips the schema.
  4. One repair. If decoding fails and attempts: allows, Jadpo asks once more with the previous answer and the DecodeProblem (its path and reason, never a value from the prompt) appended as a correction. The default is 2 attempts in all and the most is 3; each attempt counts against every limit.
  5. The result. A decoded T, or malformed carrying the last DecodeProblem. A refusal or a cut-off ends the call at once.

The built-in driver's mode is strict_subset. It sends OpenAPI's schema for T, cut to what the mode enforces. A T whose JSON isn't an object, such as an enum or a list, is asked for as the member value of one. A package's mode may be json_only or none. The details are under Generating, its fake and the local driver and A package filling ai.

as: takes most types, but not all:

as: takes Refused, and why
Any type json.read takes: objects, lists, enums, Text types, the standard types Secret values (ApiKey, Password and the rest): configuration.secret_flow, a model never makes a secret
References (X.id): a model can't know which records exist; pass the candidates in and ask for an enum or a field the code looks up (semantic.ai_output_type)
File, Bytes, PresentationText: not something JSON from a model can be

A model's answer is input from outside, like a request body: typed and checked, but never proof of anything. Policy never grants access because of it, and its text is escaped wherever it is shown, as any user's text is.

Since a model can't name records, a reply that cites help articles asks for their slugs, a field of text, picked from the articles the prompt was given. Here the action first embeds the question, finds the nearest articles (Embeddings and search) and passes them to the prompt:

example
// domains/support/types/support_answer.jadpotype ArticleSlug = Text {    max_length: 120}/// What the assistant says back, and which articles it drew on.type SupportAnswer = Output {    /// False when the articles given don't answer the question.    resolved_by_articles: Bool    /// The reply to show the customer, at most a few short paragraphs.    reply: Text {        max_length: 2000    }    /// The slugs of the articles the reply used, from those given.    cited_slugs: List<ArticleSlug> {        max_length: 5    }}
example
// domains/support/actions/answer_customer.jadpouse support/types { AssistantUnavailable, CustomerQuestion, SupportAnswer }use support/prompts { AnswerQuestion }/// Answers a customer's question from the closest help articles.action answer_customer(asked: CustomerQuestion) fails AssistantUnavailable -> SupportAnswer {    var question_meaning: ArticleMeaning = attempt ai.embed(asked.text, as: ArticleMeaning) {        timed_out: AssistantUnavailable        over_budget: AssistantUnavailable        too_long: AssistantUnavailable    }    var nearest_excerpts: List<Nearest<ArticleExcerpt>> = closest_articles(question_meaning)    return attempt ai.generate(        AnswerQuestion {            question: asked.text            articles: list.map(nearest_excerpts, found -> found.item)            reader_locale: asked.locale        },        as: SupportAnswer,        max_output: 600    ) {        refused: AssistantUnavailable        malformed: AssistantUnavailable        timed_out: AssistantUnavailable        over_budget: AssistantUnavailable        too_long: AssistantUnavailable    }}

Markdown for agents: /docs/typed-output.md.Built from Jadpo main at a707efb.