platform3 / qti / 1edtech / skill pack

QTI 1EdTech Surface Skill Pack

An installable, instructions-only pack for AI agents building apps, reports, QA probes, and deliverables against the live QTI 1EdTech API without reimplementing QTI logic.

Install

The pack follows the same Platform3 module-pack contract as every other hosted pack: skill-pack.json at the canonical root and pack/SKILL.md as the primary install file. Install the same hosted files into the agent you use; do not copy endpoint details by hand into chat.

The older nested path under pack/qti-1edtech-surface/ remains available for compatibility, but new installs should use pack/.

Skill entrypoint

Primary skill entrypoint. Claude, Codex, and Perplexity should all receive this file.

pack/SKILL.md

Pack manifest

Machine-readable install metadata and canonical documentation links for the QTI module pack.

skill-pack.json

Runnable proof check

Surface-call verifier. It calls the live API and fails unless the platform returns its pinned demo answers.

checks/qti-surface-check.sh

Claude Code

export QTI_SKILL_URL="https://platform3-andymontgomery-9773s-projects.vercel.app/qti/1edtech/skill_pack/pack"
mkdir -p "$HOME/.claude/skills/qti-1edtech-surface/agents" "$HOME/.claude/skills/qti-1edtech-surface/examples" "$HOME/.claude/skills/qti-1edtech-surface/checks"
curl -fsSLL "$QTI_SKILL_URL/SKILL.md" -o "$HOME/.claude/skills/qti-1edtech-surface/SKILL.md"
curl -fsSLL "$QTI_SKILL_URL/agents/claude-code.md" -o "$HOME/.claude/skills/qti-1edtech-surface/agents/claude-code.md"
curl -fsSLL "$QTI_SKILL_URL/examples/assessment-app-spec.md" -o "$HOME/.claude/skills/qti-1edtech-surface/examples/assessment-app-spec.md"
curl -fsSLL "$QTI_SKILL_URL/examples/qa-report-template.md" -o "$HOME/.claude/skills/qti-1edtech-surface/examples/qa-report-template.md"
curl -fsSLL "$QTI_SKILL_URL/checks/qti-surface-check.sh" -o "$HOME/.claude/skills/qti-1edtech-surface/checks/qti-surface-check.sh"
chmod +x "$HOME/.claude/skills/qti-1edtech-surface/checks/qti-surface-check.sh"

Codex CLI

export QTI_SKILL_URL="https://platform3-andymontgomery-9773s-projects.vercel.app/qti/1edtech/skill_pack/pack"
export CODEX_SKILLS_DIR="${CODEX_HOME:-$HOME/.codex}/skills/qti-1edtech-surface"
mkdir -p "$CODEX_SKILLS_DIR/agents" "$CODEX_SKILLS_DIR/examples" "$CODEX_SKILLS_DIR/checks"
curl -fsSLL "$QTI_SKILL_URL/SKILL.md" -o "$CODEX_SKILLS_DIR/SKILL.md"
curl -fsSLL "$QTI_SKILL_URL/agents/openai.yaml" -o "$CODEX_SKILLS_DIR/agents/openai.yaml"
curl -fsSLL "$QTI_SKILL_URL/agents/codex-cli.md" -o "$CODEX_SKILLS_DIR/agents/codex-cli.md"
curl -fsSLL "$QTI_SKILL_URL/examples/assessment-app-spec.md" -o "$CODEX_SKILLS_DIR/examples/assessment-app-spec.md"
curl -fsSLL "$QTI_SKILL_URL/examples/qa-report-template.md" -o "$CODEX_SKILLS_DIR/examples/qa-report-template.md"
curl -fsSLL "$QTI_SKILL_URL/checks/qti-surface-check.sh" -o "$CODEX_SKILLS_DIR/checks/qti-surface-check.sh"
chmod +x "$CODEX_SKILLS_DIR/checks/qti-surface-check.sh"

Perplexity Computer

Upload the hosted SKILL.md directly as a custom Computer Skill, or upload a small zip whose root contains SKILL.md. Perplexity accepts either a Markdown skill file or a zip with SKILL.md at the root.

export QTI_SKILL_URL="https://platform3-andymontgomery-9773s-projects.vercel.app/qti/1edtech/skill_pack/pack"
mkdir -p /tmp/qti-1edtech-surface/examples
mkdir -p /tmp/qti-1edtech-surface/checks
curl -fsSLL "$QTI_SKILL_URL/SKILL.md" -o /tmp/qti-1edtech-surface/SKILL.md
curl -fsSLL "$QTI_SKILL_URL/examples/assessment-app-spec.md" -o /tmp/qti-1edtech-surface/examples/assessment-app-spec.md
curl -fsSLL "$QTI_SKILL_URL/examples/qa-report-template.md" -o /tmp/qti-1edtech-surface/examples/qa-report-template.md
curl -fsSLL "$QTI_SKILL_URL/checks/qti-surface-check.sh" -o /tmp/qti-1edtech-surface/checks/qti-surface-check.sh
chmod +x /tmp/qti-1edtech-surface/checks/qti-surface-check.sh
(cd /tmp/qti-1edtech-surface && zip -r /tmp/qti-1edtech-surface.zip SKILL.md examples checks)

In Perplexity Computer, open Skills, choose Create skill, choose Upload a skill, then upload either /tmp/qti-1edtech-surface/SKILL.md or /tmp/qti-1edtech-surface.zip.

Pack Layout

This deliverable uses the SKILL.md shape from the Claude Skills benchmark: one manifest with YAML frontmatter, platform-specific adapter notes, and example resources the agent loads only when it needs to produce an app or report.

Path Purpose Required for install
SKILL.md Agent instructions, trigger description, credential rules, leak guard, and live workflow. Yes
agents/openai.yaml OpenAI/Codex-facing display metadata and default prompt. Codex/OpenAI environments
agents/*.md Claude Code, Codex CLI, and Perplexity Computer operator instructions. Target-specific
examples/*.md Concrete assessment-app and QA-report output shapes. When producing deliverables
checks/qti-surface-check.sh Runnable verifier that calls the live surface and compares returned values with the platform's pinned demo answers. Before app/report generation

Credentials

Demo tenant

For cold install validation, the agent mints a short-lived demo token from the canonical implementation URL:

export QTI_BASE_URL="https://platform3-andymontgomery-9773s-projects.vercel.app/qti/1edtech/implementation/api"
DEMO_TOKEN_RESPONSE=$(curl -fsSL -X POST "$QTI_BASE_URL/dev/mint?tenantId=demo")
export TOKEN="$(printf '%s' "$DEMO_TOKEN_RESPONSE" | jq -r '.token')"
export TENANT_ID="$(printf '%s' "$DEMO_TOKEN_RESPONSE" | jq -r '.tenantId')"

Real tenant

For a real tenant, the operator supplies a scoped JWT out of band. TENANT_ID may be the canonical platform.tenant.tenant_id UUID or its tenant_key; the path and JWT claim must resolve to the same tenant. Agents must redact tokens in logs, screenshots, reports, commits, and browser storage exports.

export QTI_BASE_URL="https://platform3-andymontgomery-9773s-projects.vercel.app/qti/1edtech/implementation/api"
export TOKEN="${QTI_REVIEWER_JWT:?operator must provide a scoped QTI token}"
export TENANT_ID="${QTI_REVIEWER_TENANT_ID:?operator must provide the tenant id}"

Canonical Sources

Every instruction in this pack points back to the approved hosted docs or the live service descriptor. If this page disagrees with the canonical docs, the docs win.

Leak Guard

This pack contains no surface-owned logic. It has no QTI parser, schema parser, response-processing engine, scoring engine, hash/dedup implementation, retry ledger, enum table, norms table, or database schema copy.

Allowed

  • Set QTI_BASE_URL to the canonical implementation URL.
  • Mint demo credentials or use an operator-minted real-tenant token.
  • Call the live surface and store returned identifiers.
  • Assert response values returned by the API.
  • Cite the customer website and data dictionary in generated reports.

Not Allowed

  • Infer QTI variable cardinality or scoring locally.
  • Implement idempotency replay or ETag conflict handling outside the API.
  • Copy field dictionaries into a client as business rules.
  • Add local search, filtering, joins, or paging behavior beyond the documented list endpoints.
  • Log JWTs or direct learner identity in evidence.

Workflow Spine

The pack follows the live descriptor and customer quickstart order. Local shell commands only hold returned identifiers so the next API call can use them.

Step Operation Use Canonical reference
1 mintDemoToken Get a public demo JWT, tenant id, candidate reference, and package fixture. endpoint contract
2 ingestContentPackage Import the QTI package through tenant-scoped persistence with an Idempotency-Key. qti.content_package
3-5 listPackages, listArtifacts, listArtifactVersions Use the published collection reads when an app needs catalog lists; keep any cursor opaque. API contracts
6-8 getDeliveryJson, getAuthoringJson, saveAuthoringJson Read delivery-safe and lossless projections; save with the exact If-Match ETag. qti.artifact_version
9 exportXml Export canonical XML from the saved immutable version. XML authority
10-12 startDeliverySession, submitAttempt, getCandidateRuntimeData Freeze delivery JSON, submit a response, and review one pseudonymous candidate runtime record. qti.attempt
13-14 deleteCandidateRuntimeData, runConformance Delete learner runtime rows only, then persist conformance evidence from the configured QTI bundle. qti.conformance_run

Runnable Proof Check

The pack ships a runnable check, separate from the worked example, so a developer can prove the installed agent is asking the platform instead of doing QTI work locally. The check calls only the live surface and writes redacted evidence.

checks/qti-surface-check.sh

It fails unless the platform returns its own pinned demo answers: outcomeState.SCORE is 1, the processing trace is match-correct, XML export is nonempty, runtime-data cleanup returns 204, and conformance passes with zero failed/error assertions. It also verifies that the documented list endpoints return the published items plus nextCursor envelope.

The script does not parse QTI, compute scores, validate XML, replay idempotency, or implement response processing. If a future app needs any of that locally, the pack must report a surface leak rather than adding more client logic.

Worked Example

This copy-paste run exercises the live public demo surface. It writes authoring JSON back unchanged so the agent does not learn a local projection transformation.

set -euo pipefail

export QTI_BASE_URL="https://platform3-andymontgomery-9773s-projects.vercel.app/qti/1edtech/implementation/api"
export BASE_URL="$QTI_BASE_URL"
export RUN_ID="skill-$(date -u +%Y%m%dT%H%M%SZ)-$$"
export WORK_DIR="${TMPDIR:-/tmp}/qti-skill-$RUN_ID"
mkdir -p "$WORK_DIR"

DEMO_TOKEN_RESPONSE=$(curl -fsSL -X POST "$BASE_URL/dev/mint?tenantId=demo")
export TOKEN="$(printf '%s' "$DEMO_TOKEN_RESPONSE" | jq -r '.token')"
export TENANT_ID="$(printf '%s' "$DEMO_TOKEN_RESPONSE" | jq -r '.tenantId')"
export CANDIDATE_REF="$(printf '%s' "$DEMO_TOKEN_RESPONSE" | jq -r '.demoCandidateRef')"
export DEMO_PACKAGE_ZIP_URL="$(printf '%s' "$DEMO_TOKEN_RESPONSE" | jq -r '.demoPackageZipUrl')"

curl -fsSL "$BASE_URL$DEMO_PACKAGE_ZIP_URL" -o "$WORK_DIR/qti-package.zip"

curl -fsSL -X POST "$BASE_URL/tenants/$TENANT_ID/qti/packages" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Idempotency-Key: pkg-upload-$RUN_ID" \
  -H "Content-Type: application/zip" \
  -H "X-QTI-Profile: qti-3.0" \
  --data-binary @"$WORK_DIR/qti-package.zip" \
  -o "$WORK_DIR/qti-ingest.json"

export ARTIFACT_ID="$(jq -r '.artifacts[0].artifactId' "$WORK_DIR/qti-ingest.json")"
export ARTIFACT_VERSION_ID="$(jq -r '.artifacts[0].artifactVersionId' "$WORK_DIR/qti-ingest.json")"

curl -fsSL "$BASE_URL/tenants/$TENANT_ID/qti/packages?limit=5" \
  -H "Authorization: Bearer $TOKEN" \
  -o "$WORK_DIR/qti-packages-list.json"

curl -fsSL "$BASE_URL/tenants/$TENANT_ID/qti/artifacts?limit=5" \
  -H "Authorization: Bearer $TOKEN" \
  -o "$WORK_DIR/qti-artifacts-list.json"

curl -fsSL "$BASE_URL/tenants/$TENANT_ID/qti/artifact-versions?limit=5" \
  -H "Authorization: Bearer $TOKEN" \
  -o "$WORK_DIR/qti-artifact-versions-list.json"

curl -fsSL "$BASE_URL/tenants/$TENANT_ID/qti/artifact-versions/$ARTIFACT_VERSION_ID/delivery-json" \
  -H "Authorization: Bearer $TOKEN" \
  -o "$WORK_DIR/qti-delivery.json"

curl -fsSL -D "$WORK_DIR/qti-authoring-read.headers" \
  "$BASE_URL/tenants/$TENANT_ID/qti/artifacts/$ARTIFACT_ID/authoring-json" \
  -H "Authorization: Bearer $TOKEN" \
  -o "$WORK_DIR/qti-authoring-read.json"

export ETAG="$(awk 'BEGIN{IGNORECASE=1} /^etag:/ {sub(/\r$/, "", $2); print $2}' "$WORK_DIR/qti-authoring-read.headers")"

curl -fsSL -X PUT "$BASE_URL/tenants/$TENANT_ID/qti/artifacts/$ARTIFACT_ID/authoring-json" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -H "If-Match: $ETAG" \
  --data-binary @"$WORK_DIR/qti-authoring-read.json" \
  -o "$WORK_DIR/qti-save.json"

export SAVED_ARTIFACT_VERSION_ID="$(jq -r '.artifactVersionId' "$WORK_DIR/qti-save.json")"

curl -fsSL -D "$WORK_DIR/qti-xml.headers" \
  "$BASE_URL/tenants/$TENANT_ID/qti/artifact-versions/$SAVED_ARTIFACT_VERSION_ID/xml" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Accept: application/xml" \
  -o "$WORK_DIR/qti-export.xml"

curl -fsSL -X POST "$BASE_URL/tenants/$TENANT_ID/qti/delivery-sessions" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d "{\"candidateRef\":\"$CANDIDATE_REF\",\"artifactVersionId\":\"$SAVED_ARTIFACT_VERSION_ID\"}" \
  -o "$WORK_DIR/qti-session.json"

export DELIVERY_SESSION_ID="$(jq -r '.deliverySessionId' "$WORK_DIR/qti-session.json")"

curl -fsSL -X POST "$BASE_URL/tenants/$TENANT_ID/qti/delivery-sessions/$DELIVERY_SESSION_ID/attempts" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d "{\"artifactVersionId\":\"$SAVED_ARTIFACT_VERSION_ID\",\"responses\":{\"RESPONSE\":\"ChoiceA\"}}" \
  -o "$WORK_DIR/qti-attempt.json"

curl -fsSL "$BASE_URL/tenants/$TENANT_ID/qti/candidates/$CANDIDATE_REF/runtime-data" \
  -H "Authorization: Bearer $TOKEN" \
  -o "$WORK_DIR/qti-runtime-data.json"

DELETE_STATUS=$(curl -sS -o "$WORK_DIR/qti-delete-body.txt" -w "%{http_code}" -X DELETE \
  "$BASE_URL/tenants/$TENANT_ID/qti/candidates/$CANDIDATE_REF/runtime-data" \
  -H "Authorization: Bearer $TOKEN")

curl -fsSL -X POST "$BASE_URL/qti/conformance-runs" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  --data '{}' \
  -o "$WORK_DIR/qti-conformance.json"

jq -n \
  --arg packageId "$(jq -r '.packageId' "$WORK_DIR/qti-ingest.json")" \
  --arg artifactId "$ARTIFACT_ID" \
  --arg artifactVersionId "$SAVED_ARTIFACT_VERSION_ID" \
  --arg documentId "$(jq -r '.documentId' "$WORK_DIR/qti-delivery.json")" \
  --arg score "$(jq -r '.outcomeState.SCORE' "$WORK_DIR/qti-attempt.json")" \
  --arg traceEvent "$(jq -r '.processingTrace[0].event // .processingTrace[0].rule' "$WORK_DIR/qti-attempt.json")" \
  --arg sessions "$(jq -r '.deliverySessionCount' "$WORK_DIR/qti-runtime-data.json")" \
  --arg attempts "$(jq -r '.attemptCount' "$WORK_DIR/qti-runtime-data.json")" \
  --arg deleteStatus "$DELETE_STATUS" \
  --arg xmlBytes "$(wc -c < "$WORK_DIR/qti-export.xml" | tr -d ' ')" \
  --arg conformanceStatus "$(jq -r '.status' "$WORK_DIR/qti-conformance.json")" \
  --arg byStatus "$(jq -c '.summary.byStatus' "$WORK_DIR/qti-conformance.json")" \
  '{packageId:$packageId, artifactId:$artifactId, artifactVersionId:$artifactVersionId, documentId:$documentId, score:$score, traceEvent:$traceEvent, runtime:{sessions:$sessions, attempts:$attempts}, deleteStatus:$deleteStatus, xmlBytes:$xmlBytes, conformance:{status:$conformanceStatus, byStatus:($byStatus|fromjson)}}'

Expected Result

The bundled proof check run on 2026-06-11 returned score: "1", traceEvent: "match-correct", one session, one attempt, deleteStatus: "204", nonzero XML bytes, and conformance.status: "passed" with zero failed/error assertions.

The exact identifiers and XML byte count vary per run because the demo fixture is minted with a fresh run id.

Concrete Deliverables

The installed skill must let an agent produce real customer-facing outputs, not just probe transcripts. These templates define the minimum app and report shapes the agent should generate after it calls the live surface.

Assessment app

assessment-app-spec.md defines a small item-bank, delivery-session, results, privacy-cleanup, and conformance evidence app. The app stores QTI domain data only through the API and keeps UI preferences in browser storage.

  • Credential setup panel for demo or real-tenant JWTs.
  • Item bank ingest from the demo ZIP or operator-selected package bytes.
  • Catalog lists from the documented collection endpoints.
  • Delivery preview from getDeliveryJson.
  • Attempt simulator that submits responses and shows API-returned score/evidence.
  • Runtime-data read/delete panel and conformance run summary.

QA report

qa-report-template.md gives the agent a report structure with fields for run id, canonical docs cited, endpoint evidence, redacted credential handling, and unresolved surface leaks.

  • No copied QTI schema tables or scoring rules.
  • Every result value comes from a saved response body.
  • Every field meaning links to the data dictionary.
  • Every API behavior links to the customer website.

If an agent cannot complete these deliverables without writing a local QTI parser, scoring engine, schema table, idempotency ledger, or response-processing rule, the deliverable fails the leak check and should be routed back to the surface, not patched inside the skill.

Agent Output Standard

When an agent uses this pack, its app/report should show the API-returned package, artifact, session, attempt, runtime, delete, and conformance evidence. It should cite canonical docs for field meanings and redact credentials before publishing evidence.

Good Evidence

  • packageId, artifactId, and artifactVersionId from the surface.
  • outcomeState.SCORE and processing trace event from submitAttempt.
  • Candidate runtime counts before deletion and 204 cleanup status.
  • conformanceRunId, status, and assertion counts.

Failure Signal

If a task cannot be completed without local QTI parsing, response scoring, deduping, unpublished search/filtering, or schema tables, the skill has exposed a surface leak. Report the blocked job and route it to the earliest upstream deliverable.