Sisyphus agent recreated in LangChain to figure out how it works and how to use it

This commit is contained in:
2026-04-15 12:47:38 -06:00
parent ff3419a714
commit 9bab6a0c2d
14 changed files with 1745 additions and 0 deletions
@@ -0,0 +1,100 @@
"""
Shared state schema for the Sisyphus orchestrator graph.
In LangGraph, state is the single source of truth that flows through every node.
This is analogous to Loki's per-agent session context, but unified into one typed
dictionary that the entire graph shares.
Loki Concept Mapping:
- Loki session context → SisyphusState (TypedDict)
- Loki todo__init / todo__add → SisyphusState.todos list
- Loki agent__spawn outputs → SisyphusState.agent_outputs dict
- Loki intent classification → SisyphusState.intent field
"""
from __future__ import annotations
from dataclasses import dataclass, field
from typing import Annotated, Literal
from langchain_core.messages import BaseMessage
from langgraph.graph.message import add_messages
from typing_extensions import TypedDict
# ---------------------------------------------------------------------------
# Intent types — mirrors Loki's Sisyphus classification table
# ---------------------------------------------------------------------------
IntentType = Literal[
"trivial", # Single file, known location, typo fix → handle yourself
"exploration", # "Find X", "Where is Y" → spawn explore
"implementation", # "Add feature", "Fix bug" → spawn coder
"architecture", # Design questions, oracle triggers → spawn oracle
"ambiguous", # Unclear scope → ask user
]
# ---------------------------------------------------------------------------
# Todo item — mirrors Loki's built-in todo system
# ---------------------------------------------------------------------------
@dataclass
class TodoItem:
"""A single task in the orchestrator's todo list."""
id: int
task: str
done: bool = False
def _merge_todos(existing: list[TodoItem], new: list[TodoItem]) -> list[TodoItem]:
"""
Reducer for the todos field.
LangGraph requires a reducer for any state field that can be written by
multiple nodes. This merges by id: if a todo with the same id already
exists, the incoming version wins (allows marking done).
"""
by_id = {t.id: t for t in existing}
for t in new:
by_id[t.id] = t
return list(by_id.values())
# ---------------------------------------------------------------------------
# Core graph state
# ---------------------------------------------------------------------------
class SisyphusState(TypedDict):
"""
The shared state that flows through every node in the Sisyphus graph.
Annotated fields use *reducers* — functions that merge concurrent writes.
Without reducers, parallel node outputs would overwrite each other.
"""
# Conversation history — the `add_messages` reducer appends new messages
# instead of replacing the list. This is critical: every node adds its
# response here, and downstream nodes see the full history.
#
# Loki equivalent: each agent's chat session accumulates messages the same
# way, but messages are scoped per-agent. In LangGraph the shared message
# list IS the inter-agent communication channel.
messages: Annotated[list[BaseMessage], add_messages]
# Classified intent for the current request
intent: IntentType
# Which agent the supervisor routed to last
next_agent: str
# Iteration counter — safety valve analogous to Loki's max_auto_continues
iteration_count: int
# Todo list for multi-step tracking (mirrors Loki's todo__* tools)
todos: Annotated[list[TodoItem], _merge_todos]
# Accumulated outputs from sub-agent nodes, keyed by agent name.
# The supervisor reads these to decide what to do next.
agent_outputs: dict[str, str]
# Final synthesized answer to return to the user
final_output: str
# The working directory / project path (mirrors Loki's project_dir variable)
project_dir: str