Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions docs/en/02_START_RESEARCH_GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`**
Expand Down
14 changes: 14 additions & 0 deletions docs/zh/02_START_RESEARCH_GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`**
Expand Down
45 changes: 41 additions & 4 deletions src/deepscientist/prompts/builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -729,20 +729,31 @@ 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)
custom_profile = self._custom_profile(snapshot)
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}",
f"- custom_profile: {custom_profile if launch_mode == 'custom' else 'n/a'}",
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.",
Expand Down Expand Up @@ -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:
Expand All @@ -861,13 +891,15 @@ 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 = [
f"- configured_default_locale: {default_locale}",
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",
Expand Down Expand Up @@ -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"
Expand Down
17 changes: 17 additions & 0 deletions src/skills/decision/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
3 changes: 3 additions & 0 deletions src/skills/write/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
54 changes: 53 additions & 1 deletion tests/test_prompt_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)

Expand Down