From 9bd736d14357d5a453978b1755a99c80cd793988 Mon Sep 17 00:00:00 2001 From: Feng GAO Date: Sun, 29 Mar 2026 08:55:24 +0800 Subject: [PATCH] feat: add explicit publishability gate mode --- docs/en/02_START_RESEARCH_GUIDE.md | 14 ++++++++ docs/zh/02_START_RESEARCH_GUIDE.md | 14 ++++++++ src/deepscientist/prompts/builder.py | 45 ++++++++++++++++++++--- src/skills/decision/SKILL.md | 17 +++++++++ src/skills/write/SKILL.md | 3 ++ tests/test_prompt_builder.py | 54 +++++++++++++++++++++++++++- 6 files changed, 142 insertions(+), 5 deletions(-) diff --git a/docs/en/02_START_RESEARCH_GUIDE.md b/docs/en/02_START_RESEARCH_GUIDE.md index e08abefa..5d9b2d34 100644 --- a/docs/en/02_START_RESEARCH_GUIDE.md +++ b/docs/en/02_START_RESEARCH_GUIDE.md @@ -319,6 +319,20 @@ This is the main public knob for round depth. - `user_gated` - the agent may raise a blocking decision only when continuation truly depends on the user +**Advanced startup-contract field: `publishability_gate_mode`** + +This is an optional paper-mode policy field for users who create or edit startup contracts directly. +It is not currently exposed as a first-class dialog control. + +- `off` + - disable the default explicit publishability-gate requirement +- `warn` + - keep publishability judgment visible before paper routing, but treat it as advisory +- `enforce` + - require an explicit publishability gate before paper-facing writing continues + +If omitted, DeepScientist follows the default runtime paper-delivery policy. + ### Launch mode **`launch_mode`** diff --git a/docs/zh/02_START_RESEARCH_GUIDE.md b/docs/zh/02_START_RESEARCH_GUIDE.md index 8a4cf89c..382a70f4 100644 --- a/docs/zh/02_START_RESEARCH_GUIDE.md +++ b/docs/zh/02_START_RESEARCH_GUIDE.md @@ -329,6 +329,20 @@ type StartResearchContractFields = { - `user_gated` - 只有真正依赖用户偏好时,才允许阻塞式决策请求 +**高级 startup-contract 字段:`publishability_gate_mode`** + +这是一个可选的 paper-mode 策略字段,适合直接编辑 startup contract 的高级用户。 +当前它还没有作为单独的表单控件暴露在启动对话框里。 + +- `off` + - 关闭默认的显式 publishability gate 要求 +- `warn` + - 在 paper 路由前仍然要求做 publishability 判断,但默认按建议性检查处理 +- `enforce` + - 在继续 paper-facing 写作前,要求先通过一次显式 publishability gate + +如果不填写,DeepScientist 会沿用当前默认的 paper delivery runtime 策略。 + ### 启动模式 **`launch_mode`** diff --git a/src/deepscientist/prompts/builder.py b/src/deepscientist/prompts/builder.py index 99fa068d..c0e1af81 100644 --- a/src/deepscientist/prompts/builder.py +++ b/src/deepscientist/prompts/builder.py @@ -729,6 +729,15 @@ def _manuscript_edit_mode(snapshot: dict) -> str: return value return "none" + @staticmethod + def _publishability_gate_mode(snapshot: dict) -> str: + startup_contract = snapshot.get("startup_contract") + if isinstance(startup_contract, dict): + value = str(startup_contract.get("publishability_gate_mode") or "").strip().lower() + if value in {"off", "warn", "enforce"}: + return value + return "enforce" + def _research_delivery_policy_block(self, snapshot: dict) -> str: need_research_paper = self._need_research_paper(snapshot) launch_mode = self._launch_mode(snapshot) @@ -736,6 +745,7 @@ def _research_delivery_policy_block(self, snapshot: dict) -> str: baseline_execution_policy = self._baseline_execution_policy(snapshot) review_followup_policy = self._review_followup_policy(snapshot) manuscript_edit_mode = self._manuscript_edit_mode(snapshot) + publishability_gate_mode = self._publishability_gate_mode(snapshot) lines = [ f"- need_research_paper: {need_research_paper}", f"- launch_mode: {launch_mode}", @@ -743,6 +753,7 @@ def _research_delivery_policy_block(self, snapshot: dict) -> str: f"- review_followup_policy: {review_followup_policy if custom_profile == 'review_audit' else 'n/a'}", f"- baseline_execution_policy: {baseline_execution_policy if launch_mode == 'custom' else 'n/a'}", f"- manuscript_edit_mode: {manuscript_edit_mode if custom_profile in {'review_audit', 'revision_rebuttal'} else 'n/a'}", + f"- publishability_gate_mode: {publishability_gate_mode if need_research_paper else 'n/a'}", f"- delivery_mode: {'paper_required' if need_research_paper else 'algorithm_first'}", "- requested_skill_rule: stage-specific execution detail lives in the requested skill; this block only adds runtime launch policy.", "- idea_stage_rule: every accepted idea submission should normally create a new branch/worktree and a new user-visible research node.", @@ -839,8 +850,27 @@ def _research_delivery_policy_block(self, snapshot: dict) -> str: "- delivery_goal: the quest should normally continue until at least one paper-like deliverable exists.", "- main_result_rule: a strong main experiment is evidence, not the endpoint; usually continue into analysis, writing, or strengthening work.", "- paper_branch_rule: writing should normally continue on a dedicated `paper/*` branch/worktree derived from the evidence line rather than mutating the evidence branch itself.", + ] + ) + if publishability_gate_mode != "off": + lines.extend( + [ + "- publishability_gate_rule: at minimum after scout closes, after the first meaningful main result, and before any paper-facing branch, explicitly judge whether the current line still has a credible path to a strong publishable paper; if not, record `stop` or `branch` durably and pivot instead of continuing by inertia.", + "- weak_line_stop_rule: do not keep spending budget on lines whose remaining story is only trivial metric movement, unstable claims, or packaging without clear reviewer-facing value.", + ] + ) + if publishability_gate_mode == "warn": + lines.append( + "- publishability_gate_advisory_rule: treat the publishability gate as an advisory checkpoint; surface the judgment clearly before `write`, but do not treat it as a hard write-admission contract unless some other quest policy says so." + ) + else: + lines.append( + "- paper_branch_admission_rule: a paper-like draft is not itself success; only open or continue `write` when the line still passes the publishability gate." + ) + lines.extend( + [ "- review_gate_rule: before declaring a substantial paper/draft task done, open `review` for an independent skeptical audit; if that audit finds serious gaps, route to `analysis-campaign`, `baseline`, `scout`, or `write` instead of stopping.", - "- stop_rule: do not stop with only an improved algorithm or isolated run logs unless the user explicitly narrows scope.", + "- stop_rule: do not stop with only an improved algorithm or isolated run logs unless the user explicitly narrows scope, but also do not force weak lines to continue merely to produce a paper-like artifact.", ] ) else: @@ -861,6 +891,7 @@ def _interaction_style_block(self, *, default_locale: str, user_message: str, sn bound_conversations = snapshot.get("bound_conversations") or [] need_research_paper = self._need_research_paper(snapshot) decision_policy = self._decision_policy(snapshot) + publishability_gate_mode = self._publishability_gate_mode(snapshot) launch_mode = self._launch_mode(snapshot) custom_profile = self._custom_profile(snapshot) lines = [ @@ -868,6 +899,7 @@ def _interaction_style_block(self, *, default_locale: str, user_message: str, sn f"- current_turn_language_bias: {'zh' if chinese_turn else 'en'}", f"- bound_conversation_count: {len(bound_conversations)}", f"- decision_policy: {decision_policy}", + f"- publishability_gate_mode: {publishability_gate_mode if need_research_paper else 'n/a'}", f"- launch_mode: {launch_mode}", f"- custom_profile: {custom_profile if launch_mode == 'custom' else 'n/a'}", "- collaboration_mode: long-horizon, continuity-first, artifact-aware", @@ -918,9 +950,14 @@ def _interaction_style_block(self, *, default_locale: str, user_message: str, sn ] ) if need_research_paper: - lines.append( - "- completion_protocol: for full_research and similarly end-to-end quests, do not self-stop after one stage or one launched detached run; keep advancing until a paper-like deliverable exists unless the user explicitly stops or narrows scope" - ) + if publishability_gate_mode == "off": + lines.append( + "- completion_protocol: for full_research and similarly end-to-end quests, do not self-stop after one stage or one launched detached run; keep advancing until a paper-like deliverable exists unless the user explicitly stops or narrows scope" + ) + else: + lines.append( + "- completion_protocol: for full_research and similarly end-to-end quests, do not self-stop after one stage or one launched detached run; keep advancing until either a credible paper-like deliverable exists or a durable publishability-gate decision concludes that the current line should stop or branch." + ) else: lines.append( "- completion_protocol: when `startup_contract.need_research_paper` is false, the quest goal is the strongest justified algorithmic result; keep iterating from measured main-experiment results and do not self-route into paper work by default" diff --git a/src/skills/decision/SKILL.md b/src/skills/decision/SKILL.md index cf152237..c1138861 100644 --- a/src/skills/decision/SKILL.md +++ b/src/skills/decision/SKILL.md @@ -43,6 +43,23 @@ It is a cross-cutting control skill that should be used whenever the quest must - a user preference-sensitive choice remains - a blocker needs an explicit route +## Publishability gate + +When `startup_contract.need_research_paper = true`, treat publishability as a first-class route question, not just a writing question. + +Interpret `startup_contract.publishability_gate_mode` as follows when present: + +- `off` + - do not force an explicit publishability gate by default + - still surface clear weakness if the line looks bad, but do not require a dedicated gate checkpoint before `write` +- `warn` + - run an explicit publishability judgment before `write` when the evidence looks mixed + - treat the judgment as advisory unless another quest policy makes it stricter +- `enforce` + - do not route into `write` or a paper-facing branch until the line has passed an explicit publishability gate + +If the field is missing, follow the default runtime policy injected by the prompt builder. + ## Required decision record Every consequential decision should make clear: diff --git a/src/skills/write/SKILL.md b/src/skills/write/SKILL.md index 6a6d035a..fe1eea99 100644 --- a/src/skills/write/SKILL.md +++ b/src/skills/write/SKILL.md @@ -120,8 +120,11 @@ Before writing seriously, confirm: - the claims you intend to write are backed by durable artifacts - the code/diff path is available for method fidelity checks - the evaluation contract is explicit +- if `startup_contract.publishability_gate_mode = enforce`, the current line still passes an explicit publishability gate +- if `startup_contract.publishability_gate_mode = warn`, the current line has at least been judged for publishability and any remaining weakness is being treated consciously rather than ignored If major claims lack evidence, surface the gap first. +If `startup_contract.publishability_gate_mode = off`, do not invent a fake gate requirement that the quest did not ask for. ## Truth sources diff --git a/tests/test_prompt_builder.py b/tests/test_prompt_builder.py index a30ae09e..c845f118 100644 --- a/tests/test_prompt_builder.py +++ b/tests/test_prompt_builder.py @@ -442,7 +442,8 @@ def test_prompt_builder_mentions_long_horizon_no_early_stop_rule(temp_home: Path model="gpt-5.4", ) - assert "keep advancing until a paper-like deliverable exists" in prompt + assert "keep advancing until either a credible paper-like deliverable exists" in prompt + assert "publishability-gate decision concludes that the current line should stop or branch" in prompt assert "do not self-stop after one stage or one launched detached run" in prompt assert "any new message or `/resume` will continue from the same quest" in prompt assert "standby_prefix_rule:" in prompt @@ -537,6 +538,57 @@ def test_prompt_builder_mentions_autonomous_decision_mode(temp_home: Path) -> No assert "explicit quest-completion approval is still allowed" in prompt +def test_prompt_builder_can_disable_publishability_gate_rules(temp_home: Path) -> None: + ensure_home_layout(temp_home) + ConfigManager(temp_home).ensure_files() + service = QuestService(temp_home, skill_installer=SkillInstaller(repo_root(), temp_home)) + snapshot = service.create( + "paper quest without explicit publishability-gate enforcement", + startup_contract={ + "need_research_paper": True, + "publishability_gate_mode": "off", + }, + ) + builder = PromptBuilder(repo_root(), temp_home) + + prompt = builder.build( + quest_id=snapshot["quest_id"], + skill_id="decision", + user_message="Decide the next step for this paper quest.", + model="gpt-5.4", + ) + + assert "publishability_gate_mode: off" in prompt + assert "publishability_gate_rule:" not in prompt + assert "paper_branch_admission_rule:" not in prompt + + +def test_prompt_builder_warn_mode_keeps_gate_advisory(temp_home: Path) -> None: + ensure_home_layout(temp_home) + ConfigManager(temp_home).ensure_files() + service = QuestService(temp_home, skill_installer=SkillInstaller(repo_root(), temp_home)) + snapshot = service.create( + "paper quest with advisory publishability gate", + startup_contract={ + "need_research_paper": True, + "publishability_gate_mode": "warn", + }, + ) + builder = PromptBuilder(repo_root(), temp_home) + + prompt = builder.build( + quest_id=snapshot["quest_id"], + skill_id="decision", + user_message="Decide the next step for this paper quest.", + model="gpt-5.4", + ) + + assert "publishability_gate_mode: warn" in prompt + assert "publishability_gate_rule:" in prompt + assert "paper_branch_admission_rule:" not in prompt + assert "publishability_gate_advisory_rule:" in prompt + + def test_prompt_builder_delegates_stage_specific_sop_to_skills(temp_home: Path) -> None: builder, snapshot = _make_builder(temp_home)