Skip to content
Closed
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
Empty file removed apps/worker/__init__.py
Empty file.
19 changes: 0 additions & 19 deletions apps/worker/main.py

This file was deleted.

2 changes: 1 addition & 1 deletion core/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ class Settings(BaseSettings):
@field_validator("max_budget_usd", mode="before")
@classmethod
def _coerce_budget(cls, v: Any) -> Optional[float]:
if not v:
if v is None or v == "":
return None
try:
return float(v)
Expand Down
2 changes: 1 addition & 1 deletion core/logging.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
Usage
-----
Call ``configure_logging()`` once at each app entrypoint (``apps/api/``,
``apps/mcp_server/``, ``apps/worker/``). Do **not** call it in library code
``apps/mcp_server/``). Do **not** call it in library code
or tests — tests rely on pytest's default capture.

from core.logging import configure_logging
Expand Down
38 changes: 19 additions & 19 deletions core/orchestrator/thesis_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,11 @@
ResearcherAgent,
SynthesizerAgent,
ThesisHeadProvider,
_build_placeholder_citation_audit,
_build_placeholder_critique_result,
_build_placeholder_lit_map,
_build_placeholder_research_plan,
_build_placeholder_synthesis_report,
build_placeholder_citation_audit,
build_placeholder_critique_result,
build_placeholder_lit_map,
build_placeholder_research_plan,
build_placeholder_synthesis_report,
)
from providers.unified import UnifiedLLM

Expand Down Expand Up @@ -157,7 +157,7 @@ async def _execute_inner(self, research_context: ResearchContext) -> ResearchSes
async def _run_planner() -> ResearchPlan:
if not route.activate_head_planner:
obs_logger.log_event("stage_skipped", session_id, {"stage": "head_planner"})
return _build_placeholder_research_plan(research_context.research_question)
return build_placeholder_research_plan(research_context.research_question)
try:
result = await self.head.execute(
task,
Expand All @@ -174,7 +174,7 @@ async def _run_planner() -> ResearchPlan:
obs_logger.log_event(
"stage_failed", session_id, {"stage": "head_planner", "error": str(e)}
)
return _build_placeholder_research_plan(research_context.research_question)
return build_placeholder_research_plan(research_context.research_question)

async def _run_memory() -> MemoryBrief:
try:
Expand Down Expand Up @@ -277,12 +277,12 @@ async def _run_corpus() -> None:
)
except Exception as e:
errors.append(f"researcher: {e}")
lit_map = _build_placeholder_lit_map(research_context.research_question)
lit_map = build_placeholder_lit_map(research_context.research_question)
obs_logger.log_event(
"stage_failed", session_id, {"stage": "researcher", "error": str(e)}
)
else:
lit_map = _build_placeholder_lit_map(research_context.research_question)
lit_map = build_placeholder_lit_map(research_context.research_question)
obs_logger.log_event("stage_skipped", session_id, {"stage": "researcher"})

# ── Phase C: checker ∥ synthesizer (both consume lit_map, neither
Expand All @@ -296,7 +296,7 @@ async def _run_corpus() -> None:
async def _run_checker() -> CitationAudit:
if not route.activate_checker:
obs_logger.log_event("stage_skipped", session_id, {"stage": "checker"})
return _build_placeholder_citation_audit()
return build_placeholder_citation_audit()
try:
result = await self.checker.execute(task, {"blackboard_entries": shared_entries})
audit = cast(CitationAudit, result["output"])
Expand All @@ -310,12 +310,12 @@ async def _run_checker() -> CitationAudit:
obs_logger.log_event(
"stage_failed", session_id, {"stage": "checker", "error": str(e)}
)
return _build_placeholder_citation_audit()
return build_placeholder_citation_audit()

async def _run_synthesizer() -> SynthesisReport:
if not route.activate_synthesizer:
obs_logger.log_event("stage_skipped", session_id, {"stage": "synthesizer"})
return _build_placeholder_synthesis_report(research_context.research_question)
return build_placeholder_synthesis_report(research_context.research_question)
try:
result = await self.synthesizer.execute(
task, {"blackboard_entries": shared_entries}
Expand All @@ -338,7 +338,7 @@ async def _run_synthesizer() -> SynthesisReport:
obs_logger.log_event(
"stage_failed", session_id, {"stage": "synthesizer", "error": str(e)}
)
return _build_placeholder_synthesis_report(research_context.research_question)
return build_placeholder_synthesis_report(research_context.research_question)

citation_audit, synthesis_report = await asyncio.gather(
_run_checker(),
Expand All @@ -359,12 +359,12 @@ async def _run_synthesizer() -> SynthesisReport:
)
except Exception as e:
errors.append(f"critic: {e}")
critique = _build_placeholder_critique_result()
critique = build_placeholder_critique_result()
obs_logger.log_event(
"stage_failed", session_id, {"stage": "critic", "error": str(e)}
)
else:
critique = _build_placeholder_critique_result()
critique = build_placeholder_critique_result()
obs_logger.log_event("stage_skipped", session_id, {"stage": "critic"})

# ── Stage 7: HEAD final pass (supervisor) ──
Expand All @@ -383,12 +383,12 @@ async def _run_synthesizer() -> SynthesisReport:
)
except Exception as e:
errors.append(f"head_supervisor: {e}")
final_critique = _build_placeholder_critique_result()
final_critique = build_placeholder_critique_result()
obs_logger.log_event(
"stage_failed", session_id, {"stage": "head_supervisor", "error": str(e)}
)
else:
final_critique = _build_placeholder_critique_result()
final_critique = build_placeholder_critique_result()
obs_logger.log_event("stage_skipped", session_id, {"stage": "head_supervisor"})

# ── Stage 8: Assemble ResearchSession ──
Expand Down Expand Up @@ -669,6 +669,6 @@ async def _critique_only(self, research_context: ResearchContext) -> CritiqueRes
output: Any = result.get("output")
if isinstance(output, CritiqueResult):
return output
return _build_placeholder_critique_result()
return build_placeholder_critique_result()
except Exception:
return _build_placeholder_critique_result()
return build_placeholder_critique_result()
2 changes: 1 addition & 1 deletion docs/DEVELOPMENT.md
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ This runs the full hook stack: trailing whitespace, YAML/JSON/TOML validity, lar
## Project layout

```
apps/ Entry points — CLI, FastAPI server, MCP server, worker stub.
apps/ Entry points — CLI, FastAPI server, MCP server.
Nothing in apps/ is imported by other packages.

core/ Orchestrator, router, blackboard, memory, schemas, evals, sessions.
Expand Down
2 changes: 1 addition & 1 deletion docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ Output format is driven by the `ENVIRONMENT` setting:

Each entry carries `log_level`, `logger_name`, and an ISO-8601 timestamp. `structlog.contextvars` is included in the chain so any key bound with `structlog.contextvars.bind_contextvars()` (e.g., `session_id`) propagates to every log line emitted within that async context.

`configure_logging()` must be called once at each app entrypoint (`apps/api/`, `apps/mcp_server/`, `apps/worker/`). It must not be called in library code or tests.
`configure_logging()` must be called once at each app entrypoint (`apps/api/`, `apps/mcp_server/`). It must not be called in library code or tests.

## Embedding cache

Expand Down
22 changes: 2 additions & 20 deletions providers/adapters.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from core.orchestrator.compiler import PromptCompiler
from core.schemas import Task
from providers.interfaces import HeadProvider, MiddleProvider, WorkerProvider
from providers.placeholders import generate_placeholder_json
from providers.unified import UnifiedLLM

_PROVIDER_API_KEY_ATTR = {
Expand Down Expand Up @@ -59,7 +60,7 @@ async def generate(
if self.dry_run:
await asyncio.sleep(0.01)
if response_schema:
return _generate_placeholder_json(response_schema)
return generate_placeholder_json(response_schema)
return f"[{self.provider.upper()}/{model_name} DRY_RUN] Processed: {prompt[:50]}..."
if not self.api_key:
raise ValueError(f"No API key configured for provider '{self.provider}'")
Expand Down Expand Up @@ -166,25 +167,6 @@ async def _generate_openai(
return str(response.choices[0].message.content)


def _generate_placeholder_json(schema: Dict[str, Any]) -> str:
props = schema.get("properties", {})
required = schema.get("required", [])
result: Dict[str, Any] = {}
for key, prop in props.items():
prop_type = prop.get("type", "string")
if prop_type == "string":
result[key] = "[DRY_RUN]" if key in required else ""
elif prop_type == "integer" or prop_type == "number":
result[key] = 0
elif prop_type == "boolean":
result[key] = False
elif prop_type == "array":
result[key] = []
elif prop_type == "object":
result[key] = {}
return json.dumps(result)


def _get_available_providers() -> list[str]:
from core.config import get_settings

Expand Down
97 changes: 97 additions & 0 deletions providers/placeholders.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
"""Shared placeholder builders used by adapters, unified, and thesis_flow.

These helpers fabricate well-formed but empty results for two cases:
* ``generate_placeholder_json`` — DRY_RUN mode in the LLM adapter layer.
* ``build_placeholder_*`` — orchestrator fallback when an agent fails or
is skipped, so downstream stages always receive a valid object.
"""

from __future__ import annotations

import json
from typing import Any, Dict

from core.schemas import (
CitationAudit,
CritiqueResult,
LitMap,
ResearchPlan,
SynthesisReport,
)


def generate_placeholder_json(schema: Dict[str, Any]) -> str:
props = schema.get("properties", {})
required = schema.get("required", [])
result: Dict[str, Any] = {}
for key, prop in props.items():
prop_type = prop.get("type", "string")
if prop_type == "string":
result[key] = "[DRY_RUN]" if key in required else ""
elif prop_type in ("integer", "number"):
result[key] = 0
elif prop_type == "boolean":
result[key] = False
elif prop_type == "array":
result[key] = []
elif prop_type == "object":
result[key] = {}
return json.dumps(result)


def build_placeholder_research_plan(question: str) -> ResearchPlan:
return ResearchPlan(
plan_id="placeholder-plan-001",
research_question=question,
subquestions=["What does the existing literature say?", "What methodological gaps exist?"],
strategy="broad_survey",
search_lanes=[
{
"query": "placeholder search",
"source": "semantic_scholar",
"purpose": "initial survey",
},
],
evidence_needs=["literature review", "methodology assessment"],
budget_allocation={"max_searches": 5, "max_papers_per_search": 10},
suggested_methodology="systematic review",
)


def build_placeholder_lit_map(question: str) -> LitMap:
return LitMap(
research_question=question,
supporting=[],
challenging=[],
adjacent=[],
total_found=0,
search_query_used="placeholder_query",
)


def build_placeholder_citation_audit() -> CitationAudit:
return CitationAudit(claims_checked=0, verified_claims=0)


def build_placeholder_synthesis_report(question: str = "") -> SynthesisReport:
return SynthesisReport(
research_question=question,
method_summary={},
dataset_summary={},
metric_summary={},
corpus_insights={},
recommended_reading=[],
cross_paper_comparisons=[],
)


def build_placeholder_critique_result() -> CritiqueResult:
return CritiqueResult(
strengths=[],
weaknesses=[],
gaps=[],
counterarguments=[],
suggestions=[],
methodological_notes=[],
overall_assessment="",
)
Loading