Skip to content

feat(tasks): detect eval-task formats with tasks inspect - #210

Merged
xaleryb merged 20 commits into
intel:mainfrom
napetrov:feat/task-format-detect
Oct 26, 2026
Merged

xaleryb merged 20 commits into
intel:mainfrom
napetrov:feat/task-format-detect

Conversation

@napetrov

Copy link
Copy Markdown
Contributor

Summary

New dokimi tasks inspect <path> [--json]. It works out which eval-task format a file, task directory or tree is in, then reports the kind, the item count, which parts are present or missing (instruction / environment / oracle / verifier / metadata), lint problems, and which dokimi backends can consume it today. This PR only detects formats. It doesn't run or convert any new format, and tasks list / tasks run / catalog behave as before.

 dokimi/dokimi/
+├── task_formats/        # tracks layer (added to LAYERS)
+│   ├── model.py         # Fingerprint, Report, Probe protocol
+│   ├── source.py        # parse each file once, safely (YAML aliases refused, .py never imported)
+│   ├── dirs.py          # harbor + task-md (wrap harnesses.tasks), terminal-bench v1
+│   ├── datasets.py      # swe-bench, inspect samples, openai evals, skill-creator x2, skill-reach, questions
+│   ├── configs.py       # promptfoo, Inspect @task (regex text scan)
+│   └── detect.py        # registry, ambiguity, tree scan, unknown fallback
 └── commands/tasks.py    # + `tasks inspect`
Format Kind Consumable by
harbor executable harbor (when harbor_task_problem is None), docker-*
task-md executable benchflow, docker-*
terminal-bench-v1 executable none-yet
swe-bench executable none-yet
inspect-samples answer-only none-yet
inspect-task (.py) answer-only (executable when it has sandbox=) none-yet
openai-evals answer-only none-yet
skill-creator-evals answer-only none-yet
skill-creator-trigger trigger none-yet
skill-reach trigger none-yet
dokimi-questions (v1/v2) answer-only (trigger when it has expected_skill) questions track (when load_questions accepts it)
unknown unknown lists files/keys plus likely prompt and verifier files

I took field names from upstream source, not from memory: SWE-bench swebench/types.py, Inspect dataset/_dataset.py (Sample / FieldSpec), terminal-bench original-tasks/* (241 task.yaml keys tallied), skill-creator references/schemas.md and SKILL.md, skill-reach models.py / queries.py (including the legacy id -> query_id migration and the out_of_scope rule), the promptfoo examples (149 configs tallied), and openai/evals docs/build-eval.md.

Evidence

  • Before: tasks inspect doesn't exist, and nothing reads the trigger sets that dokimi writes.
    After: a run over real upstream samples:
original-tasks/hello-world        terminal-bench-v1  executable          -> none-yet
swe-bench/lite-3.jsonl (HF Lite)  swe-bench          executable  3 items -> none-yet
openai-evals/match.jsonl (coqa)   openai-evals       answer-only 5 items -> none-yet
inspect examples/computer.py      inspect-task       executable  1 item  -> none-yet
inspect _examples/security_guide  inspect-samples    answer-only 16 items
promptfoo examples x2             promptfoo          answer-only
intel-skills skills/*/evals.json  skill-creator-evals x10
intel-skills evaluation/harbor    harbor x15         -> harbor, docker-*
benchflow demo_task               task-md            -> benchflow, docker-*  (+ drift problem)
  • tests/test_task_format_detect.py: 37 tests. They cover every format, ambiguity, unknown, malformed JSON/JSONL/YAML/TOML/YAML-alias inputs (each becomes a problem, not a crash), an Inspect file that would write a marker if imported (the marker is never written), a mixed tree, and a regression check that discover_tasks is unchanged.
  • make ci-local: tests (7840 passed), mypy, ruff, growth, import contract, coverage, mkdocs and cli-surface all pass. pip-audit fails on the setuptools GHSA-xmj3-9gf7-h52w advisory; that failure is on main too and isn't caused by this PR.

Merge Danger

Door: two-way. It adds a new command and a new package and changes no existing behaviour or persisted format. The --json output isn't a versioned artifact yet.

Blast Radius: additive

🤖 Generated with Claude Code

A probe registry (dokimi.task_formats) names the format of a file, a task
directory or a tree, and reports kind, item count, present/missing parts,
lint problems and which dokimi backends consume it today. Probes: harbor and
task-md (wrapping harnesses.tasks), terminal-bench v1, SWE-bench, Inspect AI
samples and @task files (text scan, never imported), OpenAI evals,
skill-creator evals.json and trigger sets, skill-reach QuerySet, promptfoo
and dokimi questions. Unknown paths get a best-effort structure report;
malformed files are lint problems. Detection only: nothing new runs.

Signed-off-by: Nikolay Petrov <nikolay.a.petrov@intel.com>
@github-actions github-actions Bot added the cli-interface-change Changes dokimi's user-facing CLI surface label Oct 24, 2026
@github-actions

github-actions Bot commented Oct 24, 2026 •

Copy link
Copy Markdown

CLI interface change detected

This PR changes dokimi's user-facing CLI surface (commands and/or options):

~ `dokimi tasks inspect` parameters changed:
    + added option `--out`

Updated on every push; the cli-interface-change label is on while the diff is non-empty.

@codecov-commenter

codecov-commenter commented Oct 24, 2026 •

Copy link
Copy Markdown

⚠️ Please install the 'codecov app svg image' to ensure uploads and comments are reliably processed by Codecov.

Codecov Report

❌ Patch coverage is 97.07602% with 10 lines in your changes missing coverage. Please review.
✅ Project coverage is 92.20%. Comparing base (98d709a) to head (cd85b32).
⚠️ Report is 2 commits behind head on main.

Files with missing lines Patch % Lines
dokimi/dokimi/task_formats/source.py 91.11% 4 Missing ⚠️
dokimi/dokimi/task_formats/configs.py 98.36% 0 Missing and 2 partials ⚠️
dokimi/dokimi/task_formats/datasets.py 94.28% 1 Missing and 1 partial ⚠️
dokimi/dokimi/task_formats/model.py 88.23% 1 Missing and 1 partial ⚠️
❗ Your organization needs to install the Codecov GitHub app to enable full functionality.
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #210      +/-   ##
==========================================
+ Coverage   92.07%   92.20%   +0.13%     
==========================================
  Files         233      235       +2     
  Lines       29090    29387     +297     
  Branches     4787     4836      +49     
==========================================
+ Hits        26784    27096     +312     
+ Misses       1620     1614       -6     
+ Partials      686      677       -9     
Files with missing lines Coverage Δ
dokimi/dokimi/artifacts.py 95.12% <ø> (ø)
dokimi/dokimi/commands/tasks.py 92.77% <100.00%> (+0.36%) ⬆️
dokimi/dokimi/task_formats/detect.py 98.29% <100.00%> (+10.67%) ⬆️
dokimi/dokimi/task_formats/dirs.py 92.23% <100.00%> (+2.87%) ⬆️
dokimi/dokimi/task_formats/links.py 100.00% <100.00%> (ø)
dokimi/dokimi/task_formats/pointers.py 100.00% <100.00%> (ø)
dokimi/dokimi/task_formats/configs.py 97.71% <98.36%> (+7.39%) ⬆️
dokimi/dokimi/task_formats/datasets.py 95.61% <94.28%> (+1.07%) ⬆️
dokimi/dokimi/task_formats/model.py 96.72% <88.23%> (-3.28%) ⬇️
dokimi/dokimi/task_formats/source.py 87.50% <91.11%> (+5.06%) ⬆️

... and 1 file with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

`tasks inspect --json` now prints a stamped task_inspect.v1 artifact and
`--out FILE` writes it through save_artifact, as `repo budget` does.

Probes added or widened from runs over upstream checkouts:
- a dataset file a config names is read as that config says: Inspect
  FieldSpec remaps, OpenAI evals registry specs (openai-evals-spec, with
  model-graded samples needing no ideal), promptfoo `tests: file://`
  (promptfoo-tests);
- git-LFS pointer stubs are git-lfs-pointer, with the expected format
  when a config names them;
- CSV datasets, promptfooconfig.<variant>.yaml and redteam configs,
  ExploreBench task YAML (dokimi-explore-task, localization);
- a task.md with no BenchFlow front matter and no task part directories
  is no longer a task-md task.
YAML aliases are reported as refused, not as invalid YAML.

Signed-off-by: Nikolay Petrov <nikolay.a.petrov@intel.com>
tests/fixtures/task_formats/ holds small upstream files of every format
the probes know (intel/skills, skillsbench, terminal-bench, Inspect AI,
openai/evals, promptfoo; licenses in LICENSES/) and generated ones where
no public file exists (SWE-bench row, skill-creator evals.json,
skill-reach QuerySet, and the output of dokimi's trigger-set writers).
MANIFEST.yaml records each file's source, commit and license and what
inspect must report; test_task_format_corpus.py inspects the corpus as
one mixed tree, validates every --json report as task_inspect.v1, and
reads back what to_skill_creator, to_reach and to_questions write.

The fixture tree is kept out of pytest collection, ruff and the license
header check: its test_*.py are the verifiers of copied tasks.

Fixes found on the way: a task.md scan leaked the loader's UserWarning
from the fingerprint, and a path that is neither file nor directory
(/dev/null) crashed the tree scan.

Signed-off-by: Nikolay Petrov <nikolay.a.petrov@intel.com>
napetrov added a commit to napetrov/dokimi that referenced this pull request Oct 24, 2026
…azily

A task.yaml task loads as a TaskSpec and runs under docker-oracle and
docker-claude as terminal-bench's harness runs it (solution.sh at /oracle,
run-tests.sh + tests/ at /tests, image user, TEST_DIR and compose env,
pytest-summary verdict). Tasks one docker run cannot reproduce are refused
with the reason. Harbor-backed harnesses and benchflow run a content-keyed
Harbor export whose test.sh applies the same pytest-summary rule.

Stacked on intel#210.

Signed-off-by: Nikolay Petrov <nikolay.a.petrov@intel.com>
A config naming a path with a NUL byte raised ValueError in links._key,
and a skill-reach key YAML loads as bool or int (on:, yes:, 1:) raised
TypeError in the key lint; either one killed the whole `tasks inspect`.

Signed-off-by: Alexander Rybkin <alexander.rybkin@intel.com>
Probes copy YAML values into details unchanged. --json hid it with
default=str, but --out (save_artifact) raised TypeError and wrote
nothing, and a YAML .nan went out as bare NaN in both modes.

Signed-off-by: Alexander Rybkin <alexander.rybkin@intel.com>
…iles

The 64 MiB guard trusted st_size, which is 0 for a FIFO or device: a
FIFO left in a scanned tree hung the scan, and /dev/zero was read until
memory ran out. The scan now takes regular files only and the read is bounded.

Signed-off-by: Alexander Rybkin <alexander.rybkin@intel.com>
docker-* always builds environment/Dockerfile, so a harbor or task-md
task without it errors in every cell, yet inspect listed docker-* as a
consumer whenever tests/ or verifier/ existed.

Signed-off-by: Alexander Rybkin <alexander.rybkin@intel.com>
Removing the symlink skip, the dot-directory skip, RecursionError from
the parse catch or the file-cap flag left every task-format test green;
two of those turn into crashes on real trees.

Signed-off-by: Alexander Rybkin <alexander.rybkin@intel.com>
@xaleryb

xaleryb commented Oct 25, 2026

Copy link
Copy Markdown
Contributor

Ran the suite, lint, type and security checks on Linux, tasks inspect on real catalogs, crafted edge inputs, and the stacked #212-#214 heads.

  • Single-file detect() applies no config hints: openai-evals/.../joke_fruits.jsonl alone -> inspect-samples (alternative openai-evals); in its tree -> openai-evals. feat(questions): run Inspect, OpenAI evals, skill-creator and promptfoo sets on the answers track #214 imports it as match checks with empty values. Expected: ambiguity is part of the detect() contract, and importers refuse ambiguous files unless --format is given.
  • Tree with a truncated evals.json or a >64 MiB JSONL -> the file is missing from the report and no problem is shown. Expected: the same problem a single-file inspect reports.
  • Symlinked task dirs and .claude/ -> unknown, while tasks list lists them. Expected: followed, or the skip is reported.
  • .agent-eval/tasks/* -> unknown, none-yet, although repo tasks check reads them.
  • Merge Danger says --json is unversioned. It writes task_inspect.v1, whose enums only widen in place.

Pushed:

  • 02fdd9d: NUL path or bool/int YAML key crashed the scan
  • d4987c4: YAML date/set/NaN crashed --out
  • 6cdba63: FIFO hung a scan, /dev/zero read without a bound
  • 66f87c6: docker-* claimed without environment/Dockerfile
  • 92df76a: tests for the scan guards

@xaleryb

xaleryb commented Oct 25, 2026

Copy link
Copy Markdown
Contributor

CodeFactor is red here and on the PRs built on this branch (#212, #213, #214) because of 3 findings, all in the corpus added by e4b4e80: terminal-bench/.../hello-world/solution.sh:2 (shebang not on line 1), skillsbench/.../environment/Dockerfile:9 (pip without --no-cache-dir), skillsbench/.../verifier/test.sh:5 ($? check). These files mirror upstream task formats, so changing them would weaken the corpus. A .codefactor.yml with exclude_paths: [dokimi/tests/fixtures/**] would turn the check green without touching them.

…e inspect does

Signed-off-by: Alexander Rybkin <alexander.rybkin@intel.com>
@xaleryb

xaleryb commented Oct 25, 2026

Copy link
Copy Markdown
Contributor

Pushed bc96c3c for the second point above: a tree scan now reports a file it could not read or parse as unknown with the same problem a single-file inspect gives (truncated evals.json -> not valid JSON: ..., a JSONL over the cap -> larger than 64 MiB: not read). A file that parses but no probe claims (package.json) still stays out, and a config that names a broken file relabels it as before. The FileSource already read is reused, so a big file is not read twice. Full suite, ruff, mypy and bandit pass on Linux; the new test fails on 92df76a.

One consequence to be aware of: any unclaimed file with a read problem now shows up, including an unrelated YAML file that uses aliases (refused as before, now visible in the tree output).

Still open from the first comment: single-file ambiguity in detect() (the one #214 depends on), symlinked and dot dirs, .agent-eval tasks, and the --json versioning line in the description.

… files

Signed-off-by: Alexander Rybkin <alexander.rybkin@intel.com>
@xaleryb

xaleryb commented Oct 25, 2026

Copy link
Copy Markdown
Contributor

Pushed 492020e: .codefactor.yml leaves dokimi/tests/fixtures/task_formats/** out of CodeFactor, so the three findings in the copied upstream files should clear on the next analysis.

Re-scoping our first point: single-file detect() does report the ambiguity, in alternatives (joke_fruits.jsonl -> inspect-samples with openai-evals as alternative). The wrong import is #214 ignoring that list, so it is a #214 fix, not a blocker here. Symlinked and dot dirs, .agent-eval tasks, and the --json line in the description can follow in their own PRs. Once pip-audit is green (it needs #222 on main), this is ready from our side.

Signed-off-by: Alexander Rybkin <alexander.rybkin@intel.com>
…this PR

Signed-off-by: Alexander Rybkin <alexander.rybkin@intel.com>
@xaleryb

xaleryb commented Oct 25, 2026

Copy link
Copy Markdown
Contributor

Correction to my last comment: CodeFactor did not apply .codefactor.yml here (still the same 3 findings with both exclude_paths and exclude_patterns), so I removed the file again; the branch is back to no CodeFactor config. Excluding dokimi/tests/fixtures/task_formats/** has to be done in the repository's CodeFactor settings (Ignored files), which needs a maintainer. No check is required on main, so it does not block the merge.

Signed-off-by: Alexander Rybkin <alexander.rybkin@intel.com>

intel#211 landed with a copy of this branch's first commit (f4ce883) plus its own reach consumers. Each conflicting file was merged three-way against f4ce883, so main's additions (BY_TRIGGER_SET and its consumers) and this branch's later commits are both kept.
…ntel#211

Signed-off-by: Alexander Rybkin <alexander.rybkin@intel.com>
@xaleryb

xaleryb commented Oct 25, 2026

Copy link
Copy Markdown
Contributor

Merged main after #211 landed (ddf1858, d277faa). #211 carried a copy of this branch's first commit (f4ce883) plus its own BY_TRIGGER_SET consumers, so the add/add conflicts were merged three-way against f4ce883: compared with the previous head, the only additions are #211's lines, and none of main's are lost.

That made trigger sets consumed by catalog trigger-set, which broke 4 corpus tests here. d277faa adds catalog trigger-set to the consumers enum of task_inspect.v1 (a widening), updates the three trigger-set corpus entries in MANIFEST.yaml, and the two none yet rows in docs/coding-harnesses.md. Full suite, ruff, mypy and bandit pass on Linux.

…iles

solution.sh: shebang above the canary line; Dockerfile: pip3 --no-cache-dir; verifier/test.sh: test pytest's exit code directly. Detection reads only which files exist, and MANIFEST.yaml notes each change, as its header asks.

Signed-off-by: Alexander Rybkin <alexander.rybkin@intel.com>
@xaleryb

xaleryb commented Oct 25, 2026

Copy link
Copy Markdown
Contributor

pip-audit (locks) is red here because main pins setuptools 84.0.0 and filelock 3.32.7, both with published advisories; it fails on any PR that touches pyproject.toml or a lock. The fix is #222: it bumps setuptools and documents the filelock advisories, since every litellm release caps filelock<4.0. I opened #222, so I can't approve or merge it myself; it needs a maintainer's review.

CodeFactor is green since c804980: the three findings in the copied corpus files are fixed, and MANIFEST.yaml notes each change.

Order from here:

  1. Review and merge fix(deps): setuptools 84.2.1 for GHSA-xmj3-9gf7-h52w; document the filelock advisories #222.
  2. Re-run CI on this PR so pip-audit turns green, then merge it.
  3. Rebase feat(tasks): run terminal-bench v1 tasks natively, export to Harbor lazily #212, feat(tasks): run SWE-bench rows with docker-* harnesses #213 and feat(questions): run Inspect, OpenAI evals, skill-creator and promptfoo sets on the answers track #214 onto main. They conflict with main now for the same reason this one did: feat(catalog): read trigger sets back and list skill package formats #211 landed with a copy of this branch's first commit.

@antonwolfy antonwolfy left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review at c804980. The detection model and the safety guards hold up: YAML aliases are refused, .py files are never imported, and FIFOs, symlinked children and size caps are handled in tree scans. Four things are worth fixing before merge; they are inline.

What I ran, on a Linux review host at c804980:

  • make ci-local: 26 passed, 3 failed. 8089 tests passed, and the changed-line coverage gate passed. gitleaks failed at the download step, and pip-audit fails on the setuptools and filelock advisories in the lock files, which this PR does not touch. Neither is caused by the PR.
  • Reproductions for the code findings, with no network access.
  • A mutation run of 59 mutants over the new code against test_task_format_detect.py and test_task_format_corpus.py: 21 survived. The ones that matter are in the last inline comment.

Checked, no issues

  • Docs and schema: the format, kind and consumer table in coding-harnesses.md matches the probes, and the task_inspect.v1 enums match the constants. task_inspect is a new entry in SCHEMA_VERSIONS, so no existing version is edited in place.
  • Scope claim: no file under harnesses/ changes, and a test pins discover_tasks as unchanged.
  • Repo rules: the changelog fragment is present, no generated artifacts are committed (large.parquet is a synthetic LFS pointer), and the corpus verifiers are kept out of collection.
  • Tree-scan reports: a file that cannot be read inside a tree is reported as unknown with the read error, not dropped.

Comment thread dokimi/dokimi/task_formats/configs.py Resolved
Comment thread dokimi/dokimi/task_formats/source.py Resolved
Comment thread dokimi/dokimi/task_formats/dirs.py Resolved
Comment thread dokimi/dokimi/task_formats/dirs.py Resolved
_FIELD_SPEC's [^)]* let each unclosed FieldSpec( run to the end of the
segment, and search retried at every following one: 40000 of them after
an @task file's dataset call took 43 s. The arguments are now bounded at
1000 characters and a run stops at the next FieldSpec(, so each start
scans at most up to the next one. The bound alone (the review's
[^)]{0,500}) is linear too but still took 17 s on a file at the 64 MiB
read cap; with the stop it takes 1.2 s.

Signed-off-by: Alexander Rybkin <alexander.rybkin@intel.com>
An Excel or Sheets export of an Inspect samples.csv or a promptfoo
tests.csv starts with a BOM, which csv.DictReader kept in the first
header (\ufeffinput), so the file was unknown and a BOM on __expected
hid the promptfoo marker. Both consumers drop it: Inspect's csv_dataset
reads utf-8-sig and promptfoo parses CSV with bom: true.

The BOM is stripped for CSV only, not by opening every file as
utf-8-sig. A JSON or JSONL file with a BOM stays a 'not valid JSON:
Unexpected UTF-8 BOM' problem, because that is what its readers do:
read_json_input (catalog trigger-set --trigger-set), load_questions and
Inspect's json_dataset all decode plain utf-8, so stripping it here
would advertise a consumer that refuses the file. YAML needs nothing:
the loader skips a leading BOM.

Signed-off-by: Alexander Rybkin <alexander.rybkin@intel.com>
TaskMdProbe.fingerprint reads task.md (_is_benchflow_task_md,
_has_frontmatter) and walk called detect outside any try, so one
mode-000 task.md ended inspect_path with a PermissionError and no
report for any other task. walk now catches OSError around detect, the
one place that covers every directory probe, and reports the directory
as unknown with 'cannot read: ...', as an unreadable file in a tree is.
This also covers a directory whose entries cannot be stat'ed (a mode-000
directory holding a task.toml), which failed the same way before this
PR in task_format.

Signed-off-by: Alexander Rybkin <alexander.rybkin@intel.com>
_docker_runnable needs environment/Dockerfile and a tests/ or verifier/
directory, but only the Dockerfile half was tested: returning just the
Dockerfile check passed every test. A Harbor and a task-md task with
both verifier directories removed must not be claimed for docker-*.

Signed-off-by: Alexander Rybkin <alexander.rybkin@intel.com>
- the samples of a model-graded OpenAI evals spec are read without the
  ideal lint (graded = False passed every test);
- promptfoo tests: file://tests.csv labels a CSV that has no __expected
  column (the only fixture CSV is claimed with or without the reference);
- a scan does not enter node_modules/ or venv/ (only .git and .venv were
  tested, and those are skipped as dot directories anyway).

Signed-off-by: Alexander Rybkin <alexander.rybkin@intel.com>

@xaleryb xaleryb left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approved at cd85b32: every point from our review and from antonwolfy's review is fixed or moved to a named follow-up, the full suite, ruff, mypy and bandit pass on Linux, and CI is green.

@xaleryb
xaleryb merged commit f5cca76 into intel:main Oct 26, 2026
29 checks passed
xaleryb added a commit to napetrov/dokimi that referenced this pull request Oct 26, 2026
Signed-off-by: Alexander Rybkin <alexander.rybkin@intel.com>

intel#210 landed as a squash; conflicts merged three-way against e4b4e80, the commit this branch was built on. The one hand-resolved hunk is the task_formats.model import in datasets.py, which now takes both BY_DOCKER and BY_TRIGGER_SET.
xaleryb added a commit to napetrov/dokimi that referenced this pull request Oct 26, 2026
Signed-off-by: Alexander Rybkin <alexander.rybkin@intel.com>

intel#210 landed as a squash (f5cca76); conflicts merged three-way against e4b4e80, the commit this branch was built on.
xaleryb added a commit to napetrov/dokimi that referenced this pull request Oct 26, 2026
Signed-off-by: Alexander Rybkin <alexander.rybkin@intel.com>

intel#210 landed as a squash (f5cca76), so its files conflicted add/add here; they were merged three-way against e4b4e80, the intel#210 commit this branch was built on, keeping both sides' consumers in the docs table, the schema enum and model.py.
xaleryb added a commit to napetrov/dokimi that referenced this pull request Oct 26, 2026
Main now has intel#210 as squash f5cca76, which this branch carried as its early commits f4ce883..e4b4e80; each add/add conflict was three-way merged against e4b4e80, so the branch adds exactly its own network-policy changes on top of main.

Signed-off-by: Alexander Rybkin <alexander.rybkin@intel.com>
xaleryb added a commit that referenced this pull request Oct 26, 2026
…azily (#212)

* feat(tasks): detect eval-task formats with `tasks inspect`

A probe registry (dokimi.task_formats) names the format of a file, a task
directory or a tree, and reports kind, item count, present/missing parts,
lint problems and which dokimi backends consume it today. Probes: harbor and
task-md (wrapping harnesses.tasks), terminal-bench v1, SWE-bench, Inspect AI
samples and @task files (text scan, never imported), OpenAI evals,
skill-creator evals.json and trigger sets, skill-reach QuerySet, promptfoo
and dokimi questions. Unknown paths get a best-effort structure report;
malformed files are lint problems. Detection only: nothing new runs.

Signed-off-by: Nikolay Petrov <nikolay.a.petrov@intel.com>

* feat(tasks): task_inspect.v1 report, config-named datasets, LFS pointers

`tasks inspect --json` now prints a stamped task_inspect.v1 artifact and
`--out FILE` writes it through save_artifact, as `repo budget` does.

Probes added or widened from runs over upstream checkouts:
- a dataset file a config names is read as that config says: Inspect
  FieldSpec remaps, OpenAI evals registry specs (openai-evals-spec, with
  model-graded samples needing no ideal), promptfoo `tests: file://`
  (promptfoo-tests);
- git-LFS pointer stubs are git-lfs-pointer, with the expected format
  when a config names them;
- CSV datasets, promptfooconfig.<variant>.yaml and redteam configs,
  ExploreBench task YAML (dokimi-explore-task, localization);
- a task.md with no BenchFlow front matter and no task part directories
  is no longer a task-md task.
YAML aliases are reported as refused, not as invalid YAML.

Signed-off-by: Nikolay Petrov <nikolay.a.petrov@intel.com>

* test(tasks): real-format corpus for tasks inspect, with its manifest

tests/fixtures/task_formats/ holds small upstream files of every format
the probes know (intel/skills, skillsbench, terminal-bench, Inspect AI,
openai/evals, promptfoo; licenses in LICENSES/) and generated ones where
no public file exists (SWE-bench row, skill-creator evals.json,
skill-reach QuerySet, and the output of dokimi's trigger-set writers).
MANIFEST.yaml records each file's source, commit and license and what
inspect must report; test_task_format_corpus.py inspects the corpus as
one mixed tree, validates every --json report as task_inspect.v1, and
reads back what to_skill_creator, to_reach and to_questions write.

The fixture tree is kept out of pytest collection, ruff and the license
header check: its test_*.py are the verifiers of copied tasks.

Fixes found on the way: a task.md scan leaked the loader's UserWarning
from the fingerprint, and a path that is neither file nor directory
(/dev/null) crashed the tree scan.

Signed-off-by: Nikolay Petrov <nikolay.a.petrov@intel.com>

* feat(harnesses): per-phase network policy for docker-* runs, off by default

build, solve and verify each get none, allowlist (egress sidecar) or public.
Defaults: build public, solve and verify none. Sources, later wins: the
defaults, task.toml ([environment]/[agent]/[verifier] network_mode and
allowed_hosts, Harbor's allow_internet, environment.egress), then
dokimi tasks run --network PHASE=MODE[:hosts]. The plan and each phase's
source land in metrics.network; a skill's SKILL.md metadata.network is
recorded, not granted.

Signed-off-by: Nikolay Petrov <nikolay.a.petrov@intel.com>

* feat(harnesses): write network.json in every docker-* cell dir, errored cells too

Signed-off-by: Nikolay Petrov <nikolay.a.petrov@intel.com>

* feat(tasks): run terminal-bench v1 tasks natively, export to Harbor lazily

A task.yaml task loads as a TaskSpec and runs under docker-oracle and
docker-claude as terminal-bench's harness runs it (solution.sh at /oracle,
run-tests.sh + tests/ at /tests, image user, TEST_DIR and compose env,
pytest-summary verdict). Tasks one docker run cannot reproduce are refused
with the reason. Harbor-backed harnesses and benchflow run a content-keyed
Harbor export whose test.sh applies the same pytest-summary rule.

Stacked on #210.

Signed-off-by: Nikolay Petrov <nikolay.a.petrov@intel.com>

* feat(tasks): give terminal-bench v1 tasks 8 GB of memory, per task from compose

terminal-bench sets no memory limit; dokimi's 2 GB default OOM-killed
add-benchmark-lm-eval-harness and predicate-pushdown-bench. A v1 task now
reads compose deploy.resources.limits.memory when set, else 8192 MB
(DOKIMI_TBENCH_V1_MEMORY_MB overrides), and the Harbor export writes it to
task.toml.

Signed-off-by: Nikolay Petrov <nikolay.a.petrov@intel.com>

* test(tasks): tb-v1 on the network policy: public oracle/verify, agent solve default

Rebased onto #218. The corpus manifest's terminal-bench hello-world now
names its consumers (docker-*, harbor, benchflow), not none-yet.

Signed-off-by: Nikolay Petrov <nikolay.a.petrov@intel.com>

* fix(tasks): score a tb-v1 verifier on its one ordered output stream

docker-* parsed stdout followed by all of stderr, so a stderr line such
as pip's "ERROR: pip's dependency resolver ..." landed after the pytest
summary and failed a passing task. Run run-tests.sh with 2>&1, as
terminal-bench's pane and the Harbor export's test.sh read it.

Signed-off-by: Alexander Rybkin <alexander.rybkin@intel.com>

* fix(tasks): key the Harbor export on the task.toml it writes

The cache key hashed only the task's files, so a cached export kept the
memory_mb of its first run after DOKIMI_TBENCH_V1_MEMORY_MB changed,
while docker-* used the new value. Hash the resolved task.toml too.

Signed-off-by: Alexander Rybkin <alexander.rybkin@intel.com>

* fix(tasks): keep the Harbor export cache per user, not in shared /tmp

The cache sat at a predictable $TMPDIR path, created group-writable,
and any dir there with a task.toml was run and scored as the export.
Default to ~/.cache/dokimi/harbor-export, created 0700.

Signed-off-by: Alexander Rybkin <alexander.rybkin@intel.com>

* fix(tasks): no harbor/benchflow consumer for a solution.yaml tb-v1 task

export() refuses solution.yaml on every run, yet tasks inspect listed
harbor and benchflow, and each refusal left a .staging-* dir in the
cache. Report the refusal as a problem, clean staging in a finally, and
say tasks list/run read three formats now.

Signed-off-by: Alexander Rybkin <alexander.rybkin@intel.com>

* fix(tasks): export a named tb-v1 Dockerfile as environment/Dockerfile

Every Harbor environment builds environment/Dockerfile, so a task whose
compose names build.dockerfile: Dockerfile.x exported with nothing to
build. Rename it, refuse a clash, and pin the subdir-context, kept
COPY tests/solution.sh and working_dir branches with tests.

Signed-off-by: Alexander Rybkin <alexander.rybkin@intel.com>

* docs: count xfailed tests as passing in the tb-v1 verdict rule

Signed-off-by: Anton Volkov <antonwolfy@gmail.com>

* fix(tasks): write non-BMP task metadata into the exported task.toml as TOML reads it

json.dumps escaped a character above U+FFFF as a surrogate pair, which tomllib rejects, so every Harbor and BenchFlow run of such a task failed on parse.

Signed-off-by: Alexander Rybkin <alexander.rybkin@intel.com>

* fix(tasks): key the tb-v1 pytest results by node id, as the exported test.sh reads them

Keyed by the name after the file, a later PASSED b.py::test_x hid an earlier FAILED a.py::test_x: docker-* scored 1.0 where the Harbor export's test.sh scored 0.

Signed-off-by: Alexander Rybkin <alexander.rybkin@intel.com>

* test(tasks): run the exported tb-v1 test.sh against a fake run-tests.sh

Only its awk was run before: dropping the exit code, the tee, the 2>&1, TEST_DIR or the reward file name passed the suite.

Signed-off-by: Alexander Rybkin <alexander.rybkin@intel.com>

* fix(tasks): skip a task.yaml without an instruction when discovering tasks

A non-task directory with a task.yaml and a Dockerfile made tasks list and --all fail for the whole root; it is skipped with a warning now, as a task.md without frontmatter is, and load_task on it still raises.

Signed-off-by: Alexander Rybkin <alexander.rybkin@intel.com>

* fix(tasks): refuse a tb-v1 service command, build arg or target, or host environment on docker-*

One docker run of the default stage, idling, with literal values did none of these, so such a task ran and its failure read as the task's, not a refusal. The idle sleep infinity and TEST_DIR stay accepted; the top-level-key and extra-volume refusals are now pinned too.

Signed-off-by: Alexander Rybkin <alexander.rybkin@intel.com>

* test(tasks): pin that the benchflow harness runs a tb-v1 task from its Harbor export

Dropping harbor_view from BenchflowHarness.run passed the suite; bench would then get a task.yaml directory with no task.toml.

Signed-off-by: Alexander Rybkin <alexander.rybkin@intel.com>

* test(tasks): pin the host-side agent refusal for a tb-v1 task

Narrowing the refusal to task.md tasks passed the suite.

Signed-off-by: Alexander Rybkin <alexander.rybkin@intel.com>

---------

Signed-off-by: Nikolay Petrov <nikolay.a.petrov@intel.com>
Signed-off-by: Alexander Rybkin <alexander.rybkin@intel.com>
Signed-off-by: Anton Volkov <antonwolfy@gmail.com>
Co-authored-by: Alexander Rybkin <alexander.rybkin@intel.com>
Co-authored-by: Anton Volkov <antonwolfy@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

cli-interface-change Changes dokimi's user-facing CLI surface

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants