Run Isolate Lens from your own code
Both metered lanes are available over HTTP and each returns one JSON object. The division of labour: the page scans the Dart - every call matched to its closing bracket, its named arguments read at their own depth - finds build methods, Isolate.run and compute closures, spawn entry points and builders, maps every piece of I/O and CPU work to the isolate it runs on, and checks the skill's decision matrix, the worker handshake and lifecycle, and builder states, then hands it all to the run as facts. The run writes the review or the rewrite; the page then holds it to its own facts and re-scans a rewritten file with the same scanner (isoreply.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":"isolate-lens"}), 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, unit, code, facts) is missing. The page always sends all five of task, unit, code, carry, facts; carry is an empty string for review. |
| 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 - inspect, then produce:
review- each of the page's findings confirmed (with the issue, the fix and a short code fix) or cleared (with why), up to five issues a scan cannot see on real lines, and the fixes in the order to make them, each naming its ids.rewrite- the whole corrected file - I/O on async/await, heavy work in an awaited Isolate.run that sends only plain data, a static worker entry point with the SendPort handshake, ports closed and isolates killed, every builder state handled - with what changed and what was kept, every function and method name unchanged.
A missing or unknown task is answered as review. Both lanes send the same five fields. The facts are the page's scan, one finding per line with its id, and a line starting "must:" is a rule the reply has to hold. The page's handoff carries the review's decisions into carry for the rewrite lane. The review sends at most 24,000 characters of code (the middle of a longer file is cut on whole lines and marked; every finding is still in the facts); the rewrite lane takes at most 16,000.
The input
| Field | Required | What |
|---|---|---|
task | yes | review or rewrite. If it is missing or neither, do review and set "task": "review". |
unit | yes | the file's path and what it is (a screen, a view model or service, a background worker). |
code | yes | the Dart code. A line starting "// [... N lines cut by the page" marks code the page scanned but did not send. |
carry | send it (empty for review) | rewrite task only: the review's decisions, one per line as id | confirmed or cleared or extra | where | fix or reason; may be empty. |
facts | yes | the page's scan: one finding per line as id | area | severity | line | where | evidence | source, and the work map, one operation per line as line | operation | kind | where it runs | fine as it stands. 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, @flutter/flutter-handling-concurrency - the only standard the run applies:
- I/O-bound work (an HTTP request, a file read, a database query) uses async/await on the main isolate - the event loop keeps running while the Future waits. Nothing blocks the main isolate: no sleep(), no synchronous file I/O.
- CPU-bound work that finishes inside a frame (under 16 ms) stays on the main isolate with async/await. CPU work that takes significant time and runs once (decoding a large JSON payload) moves to Isolate.run.
- Isolate.run: extract the computation into a standalone function, pass it to Isolate.run, await the result on the main isolate, then assign the returned value to state. Isolates share no memory and talk only by messages, so what crosses must be plain data - never a BuildContext, setState, the widget, a controller or the State object.
- Repeated work over time uses Isolate.spawn: a ReceivePort on the main isolate, its sendPort passed as the spawn message, an entry point that is a top-level or static function, a ReceivePort in the worker whose SendPort it sends back first, listeners on both ports, and every port closed (and the isolate killed) when the worker is no longer needed.
- A Future in the UI goes through FutureBuilder (a Stream through StreamBuilder), whose builder handles ConnectionState.waiting, hasError and hasData.
- The page adds one check of its own, labelled in its facts: a FutureBuilder whose future is created inside build restarts on every rebuild, so the future is made once (initState or the view model) and kept in a field.
House rules
- Use only the input. Never invent a class, a method, a field, a package or a value the code does not hold. When a fix needs a helper the file does not define, write it in full or say in a caveat what the team supplies; when the size of a payload decides the fix and the code does not show it, say so instead of guessing.
- The page's ids and lines are measured facts: never renumber an id.
- No web links (http or https) in prose.
- Write in English. Keep the JSON keys and the ids exactly as given.
- Never use backticks. Name calls plainly (await Isolate.run(...), readAsString()). Words from the rules above are free to use; what you never do is paste a whole sentence from this prompt.
- Never write a gendered pronoun for a user; write they, the user or the person.
- Reply with ONLY one JSON object - no prose before or after, no code fences.
The replies
review
Confirm or clear each of the page's findings, add what a scan cannot see, and put the fixes in order.
summary- one sentence, at most 40 words: what freezes or breaks today, and the first fix.findings- one object per page finding you confirm, in id order:id(the page's id),widget(where it sits, e.g. "_CatalogScreenState.build"),issue(one sentence: what the user sees - a frozen frame, a thrown error, a leak, a stuck spinner),fix(one sentence: the change),code(the corrected Dart for that piece only, at most 6 lines; "" when fix says it all).cleared- one object per page finding that is not a problem in this code, withidandwhy(one sentence tied to the code). Every page id appears exactly once, in findings or in cleared.extra- at most 5 issues the scan cannot see, each tied to a line of the code:id(X1, X2, ...),area(main, isolates, lifecycle or ui),line(a number),widget,issue,fix. Examples: a helper of the file's own that does heavy work on the main isolate, a loop over a list that can grow large, a setState after an await with no mounted check, a worker whose errors never reach the main isolate, a listener or subscription never cancelled.[]when there are none.plan- 2 to 10 short strings, the fixes in the order to make them (what throws or freezes first, then work on the main isolate, then ports and lifecycle, then builder states), each naming the ids it covers (e.g. "Pass only the body string to Isolate.run and await it (J1, J2)"). Every confirmed id appears in a step.[]only when nothing is confirmed.caveats- at most 4 short strings;[]when there is nothing to say.
{
"task": "review",
"summary": "one sentence",
"findings": [ { "id": "J1", "widget": "_CatalogScreenState._refresh", "issue": "one sentence", "fix": "one sentence", "code": "final items = await Isolate.run(() => jsonDecode(body) as List<dynamic>);\nsetState(() => _items = items);" } ],
"cleared": [ { "id": "J4", "why": "one sentence" } ],
"extra": [ { "id": "X1", "area": "ui", "line": 20, "widget": "_CatalogScreenState._refresh", "issue": "one sentence", "fix": "one sentence" } ],
"plan": [ "Pass only the body string to Isolate.run and await it (J1, J2)" ],
"caveats": [ ]
}
The page holds it to its own facts (check() in isoreply.js; any warning makes the reply flagged): the reply answers the task it was sent; every page finding id appears exactly once across findings and cleared, and no id the page never found; a cleared id has a reason; a confirmed id has an issue and a fix; at most five extra issues, each on a line inside the paste; a fix order of 2 to 10 steps that names every confirmed id; a summary; no web link in the prose.
rewrite
Rewrite the file with the fixes applied.
summary- one sentence, at most 40 words: what the rewrite changes and what it leaves for the team.code- the complete rewritten file. Keep every import, class, function and method, with every name unchanged; change only what the findings name, plus the imports and fields a change needs. Never shorten the file with "..." or "unchanged" - write every line. If the code holds a line starting "// [... N lines cut by the page", keep that line exactly where it is. sleep() - await Future.delayed(the same Duration), with the function made async. Synchronous file I/O - the async form (readAsString(), writeAsString(), list()) with await; when it sat in build, load it once in initState into a field and show it through a FutureBuilder or state. CPU work in build - done once outside build and kept in state; a large decode goes through await Isolate.run. An Isolate.run or compute closure that captures context, setState, the widget or a controller - copy the plain data into a local first, pass only that, await the result, then call setState with it on the main isolate. An Isolate.run result not awaited - await it and use the value. A spawn entry point that is an instance method - make it static (or top-level), keeping its name. No SendPort handshake - in the entry point, create a ReceivePort and send its sendPort back first. A ReceivePort never closed or an isolate never killed - close it and kill the isolate in dispose (add a dispose method when the class has none). A builder missing a state - handle ConnectionState.waiting (a CircularProgressIndicator), hasError (show snapshot.error) and the data. A future created in build - a late final field set in initState, passed to the builder.changes- one object per page finding you fixed, in id order:id,change(one sentence: what changed, naming where). Extra ids (X1, ...) from the carried review may appear too.kept- one object per page finding you did not change:id,why(one sentence - the review cleared it, or it needs a decision the code cannot make). Every page id appears exactly once, in changes or in kept; an id the carried review cleared belongs in kept.caveats- at most 4 short strings;[]when there is nothing to say.
{
"task": "rewrite",
"summary": "one sentence",
"code": "the complete rewritten file",
"changes": [ { "id": "J1", "change": "one sentence" } ],
"kept": [ { "id": "J4", "why": "one sentence" } ],
"caveats": [ ]
}
The page requires: the reply answers the task it was sent; every page finding id appears exactly once across changes and kept; every class of the paste survives; the file is not shortened (at least 70% of its lines) and elides nothing; and the rewritten code is scanned again - a rule the changes claim fixed that still fires, or a rule the paste never had, is flagged. A function or method the rewrite drops or renames is flagged.
Worked examples
Three runs from the page's free samples (invented apps): the review of a catalog screen that freezes on every rebuild, the rewrite of the same file carrying that review, and the review of a long-lived worker that never hands back its port. Each input is exactly what the page sends; each reply is the saved reply the sample replays, shipped unedited - but the code, 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.
review - A screen that freezes on every rebuild
The page read it as: Breaks or freezes - 1 blocker, 5 major, 1 minor; findings J1 captures-unsendable, J2 run-not-awaited, J3 sync-io, J4 cpu-in-build, J5 builder-no-error, J6 builder-no-waiting, J7 future-in-build.
Input (facts is 2194 characters; the first 1388 are shown):
{
"task": "review",
"unit": "File: lib/catalog/catalog_screen.dart\nKind: a screen (widgets and their State)",
"code": "import 'dart:convert';\nimport 'dart:io';\nimport 'dart:isolate';\nimport 'package:flutter/material.dart';\nimport 'package:shop/catalog/catalog_api.dart';\n\nclass CatalogScreen extends StatefulWidget {\n const CatalogScreen({super.key, required this.api, required this.cachePath});\n final CatalogApi api;\n final String cachePath;\n\n @override\n State<CatalogScreen> createState() => _CatalogScreenState();\n}\n\nclass _CatalogScreenState extends State<CatalogScreen> {\n List<dynamic> _items = [];\n\n Future<void> _refresh() async {\n final body = await widget.api.fetchRaw();\n Isolate.run(() {\n setState(() => _items = jsonDecode(body) as List<dynamic>);\n });\n }\n\n @override\n Widget build(BuildContext context) {\n final cached = File(widget.cachePath).readAsStringSync();\n final featured = jsonDecode(cached) as List<dynamic>;\n return Column(\n children: [\n Text('${featured.length} featured'),\n FutureBuilder<List<dynamic>>(\n future: widget.api.fetchSaved(),\n builder: (context, snapshot) {\n final saved = snapshot.data!;\n return Text('${saved.length} saved');\n },\n ),\n TextButton(onPressed: _refresh, child: const Text('Refresh')),\n ],\n );\n }\n}\n",
"carry": "",
"facts": "The page's scan of 45 lines - 1 Isolate.run/compute call, 0 Isolate.spawn calls, 0 ReceivePorts, 1 FutureBuilder/StreamBuilder; verdict: Breaks or freezes - 1 blocker, 5 major, 1 minor.\nFindings (id | area | severity | line | where | evidence | source):\n- J1 | isolates | blocker | line 21 | Isolate.run | The Isolate.run closure refers to setState, which cannot be sent to another isolate - the call throws (or drags the whole State object across). | source: Isolate.run(() {\n- J2 | isolates | major | line 21 | Isolate.run | Isolate.run starts a worker but its result is not awaited, so the caller carries on before the value exists. | source: Isolate.run(() {\n- J3 | main | major | line 28 | build (main isolate) | readAsStringSync() reads or writes the disk synchronously on the main isolate, so the UI freezes for the length of the I/O. | source: final cached = File(widget.cachePath).readAsStringSync();\n- J4 | main | major | line 29 | build (main isolate) | jsonDecode runs inside build, so the work repeats on every rebuild on the main isolate. | source: final featured = jsonDecode(cached) as List<dynamic>;\n- J5 | ui | major | line 33 | FutureBuilder | FutureBuilder's builder never checks snapshot.hasError, so a failed request shows the loading state (or crashes on snapshot.data!) forever. | source: FutureBuilder<List<dynamic>>(\n- J6 | ui | minor | line 33 | FutureBuilder |\n\n[... 806 more characters cut from this display ...]"
}
Reply:
{
"task": "review",
"summary": "Tapping Refresh throws because the Isolate.run closure captures setState, and every build blocks the main isolate on a synchronous cache read; first await a plain decode in Isolate.run and set state afterwards.",
"findings": [
{
"id": "J1",
"widget": "_CatalogScreenState._refresh",
"issue": "Tapping Refresh throws an error because the closure sent to the worker carries setState and with it the whole State object, so the list never updates.",
"fix": "Decode only the body string inside Isolate.run and call setState with the returned list back on the main isolate.",
"code": "final items = await Isolate.run(() => jsonDecode(body) as List<dynamic>);\nif (!mounted) return;\nsetState(() => _items = items);"
},
{
"id": "J2",
"widget": "_CatalogScreenState._refresh",
"issue": "The Isolate.run call is fired and forgotten, so _refresh finishes before the decoded list exists and any error from the worker is lost.",
"fix": "Await the Isolate.run call and use its returned value, as in the J1 code.",
"code": ""
},
{
"id": "J3",
"widget": "_CatalogScreenState.build",
"issue": "Every rebuild stalls the frame while readAsStringSync() reads the cache file from disk on the main isolate.",
"fix": "Read the cache once in initState with the async readAsString() and keep the resulting Future in a field.",
"code": "late final Future<String> _cached;\n@override\nvoid initState() {\n super.initState();\n _cached = File(widget.cachePath).readAsString();\n}"
},
{
"id": "J4",
"widget": "_CatalogScreenState.build",
"issue": "The cached JSON is decoded again on every rebuild on the main isolate, adding jank to each frame.",
"fix": "Decode the cached text once when it is loaded, not in build, and move the decode to Isolate.run if the cache file is large.",
"code": "late final Future<List<dynamic>> _featured =\n _cached.then((text) => jsonDecode(text) as List<dynamic>);"
},
{
"id": "J5",
"widget": "_CatalogScreenState.build FutureBuilder",
"issue": "When fetchSaved() fails, snapshot.data! throws and the user sees an error screen instead of a message.",
"fix": "Check snapshot.hasError in the builder and show snapshot.error before touching the data.",
"code": "if (snapshot.hasError) return Text('Could not load: ${snapshot.error}');"
},
{
"id": "J6",
"widget": "_CatalogScreenState.build FutureBuilder",
"issue": "On the first frame the future has not completed, so snapshot.data! throws before any saved count can appear.",
"fix": "Return a CircularProgressIndicator while the connection state is waiting.",
"code": "if (snapshot.connectionState == ConnectionState.waiting) {\n return const CircularProgressIndicator();\n}"
},
{
"id": "J7",
"widget": "_CatalogScreenState.build FutureBuilder",
"issue": "Each rebuild, including the one Refresh triggers, calls fetchSaved() again, so the request repeats and the builder drops back to waiting.",
"fix": "Create the fetchSaved() future once in initState, keep it in a field and pass that field to the FutureBuilder.",
"code": "late final Future<List<dynamic>> _saved;\n// in initState, after super.initState():\n_saved = widget.api.fetchSaved();\n// in build:\nfuture: _saved,"
}
],
"cleared": [],
"extra": [
{
"id": "X1",
"area": "lifecycle",
"line": 22,
"widget": "_CatalogScreenState._refresh",
"issue": "Once the decode is awaited, setState can run after the user has left the screen, which throws because the State is no longer mounted.",
"fix": "Check mounted after the await and before calling setState."
},
{
"id": "X2",
"area": "ui",
"line": 20,
"widget": "_CatalogScreenState._refresh",
"issue": "If fetchRaw() fails, the error escapes the button handler unhandled and the user gets no feedback.",
"fix": "Wrap the fetch and decode in try/catch and show the failure in state."
}
],
"plan": [
"Decode only the body string in an awaited Isolate.run and set state with the result on the main isolate (J1, J2)",
"Check mounted after the await and catch a failed fetchRaw() (X1, X2)",
"Load and decode the cache once in initState with readAsString() instead of in build (J3, J4)",
"Create the fetchSaved() future once in initState and pass the field to the FutureBuilder (J7)",
"Handle the waiting and error states in the builder before reading the data (J5, J6)"
],
"caveats": [
"The code does not show how large the cache file or the fetchRaw() body is; for a small payload the decode can stay on the main isolate.",
"Showing the featured count from the loaded cache needs its own FutureBuilder (or state set after initState)."
]
}
What step 5 prints for it: J1: Decode only the body string inside Isolate.run and call setState with the returned list back on the main isolate. | J2: Await the Isolate.run call and use its returned value, as in the J1 code. | J3: Read the cache once in initState with the async readAsString() and keep the resulting Future in a field. | J4: Decode the cached text once when it is loaded, not in build, and move the decode to Isolate.run if the cache file is large. | J5: Check snapshot.hasError in the builder and show snapshot.error before touching the data. | J6: Return a CircularProgressIndicator while the connection state is waiting. | J7: Create the fetchSaved() future once in initState, keep it in a field and pass that field to the FutureBuilder.
The page's check: kept - nothing to flag
rewrite - A screen that freezes on every rebuild
The input carries the saved review (R.laneForm fills carry from the review reply), as the page's handoff does. The page re-scanned the rewritten code: 7 findings before, 0 after.
Input (facts is 2194 characters; the first 1388 are shown):
{
"task": "rewrite",
"unit": "File: lib/catalog/catalog_screen.dart\nKind: a screen (widgets and their State)",
"code": "import 'dart:convert';\nimport 'dart:io';\nimport 'dart:isolate';\nimport 'package:flutter/material.dart';\nimport 'package:shop/catalog/catalog_api.dart';\n\nclass CatalogScreen extends StatefulWidget {\n const CatalogScreen({super.key, required this.api, required this.cachePath});\n final CatalogApi api;\n final String cachePath;\n\n @override\n State<CatalogScreen> createState() => _CatalogScreenState();\n}\n\nclass _CatalogScreenState extends State<CatalogScreen> {\n List<dynamic> _items = [];\n\n Future<void> _refresh() async {\n final body = await widget.api.fetchRaw();\n Isolate.run(() {\n setState(() => _items = jsonDecode(body) as List<dynamic>);\n });\n }\n\n @override\n Widget build(BuildContext context) {\n final cached = File(widget.cachePath).readAsStringSync();\n final featured = jsonDecode(cached) as List<dynamic>;\n return Column(\n children: [\n Text('${featured.length} featured'),\n FutureBuilder<List<dynamic>>(\n future: widget.api.fetchSaved(),\n builder: (context, snapshot) {\n final saved = snapshot.data!;\n return Text('${saved.length} saved');\n },\n ),\n TextButton(onPressed: _refresh, child: const Text('Refresh')),\n ],\n );\n }\n}\n",
"carry": "J1 | confirmed | _CatalogScreenState._refresh | Decode only the body string inside Isolate.run and call setState with the returned list back on the main isolate.\nJ2 | confirmed | _CatalogScreenState._refresh | Await the Isolate.run call and use its returned value, as in the J1 code.\nJ3 | confirmed | _CatalogScreenState.build | Read the cache once in initState with the async readAsString() and keep the resulting Future in a field.\nJ4 | confirmed | _CatalogScreenState.build | Decode the cached text once when it is loaded, not in build, and move the decode to Isolate.run if the cache file is large.\nJ5 | confirmed | _CatalogScreenState.build FutureBuilder | Check snapshot.hasError in the builder and show snapshot.error before touching the data.\nJ6 | confirmed | _CatalogScreenState.build FutureBuilder | Return a CircularProgressIndicator while the connection state is waiting.\nJ7 | confirmed | _CatalogScreenState.build FutureBuilder | Create the fetchSaved() future once in initState, keep it in a field and pass that field to the FutureBuilder.\nX1 | extra | _CatalogScreenState._refresh (line 22) | Check mounted after the await and before calling setState.\nX2 | extra | _CatalogScreenState._refresh (line 20) | Wrap the fetch and decode in try/catch and show the failure in state.",
"facts": "The page's scan of 45 lines - 1 Isolate.run/compute call, 0 Isolate.spawn calls, 0 ReceivePorts, 1 FutureBuilder/StreamBuilder; verdict: Breaks or freezes - 1 blocker, 5 major, 1 minor.\nFindings (id | area | severity | line | where | evidence | source):\n- J1 | isolates | blocker | line 21 | Isolate.run | The Isolate.run closure refers to setState, which cannot be sent to another isolate - the call throws (or drags the whole State object across). | source: Isolate.run(() {\n- J2 | isolates | major | line 21 | Isolate.run | Isolate.run starts a worker but its result is not awaited, so the caller carries on before the value exists. | source: Isolate.run(() {\n- J3 | main | major | line 28 | build (main isolate) | readAsStringSync() reads or writes the disk synchronously on the main isolate, so the UI freezes for the length of the I/O. | source: final cached = File(widget.cachePath).readAsStringSync();\n- J4 | main | major | line 29 | build (main isolate) | jsonDecode runs inside build, so the work repeats on every rebuild on the main isolate. | source: final featured = jsonDecode(cached) as List<dynamic>;\n- J5 | ui | major | line 33 | FutureBuilder | FutureBuilder's builder never checks snapshot.hasError, so a failed request shows the loading state (or crashes on snapshot.data!) forever. | source: FutureBuilder<List<dynamic>>(\n- J6 | ui | minor | line 33 | FutureBuilder |\n\n[... 806 more characters cut from this display ...]"
}
Reply (long strings shortened for display - see the cuts below):
{
"task": "rewrite",
"summary": "Decodes only plain strings in awaited Isolate.run calls, loads the cache and the saved list once in initState, and gives both builders waiting and error states; the cache size that justifies a worker is the team's call.",
"code": "import 'dart:convert';\nimport 'dart:io';\nimport 'dart:isolate';\nimport 'package:flutter/material.dart';\nimport 'package:shop/catalog/catalog_api.dart';\n\nclass CatalogScreen extends StatefulWidget {\n const CatalogScreen({super.key, required this.api, required this.cachePath});\n final CatalogApi api;\n final String [... 2302 more characters cut from this display]",
"changes": [
{
"id": "J1",
"change": "In _CatalogScreenState._refresh the Isolate.run closure now decodes only the local body string, and setState runs back on the main isolate with the returned list."
},
{
"id": "J2",
"change": "In _CatalogScreenState._refresh the Isolate.run call is awaited and its value assigned to _items."
},
{
"id": "J3",
"change": "The cache is read once with await readAsString() in _loadFeatured, called from initState and kept in the _featured field."
},
{
"id": "J4",
"change": "The cached text is decoded once inside _loadFeatured through await Isolate.run(...) instead of on every build."
},
{
"id": "J5",
"change": "Both FutureBuilder builders in _CatalogScreenState.build check snapshot.hasError and show snapshot.error before reading the data."
},
{
"id": "J6",
"change": "Both FutureBuilder builders in _CatalogScreenState.build return a CircularProgressIndicator while the connection state is waiting."
},
{
"id": "J7",
"change": "fetchSaved() is called once in initState and kept in the late final _saved field passed to the FutureBuilder."
},
{
"id": "X1",
"change": "_CatalogScreenState._refresh checks mounted after the awaits and before each setState."
},
{
"id": "X2",
"change": "_CatalogScreenState._refresh wraps the fetch and decode in try/catch, keeps the failure in _refreshError and build shows it."
}
],
"kept": [],
"caveats": [
"The code does not show how large the cache file is; for a small file the decode in _loadFeatured could stay on the main isolate.",
"The saved list and the cache are loaded once per screen; refreshing them again needs a decision the code does not make."
]
}
Cut from this display only (the saved reply and the check use the full text): code (2618 characters, 316 shown).
What step 5 prints for it: J1: In _CatalogScreenState._refresh the Isolate.run closure now decodes only the local body string, and setState runs back on the main isolate with the returned list. | J2: In _CatalogScreenState._refresh the Isolate.run call is awaited and its value assigned to _items. | J3: The cache is read once with await readAsString() in _loadFeatured, called from initState and kept in the _featured field. | J4: The cached text is decoded once inside _loadFeatured through await Isolate.run(...) instead of on every build. | J5: Both FutureBuilder builders in _CatalogScreenState.build check snapshot.hasError and show snapshot.error before reading the data. | J6: Both FutureBuilder builders in _CatalogScreenState.build return a CircularProgressIndicator while the connection state is waiting. | J7: fetchSaved() is called once in initState and kept in the late final _saved field passed to the FutureBuilder. | X1: _CatalogScreenState._refresh checks mounted after the awaits and before each setState. | X2: _CatalogScreenState._refresh wraps the fetch and decode in try/catch, keeps the failure in _refreshError and build shows it.
The page's check: kept - nothing to flag
review - A worker that never hands back its port
The page read it as: Breaks or freezes - 1 blocker, 2 major, 1 minor; findings J1 port-not-closed, J2 isolate-not-killed, J3 spawn-instance-entry, J4 no-handshake.
Input:
{
"task": "review",
"unit": "File: lib/media/thumbnail_worker.dart\nKind: a background worker",
"code": "import 'dart:isolate';\n\nclass ThumbnailWorker {\n final ReceivePort _fromWorker = ReceivePort();\n SendPort? _toWorker;\n Isolate? _isolate;\n\n Future<void> start() async {\n _isolate = await Isolate.spawn(_entry, _fromWorker.sendPort);\n _fromWorker.listen((message) {\n if (message is SendPort) {\n _toWorker = message;\n } else {\n onThumbnail?.call(message as List<int>);\n }\n });\n }\n\n void Function(List<int>)? onThumbnail;\n\n void request(String path) => _toWorker?.send(path);\n\n void _entry(SendPort toMain) {\n final inbox = ReceivePort();\n inbox.listen((path) {\n toMain.send(makeThumbnail(path as String));\n });\n }\n}\n\nList<int> makeThumbnail(String path) => const [];\n",
"carry": "",
"facts": "The page's scan of 32 lines - 0 Isolate.run/compute calls, 1 Isolate.spawn call, 2 ReceivePorts, 0 FutureBuilder/StreamBuilders; verdict: Breaks or freezes - 1 blocker, 2 major, 1 minor.\nFindings (id | area | severity | line | where | evidence | source):\n- J1 | lifecycle | major | line 4 | ReceivePort _fromWorker | ReceivePort _fromWorker is never closed, so it keeps the isolate alive and leaks. | source: final ReceivePort _fromWorker = ReceivePort();\n- J2 | lifecycle | minor | line 9 | Isolate.spawn | The spawned isolate is never killed, so it keeps running (and holding memory) after the screen or service is gone. | source: _isolate = await Isolate.spawn(_entry, _fromWorker.sendPort);\n- J3 | isolates | blocker | line 9 | Isolate.spawn | Isolate.spawn's entry point _entry is an instance method - an isolate entry point must be a top-level or static function. | source: _isolate = await Isolate.spawn(_entry, _fromWorker.sendPort);\n- J4 | isolates | major | line 23 | _entry | The worker entry _entry never sends its own ReceivePort's sendPort back, so the main isolate has no way to send it work. | source: void _entry(SendPort toMain) {\nThe work map (line | operation | kind | where it runs | fine as it stands):\n- none found\nmust: account for every finding id above exactly once - in findings (confirmed) or in cleared (with the reason)."
}
Reply:
{
"task": "review",
"summary": "start() fails because the spawn entry point _entry is an instance method, and even once spawned the worker never sends its SendPort back; make _entry static and send inbox.sendPort first.",
"findings": [
{
"id": "J1",
"widget": "ThumbnailWorker._fromWorker",
"issue": "The ReceivePort is never closed, so its listener and the port stay alive and leak after the worker is no longer needed.",
"fix": "Add a dispose method that closes _fromWorker.",
"code": "void dispose() {\n _fromWorker.close();\n _isolate?.kill(priority: Isolate.immediate);\n _isolate = null;\n _toWorker = null;\n}"
},
{
"id": "J2",
"widget": "ThumbnailWorker.start",
"issue": "The spawned isolate is never killed, so it keeps running and holding memory after the service is gone.",
"fix": "Kill _isolate in the same dispose method that closes the port, as in the J1 code.",
"code": ""
},
{
"id": "J3",
"widget": "ThumbnailWorker.start",
"issue": "Isolate.spawn rejects the instance method _entry as an entry point, so start() throws and no thumbnails are ever made.",
"fix": "Declare _entry as a static method, keeping its name.",
"code": "static void _entry(SendPort toMain) {"
},
{
"id": "J4",
"widget": "ThumbnailWorker._entry",
"issue": "The worker never sends back its inbox port, so _toWorker stays null and every request() call is silently dropped.",
"fix": "Send inbox.sendPort to the main isolate before listening for paths.",
"code": "static void _entry(SendPort toMain) {\n final inbox = ReceivePort();\n toMain.send(inbox.sendPort);\n inbox.listen((path) => toMain.send(makeThumbnail(path as String)));\n}"
}
],
"cleared": [],
"extra": [
{
"id": "X1",
"area": "isolates",
"line": 9,
"widget": "ThumbnailWorker.start",
"issue": "An exception thrown in the worker never reaches the main isolate, so a failed thumbnail leaves the caller waiting with no error.",
"fix": "Pass onError with the sendPort of a separate ReceivePort to Isolate.spawn and report what it receives, closing that port in dispose too."
},
{
"id": "X2",
"area": "isolates",
"line": 21,
"widget": "ThumbnailWorker.request",
"issue": "A request() made before the worker's SendPort arrives is dropped without notice because _toWorker is still null.",
"fix": "Complete a Completer<SendPort> when the handshake message arrives and have start() await it before returning."
}
],
"plan": [
"Make _entry a static method so Isolate.spawn accepts it (J3)",
"Create the worker's ReceivePort and send its sendPort back before any work (J4)",
"Have start() wait for the handshake so early requests are not dropped (X2)",
"Route worker errors to the main isolate through an onError port (X1)",
"Add dispose to close _fromWorker and kill _isolate (J1, J2)"
],
"caveats": [
"makeThumbnail returns an empty list here; the real thumbnail code is supplied by the team and must stay a top-level function."
]
}
What step 5 prints for it: J1: Add a dispose method that closes _fromWorker. | J2: Kill _isolate in the same dispose method that closes the port, as in the J1 code. | J3: Declare _entry as a static method, keeping its name. | J4: Send inbox.sendPort to the main isolate before listening for paths.
The page's check: kept - X1 repeats what the page already found on line 9.
Build the input yourself
Load the engine in node (global.window = global; require("./isokit.js"); require("./isoreply.js")), then:
const K = IsoKit, R = IsoReply;
const f = K.form({ name: "lib/load.dart", targets: "service", carry: "",
code: "String load(String p) => File(p).readAsStringSync();\nvoid wait() {\n sleep(const Duration(seconds: 1));\n}" });
// f.findings, f.scan.work (every operation and where it runs), f.checklist, f.moves (the decision matrix applied)
// K.findingsCsv(f), K.workCsv(f), K.checklistMd(f) - the free exports
const b = K.buildInput(f, "review"); // or "rewrite" (R.laneForm(form, "rewrite", review) fills carry)
const wire = K.toWire(b.input); // POST this to /estimate and /run
// then: const reply = R.normalize(R.parseReply(job.output.output), "review");
// R.check(reply, b) // {status: kept or flags, 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://isolate-lens.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://isolate-lens.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"]
// isolate-lens.mjs -- Node 18+ (global fetch)
const BASE = "https://api.skillsafe.ai/v1/app-api";
let TOKEN = "YOUR_TOKEN"; // from https://isolate-lens.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://isolate-lens.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
}
// IsolateLens.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 IsolateLens {
static final String BASE = "https://api.skillsafe.ai/v1/app-api";
static String token = "YOUR_TOKEN"; // from https://isolate-lens.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://isolate-lens.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://isolate-lens.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://isolate-lens.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":"isolate-lens"}' | 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": "isolate-lens"})["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: "isolate-lens" })).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": "isolate-lens"}, 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", "isolate-lens"), 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: "isolate-lens" })["token"])
// a guest token (enough for /me and /estimate); a run needs your personal token from /tokens.html
$TOKEN = "";
$TOKEN = api("POST", "/guest", ["slug" => "isolate-lens"])["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 = "isolate-lens" }))!["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": "review", "unit": "File: lib/catalog/catalog_screen.dart
Kind: a screen (widgets and their State)", "code": "import 'dart:convert';
...", "carry": "", "facts": "The page's scan of 45 lines - 1 Isolate.run/compute call ...
Findings (id | area | severity | line | where | evidence | source):
- J1 | isolates | blocker | line 21 | ..."}
# - 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": "review", "unit": "File: lib/catalog/catalog_screen.dart
Kind: a screen (widgets and their State)", "code": "import 'dart:convert';
...", "carry": "", "facts": "The page's scan of 45 lines - 1 Isolate.run/compute call ...
Findings (id | area | severity | line | where | evidence | source):
- J1 | isolates | blocker | line 21 | ..."}
# - 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: "review" or "rewrite", unit, code, 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 isolate-lens:<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 review reply carries summary, findings, cleared, extra, plan and caveats; a rewrite reply carries summary, code, changes, kept and caveats. The code below prints the gist of either: each confirmed finding and its fix, or each change.
KEY="isolate-lens:$(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 == "rewrite" then (.changes[] | "\(.id): \(.change)")
else (.findings[] | "\(.id): \(.fix)") end' reply.json
import hashlib, time
key = "isolate-lens:" + 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"] == "rewrite":
for x in reply["changes"]:
print(x["id"], x["change"])
else:
for x in reply["findings"]:
print(x["id"], x["fix"])
import { createHash } from "node:crypto";
const key = `isolate-lens:${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 === "rewrite") { for (const x of reply.changes) console.log(x.id, x.change); }
else { for (const x of reply.findings) console.log(x.id, x.fix); }
sum := sha256.Sum256(raw) // add "crypto/sha256", "encoding/hex", "time" to the imports
key := "isolate-lens:" + 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, b := "findings", "fix"
if reply["task"] == "rewrite" {
key, b = "changes", "change"
}
xs, _ := reply[key].([]any)
for _, x := range xs {
xx := x.(map[string]any)
fmt.Println(xx["id"], xx[b])
}
var digest = java.security.MessageDigest.getInstance("SHA-256").digest(M.writeValueAsBytes(input));
String key = "isolate-lens:" + 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("rewrite")) {
for (JsonNode x : reply.path("changes")) System.out.println(x.path("id").asText() + " " + x.path("change").asText());
} else {
for (JsonNode x : reply.path("findings")) System.out.println(x.path("id").asText() + " " + x.path("fix").asText());
}
require "digest"
key = "isolate-lens:#{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"] == "rewrite"
reply["changes"].each { |x| puts "#{x["id"]} #{x["change"]}" }
else
reply["findings"].each { |x| puts "#{x["id"]} #{x["fix"]}" }
end
$key = "isolate-lens:" . $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"] === "rewrite") { foreach ($reply["changes"] as $x) echo $x["id"], " ", $x["change"], "\n"; }
else { foreach ($reply["findings"] as $x) echo $x["id"], " ", $x["fix"], "\n"; }
var digest = System.Security.Cryptography.SHA256.HashData(System.Text.Encoding.UTF8.GetBytes(input.ToJsonString()));
var key = $"isolate-lens:{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 == "rewrite") { foreach (var x in reply["changes"]!.AsArray()) Console.WriteLine($"{x!["id"]} {x["change"]}"); }
else { foreach (var x in reply["findings"]!.AsArray()) Console.WriteLine($"{x!["id"]} {x["fix"]}"); }
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: isolate-lens:$(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": "review", "unit": "File: lib/catalog/catalog_screen.dart
Kind: a screen (widgets and their State)", "code": "import 'dart:convert';
...", "carry": "", "facts": "The page's scan of 45 lines - 1 Isolate.run/compute call ...
Findings (id | area | severity | line | where | evidence | source):
- J1 | isolates | blocker | line 21 | ..."}
# - 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": "isolate-lens:" + 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": `isolate-lens:${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", "isolate-lens:"+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", "isolate-lens:" + 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" => "isolate-lens:#{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: isolate-lens:" . 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", $"isolate-lens:{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);