Implement Phase 1: LangGraph backend MVP

Sets up the full backend foundation for CouncilOS:

- CouncilState TypedDict with all required fields and LangGraph reducers
- Three agent nodes: master_agent (drafts), critic_agent (scores + routes),
  writer_agent (final polish)
- LangGraph graph with cyclic rework loop: Master → Critic → (score < 8:
  back to Master | score ≥ 8: Writer → END)
- Safety valve: MAX_ITERATIONS=5 prevents infinite loops
- FastAPI app with REST endpoints (POST /api/councils/run, GET /api/councils/run/{id})
  and WebSocket endpoint (/ws/council/{run_id}) for real-time agent status events
- In-memory RunStore for Phase 1 (PostgreSQL-backed in Phase 3)
- pytest test suite: state, routing logic, critic parser, agent nodes, API endpoints
- .env.example, .gitignore, docker-compose.yml, Dockerfile

https://claude.ai/code/session_01RfMpt3TbMjZEtK3CAyP5iQ
This commit is contained in:
Claude 2026-02-20 16:33:39 +00:00
parent 34dcfb3dcd
commit 797f02c74d
No known key found for this signature in database
24 changed files with 1472 additions and 0 deletions

47
backend/state.py Normal file
View file

@ -0,0 +1,47 @@
"""
CouncilState the central data structure passed between all agents in LangGraph.
All agents must read from and write to this TypedDict. Agents must not store
state internally; everything passes through CouncilState.
"""
from typing import Annotated, List, Optional
import operator
from typing_extensions import TypedDict
class CouncilState(TypedDict):
"""
The global state shared across all agents in a council run.
Fields:
input_topic: The user's original prompt or uploaded PDF content.
current_draft: The document currently being worked on.
feedback_history: All critic feedback accumulated across loop iterations.
Agents append here never overwrite.
route_decision: Routing signal used by conditional edges.
Values: "rework" | "approve" | custom strings.
messages: LLM message history (system prompts + responses).
Uses operator.add reducer so messages accumulate.
iteration_count: Tracks how many rework loops have occurred.
critic_score: The numeric score (010) assigned by the critic agent.
run_id: Unique identifier for this council run (for WebSocket events).
active_node: Name of the currently executing agent node (for UI updates).
"""
input_topic: str
current_draft: str
feedback_history: Annotated[List[str], operator.add]
route_decision: str
messages: Annotated[list, operator.add]
iteration_count: int
critic_score: Optional[float]
run_id: str
active_node: str
# Approval threshold: critic score must reach this value to exit the loop
APPROVAL_THRESHOLD = 8.0
# Safety limit: maximum number of rework iterations before forcing approval
MAX_ITERATIONS = 5