Architecture & Concepts

How yeaboi is built — the four architectural layers, the LangGraph agent graph, prompt construction, guardrails, multi-provider LLM support, and a condensed reference for the underlying LangGraph/LangChain APIs.

Four Layers

LayerImplementation
InterfaceFull-screen TUI with animated splash, mode selection, session editor, pipeline progress, streaming output, and dark/light themes
Prompt ConstructionScrum Master persona, ARC-structured prompts per node, few-shot examples, adaptive question templates
ModelAnthropic Claude (primary), OpenAI GPT, Google Gemini — swappable via LLM_PROVIDER env var
Data & StorageSQLite session store (~/.yeaboi/data/sessions.db) with team analysis profiles, token usage tracking, optional Jira/Confluence/Azure DevOps integration

Design Principles

Three design principles guide the codebase:

  1. Robust Infrastructure — agent frameworks (LangChain, LangGraph), graceful rate-limit retry with exponential backoff, crash-safe session persistence
  2. Modularity — decoupled CLI/TUI/REPL/agent/tools/prompts, one concern per module, UI system with 4 subsystems
  3. Continuous Evaluation — golden dataset evaluators, contract tests with VCR cassettes, token budget monitoring

Agent Graph (LangGraph)

Auto-generated via make graph:

Agent Graph
graph TD
    START([START]) --> intake[project_intake]
    intake --> |questionnaire loop| intake
    intake --> analyzer[project_analyzer]
    analyzer --> features[feature_generator]
    features --> |human review| features
    features --> stories[story_writer]
    stories --> |human review| stories
    stories --> tasks[task_decomposer]
    tasks --> |human review| tasks
    tasks --> sprints[sprint_planner]
    sprints --> |human review| sprints
    sprints --> sync[jira_sync]
    sync --> END([END])

Node Descriptions

NodeResponsibility
Project IntakeRuns the discovery questionnaire (smart/standard/quick mode) to gather all project context
Project AnalyzerSynthesizes questionnaire answers into a structured ProjectAnalysis with name, type, goals, tech stack, constraints, and risks
Feature GeneratorDecomposes the analysis into high-level features with priority levels. For small projects (≤2 sprints, ≤3 goals), creates a single sentinel epic instead
Story WriterBreaks features into user stories with persona/goal/benefit, short titles, Given/When/Then acceptance criteria, Fibonacci story points (auto-split >8), discipline tagging, DoD flags, and points rationale
Task DecomposerBreaks stories into concrete tasks with auto-tagged labels (Code/Documentation/Infrastructure/Testing), test plans for code tasks, AI coding prompts, and dedicated documentation sub-tasks
Sprint PlannerAllocates stories to sprints using per-sprint net velocity (bank holidays, PTO, unplanned %, onboarding, KTLO deducted). Handles capacity overflow with 3 options. Highlights impacted sprints
Jira SyncPushes the finalized plan to Jira with idempotent batch creation: Features → Labels, Stories → linked to Epic, Tasks → Sub-tasks, Sprints → created and assigned

The ReAct Loop

The foundational reasoning pattern:

Thought → Action → Observation → (repeat until done)
  1. Thought — reason about the current state and what to do next
  2. Action — call a tool or take a step
  3. Observation — see the result, decide whether to continue or answer

TUI System

The ui/ package provides a full-screen terminal UI with four subsystems:

SubsystemPurpose
mode_select/Full-screen mode selection with ASCII art titles, project cards with pipeline progress indicators, project list with half-card peek stubs at viewport edges. Includes Analysis, Planning, Usage, and Settings pages
provider_select/LLM and tool provider selection (block-character logos for Claude/GPT/Gemini), issue tracking setup, verification flow
session/Main session UI — description input, intake questions, summary review, pipeline stages with artifact editing, epic review, calibration banners, Jira/Azure DevOps export, chat. Dry-run mode with mock data
shared/Animations (typewriter, pulse), ASCII font rendering, reusable components (Theme, buttons, scrollbar, progress dots, viewport), mouse scroll handling

Visual features:

  • Rounded borders with consistent padding and arrow-key navigation
  • Sticky group headers — epic titles pin at top when scrolling, with decryption-style morph animation between sections
  • Scrollbar — vertical track with thumb for pipeline stages and summary review
  • Capacity bars — per-sprint with reduced velocity for bank-holiday/PTO-impacted sprints (amber border + annotations)
  • Project cards — one-shot white pulse animation on Enter, pipeline progress badges

The repl/ package is the legacy REPL kept for backwards compatibility and CLI-flag-driven flows (--quick, --full-intake, --questionnaire, --mode).

State Schema

  • ScrumState is a TypedDict (LangGraph convention for graph state)
  • messages uses Annotated[list[BaseMessage], add_messages] for append semantics
  • Frozen dataclasses for artifacts — Feature, UserStory, Task, Sprint, ProjectAnalysis (immutable once created, serializable via asdict())
  • Mutable dataclass for QuestionnaireState — updated incrementally by the intake node
  • Artifact lists use Annotated[list[...], operator.add] so nodes can return items that get appended

Agent Classification

PropertyValue
Agency LevelLevel 3–4 (self-looping + multi-agent coordination)
Reasoning PatternReAct (Thought → Action → Observation → repeat)
InterfaceTerminal CLI (full-screen TUI + legacy REPL)
DomainScrum project management

Prompt Construction

System Prompt Persona

The agent operates as a senior Scrum Master and enforces all standards defined in the Scrum Standards section.

Core constraints:

  • User stories follow the format: "As a [persona], I want to [goal], so that [benefit]"
  • Every story includes acceptance criteria in Given/When/Then format covering happy path, negative path, and edge cases
  • Story points use the Fibonacci scale (1, 2, 3, 5, 8)
  • Maximum 8 points per story — auto-split if exceeded
  • Issue hierarchy enforced: Epic → Feature → User Story → Sub-Task (plus Spikes)
  • Definition of Done validated against checklists
  • Sprint capacity respected — no overloading

Prompting Techniques

TechniqueWhere Applied
ARC FrameworkEvery node prompt — Ask (what), Requirements (constraints), Context (background)
Few-Shot PromptingStory Writer node — examples of well-written user stories
Chain-of-ThoughtFeature Generator — step-by-step reasoning about scope decomposition
The Flipped PromptProject Intake — agent asks the user what information it needs before proceeding
Iterative PromptingRefinement loop — output improves with each round of user feedback
Neutral PromptsEvaluation — avoid leading phrasing that biases the LLM

Multimodal Content Blocks (Screenshot Paste)

LangChain message content is either a plain string or a list of typed content blocks. When the user pastes a screenshot with Ctrl+V, the prompt is sent as:

HumanMessage(content=[
    {"type": "text", "text": prompt},
    {"type": "image", "source_type": "base64", "mime_type": "image/png", "data": "<b64>"},
])

This is LangChain's portable block shape — langchain-core translates it into each provider's native format, so the same code works for Anthropic, OpenAI, Google, and Bedrock. Three design rules keep it robust (agent/llm.py):

  • Paths, not bytes, in state — pasted images are saved as PNG files under ~/.yeaboi/attachments/<scope>/; graph state carries only the file paths (pasted_images, review_feedback_images, chat_images), so sessions stay small and screenshots survive --resume. Base64 encoding happens only at the moment of the LLM call (invoke_with_images).
  • state["messages"] stays text-only — many nodes do string operations on message content, so images are attached to the outgoing message list at invoke time and never stored in history.
  • Graceful degradation — a deleted file is skipped with a warning; a non-vision model that rejects image blocks triggers one text-only retry. The pipeline never fails because of a screenshot.

Deleting an [image #N] chip from the textbox detaches that screenshot before send (ui/shared/_attachments.py:referenced_images).

Guardrails

Input Guardrails (4 layers)

LayerMethodDescription
Length capRegex (instant)Max 5,000 characters — prevents accidental file pastes
Prompt injectionRegex (instant)10+ patterns: "ignore previous instructions", "you are now", "act as", "override", etc.
Profanity filterRegex (instant)Catches obvious abuse and low-quality inputs
Relevance classifierLLM (cheap)Allowlist passes known-good inputs; unknowns go to a cheap classifier (Haiku/gpt-4o-mini) to check RELEVANT vs OFF_TOPIC. Falls back to allowing on failure.

Output Guardrails (4 layers)

LayerDescription
Story formatValidates all stories have non-trivial persona, goal, and benefit (>=2 chars each)
AC coverageEach story should have >=2 acceptance criteria, with at least one covering negative/edge/error scenarios
Sprint capacityNo sprint exceeds team velocity
Unrealistic loadsFlags sprints packed to the limit

Human-in-the-Loop

Every pipeline stage has an accept/edit/reject checkpoint. High-risk tool calls (Jira writes, Confluence writes) require explicit user confirmation.

Multi-Provider LLM Support

The agent supports four LLM providers. Set via LLM_PROVIDER in .env:

ProviderEnv VarKey FormatValue
Anthropic (default)ANTHROPIC_API_KEYsk-ant-...anthropic
OpenAIOPENAI_API_KEYsk-...openai
GoogleGOOGLE_API_KEYAIza...google
AWS BedrockAWS_REGIONIAM credentials (no key)bedrock

OpenAI, Google, and Bedrock are lazy-imported — install with uv sync --extra all-providers or individually with --extra openai / --extra google / --extra bedrock.

Bedrock uses IAM credentials automatically (instance role, ~/.aws/credentials, or env vars). On Lightsail/EC2, the AWS profile is auto-detected from ~/.aws/config. No API key required.

Agentic Blueprint Reference

Condensed technical reference for the LangGraph patterns and LangChain APIs used.

Core Graph Setup

from langgraph.graph import StateGraph, MessagesState, START, END
from langgraph.prebuilt import ToolNode
from langchain_openai import ChatOpenAI

llm = ChatOpenAI(model="gpt-4o-mini")
model_with_tools = llm.bind_tools(tools)

graph = StateGraph(MessagesState)

The Two Core Nodes

def call_model(state: MessagesState):
    """Call the LLM with current messages."""
    response = model_with_tools.invoke(state["messages"])
    return {"messages": [response]}

def should_continue(state: MessagesState):
    """Route: tools if tool_calls present, otherwise END."""
    last_message = state["messages"][-1]
    if last_message.tool_calls:
        return "tools"
    return END

Wiring the Graph

tool_node = ToolNode(tools)

graph.add_node("agent", call_model)
graph.add_node("tools", tool_node)

graph.add_edge(START, "agent")
graph.add_conditional_edges("agent", should_continue, ["tools", END])
graph.add_edge("tools", "agent")

app = graph.compile()
    START → agent ──should_continue?──→ END
               ▲          │
               │       "tools"
               │          ▼
               └─────── tools

Creating Tools

from langchain_core.tools import tool

@tool
def search_database(query: str) -> str:
    """Search the product database for items matching the query."""
    return results

The docstring is critical — the LLM reads it to decide when to use the tool.

Memory

from langgraph.checkpoint.memory import MemorySaver

memory = MemorySaver()
app = graph.compile(checkpointer=memory)

config = {"configurable": {"thread_id": "user-123"}}
app.invoke({"messages": [("human", "My name is Omar")]}, config)

Streaming

from langchain_core.messages import AIMessageChunk, HumanMessage

for chunk, metadata in app.stream(
    {"messages": [HumanMessage(content="Plan my project")]},
    config,
    stream_mode="messages",
):
    if isinstance(chunk, AIMessageChunk) and chunk.content:
        print(chunk.content, end="", flush=True)

Quick Reference — All APIs

Prompting: ChatPromptTemplate | FewShotPromptTemplate | ARC framework | pipe operator | | StrOutputParser | sequential chains

Graph: StateGraph | MessagesState | START / END | .add_node() | .add_edge() | .add_conditional_edges() | .compile()

Tools: @tool decorator | ToolNode | .bind_tools() | create_react_agent

Memory: MemorySaver | checkpointer | thread_id

Streaming: app.stream() | stream_mode="messages" | AIMessageChunk