# Quest server examples

Use a scoped Quest server key and an assignment in the same environment. The browser Sandbox uses your signed-in account instead. These examples require Node 24+ for TypeScript and intent preparation; Python 3.11+ is an independent standard-library transport. The cURL example uses Python for the same intent and receipt checks. No package installation is needed.

Set `FOREVERLEARNING_API_KEY` on your server, `FOREVERLEARNING_ASSIGNMENT_ID`, and a stable pseudonymous `FOREVERLEARNING_USER_ID` (this example accepts letters, digits, underscores and hyphens). Set `FOREVERLEARNING_BASE_URL` to your assigned host ending in `/v1`; preparation defaults to staging. Never put a server key in browser code or commit it. Use synthetic data in staging.

Prepare a request once, then send that saved file with one transport:

```sh
node prepare.mjs setup setup.intent.json
node quest.ts setup.intent.json setup.response.json
# Equivalent transports, using the SAME saved intent:
python quest.py setup.intent.json setup-python.response.json
bash quest.sh setup.intent.json setup-curl.response.json
```

Setup returns `data.session_id` without generating an answer or using a trial attempt. Save that identity as `SESSION_ID`. Each command below prepares a distinct explicit operation. Do not run a preparation command again to recover a disconnected request. Output files must be new so a later result cannot overwrite earlier evidence.

```sh
node prepare.mjs guided guided.intent.json "$SESSION_ID"
node quest.ts guided.intent.json guided.response.json
node prepare.mjs feedback feedback.intent.json "$SESSION_ID"
node quest.ts feedback.intent.json feedback.response.json
node prepare.mjs explain explain.intent.json "$SESSION_ID"
node quest.ts explain.intent.json explain.response.json
node prepare.mjs default default.intent.json "$SESSION_ID"
node quest.ts default.intent.json default.response.json
```

The feedback example includes fictional learner work; Explain includes a supplied solution. These continue the same session and change posture explicitly. `default` omits posture and therefore uses Guided, even after Explain. A client can retain a selected posture by sending it on every turn. `opening` requests a generated opening explicitly; an ordinary first message is preserved as learner input. Setup task and learner preferences are immutable for that session. Support requests ask for `auto`, `hint`, `framework`, `example` or `coaching`; they do not override learning safeguards. `team_dialog` is an optional per-turn boolean.

Attach fictional work with a separate upload. Use PNG/JPEG/WebP up to 8 MiB, 6,000 pixels per side and 36 million pixels, or UTF-8 text/Markdown up to 64 KiB. The server validates type and dimensions. Do not send a data URL in `data`; it is plain base64. There are at most four handles per turn. Uploads expire 30 days after creation, including after reuse or reads.

```sh
node prepare.mjs upload image.intent.json "$SESSION_ID" synthetic-work.png image/png
node quest.ts image.intent.json image.response.json
# Save data.attachment_id as ATTACHMENT_ID, then prepare an ordinary feedback turn:
node prepare.mjs feedback image-feedback.intent.json "$SESSION_ID" "$ATTACHMENT_ID"
node quest.ts image-feedback.intent.json image-feedback.response.json
```

Comma-separated attachment IDs are supported in the last argument. The `quest.attachment_ids` field cannot accompany legacy inline `images`, an opening or closeout. Handles are private to the learner, assignment, region and environment; session-bound uploads cannot move to another session. Do not persist upload bytes in browser operation storage. Uploading and deleting files do not buy a generation.

The transport verifies request/session identity, safety, the closed `quest_receipt`, both public parts and final `done`. Only then does it return a committed answer. Render `parts` with `part: conversation` and `part: display` using safe text/Markdown, with any explicitly requested team dialog separate. Do not extract internal frames from text. A preview or HTTP 200 is insufficient. `quest` describes serving identity, not learning state. Safety status is a separate result; `unknown` is not an all-clear.

If interrupted, retry the exact saved file with a new response filename. This preserves assignment, user, body and idempotency key. Alternatively, prepare `recover` using the original request ID from metadata or the response header. `in_progress` means wait and check the same request. `unavailable` is not permission to purchase another generation. Resolve the original outcome first. A changed body with the same key conflicts.

```sh
node quest.ts guided.intent.json guided-recovery.response.json
node prepare.mjs recover lookup.intent.json "$REQUEST_ID"
node quest.ts lookup.intent.json lookup.response.json
node prepare.mjs end closeout.intent.json "$SESSION_ID"
node quest.ts closeout.intent.json closeout.response.json
node prepare.mjs summary summary.intent.json "$SESSION_ID"
node quest.ts summary.intent.json summary.response.json
```

Closeout generates and saves the summary. Require final completion and `session_ended`. Until a closeout is saved, summary status is `unavailable`. `discard` removes session content without generating closeout. `erase` uses the existing user-erasure operation and needs `data:erase`; do not confuse it with ending a session. The public Quest launch price is $0.05 per successful generated turn, including opening and closeout, with supported attachments included. Failed uncommitted generation is uncharged; the single internal retry and replay do not add a customer charge. Test mode grants 20 lifetime attempts per workspace and stable Quest listing, shared across members and keys; failed dispatched attempts and closeout count. Reserve an attempt for closeout. Exhaustion never silently switches to live charging. Older private assignments retain their pinned rates.

Other prepared reads: `catalog`, `sessions`, `session SESSION_ID`, `state SESSION_ID`, `history SESSION_ID`, and `profile`. State and profile contain saved observations, not mastery, grades or diagnoses. Empty fields remain unavailable; a read never generates missing evidence. `export` needs `data:export` and returns paged sessions, profile and attachment inventory; `export-file ATTACHMENT_ID` downloads each retained file under the same scope. Follow `next_cursor` by adding it to the saved request's `query` for subsequent pages. Downloads return raw bytes, so choose an appropriate output extension. `download` uses `sessions:read`; `delete-file` is idempotent. Generic `/data/export` remains the full lifecycle export including message history.

For a local browser-to-backend demonstration, prepare a saved intent, then run:

```sh
node server.ts guided.intent.json new-private-receipts
```

Open `http://127.0.0.1:4317`. The browser sends a same-origin action to its own backend; the backend owns the key, saved body, user and operation ID. The demo permits only that saved intent and writes a new receipt for each explicit replay. It listens on loopback and is not a hosted authentication implementation. A deployed application must bind its authenticated end user and workspace on the server, authorize operations, and persist operation identities before dispatch.
