Skip to content
Merged
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
3 changes: 3 additions & 0 deletions council/researcher.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
from council.research import (extract_date_hint, fetch_extract, inject_recency, is_breaking,
is_time_sensitive, order_by_recency, route_engines,
search_structured)
from council.worker import LENSES


def _num_env(key: str, default, cast):
Expand Down Expand Up @@ -161,9 +162,11 @@ def _draft(self, brief: str, evidence: list[dict], local: list[dict] | None = No
cite = " and ".join(p for p in (
("[S#] for web sources" if evidence else ""),
("[L#] for your documents" if local else "")) if p) or "[S#]"
lens_instruction = LENSES.get(self.lens, LENSES["neutral"])
return self._generate(
f"{role} Today is {self._today()}. Using ONLY the sources below, "
"write your findings on the brief:\n"
f" • {lens_instruction}\n"
" • Lead with the most decision-relevant findings; concrete numbers and dates.\n"
" • CURRENCY MATTERS: when sources span time or conflict, trust the MOST RECENT "
"(sources are listed with their dates, freshest first), and STATE the date of any "
Expand Down
3 changes: 2 additions & 1 deletion docs/GLOSSARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,8 @@ uses the old names in its older entries.
work over time while leaving ungated work open to newcomers.
- **Lens** — an angle of attack given to an analyst/node via its prompt (e.g.
`opportunity`, `skeptic`, `first_principles`, `practical`) to pull different ideas
from the same or different models.
from the same or different models (applies uniformly to both chat jobs, `pw ask`,
and research jobs, `pw research`/network research jobs — R38).
- **Model-downgrade** — the real cheat under a non-cash credit: a node silently running
a smaller or more-quantized model than it claims, to earn credit for less work.
- **Cross-hardware verification** — confirming that honest answers from different
Expand Down
46 changes: 46 additions & 0 deletions tests/test_researcher_lens.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
"""R38 (lens-axis) — lens now drives the research-job prompt too, not just chat's
council/worker.py::PerspectiveWorker.answer(). Mirrors that file's own
LENSES.get(self.lens, LENSES["neutral"]) fallback pattern in
ResearchWorker._draft(), and locks in backward compatibility for
council/local.py, which hardcodes lens="independent analyst" for single-player
`pw research` — that string is not a LENSES key, so it must safely fall
through to the neutral instruction rather than raising."""
import council.researcher as RW
from council.worker import LENSES

EVIDENCE = [{"title": "T", "url": "https://x.test/1", "host": "x.test",
"snippet": "s", "date_hint": ""}]


def _captured_prompt(monkeypatch, lens):
captured = {}

def fake_generate(self, prompt, num_predict):
captured["prompt"] = prompt
return "draft [S1].", 5

monkeypatch.setattr(RW.ResearchWorker, "_generate", fake_generate)
w = RW.ResearchWorker(worker_id="m", model="m", lens=lens)
w._draft("a brief about basic chemistry facts", EVIDENCE)
return captured["prompt"]


def test_draft_prompt_carries_skeptic_lens(monkeypatch):
prompt = _captured_prompt(monkeypatch, "skeptic")
assert LENSES["skeptic"] in prompt


def test_draft_prompt_carries_neutral_lens_by_default(monkeypatch):
# default ResearchWorker.lens == "neutral" — same backward-compat text as worker.py
prompt = _captured_prompt(monkeypatch, "neutral")
assert LENSES["neutral"] in prompt


def test_draft_prompt_falls_back_to_neutral_for_local_py_lens_string(monkeypatch):
# council/local.py hardcodes lens="independent analyst" for single-player `pw
# research` — not a LENSES key. Must not raise, and must fall through to the
# same neutral instruction LENSES.get(..., LENSES["neutral"]) uses everywhere
# else. This is the regression test guaranteeing local.py keeps working
# unmodified (council/local.py is intentionally untouched by R38).
prompt = _captured_prompt(monkeypatch, "independent analyst")
assert LENSES["neutral"] in prompt
Loading