Documentation ziprack/1SDK 0.3.0

Capabilities

AI

ai@1capability contract

On this page

Introduction

ai@1 lets your applet put a language model to work without ever holding a key. The applet never sees a key and never sends a prompt. Instead, it calls a prompt template that the manifest declared and the Rack stores, supplying variables, never instructions:

TypeScript
const { data } = await Rack.ai.ask('score_pitch', { pitch, track });

This has a few consequences for you:

  • Model, system prompt and output schema are owned by the Rack, so they can be tuned or swapped without republishing the applet.
  • Cost is capped per prompt, per attempt, per participant and per applet, enforced on the server. See Budgets.
  • No CSP change is required. The call travels as a postMessage, not a fetch.

Declaring Prompts

You declare each prompt your applet may call in the manifest's prompts array. For example, a prompt that scores a team's pitch might look like this:

JSON
"prompts": [
    {
        "id": "score_pitch",
        "mode": "analysis",
        "template": "Score this team pitch for clarity and specificity.\n\nPitch: {{pitch}}\nTrack: {{track}}",
        "vars": {
            "type": "object",
            "properties": { "pitch": { "type": "string" }, "track": { "type": "string" } },
            "required": ["pitch"]
        },
        "output_schema": {
            "type": "object",
            "properties": {
                "clarity":  { "type": "integer", "minimum": 1, "maximum": 5 },
                "feedback": { "type": "string" }
            },
            "required": ["clarity", "feedback"]
        },
        "budget": { "max_calls_per_attempt": 3, "max_output_tokens": 400 }
    }
]

Each prompt accepts the following fields:

Field Rules
id Required. ^[a-z][a-z0-9_.-]*$, max 100, unique.
mode Required. analysis or chat.
template Required, max 20 000. {{placeholder}} for each variable.
vars Optional JSON Schema (type: object). If present, every placeholder must be declared in it.
output_schema Required for analysis, type: object.
budget Optional. max_calls_per_attempt, max_calls_per_participant, max_output_tokens. See Budgets.
model Optional hint. Honoured only if the Rack allows that model.

The Rack stores the template. Your applet's code only ever names the prompt by its id and passes values for its variables.

Analysis

An analysis prompt returns structured output that conforms to the declared output_schema. Typically, you would use it for scoring a response, summarising back to the participant, or branching on what someone wrote. You may call one using the ask method:

TypeScript
interface Score { clarity: number; feedback: string }

const result = await Rack.ai.ask<Score>('score_pitch', { pitch, track: 'rd' });

if (! result.ok) {
    return result.error === 'budget_exceeded'
        ? showGenericFeedback()
        : showRetry();
}

render(result.data.output.feedback, result.data.output.clarity);
result.data.usage;   // { input_tokens, output_tokens }

The Rack validates the model's output against your schema before it acks, so output either matches the schema or the call fails. You don't need defensive parsing, but you do need a path for the failure case, because a model can be unavailable.

Always write the non-AI fallback. Your experience should keep working when a call fails, rather than stalling on the model.

Chat

A chat prompt is conversational. The Rack holds the history, scoped to the attempt or room, so the applet never holds the transcript. To start a conversation, you may use the chat method and then send each message:

TypeScript
const chat = Rack.ai.chat('coach');

const reply = await chat.send(message, (delta, soFar) => {
    bubble.textContent = soFar;          // streams as it arrives
});

if (reply.ok) {
    bubble.textContent = reply.data.text;
    reply.data.message_id;
}

await chat.reset();                      // start a fresh conversation

Streaming works through chunked acks: partials carry delta, and the terminal ack carries the full text and usage. Each chunk resets the ack timeout, and the whole call allows 60 seconds.

Messages cap at 4000 characters, and history at 40 messages. Older turns fall off rather than failing the call. When you want to begin again, the reset method starts a fresh conversation.

Budgets

The Rack enforces every budget layer on the server, and the applet cannot raise any of them. Some limits you declare per prompt; the rest belong to the Rack:

Limit Declared in Default Ceiling
Calls per attempt budget.max_calls_per_attempt 20 —
Calls per participant budget.max_calls_per_participant 200 —
Output tokens budget.max_output_tokens 800 2000
Calls per applet per day The Rack 5000 —
Calls per Rack per month The Rack 100 000 —
Variable payload The Rack 16 KB —

Exceeding any of them returns budget_exceeded. Declare the budget your experience actually needs; the number is visible to whoever reviews the capability diff.

Untrusted Variables

Variables still reach a model, and participants can type anything into a field you pass through. Design the template so that it would not matter:

  • Put participant text after the instructions, clearly delimited, and say what it is.
  • Never ask the model to follow instructions found inside a variable.
  • Prefer analysis with a tight output_schema over free-form text you will parse yourself.
  • Show model output as model output. Don't render it as if it were guidance authored by the Rack.

Not This Capability

A Rack may also summarise free-text answers across many attempts for the people running a session. That is a Rack feature operating on collected statements, not part of ai@1. It works for any applet declaring activity@1, whether or not that applet uses ai@1 at all, and it is budgeted separately from your prompts.