Run Sample Triage from your own code
Both metered lanes are available over HTTP and each returns one JSON object. The division of labour: the page triages every repo by the skill's ordered rules, sums the kept upkeep against capacity, ranks the shape, reads the freshness contract against the sandbox and gives every archive a successor, then hands it all to the run as facts. The run writes the portfolio strategy or the cleanup plan; the page then holds it to its own facts (streply.js).
Base URL and the response shape
Every endpoint lives under https://api.skillsafe.ai/v1/app-api, and every response has one shape:
{ "ok": true, "data": { ... } }
{ "ok": false, "error": { "code": "...", "message": "..." } }
The token is minted for this app (the guest call takes {"slug":"sample-triage"}), so no slug header is needed afterwards. Send it as Authorization: Bearer YOUR_TOKEN. The run body is the input object, sent bare.
| Status | Code | Meaning |
|---|---|---|
| 400 | validation_error | The body is not a JSON object, or a required key (task, platform, register, facts) is missing. The page always sends all five of task, platform, register, carry, facts; carry is an empty string for strategy. |
| 401 | unauthorized | No token, or a stale one. Mint a guest token or sign in again. |
| 402 | insufficient_credits | The balance is under min_credits. Price with /estimate first. |
| 403 | forbidden | A guest token tried to run: both lanes are metered and need a personal sign-in. |
| 404 | not_found | Unknown job id. |
| 429 | rate_limited | Back off and retry. |
| 5xx | server_error | Transient. Retry with the same Idempotency-Key so a retry never double-bills. |
The task field comes first
Two lanes, chosen by task, in the order the work runs - what the portfolio is, then what changes now:
strategy- the portfolio strategy: the ranked shape, the seven developer jobs served or explained, coverage, the five-term freshness contract, archive rules, the canonical index and measurement.cleanup- the cleanup plan: every row the page did not simply keep, with its verdict, successor, action, banner and who to tell, and the sequence.
A missing or unknown task is answered as strategy. Both lanes send the same five fields. The facts are the page's checks, one row per line, and a line starting "must:" is a rule the reply has to hold. The page's handoff carries the strategy's decisions into carry for the cleanup lane.
The input
| Field | Required | What |
|---|---|---|
task | yes | strategy or cleanup. If it is missing or neither, do strategy and set "task": "strategy". |
platform | yes | the platform, who integrates, the tier-1 and dominant languages, the sandbox, the usage signal, retired versions, capacity and cleanup hours, the SLA, the regulated-flow answer, deadline and policy; "not given" means not given. |
register | yes | every repo with its type, job, languages, pins, owner, CI, usage, tickets, links and the page's verdict and successor, then the kept upkeep. |
carry | send it (empty for strategy) | cleanup task only: the strategy's decisions, when the strategy task ran first; may be empty. |
facts | yes | the page's checks, one line each as area | id | status | evidence, the verdict and the ranked shapes. A line starting "must:" applies to your reply. |
retry_note | no | optional; if present, your previous reply was unusable - follow it. |
The rules the model applies
From the source skill, @samber/sample-app-strategy - the only standard the run applies:
- A sample is an executable promise sized to one developer job, and every artifact carries a standing upkeep cost. Size the portfolio to the hours that keep it green, never to the demand that would fill it.
- Shapes: lean golden path (the default - one flagship quickstart at tier-1 parity, single-concern scenarios in one shared repo, no reference apps), gallery-led (deploy templates and a gallery with a CLI scaffold), reference-app anchored (the starved shape - only when a regulated or multi-service flow drives deals and the capacity still fits). A shape may leave a job unserved on purpose; write "not served" with the condition that would add it.
- Jobs and default artifacts: evaluate without installing - a hosted collection; first call - a tiny quickstart (a flagship multi-language quickstart when tier 1 spans several languages); integrate one workflow - a scenario sample in the shared repo; learn the production pattern - a docs architecture page plus scenarios (a reference app for a regulated or multi-service flow); start a project - a starter template with a deploy button; mobile or device - one app per platform when the mobile SDK is tier 1; coding agents - the canonical index plus an agent guide.
- Coverage: full language parity goes to the flagship quickstart only, across the SDK tier-1 set; scenario samples ship in the two or three languages API traffic shows dominate; new products ship one language first; everything else is community-labelled or absent, and the tier table is published. Upkeep estimates are self-set; label them so. Over capacity, cut languages within a job first, then step down to the default artifact, then across jobs; never cut the last artifact serving a tier-1 SDK's job.
- Triage, first match wins: a leaked credential or vulnerable dependency is fixed or archived this week; a retired SDK major or API version is archived with a redirect, and a language or job left out is community-labelled when green with a community maintainer, archived otherwise; the adoption path is fixed in place (rebuilt when the fix costs more) and never archived for lack of an owner; no accepted owner is archived; a duplicate is merged into the survivor; a single-concern scenario in its own repo is merged into the shared samples repo; red and measurably unused is archived; otherwise kept (
keep) under the freshness contract. Unmeasured usage is never read as zero. A sales or partner link is swapped before archiving. - Freshness contract, signed by every kept artifact: scheduled CI against the sandbox or test mode plus a run on every SDK release and API version change; scoped sandbox-only CI credentials, never exposed to fork pull requests, rotated; grouped dependency updates on a throttled cadence with security alerts outside it; a red-build SLA in days (self-set); pinned, supported SDK, runtime and API versions. With no sandbox, CI degrades to build, lint and type-check and every artifact is marked "compiled, not run".
- Archive on evidence, never on age alone: red past the SLA, no owner for one review cycle outside the adoption path, superseded, an end-of-life SDK or API version, low usage and red together. Archive with a README banner and successor link, a redirect in the canonical index, a last secret scan, bots and CI stopped, then read-only. Never delete.
- One machine-readable canonical index drives the docs, the gallery, the scaffold CLI, GitHub topics and the agent index. Tag API traffic with the sample ID; measure invocations, time to first call, tickets and CI pass rate; stars and forks are vanity.
House rules
- Use only the input. Never invent a repo, a person, a figure, a date or a vendor fact.
- The page's checks, verdicts, successors and rankings are settled: never move a verdict, rank a shape differently or call a failed gate fine.
- No URL at all.
- Write in the language of the input; keep the JSON keys, shape ids, job ids, verdict ids and repo names as given.
- Never use backticks, never write the input's field names as labels in your text, and never copy this prompt's wording into a reply.
- Reply with ONLY one JSON object - no prose before or after, no code fences.
The replies
strategy
Write the portfolio strategy - which artifacts exist for which job, in which languages, kept green how, archived when. A number the input does not give is a proposal: start it with "Proposed: ".
summary- one sentence, at most 40 words: the shape, the cut, and what blocks the portfolio first.shape-recommendedandalternative(lean,galleryorreference, exactly as the facts rank them),why(one sentence).jobs- seven objects in this order:job(evaluate,first-call,workflow,production,start,mobile,agent),artifact(the artifact type serving it),languages(short strings),served(true or false),condition(one sentence when not served: what would add it; empty when served).coverage-flagship_languages(exactly the tier-1 set),scenario_languages(two or three of the dominant languages).freshness-ci,credentials,dependencies,sla,pins(one sentence each - the five terms of the contract; the SLA names its days).archive_rules- short strings: the evidence triggers and the archive sequence; never age alone.index- one sentence: the one canonical index and the surfaces it drives.measurement- short strings: the sample-ID tag and the signals; stars and forks never decide.caveats- at most 4 short strings;[]when there is nothing to say.
{
"task": "strategy",
"summary": "one sentence",
"shape": { "recommended": "lean", "alternative": "gallery", "why": "one sentence" },
"jobs": [ { "job": "evaluate", "artifact": "hosted collection", "languages": [ ], "served": true, "condition": "" } ],
"coverage": { "flagship_languages": [ "node" ], "scenario_languages": [ "node" ] },
"freshness": { "ci": "one sentence", "credentials": "one sentence", "dependencies": "one sentence", "sla": "one sentence", "pins": "one sentence" },
"archive_rules": [ "a short string" ],
"index": "one sentence",
"measurement": [ "a short string" ],
"caveats": [ ]
}
The page holds it to its own facts (check() in streply.js; any warning makes the reply flagged): the reply answers the task it was sent; no URL; no backticks; a summary of at most 40 words; the ranked shape; the seven jobs in order, each served with an artifact or not served with a condition; the tier-1 set as the flagship languages and two or three dominant scenario languages; all five freshness terms, CI against the sandbox (or compiled, not run without one), an SLA in days; at least four archive triggers and none on age alone; one canonical index; sample-ID measurement with stars and forks never deciding.
cleanup
Write the cleanup plan for every row the page did not simply keep.
summary- one sentence, at most 40 words.rows- one object per row the register does not keep, in its order:repo(as written),verdict(secure,archive,community,fix,rebuildormerge, exactly as the register reads it),successor(exactly as the register gives it; empty for fix and rebuild),action(one sentence: what happens - a secure row is fixed or archived this week; a fix names who owns it, escalated when nobody does),banner(for archive, merge and community: the README banner text naming the successor, or that there is none; empty otherwise),notify(who to tell first when the row is linked from sales or partner material; empty otherwise).sequence- short strings in order: scans first, then archives and merges, then adoption-path fixes before the deadline.caveats- at most 4 short strings;[]when there is nothing to say.
{
"task": "cleanup",
"summary": "one sentence",
"rows": [ { "repo": "checkout-legacy", "verdict": "archive", "successor": "checkout-starter", "action": "one sentence", "banner": "a short string", "notify": "" } ],
"sequence": [ "a short string" ],
"caveats": [ ]
}
The page requires: the reply answers the task it was sent; the same text rules; exactly the page's non-kept rows in order with its verdicts and successors; a banner on every archive, merge and community row; no deletion; a scan hit handled this week; the owner of a sales or partner link told first; an action each; a sequence that starts with the scans.
Worked examples
Three runs from the page's free samples (invented platforms): the strategy for a payments samples org over capacity, the cleanup for the same org carrying that strategy, and the strategy for an infrastructure portfolio close to green. Each input is exactly what the page sends; each reply is the saved reply the sample replays, shipped unedited - but the facts and long strings are shortened in this display only, and every cut is listed under the example. The last line is the page's own check of the full reply.
strategy - A payments samples org over capacity
The page read it as: a secret or vulnerability first; shape lean; 93 upkeep hours a quarter kept against 60; repos: accept-a-payment (keep), pennaroo-quickstart-php (archive), samples/subscriptions (keep), samples/disputes (archive), refunds-standalone (merge), webhook-relay (secure), checkout-legacy (archive), checkout-starter (keep), dashboard-demo (archive), payouts-reference (keep), ios-card-payments (keep), pennaroo-postman (keep); 3 of 5 gates; failing checks: secrets, capacity.
Input (register is 2666 characters; the first 1399 are shown; facts is 1873 characters; the first 1397 are shown):
{
"task": "strategy",
"platform": "Platform: Pennaroo\nWho integrates and what samples exist: A payments API; merchants' backend developers integrate checkout, subscriptions and payouts, and mobile apps take card payments in-app. The samples org has grown to twelve repos.\nKind: Payments or fintech; SDK tier-1 languages: Node, Python, Ruby, Java, Go; languages API traffic shows dominate: Node, Python; mobile SDK tier 1: yes\nCI can run against: A sandbox or test mode with test keys; usage signal: CLI or deploy-button invocations; retired SDK majors or API versions: sdk v2\nUpkeep hours a quarter: 60; one-off cleanup hours: 80; red-build SLA days: 7; a regulated or multi-service flow drives deals: no\nDeadline near: no; a standing quarterly policy; as-of: 2026-11-01",
"register": "Register in the order given (repo | type | job | languages | pins | owner | CI | usage | tickets | links | verdict):\n- accept-a-payment | flagship | first-call | node, python, ruby, java, go | pins sdk v5 | owner Ana Ruiz | CI green | usage 4200 | 3 tickets | links docs | KEEP: on the adoption path - green | 15 h/quarter\n- pennaroo-quickstart-php | quickstart | first-call | php | pins sdk v5 | owner none | CI red | usage 120 | 2 tickets | links docs | ARCHIVE -> accept-a-payment: php is outside the coverage\n- samples/subscriptions | scenario | workflow | node, python | pins sdk v5 | owner Jo Mensah | CI green | usage 800 | 9 tickets | links docs | KEEP: on the adoption path - green | 8 h/quarter\n- samples/disputes | scenario | workflow | ruby | pins sdk v5 | owner Kim Park | CI red | usage 3 | 0 tickets | links none | ARCHIVE -> samples/subscriptions: ruby is outside the coverage\n- refunds-standalone | scenario | workflow | python | pins sdk v5 | owner Lee Chan | CI green | usage 300 | 1 ticket | links docs | MERGE -> the shared samples repo: a single-concern scenario in its own repo - one CI pipeline replaces many\n- webhook-relay | scenario | workflow | node | pins sdk v5 | owner Lee Chan | CI green | usage 200 | 0 tickets | links docs | SCAN HIT | SECURE: a secret or vulnerable-dependency hit overrides every other verdict\n- checkout-legacy | template | start | node | pins sdk\n\n[... 1267 more characters cut from this display ...]",
"carry": "",
"facts": "The page's checks (2 fail, 2 warn, 4 pass, 1 n/a); verdict: a secret or vulnerability first:\n- The register | owners | pass | 6 kept artifacts, each with a named person\n- The register | secrets | fail | webhook-relay - fix or archive this week, before any other verdict\n- Coverage and capacity | capacity | fail | 93 hours a quarter kept (self-set estimates) against 60 - cut languages within a job first, then step down to the default artifact, then across jobs from the bottom of the efficiency line\n- Coverage and capacity | shape | na | Lean golden path first, gallery-led as the alternative\n- Coverage and capacity | jobs | warn | served: evaluate, first-call, workflow, production, start, mobile; not served: agent - say so with the condition that would add it\n- Freshness and archives | ci | pass | A sandbox or test mode with test keys\n- Freshness and archives | sla | pass | fix-or-deprecate decision after 7 days (self-set)\n- Freshness and archives | successors | pass | pennaroo-quickstart-php -> accept-a-payment; samples/disputes -> samples/subscriptions; refunds-standalone -> the shared samples repo; checkout-legacy -> checkout-starter; dashboard-demo -> checkout-starter\n- Freshness and archives | links | warn | checkout-legacy - notify that owner and swap the link first\nShapes by the skill's ranking, in order: lean, gallery, reference.\nmust: shape recommended lean, alternative\n\n[... 476 more characters cut from this display ...]"
}
Reply:
{
"task": "strategy",
"summary": "Lean golden path: step the Java payouts reference app down to a docs architecture page plus scenarios, bringing 93 self-set hours toward 53 against 60, after webhook-relay's scan hit is fixed or archived this week.",
"shape": {
"recommended": "lean",
"alternative": "gallery",
"why": "No regulated or multi-service flow drives deals, so one flagship quickstart at tier-1 parity plus scenarios in the shared repo serves the jobs within the quarter's hours."
},
"jobs": [
{
"job": "evaluate",
"artifact": "hosted collection (pennaroo-postman)",
"languages": [],
"served": true,
"condition": ""
},
{
"job": "first-call",
"artifact": "flagship multi-language quickstart (accept-a-payment)",
"languages": [
"node",
"python",
"ruby",
"java",
"go"
],
"served": true,
"condition": ""
},
{
"job": "workflow",
"artifact": "scenario samples in the shared repo (samples/subscriptions, refunds merged in)",
"languages": [
"node",
"python"
],
"served": true,
"condition": ""
},
{
"job": "production",
"artifact": "docs architecture page plus scenarios, stepped down from payouts-reference to fit capacity",
"languages": [
"node",
"python"
],
"served": true,
"condition": ""
},
{
"job": "start",
"artifact": "starter template with a deploy button (checkout-starter)",
"languages": [
"node"
],
"served": true,
"condition": ""
},
{
"job": "mobile",
"artifact": "one app per platform (ios-card-payments)",
"languages": [
"swift"
],
"served": true,
"condition": ""
},
{
"job": "agent",
"artifact": "canonical index plus an agent guide",
"languages": [],
"served": false,
"condition": "Publish the canonical index with an agent guide once tagged traffic shows coding agents making first calls and the hours freed by the step-down cover it."
}
],
"coverage": {
"flagship_languages": [
"node",
"python",
"ruby",
"java",
"go"
],
"scenario_languages": [
"node",
"python"
]
},
"freshness": {
"ci": "Every kept artifact runs scheduled CI against the sandbox with test keys, plus a run on every SDK release and API version change.",
"credentials": "CI uses scoped sandbox-only test keys, never exposed to fork pull requests and rotated on a schedule.",
"dependencies": "Dependency updates arrive grouped on a throttled cadence, while security alerts are handled outside that cadence.",
"sla": "A red build opens an owner ticket on the first red run and forces a fix-or-deprecate decision after 7 days (self-set).",
"pins": "Every artifact pins a supported SDK major (sdk v5), runtime and API version, and nothing pins the retired sdk v2."
},
"archive_rules": [
"Red past the 7-day SLA",
"No owner for one review cycle, outside the adoption path",
"Superseded by a successor",
"Pins an end-of-life SDK or API version, such as sdk v2",
"Low measured usage and red together; unmeasured usage is never read as zero",
"Never archive on age alone",
"Sequence: swap any sales or partner link, README banner with successor link, redirect in the canonical index, last secret scan, bots and CI stopped, then read-only",
"Never delete"
],
"index": "One machine-readable canonical index drives the docs, the gallery, the scaffold CLI, GitHub topics and the agent index.",
"measurement": [
"Tag API traffic with the sample ID",
"Invocations per sample (CLI and deploy-button)",
"Time to first call",
"Tickets per sample",
"CI pass rate",
"Stars and forks are vanity and never decide"
],
"caveats": [
"Upkeep hours are self-set per-type estimates, not measured.",
"Stepping down payouts-reference frees 40 self-set hours; swap its partner link first.",
"payouts-reference usage is not measured, so it is never read as zero.",
"checkout-legacy sits in a sales deck; notify that owner and swap the link before archiving."
]
}
What step 5 prints for it: evaluate served | first-call served | workflow served | production served | start served | mobile served | agent not served
The page's check: kept - nothing to flag
cleanup - A payments samples org over capacity
The input carries the saved strategy's decisions (R.laneForm fills carry from the strategy reply), as the page's handoff does. The rows to change: pennaroo-quickstart-php (archive), samples/disputes (archive), refunds-standalone (merge), webhook-relay (secure), checkout-legacy (archive), dashboard-demo (archive).
Input (register is 2666 characters; the first 1399 are shown; facts is 1850 characters; the first 1380 are shown):
{
"task": "cleanup",
"platform": "Platform: Pennaroo\nWho integrates and what samples exist: A payments API; merchants' backend developers integrate checkout, subscriptions and payouts, and mobile apps take card payments in-app. The samples org has grown to twelve repos.\nKind: Payments or fintech; SDK tier-1 languages: Node, Python, Ruby, Java, Go; languages API traffic shows dominate: Node, Python; mobile SDK tier 1: yes\nCI can run against: A sandbox or test mode with test keys; usage signal: CLI or deploy-button invocations; retired SDK majors or API versions: sdk v2\nUpkeep hours a quarter: 60; one-off cleanup hours: 80; red-build SLA days: 7; a regulated or multi-service flow drives deals: no\nDeadline near: no; a standing quarterly policy; as-of: 2026-11-01",
"register": "Register in the order given (repo | type | job | languages | pins | owner | CI | usage | tickets | links | verdict):\n- accept-a-payment | flagship | first-call | node, python, ruby, java, go | pins sdk v5 | owner Ana Ruiz | CI green | usage 4200 | 3 tickets | links docs | KEEP: on the adoption path - green | 15 h/quarter\n- pennaroo-quickstart-php | quickstart | first-call | php | pins sdk v5 | owner none | CI red | usage 120 | 2 tickets | links docs | ARCHIVE -> accept-a-payment: php is outside the coverage\n- samples/subscriptions | scenario | workflow | node, python | pins sdk v5 | owner Jo Mensah | CI green | usage 800 | 9 tickets | links docs | KEEP: on the adoption path - green | 8 h/quarter\n- samples/disputes | scenario | workflow | ruby | pins sdk v5 | owner Kim Park | CI red | usage 3 | 0 tickets | links none | ARCHIVE -> samples/subscriptions: ruby is outside the coverage\n- refunds-standalone | scenario | workflow | python | pins sdk v5 | owner Lee Chan | CI green | usage 300 | 1 ticket | links docs | MERGE -> the shared samples repo: a single-concern scenario in its own repo - one CI pipeline replaces many\n- webhook-relay | scenario | workflow | node | pins sdk v5 | owner Lee Chan | CI green | usage 200 | 0 tickets | links docs | SCAN HIT | SECURE: a secret or vulnerable-dependency hit overrides every other verdict\n- checkout-legacy | template | start | node | pins sdk\n\n[... 1267 more characters cut from this display ...]",
"carry": "Shape: lean\nJobs: evaluate - hosted collection (pennaroo-postman); first-call - flagship multi-language quickstart (accept-a-payment); workflow - scenario samples in the shared repo (samples/subscriptions, refunds merged in); production - docs architecture page plus scenarios, stepped down from payouts-reference to fit capacity; start - starter template with a deploy button (checkout-starter); mobile - one app per platform (ios-card-payments); agent - not served\nCoverage: flagship node, python, ruby, java, go; scenarios node, python\nFreshness: Every kept artifact runs scheduled CI against the sandbox with test keys, plus a run on every SDK release and API version change. / A red build opens an owner ticket on the first red run and forces a fix-or-deprecate decision after 7 days (self-set).\nIndex: One machine-readable canonical index drives the docs, the gallery, the scaffold CLI, GitHub topics and the agent index.",
"facts": "The page's checks (2 fail, 2 warn, 4 pass, 1 n/a); verdict: a secret or vulnerability first:\n- The register | owners | pass | 6 kept artifacts, each with a named person\n- The register | secrets | fail | webhook-relay - fix or archive this week, before any other verdict\n- Coverage and capacity | capacity | fail | 93 hours a quarter kept (self-set estimates) against 60 - cut languages within a job first, then step down to the default artifact, then across jobs from the bottom of the efficiency line\n- Coverage and capacity | shape | na | Lean golden path first, gallery-led as the alternative\n- Coverage and capacity | jobs | warn | served: evaluate, first-call, workflow, production, start, mobile; not served: agent - say so with the condition that would add it\n- Freshness and archives | ci | pass | A sandbox or test mode with test keys\n- Freshness and archives | sla | pass | fix-or-deprecate decision after 7 days (self-set)\n- Freshness and archives | successors | pass | pennaroo-quickstart-php -> accept-a-payment; samples/disputes -> samples/subscriptions; refunds-standalone -> the shared samples repo; checkout-legacy -> checkout-starter; dashboard-demo -> checkout-starter\n- Freshness and archives | links | warn | checkout-legacy - notify that owner and swap the link first\nShapes by the skill's ranking, in order: lean, gallery, reference.\nmust: rows lists exactly\n\n[... 470 more characters cut from this display ...]"
}
Reply:
{
"task": "cleanup",
"summary": "Secure webhook-relay this week, then archive four repos and merge refunds-standalone into the shared samples repo, swapping the checkout-legacy sales deck link first; nothing is deleted.",
"rows": [
{
"repo": "pennaroo-quickstart-php",
"verdict": "archive",
"successor": "accept-a-payment",
"action": "Archive it because php sits outside the coverage: add the banner and successor link, redirect it in the canonical index to accept-a-payment, run a last secret scan, stop bots and CI, then set it read-only and never delete it.",
"banner": "Archived: PHP is outside Pennaroo's supported languages. Use accept-a-payment, the flagship quickstart, instead.",
"notify": ""
},
{
"repo": "samples/disputes",
"verdict": "archive",
"successor": "samples/subscriptions",
"action": "Kim Park archives it because ruby sits outside the scenario coverage: banner and successor link, a redirect in the canonical index to samples/subscriptions, a last secret scan, bots and CI stopped, then read-only, never deleted.",
"banner": "Archived: Ruby scenarios are no longer maintained. See samples/subscriptions for the maintained workflow samples.",
"notify": ""
},
{
"repo": "refunds-standalone",
"verdict": "merge",
"successor": "the shared samples repo",
"action": "Lee Chan moves the refunds scenario into the shared samples repo under its single CI pipeline, then this repo gets the banner, a redirect in the canonical index, a last secret scan, bots and CI stopped, then goes read-only, never deleted.",
"banner": "Moved: this refunds sample now lives in the shared samples repo. This copy is read-only.",
"notify": ""
},
{
"repo": "webhook-relay",
"verdict": "secure",
"successor": "",
"action": "Lee Chan fixes the scan hit this week by rotating the exposed credential or patching the vulnerable dependency and re-running the secret scan, or archives the repo this week if the fix cannot land.",
"banner": "",
"notify": ""
},
{
"repo": "checkout-legacy",
"verdict": "archive",
"successor": "checkout-starter",
"action": "Once the sales deck points at checkout-starter, Sam Ito archives it for pinning the retired sdk v2: banner and successor link, a redirect in the canonical index, a last secret scan, bots and CI stopped, then read-only, never deleted.",
"banner": "Archived: this template pins the retired SDK v2. Start from checkout-starter instead.",
"notify": "Tell Sam Ito first and swap the sales deck link to checkout-starter before archiving."
},
{
"repo": "dashboard-demo",
"verdict": "archive",
"successor": "checkout-starter",
"action": "Archive it because no named owner accepted it: banner and successor link, a redirect in the canonical index to checkout-starter, a last secret scan, bots and CI stopped, then read-only, never deleted.",
"banner": "Archived: this demo has no maintainer. Start from checkout-starter instead.",
"notify": ""
}
],
"sequence": [
"Secure webhook-relay this week and run a last secret scan on every repo about to be archived or merged.",
"Swap the checkout-legacy sales deck link to checkout-starter.",
"Archive pennaroo-quickstart-php, samples/disputes, checkout-legacy and dashboard-demo, and merge refunds-standalone into the shared samples repo.",
"No adoption-path fix is open; payouts-reference stays kept while red, with an owner ticket for Mo Haddad under the 7-day SLA."
],
"caveats": [
"Kept upkeep is 93 hours a quarter against 60, so the cleanup alone does not close the capacity gap.",
"payouts-reference usage is not measured, which is never read as zero.",
"Upkeep hours are self-set estimates."
]
}
What step 5 prints for it: pennaroo-quickstart-php archive | samples/disputes archive | refunds-standalone merge | webhook-relay secure | checkout-legacy archive | dashboard-demo archive
The page's check: kept - nothing to flag
strategy - An infrastructure portfolio close to green
The page read it as: a portfolio the team can keep green; shape lean; 18 upkeep hours a quarter kept against 40; repos: grindleaf-quickstart (keep), samples/networks (keep), samples/networks-copy (merge), grindleaf-starter (keep), terraform-demo-2024 (archive); 5 of 5 gates; failing checks: none.
Input (facts is 1564 characters; the first 1396 are shown):
{
"task": "strategy",
"platform": "Platform: Grindleaf\nWho integrates and what samples exist: An infrastructure provisioning API; platform engineers start from a starter template and the Go quickstart.\nKind: Infrastructure; SDK tier-1 languages: Go, Python; languages API traffic shows dominate: Go, Python; mobile SDK tier 1: no\nCI can run against: A sandbox or test mode with test keys; usage signal: CLI or deploy-button invocations; retired SDK majors or API versions: api 2024-01\nUpkeep hours a quarter: 40; one-off cleanup hours: 20; red-build SLA days: 5; a regulated or multi-service flow drives deals: no\nDeadline near: no; a standing quarterly policy; as-of: 2026-11-01",
"register": "Register in the order given (repo | type | job | languages | pins | owner | CI | usage | tickets | links | verdict):\n- grindleaf-quickstart | flagship | first-call | go, python | pins api 2026-03 | owner Nia Cole | CI green | usage 2100 | 1 ticket | links docs | KEEP: on the adoption path - green | 6 h/quarter\n- samples/networks | scenario | workflow | go | pins api 2026-03 | owner Bo Lind | CI green | usage 400 | 3 tickets | links docs | KEEP: on the adoption path - green | 4 h/quarter\n- samples/networks-copy | scenario | workflow | go | pins api 2026-03 | owner Bo Lind | CI green | usage 30 | 0 tickets | links none | MERGE -> samples/networks: duplicates samples/networks for the same job and languages\n- grindleaf-starter | template | start | go | pins api 2026-03 | owner Nia Cole | CI green | usage 700 | 0 tickets | links docs | KEEP: green | 8 h/quarter\n- terraform-demo-2024 | template | start | go | pins api 2024-01 | owner Bo Lind | CI red | usage 20 | 0 tickets | links partner | ARCHIVE -> grindleaf-starter: pins a retired SDK major or API version (api 2024-01)\nKept upkeep: 18 hours a quarter against 40 (self-set per-type estimates: reference 40, mobile 20, playground 20, flagship 3 per language, template 8 per language, scenario 4 per language, quickstart 2 per language, collection 2).",
"carry": "",
"facts": "The page's checks (0 fail, 2 warn, 6 pass, 1 n/a); verdict: a portfolio the team can keep green:\n- The register | owners | pass | 3 kept artifacts, each with a named person\n- The register | secrets | pass | every repo scanned clean\n- Coverage and capacity | capacity | pass | 18 hours a quarter kept (self-set estimates) against 40\n- Coverage and capacity | shape | na | Lean golden path first, gallery-led as the alternative\n- Coverage and capacity | jobs | warn | served: first-call, workflow, start; not served: evaluate, production, mobile, agent - say so with the condition that would add it\n- Freshness and archives | ci | pass | A sandbox or test mode with test keys\n- Freshness and archives | sla | pass | fix-or-deprecate decision after 5 days (self-set)\n- Freshness and archives | successors | pass | samples/networks-copy -> samples/networks; terraform-demo-2024 -> grindleaf-starter\n- Freshness and archives | links | warn | terraform-demo-2024 - notify that owner and swap the link first\nShapes by the skill's ranking, in order: lean, gallery, reference.\nmust: shape recommended lean, alternative gallery; jobs lists the seven jobs in this order: evaluate, first-call, workflow, production, start, mobile, agent, each served or not served with the condition that would add it; flagship languages go, python; scenario languages two or three of go, python; the freshness contract's five\n\n[... 168 more characters cut from this display ...]"
}
Reply:
{
"task": "strategy",
"summary": "Lean golden path at 18 of 40 self-set hours, nothing to cut; the first blocker is terraform-demo-2024, whose partner link must be swapped before it is archived to grindleaf-starter.",
"shape": {
"recommended": "lean",
"alternative": "gallery",
"why": "Platform engineers start from the Go quickstart and a starter template, and no regulated or multi-service flow drives deals, so a flagship plus shared scenarios fits."
},
"jobs": [
{
"job": "evaluate",
"artifact": "hosted collection",
"languages": [],
"served": false,
"condition": "Add a hosted collection against the sandbox once tagged traffic shows engineers evaluating before they install."
},
{
"job": "first-call",
"artifact": "flagship multi-language quickstart (grindleaf-quickstart)",
"languages": [
"go",
"python"
],
"served": true,
"condition": ""
},
{
"job": "workflow",
"artifact": "scenario samples in the shared repo (samples/networks, copy merged in)",
"languages": [
"go"
],
"served": true,
"condition": ""
},
{
"job": "production",
"artifact": "docs architecture page plus scenarios",
"languages": [],
"served": false,
"condition": "Add a docs architecture page backed by the shared scenarios once tickets or tagged traffic show engineers asking for the production pattern."
},
{
"job": "start",
"artifact": "starter template with a deploy button (grindleaf-starter)",
"languages": [
"go"
],
"served": true,
"condition": ""
},
{
"job": "mobile",
"artifact": "one app per platform",
"languages": [],
"served": false,
"condition": "Add one app per platform only when the mobile SDK reaches tier 1."
},
{
"job": "agent",
"artifact": "canonical index plus an agent guide",
"languages": [],
"served": false,
"condition": "Publish the canonical index with an agent guide once tagged traffic shows coding agents making first calls."
}
],
"coverage": {
"flagship_languages": [
"go",
"python"
],
"scenario_languages": [
"go",
"python"
]
},
"freshness": {
"ci": "Every kept artifact runs scheduled CI against the sandbox with test keys, plus a run on every SDK release and API version change.",
"credentials": "CI uses scoped sandbox-only test keys, never exposed to fork pull requests and rotated on a schedule.",
"dependencies": "Dependency updates arrive grouped on a throttled cadence, while security alerts are handled outside that cadence.",
"sla": "A red build opens an owner ticket on the first red run and forces a fix-or-deprecate decision after 5 days (self-set).",
"pins": "Every artifact pins a supported SDK, runtime and API version (api 2026-03), and nothing pins the retired api 2024-01."
},
"archive_rules": [
"Red past the 5-day SLA",
"No owner for one review cycle, outside the adoption path",
"Superseded or duplicated by a survivor",
"Pins an end-of-life SDK or API version, such as api 2024-01",
"Low measured usage and red together; unmeasured usage is never read as zero",
"Never archive on age alone",
"Sequence: swap any sales or partner link, README banner with successor link, redirect in the canonical index, last secret scan, bots and CI stopped, then read-only",
"Never delete"
],
"index": "One machine-readable canonical index drives the docs, the gallery, the scaffold CLI, GitHub topics and the agent index.",
"measurement": [
"Tag API traffic with the sample ID",
"Invocations per sample (CLI and deploy-button)",
"Time to first call",
"Tickets per sample",
"CI pass rate",
"Stars and forks are vanity and never decide"
],
"caveats": [
"Upkeep hours are self-set per-type estimates, not measured.",
"Scenario samples ship in Go only today; Python is the second dominant language to add within the spare hours.",
"terraform-demo-2024 sits in partner material; notify that owner and swap the link before archiving."
]
}
What step 5 prints for it: evaluate not served | first-call served | workflow served | production not served | start served | mobile not served | agent not served
The page's check: kept - nothing to flag
Build the input yourself
Load the engine in node (global.window = global; require("./stkit.js"); require("./streply.js")), then:
const K = SampleKit, R = SampleReply;
const f = K.form(Object.assign(K.blank(), { name: "Acme", tier1: "Node, Python", toplangs: "Node", retired: "sdk v1", hours: "40", sandbox: "sandbox",
register: "quickstart | quickstart | first call | Node | sdk v3 | Ann | green | 900 | 2 | docs | clean\nold-demo | template | start | Node | sdk v1 | Bo | red | 10 | 0 | sales | clean" }));
// f.regRows (each repo with its verdict, why, successor and upkeep), f.need, f.rank (the ranked shapes), f.gate
// K.registerMd(f), K.indexJson(f) - the register table and the canonical index
const b = K.buildInput(f, "strategy"); // or "cleanup" (R.laneForm(form, "cleanup", strategy) fills carry)
const wire = K.toWire(b.input); // POST this to /estimate and /run
// then: const reply = R.normalize(R.parseReply(job.output.output), "strategy");
// R.check(reply, b) // {status: kept or flagged, notes}
Get a token for the steps below from /tokens.html.
Step 1 — a tiny client helper
One function that sends JSON and unwraps the response. Paste a personal token from /tokens.html, or leave it empty and mint a guest token in step 2.
BASE="https://api.skillsafe.ai/v1/app-api"
TOKEN="YOUR_TOKEN" # from https://sample-triage.skillsafe.ai/tokens.html
api() { # api METHOD PATH [JSON-BODY]
curl -s -X "$1" "$BASE$2" -H "Content-Type: application/json" \
${TOKEN:+-H "Authorization: Bearer $TOKEN"} ${3:+--data "$3"}
}
import json, urllib.request
BASE = "https://api.skillsafe.ai/v1/app-api"
TOKEN = "YOUR_TOKEN" # from https://sample-triage.skillsafe.ai/tokens.html
def api(method, path, body=None, headers=None):
req = urllib.request.Request(BASE + path, method=method,
data=None if body is None else json.dumps(body).encode(),
headers={"Content-Type": "application/json", **({"Authorization": "Bearer " + TOKEN} if TOKEN else {}), **(headers or {})})
with urllib.request.urlopen(req) as r:
env = json.load(r)
if env.get("error"):
raise RuntimeError(env["error"])
return env["data"]
// sample-triage.mjs -- Node 18+ (global fetch)
const BASE = "https://api.skillsafe.ai/v1/app-api";
let TOKEN = "YOUR_TOKEN"; // from https://sample-triage.skillsafe.ai/tokens.html
async function api(method, path, body, headers = {}) {
const r = await fetch(BASE + path, { method, headers: { "Content-Type": "application/json",
...(TOKEN ? { Authorization: "Bearer " + TOKEN } : {}), ...headers },
body: body === undefined ? undefined : JSON.stringify(body) });
const env = await r.json();
if (env.error) throw new Error(env.error.code + ": " + env.error.message);
return env.data;
}
package main
import ("bytes"; "encoding/json"; "fmt"; "net/http")
const base = "https://api.skillsafe.ai/v1/app-api"
var token = "YOUR_TOKEN" // from https://sample-triage.skillsafe.ai/tokens.html
func api(method, path string, body any, headers map[string]string) (map[string]any, error) {
var buf bytes.Buffer
if body != nil { json.NewEncoder(&buf).Encode(body) }
req, _ := http.NewRequest(method, base+path, &buf)
req.Header.Set("Content-Type", "application/json")
if token != "" { req.Header.Set("Authorization", "Bearer "+token) }
for k, v := range headers { req.Header.Set(k, v) }
res, err := http.DefaultClient.Do(req)
if err != nil { return nil, err }
defer res.Body.Close()
var env struct { Data map[string]any; Error map[string]any }
json.NewDecoder(res.Body).Decode(&env)
if env.Error != nil { return nil, fmt.Errorf("%v", env.Error) }
return env.Data, nil
}
// Sample Triage.java -- Java 17+, with com.fasterxml.jackson.core:jackson-databind
import com.fasterxml.jackson.databind.*;
import java.net.URI; import java.net.http.*; import java.util.*;
public class Sample Triage {
static final String BASE = "https://api.skillsafe.ai/v1/app-api";
static String token = "YOUR_TOKEN"; // from https://sample-triage.skillsafe.ai/tokens.html
static final ObjectMapper M = new ObjectMapper();
static final HttpClient C = HttpClient.newHttpClient();
static JsonNode api(String method, String path, Object body, Map<String, String> headers) throws Exception {
var b = HttpRequest.newBuilder(URI.create(BASE + path)).header("Content-Type", "application/json");
if (!token.isEmpty()) b.header("Authorization", "Bearer " + token);
if (headers != null) headers.forEach(b::header);
b.method(method, body == null ? HttpRequest.BodyPublishers.noBody() : HttpRequest.BodyPublishers.ofString(M.writeValueAsString(body)));
JsonNode env = M.readTree(C.send(b.build(), HttpResponse.BodyHandlers.ofString()).body());
if (env.hasNonNull("error")) throw new RuntimeException(env.get("error").toString());
return env.get("data");
}
}
require "json"
require "net/http"
BASE = "https://api.skillsafe.ai/v1/app-api"
TOKEN = "YOUR_TOKEN" # from https://sample-triage.skillsafe.ai/tokens.html
def api(method, path, body = nil, headers = {})
uri = URI(BASE + path)
req = Net::HTTPGenericRequest.new(method, !body.nil?, true, uri.request_uri,
{ "Content-Type" => "application/json" }.merge(TOKEN.empty? ? {} : { "Authorization" => "Bearer #{TOKEN}" }).merge(headers))
req.body = JSON.generate(body) unless body.nil?
env = JSON.parse(Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |h| h.request(req) }.body)
raise env["error"].to_s if env["error"]
env["data"]
end
<?php
const BASE = "https://api.skillsafe.ai/v1/app-api";
$TOKEN = "YOUR_TOKEN"; // from https://sample-triage.skillsafe.ai/tokens.html
function api(string $method, string $path, $body = null, array $headers = []) {
global $TOKEN;
$h = ["Content-Type: application/json"];
if ($TOKEN) $h[] = "Authorization: Bearer $TOKEN";
foreach ($headers as $k => $v) $h[] = "$k: $v";
$ch = curl_init(BASE . $path);
curl_setopt_array($ch, [CURLOPT_CUSTOMREQUEST => $method, CURLOPT_HTTPHEADER => $h, CURLOPT_RETURNTRANSFER => true]);
if ($body !== null) curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($body));
$env = json_decode(curl_exec($ch), true);
if (!empty($env["error"])) throw new Exception(json_encode($env["error"]));
return $env["data"];
}
// Program.cs -- .NET 6+ console app
using System.Net.Http.Json; using System.Text.Json; using System.Text.Json.Nodes;
static class Api {
public const string Base = "https://api.skillsafe.ai/v1/app-api";
public static string Token = "YOUR_TOKEN"; // from https://sample-triage.skillsafe.ai/tokens.html
static readonly HttpClient C = new();
public static async Task<JsonNode?> Call(string method, string path, object? body = null, Dictionary<string, string>? headers = null) {
var req = new HttpRequestMessage(new HttpMethod(method), Base + path);
if (Token != "") req.Headers.Add("Authorization", "Bearer " + Token);
if (headers != null) foreach (var h in headers) req.Headers.Add(h.Key, h.Value);
if (body != null) req.Content = JsonContent.Create(body);
var env = JsonNode.Parse(await (await C.SendAsync(req)).Content.ReadAsStringAsync())!;
if (env["error"] is JsonNode e && e.GetValueKind() != JsonValueKind.Null) throw new Exception(e.ToJsonString());
return env["data"];
}
}
Step 2 — a token
A guest token is enough for /me and /estimate. A run is metered, so it needs a personal token: sign in on /tokens.html and copy it from there.
# a guest token (enough for /me and /estimate); a run needs your personal token from /tokens.html
TOKEN=$(curl -s -X POST "$BASE/guest" -H "Content-Type: application/json" -d '{"slug":"sample-triage"}' | jq -r '.data.token')
# a guest token (enough for /me and /estimate); a run needs your personal token from /tokens.html
TOKEN = ""
TOKEN = api("POST", "/guest", {"slug": "sample-triage"})["token"]
// a guest token (enough for /me and /estimate); a run needs your personal token from /tokens.html
TOKEN = "";
TOKEN = (await api("POST", "/guest", { slug: "sample-triage" })).token;
// inside main: a guest token (enough for /me and /estimate); a run needs your personal token
token = ""
g, err := api("POST", "/guest", map[string]string{"slug": "sample-triage"}, nil)
if err != nil { panic(err) }
token = g["token"].(string)
// inside main: a guest token (enough for /me and /estimate); a run needs your personal token
token = "";
token = api("POST", "/guest", Map.of("slug", "sample-triage"), null).get("token").asText();
# a guest token (enough for /me and /estimate); a run needs your personal token from /tokens.html
TOKEN.replace("")
TOKEN.replace(api("POST", "/guest", { slug: "sample-triage" })["token"])
// a guest token (enough for /me and /estimate); a run needs your personal token from /tokens.html
$TOKEN = "";
$TOKEN = api("POST", "/guest", ["slug" => "sample-triage"])["token"];
// a guest token (enough for /me and /estimate); a run needs your personal token from /tokens.html
Api.Token = "";
Api.Token = (await Api.Call("POST", "/guest", new { slug = "sample-triage" }))!["token"]!.GetValue<string>();
Step 3 — GET /me
Who the token is (subject_type: user or guest) and the credit balance.
api GET /me | jq '.data | {subject_type, credits}'
me = api("GET", "/me")
print(me["subject_type"], me.get("credits"))
const me = await api("GET", "/me");
console.log(me.subject_type, me.credits);
me, _ := api("GET", "/me", nil, nil)
fmt.Println(me["subject_type"], me["credits"])
JsonNode me = api("GET", "/me", null, null);
System.out.println(me.get("subject_type") + " " + me.get("credits"));
me = api("GET", "/me")
puts [me["subject_type"], me["credits"]].inspect
$me = api("GET", "/me");
echo $me["subject_type"], " ", $me["credits"] ?? "", "\n";
var me = await Api.Call("GET", "/me");
Console.WriteLine($"{me!["subject_type"]} {me["credits"]}");
Step 4 — POST /estimate
Free. The body is the run input itself, the bare object (never wrapped in {"input": ...}). hold_credits is a reservation, not the price; min_credits is what the balance must cover; model_alias reads gpt-terra.
# input.json: the run input object - {"task": "strategy", "platform": "Platform: Pennaroo
Who integrates and what samples exist: ...", "register": "Register in the order given (repo | type | job | ...):
- accept-a-payment | flagship | first-call | ... | KEEP: ...", "carry": "", "facts": "The page's checks (...); verdict: a secret or vulnerability first:
- The register | owners | pass | ..."}
# - see the input table
api POST /estimate "$(cat input.json)" | jq '.data | {hold_credits, min_credits, model, model_alias, markup_bps, warnings}'
inp = json.load(open("input.json")) # {"task": "strategy", "platform": "Platform: Pennaroo
Who integrates and what samples exist: ...", "register": "Register in the order given (repo | type | job | ...):
- accept-a-payment | flagship | first-call | ... | KEEP: ...", "carry": "", "facts": "The page's checks (...); verdict: a secret or vulnerability first:
- The register | owners | pass | ..."}
# - see the input table
est = api("POST", "/estimate", inp)
print(est["hold_credits"], est["min_credits"], est["model"], est.get("warnings"))
import { readFileSync } from "node:fs";
const input = JSON.parse(readFileSync("input.json", "utf8")); // {task: "strategy" or "cleanup", platform, register, carry, facts} (see the input table)
const est = await api("POST", "/estimate", input);
console.log(est.hold_credits, est.min_credits, est.model, est.warnings);
raw, _ := os.ReadFile("input.json") // add "os" to the imports
var input map[string]any
json.Unmarshal(raw, &input)
est, _ := api("POST", "/estimate", input, nil)
fmt.Println(est["hold_credits"], est["min_credits"], est["model"])
Map<String, Object> input = M.readValue(new java.io.File("input.json"), Map.class);
JsonNode est = api("POST", "/estimate", input, null);
System.out.println(est.get("hold_credits") + " " + est.get("min_credits") + " " + est.get("model"));
input = JSON.parse(File.read("input.json"))
est = api("POST", "/estimate", input)
puts [est["hold_credits"], est["min_credits"], est["model"]].inspect
$input = json_decode(file_get_contents("input.json"), true);
$est = api("POST", "/estimate", $input);
echo $est["hold_credits"], " ", $est["min_credits"], " ", $est["model"], "\n";
var input = JsonNode.Parse(File.ReadAllText("input.json"))!;
var est = await Api.Call("POST", "/estimate", input);
Console.WriteLine($"{est!["hold_credits"]} {est["min_credits"]} {est["model"]}");
Step 5 — POST /run, then poll the job
Send an Idempotency-Key built from the lane and the input: a retry with the same key never bills twice. The page uses sample-triage:<task>:<inputHash>:a<attempt> - the lane is part of the key, retry_note is left out of the hash - and its one automatic reformat retry is a new attempt number, named in the page as one extra run. A strategy reply carries summary, shape, jobs, coverage, freshness, archive_rules, index, measurement and caveats; a cleanup reply carries summary, rows, sequence and caveats. The code below prints the gist of either: each job and whether it is served, or each row and its verdict.
KEY="sample-triage:$(jq -r .task input.json):$(shasum -a 256 input.json | cut -c1-16):a1"
JOB=$(curl -s -X POST "$BASE/run" -H "Content-Type: application/json" -H "Authorization: Bearer $TOKEN" \
-H "Idempotency-Key: $KEY" --data @input.json | jq -r '.data.job_id')
while :; do
J=$(api GET "/jobs/$JOB"); S=$(echo "$J" | jq -r '.data.status')
case "$S" in succeeded|failed|cancelled) break;; esac; sleep 2
done
echo "$J" | jq -r '.data.output.output' > reply.json # one JSON object, the reply contract
echo "$J" | jq '.data | {charged_credits, truncated}'
jq -r 'if .task == "cleanup" then (.rows[] | "\(.repo): \(.verdict)")
else (.jobs[] | "\(.job): \(.served)") end' reply.json
import hashlib, time
key = "sample-triage:" + inp["task"] + ":" + hashlib.sha256(json.dumps(inp).encode()).hexdigest()[:16] + ":a1"
job = api("POST", "/run", inp, {"Idempotency-Key": key})
while True:
j = api("GET", "/jobs/" + job["job_id"])
if j["status"] in ("succeeded", "failed", "cancelled"):
break
time.sleep(2)
reply = json.loads(j["output"]["output"]) # the reply as one JSON object
print(j["charged_credits"], j.get("truncated"), reply["task"])
if reply["task"] == "cleanup":
for x in reply["rows"]:
print(x["repo"], x["verdict"])
else:
for x in reply["jobs"]:
print(x["job"], x["served"])
import { createHash } from "node:crypto";
const key = `sample-triage:${input.task}:${createHash("sha256").update(JSON.stringify(input)).digest("hex").slice(0, 16)}:a1`;
const job = await api("POST", "/run", input, { "Idempotency-Key": key });
let j;
do { await new Promise((r) => setTimeout(r, 2000)); j = await api("GET", "/jobs/" + job.job_id); }
while (!["succeeded", "failed", "cancelled"].includes(j.status));
const reply = JSON.parse(j.output.output);
console.log(j.charged_credits, j.truncated, reply.task);
if (reply.task === "cleanup") { for (const x of reply.rows) console.log(x.repo, x.verdict); }
else { for (const x of reply.jobs) console.log(x.job, x.served); }
sum := sha256.Sum256(raw) // add "crypto/sha256", "encoding/hex", "time" to the imports
key := "sample-triage:" + input["task"].(string) + ":" + hex.EncodeToString(sum[:8]) + ":a1"
job, _ := api("POST", "/run", input, map[string]string{"Idempotency-Key": key})
var j map[string]any
for {
time.Sleep(2 * time.Second)
j, _ = api("GET", "/jobs/"+job["job_id"].(string), nil, nil)
if s := j["status"]; s == "succeeded" || s == "failed" || s == "cancelled" { break }
}
var reply map[string]any
json.Unmarshal([]byte(j["output"].(map[string]any)["output"].(string)), &reply)
fmt.Println(j["charged_credits"], j["truncated"], reply["task"])
key, name, field := "jobs", "job", "served"
if reply["task"] == "cleanup" {
key, name, field = "rows", "repo", "verdict"
}
ls, _ := reply[key].([]any)
for _, x := range ls {
xx := x.(map[string]any)
fmt.Println(xx[name], xx[field])
}
var digest = java.security.MessageDigest.getInstance("SHA-256").digest(M.writeValueAsBytes(input));
String key = "sample-triage:" + input.get("task") + ":" + HexFormat.of().formatHex(digest, 0, 8) + ":a1";
JsonNode job = api("POST", "/run", input, Map.of("Idempotency-Key", key)), j;
do { Thread.sleep(2000); j = api("GET", "/jobs/" + job.get("job_id").asText(), null, null); }
while (!Set.of("succeeded", "failed", "cancelled").contains(j.get("status").asText()));
JsonNode reply = M.readTree(j.get("output").get("output").asText());
System.out.println(j.get("charged_credits") + " " + reply.path("task").asText());
if (reply.path("task").asText().equals("cleanup")) {
for (JsonNode x : reply.path("rows")) System.out.println(x.path("repo").asText() + " " + x.path("verdict").asText());
} else {
for (JsonNode x : reply.path("jobs")) System.out.println(x.path("job").asText() + " " + x.path("served").asBoolean());
}
require "digest"
key = "sample-triage:#{input["task"]}:#{Digest::SHA256.hexdigest(JSON.generate(input))[0, 16]}:a1"
job = api("POST", "/run", input, { "Idempotency-Key" => key })
j = nil
loop do
sleep 2
j = api("GET", "/jobs/#{job["job_id"]}")
break if %w[succeeded failed cancelled].include?(j["status"])
end
reply = JSON.parse(j["output"]["output"])
puts [j["charged_credits"], j["truncated"], reply["task"]].inspect
if reply["task"] == "cleanup"
reply["rows"].each { |x| puts "#{x["repo"]} #{x["verdict"]}" }
else
reply["jobs"].each { |x| puts "#{x["job"]} #{x["served"]}" }
end
$key = "sample-triage:" . $input["task"] . ":" . substr(hash("sha256", json_encode($input)), 0, 16) . ":a1";
$job = api("POST", "/run", $input, ["Idempotency-Key" => $key]);
do { sleep(2); $j = api("GET", "/jobs/" . $job["job_id"]); }
while (!in_array($j["status"], ["succeeded", "failed", "cancelled"]));
$reply = json_decode($j["output"]["output"], true);
echo $j["charged_credits"], " ", $reply["task"], "\n";
if ($reply["task"] === "cleanup") { foreach ($reply["rows"] as $x) echo $x["repo"], " ", $x["verdict"], "\n"; }
else { foreach ($reply["jobs"] as $x) echo $x["job"], " ", $x["served"] ? "served" : "not served", "\n"; }
var digest = System.Security.Cryptography.SHA256.HashData(System.Text.Encoding.UTF8.GetBytes(input.ToJsonString()));
var key = $"sample-triage:{input["task"]}:{Convert.ToHexString(digest, 0, 8).ToLowerInvariant()}:a1";
var job = await Api.Call("POST", "/run", input, new() { ["Idempotency-Key"] = key });
JsonNode j;
do { await Task.Delay(2000); j = (await Api.Call("GET", "/jobs/" + job!["job_id"]))!; }
while (!new[] { "succeeded", "failed", "cancelled" }.Contains(j["status"]!.GetValue<string>()));
var reply = JsonNode.Parse(j["output"]!["output"]!.GetValue<string>())!;
var task = (string?)reply["task"];
Console.WriteLine($"{j["charged_credits"]} {task}");
if (task == "cleanup") { foreach (var x in reply["rows"]!.AsArray()) Console.WriteLine($"{x!["repo"]} {x["verdict"]}"); }
else { foreach (var x in reply["jobs"]!.AsArray()) Console.WriteLine($"{x!["job"]} {x["served"]}"); }
Step 6 — POST /run-stream
The same run as Server-Sent Events, with the same input.json. Browsers receive progress ticks rather than text deltas; the final done event carries the reply.
# Server-Sent Events: tick/delta events while the job runs, then a final event with the job
curl -N -s -X POST "$BASE/run-stream" -H "Content-Type: application/json" -H "Authorization: Bearer $TOKEN" \
-H "Idempotency-Key: sample-triage:$(jq -r .task input.json):$(shasum -a 256 input.json | cut -c1-16):a1" --data @input.json
rev = json.load(open("input.json")) # the same run input as input.json: {"task": "strategy", "platform": "Platform: Pennaroo
Who integrates and what samples exist: ...", "register": "Register in the order given (repo | type | job | ...):
- accept-a-payment | flagship | first-call | ... | KEEP: ...", "carry": "", "facts": "The page's checks (...); verdict: a secret or vulnerability first:
- The register | owners | pass | ..."}
# - see the input table
req = urllib.request.Request(BASE + "/run-stream", method="POST", data=json.dumps(rev).encode(),
headers={"Content-Type": "application/json", "Authorization": "Bearer " + TOKEN,
"Idempotency-Key": "sample-triage:" + rev["task"] + ":" + hashlib.sha256(json.dumps(rev).encode()).hexdigest()[:16] + ":a1"})
with urllib.request.urlopen(req) as r:
for line in r:
line = line.decode().strip()
if line.startswith("data:"):
print(line[5:].strip()[:200])
const rev = JSON.parse(readFileSync("input.json", "utf8"));
const r = await fetch(BASE + "/run-stream", { method: "POST", body: JSON.stringify(rev), headers: {
"Content-Type": "application/json", Authorization: "Bearer " + TOKEN,
"Idempotency-Key": `sample-triage:${rev.task}:${createHash("sha256").update(JSON.stringify(rev)).digest("hex").slice(0, 16)}:a1` } });
const dec = new TextDecoder();
for await (const chunk of r.body) for (const line of dec.decode(chunk).split("\n"))
if (line.startsWith("data:")) console.log(line.slice(5).trim().slice(0, 200));
rraw, _ := os.ReadFile("input.json") // add "bufio", "strings" to the imports
rs := sha256.Sum256(rraw)
req, _ := http.NewRequest("POST", base+"/run-stream", bytes.NewReader(rraw))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", "Bearer "+token)
req.Header.Set("Idempotency-Key", "sample-triage:"+input["task"].(string)+":"+hex.EncodeToString(rs[:8])+":a1")
res, _ := http.DefaultClient.Do(req)
sc := bufio.NewScanner(res.Body)
for sc.Scan() { if l := sc.Text(); strings.HasPrefix(l, "data:") { fmt.Println(l[5:]) } }
String rev = java.nio.file.Files.readString(java.nio.file.Path.of("input.json"));
var rd = java.security.MessageDigest.getInstance("SHA-256").digest(rev.getBytes());
var sreq = HttpRequest.newBuilder(URI.create(BASE + "/run-stream")).header("Content-Type", "application/json")
.header("Authorization", "Bearer " + token).header("Idempotency-Key", "sample-triage:" + input.get("task") + ":" + HexFormat.of().formatHex(rd, 0, 8) + ":a1")
.POST(HttpRequest.BodyPublishers.ofString(rev)).build();
C.send(sreq, HttpResponse.BodyHandlers.ofLines()).body().filter(l -> l.startsWith("data:")).forEach(System.out::println);
rev = File.read("input.json")
uri = URI(BASE + "/run-stream")
req = Net::HTTP::Post.new(uri, "Content-Type" => "application/json", "Authorization" => "Bearer #{TOKEN}",
"Idempotency-Key" => "sample-triage:#{JSON.parse(rev)["task"]}:#{Digest::SHA256.hexdigest(rev)[0, 16]}:a1")
req.body = rev
Net::HTTP.start(uri.host, uri.port, use_ssl: true) do |h|
h.request(req) { |res| res.read_body { |chunk| chunk.each_line { |l| puts l if l.start_with?("data:") } } }
end
$rev = file_get_contents("input.json");
$ch = curl_init(BASE . "/run-stream");
curl_setopt_array($ch, [CURLOPT_POST => true, CURLOPT_POSTFIELDS => $rev, CURLOPT_HTTPHEADER => [
"Content-Type: application/json", "Authorization: Bearer $TOKEN",
"Idempotency-Key: sample-triage:" . json_decode($rev, true)["task"] . ":" . substr(hash("sha256", $rev), 0, 16) . ":a1"],
CURLOPT_WRITEFUNCTION => function ($ch, $chunk) { foreach (explode("\n", $chunk) as $l) if (str_starts_with($l, "data:")) echo $l, "\n"; return strlen($chunk); }]);
curl_exec($ch);
var rev = File.ReadAllText("input.json");
var rd = System.Security.Cryptography.SHA256.HashData(System.Text.Encoding.UTF8.GetBytes(rev));
var sreq = new HttpRequestMessage(HttpMethod.Post, Api.Base + "/run-stream") { Content = new StringContent(rev, System.Text.Encoding.UTF8, "application/json") };
sreq.Headers.Add("Authorization", "Bearer " + Api.Token);
sreq.Headers.Add("Idempotency-Key", $"sample-triage:{input["task"]}:{Convert.ToHexString(rd, 0, 8).ToLowerInvariant()}:a1");
using var http = new HttpClient();
using var res = await http.SendAsync(sreq, HttpCompletionOption.ResponseHeadersRead);
using var rdr = new StreamReader(await res.Content.ReadAsStreamAsync());
for (string? l; (l = await rdr.ReadLineAsync()) != null;) if (l.StartsWith("data:")) Console.WriteLine(l);