Skip to content

Commit f95c836

Browse files
author
zxhwolfe
committed
fix: make Radar helper invocation deterministic
1 parent 5f81fa7 commit f95c836

9 files changed

Lines changed: 104 additions & 2 deletions

File tree

‎CHANGELOG.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ Release candidate prepared for validation. This version is not tagged or publish
1818
- Required successful `feed`/`sources`/`history` helper execution without an explicit custom origin and with contract-valid Radar JSON before Host Eval records runtime workflow evidence; source reads, help, failed calls, and composed shell commands no longer qualify.
1919
- Synced authenticated Premium identity forwarding guidance and preserved the published v0.2.1 release record below.
2020
- Made live Host Eval networking an explicit, single-domain, workspace-write opt-in executed only in a clean temporary detached worktree; release evidence now binds and verifies that network/sandbox policy.
21+
- Fixed portable Radar helper invocation to require `python3`, global options before the subcommand, and compact shortlist scans that default to 12 and normally cap at 24 candidates.
2122

2223
## [Unreleased]
2324

‎references/topic-intelligence-quality-contract.md‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,18 @@ After the first domain-preserving bounded query:
4444

4545
For example, a title containing the letters `ai` only as part of an unrelated word must not become an AI finalist merely because `/feed?q=AI` returned it.
4646

47+
### Use the portable helper deterministically
48+
49+
Resolve `scripts/topic_radar_client.py` relative to the current Skill's `SKILL.md`; never hard-code an installation directory. `python3` is the only supported interpreter entry point. Do not use `python`, `python2`, or execute the helper directly without an interpreter.
50+
51+
Place helper-wide arguments before the subcommand. The canonical shortlist form is:
52+
53+
```text
54+
python3 <skill-local-helper> --timeout 30 feed --q AI --limit 12
55+
```
56+
57+
For a normal shortlist or topic-selection task, the initial candidate set defaults to 12 and should not exceed 24. Do not fetch 100 candidates merely to select three topics. Exceed 24 only when the user explicitly asks for a large list, export, or larger sample.
58+
4759
## 2. Public Skill cost boundary: no AI Workstation model spend
4860

4961
The downloadable/public Topic Intelligence Skills are intended to spread widely without consuming the publisher's server-side LLM quota for anonymous users.

‎scripts/grade_host_eval.py‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ def _runtime_invocation(command: str, skill: str) -> str | None:
123123
return None
124124
marker = f"/{skill}/scripts/topic_radar_client.py"
125125
executable = tokens[0].replace("\\", "/").rsplit("/", 1)[-1]
126-
if executable not in {"python", "python3"}:
126+
if executable != "python3":
127127
return None
128128
helper = tokens[1].replace("\\", "/") if len(tokens) > 1 else ""
129129
if not helper.endswith(marker):
@@ -426,7 +426,7 @@ def grade_report(payload: Mapping[str, Any]) -> dict[str, Any]:
426426
"cases": graded_cases,
427427
"grading_note": (
428428
"passive Skill names/file reads are discovery evidence, not invocation; "
429-
"runtime use requires a successful Python invocation of a Skill-local helper "
429+
"runtime use requires a successful python3 invocation of a Skill-local helper "
430430
"feed/sources/history operation plus validated JSON output, and handoff use "
431431
"requires the schema in an agent message"
432432
),

‎skills/creator-topic-opportunity-research/SKILL.md‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,16 @@ Resolve the bundled helper relative to this `SKILL.md`:
6060
scripts/topic_radar_client.py
6161
```
6262

63+
Invoke that Skill-local helper only with `python3`. Do not use `python`, `python2`, or execute the helper directly without an interpreter. Place helper-wide arguments such as `--timeout` before the `feed`, `sources`, or `history` subcommand. Resolve the helper relative to this `SKILL.md`; do not hard-code an installation directory.
64+
65+
Use this canonical shortlist form:
66+
67+
```text
68+
python3 <skill-local-helper> --timeout 30 feed --q AI --limit 12
69+
```
70+
71+
For a normal shortlist or topic-selection task, prefer an initial `feed --limit 12` and do not exceed 24 candidates. Exceed 24 only when the user explicitly asks for a large list, export, or larger sample.
72+
6373
Do not assume a repository-root helper, sibling checkout, or `../akaiagents` exists. The helper is read-only transport only: no crawler, score, persistence, topic matching, model backend, Premium Insight, or credential storage.
6474

6575
### Live evidence is exclusive

‎skills/creator-topic-opportunity-research/references/quality-contract.md‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,18 @@ After the first domain-preserving bounded query:
4444

4545
For example, a title containing the letters `ai` only as part of an unrelated word must not become an AI finalist merely because `/feed?q=AI` returned it.
4646

47+
### Use the portable helper deterministically
48+
49+
Resolve `scripts/topic_radar_client.py` relative to the current Skill's `SKILL.md`; never hard-code an installation directory. `python3` is the only supported interpreter entry point. Do not use `python`, `python2`, or execute the helper directly without an interpreter.
50+
51+
Place helper-wide arguments before the subcommand. The canonical shortlist form is:
52+
53+
```text
54+
python3 <skill-local-helper> --timeout 30 feed --q AI --limit 12
55+
```
56+
57+
For a normal shortlist or topic-selection task, the initial candidate set defaults to 12 and should not exceed 24. Do not fetch 100 candidates merely to select three topics. Exceed 24 only when the user explicitly asks for a large list, export, or larger sample.
58+
4759
## 2. Public Skill cost boundary: no AI Workstation model spend
4860

4961
The downloadable/public Topic Intelligence Skills are intended to spread widely without consuming the publisher's server-side LLM quota for anonymous users.

‎skills/evidence-backed-content-brief/SKILL.md‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,16 @@ Resolve the bundled public helper relative to this `SKILL.md`:
5858
scripts/topic_radar_client.py
5959
```
6060

61+
Invoke that Skill-local helper only with `python3`. Do not use `python`, `python2`, or execute the helper directly without an interpreter. Place helper-wide arguments such as `--timeout` before the `feed`, `sources`, or `history` subcommand. Resolve the helper relative to this `SKILL.md`; do not hard-code an installation directory.
62+
63+
Use this canonical shortlist form:
64+
65+
```text
66+
python3 <skill-local-helper> --timeout 30 feed --q AI --limit 12
67+
```
68+
69+
For a normal shortlist or topic-selection task, prefer an initial `feed --limit 12` and do not exceed 24 candidates. Exceed 24 only when the user explicitly asks for a large list, export, or larger sample.
70+
6171
Do not assume a repository-root helper, sibling checkout, or `../akaiagents` exists. A native host connection may call the same public no-cost contract directly.
6272

6373
The helper is transport-only: no crawler, score, persistence, topic matching, model backend, or Premium Insight is implemented inside the Skill.

‎skills/evidence-backed-content-brief/references/quality-contract.md‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,18 @@ After the first domain-preserving bounded query:
4444

4545
For example, a title containing the letters `ai` only as part of an unrelated word must not become an AI finalist merely because `/feed?q=AI` returned it.
4646

47+
### Use the portable helper deterministically
48+
49+
Resolve `scripts/topic_radar_client.py` relative to the current Skill's `SKILL.md`; never hard-code an installation directory. `python3` is the only supported interpreter entry point. Do not use `python`, `python2`, or execute the helper directly without an interpreter.
50+
51+
Place helper-wide arguments before the subcommand. The canonical shortlist form is:
52+
53+
```text
54+
python3 <skill-local-helper> --timeout 30 feed --q AI --limit 12
55+
```
56+
57+
For a normal shortlist or topic-selection task, the initial candidate set defaults to 12 and should not exceed 24. Do not fetch 100 candidates merely to select three topics. Exceed 24 only when the user explicitly asks for a large list, export, or larger sample.
58+
4759
## 2. Public Skill cost boundary: no AI Workstation model spend
4860

4961
The downloadable/public Topic Intelligence Skills are intended to spread widely without consuming the publisher's server-side LLM quota for anonymous users.

‎tests/test_host_eval_evidence.py‎

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,16 @@ def _helper_event(
5858
})
5959

6060

61+
def _helper_command_event(command: str) -> str:
62+
return _event({
63+
"type": "command_execution",
64+
"command": command,
65+
"aggregated_output": _feed_payload(),
66+
"exit_code": 0,
67+
"status": "completed",
68+
})
69+
70+
6171
def _trigger_case(expected_skill: str | None) -> dict[str, object]:
6272
return {
6373
"id": "case",
@@ -115,6 +125,22 @@ def test_runtime_helper_command_is_strong_use_evidence(self) -> None:
115125
"pass_expected_skill_runtime_observed",
116126
)
117127

128+
def test_only_python3_is_runtime_evidence(self) -> None:
129+
helper = f"/skills/{CREATOR}/scripts/topic_radar_client.py"
130+
valid = observe_evidence(_helper_command_event(
131+
f"python3 {helper} --timeout 30 feed --q AI --limit 12"
132+
))
133+
self.assertEqual(valid["runtime_operations"], [f"{CREATOR}:feed"])
134+
135+
for command in (
136+
f"python {helper} --timeout 30 feed --q AI --limit 12",
137+
f"python2 {helper} --timeout 30 feed --q AI --limit 12",
138+
f"{helper} --timeout 30 feed --q AI --limit 12",
139+
):
140+
with self.subTest(command=command):
141+
evidence = observe_evidence(_helper_command_event(command))
142+
self.assertEqual(evidence["runtime_use_skills"], [])
143+
118144
def test_wrong_runtime_helper_is_a_real_negative_signal(self) -> None:
119145
stdout = _helper_event(BRIEF, arguments=" --limit 3")
120146
evidence = observe_evidence(stdout)

‎tests/test_skill_quality_contract.py‎

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,25 @@ def test_quality_contract_blocks_duplicate_selection_after_handoff(self) -> None
5959
self.assertIn("do **not** run another broad/bounded candidate-selection feed pass", content)
6060
self.assertIn("finalist `/history` request is normal", content)
6161

62+
def test_portable_helper_invocation_is_deterministic(self) -> None:
63+
canonical = CANONICAL.read_text(encoding="utf-8")
64+
self.assertIn("`python3` is the only supported interpreter entry point", canonical)
65+
self.assertIn("Do not use `python`, `python2`", canonical)
66+
self.assertIn("helper-wide arguments before the subcommand", canonical)
67+
self.assertIn("--timeout 30 feed --q AI --limit 12", canonical)
68+
self.assertIn("defaults to 12", canonical)
69+
self.assertIn("should not exceed 24", canonical)
70+
self.assertIn("Do not fetch 100 candidates", canonical)
71+
72+
for skill in (CREATOR, BRIEF):
73+
content = (skill / "SKILL.md").read_text(encoding="utf-8")
74+
self.assertIn("only with `python3`", content)
75+
self.assertIn("Do not use `python`, `python2`", content)
76+
self.assertIn("helper-wide arguments", content)
77+
self.assertIn("--timeout 30 feed --q AI --limit 12", content)
78+
self.assertIn("initial `feed --limit 12`", content)
79+
self.assertIn("do not exceed 24 candidates", content)
80+
6281
def test_skill_definitions_and_openai_metadata_point_to_quality_contract(self) -> None:
6382
creator_skill = (CREATOR / "SKILL.md").read_text(encoding="utf-8")
6483
brief_skill = (BRIEF / "SKILL.md").read_text(encoding="utf-8")

0 commit comments

Comments
 (0)