Skill entrypoint
Primary skill entrypoint. Claude, Codex, and Perplexity should all receive this file.
platform3 / qti / 1edtech / 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.
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/.
Primary skill entrypoint. Claude, Codex, and Perplexity should all receive this file.
Machine-readable install metadata and canonical documentation links for the QTI module pack.
Optional UI metadata for environments that read agents/openai.yaml.
Platform-specific operator notes are included for each named install target.
Concrete output shapes the installed agent can produce after calling the surface.
Surface-call verifier. It calls the live API and fails unless the platform returns its pinned demo answers.
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"
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"
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.
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 |
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')"
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}"
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.
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.
QTI_BASE_URL to the canonical implementation URL.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 |
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.
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)}}'
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.
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-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.
getDeliveryJson.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.
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.
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.
packageId, artifactId, and artifactVersionId from the surface.outcomeState.SCORE and processing trace event from submitAttempt.204 cleanup status.conformanceRunId, status, and assertion counts.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.