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
| Layer | Implementation |
|---|---|
| Interface | Full-screen TUI with animated splash, mode selection, session editor, pipeline progress, streaming output, and dark/light themes |
| Prompt Construction | Scrum Master persona, ARC-structured prompts per node, few-shot examples, adaptive question templates |
| Model | Anthropic Claude (primary), OpenAI GPT, Google Gemini — swappable via LLM_PROVIDER env var |
| Data & Storage | SQLite 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:
- Robust Infrastructure — agent frameworks (LangChain, LangGraph), graceful rate-limit retry with exponential backoff, crash-safe session persistence
- Modularity — decoupled CLI/TUI/REPL/agent/tools/prompts, one concern per module, UI system with 4 subsystems
- Continuous Evaluation — golden dataset evaluators, contract tests with VCR cassettes, token budget monitoring
Agent Graph (LangGraph)
Auto-generated via make 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
| Node | Responsibility |
|---|---|
| Project Intake | Runs the discovery questionnaire (smart/standard/quick mode) to gather all project context |
| Project Analyzer | Synthesizes questionnaire answers into a structured ProjectAnalysis with name, type, goals, tech stack, constraints, and risks |
| Feature Generator | Decomposes the analysis into high-level features with priority levels. For small projects (≤2 sprints, ≤3 goals), creates a single sentinel epic instead |
| Story Writer | Breaks 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 Decomposer | Breaks 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 Planner | Allocates 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 Sync | Pushes 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)
- Thought — reason about the current state and what to do next
- Action — call a tool or take a step
- Observation — see the result, decide whether to continue or answer
TUI System
The ui/ package provides a full-screen terminal UI with four subsystems:
| Subsystem | Purpose |
|---|---|
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) messagesusesAnnotated[list[BaseMessage], add_messages]for append semantics- Frozen dataclasses for artifacts —
Feature,UserStory,Task,Sprint,ProjectAnalysis(immutable once created, serializable viaasdict()) - 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
| Property | Value |
|---|---|
| Agency Level | Level 3–4 (self-looping + multi-agent coordination) |
| Reasoning Pattern | ReAct (Thought → Action → Observation → repeat) |
| Interface | Terminal CLI (full-screen TUI + legacy REPL) |
| Domain | Scrum 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
| Technique | Where Applied |
|---|---|
| ARC Framework | Every node prompt — Ask (what), Requirements (constraints), Context (background) |
| Few-Shot Prompting | Story Writer node — examples of well-written user stories |
| Chain-of-Thought | Feature Generator — step-by-step reasoning about scope decomposition |
| The Flipped Prompt | Project Intake — agent asks the user what information it needs before proceeding |
| Iterative Prompting | Refinement loop — output improves with each round of user feedback |
| Neutral Prompts | Evaluation — 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)
| Layer | Method | Description |
|---|---|---|
| Length cap | Regex (instant) | Max 5,000 characters — prevents accidental file pastes |
| Prompt injection | Regex (instant) | 10+ patterns: "ignore previous instructions", "you are now", "act as", "override", etc. |
| Profanity filter | Regex (instant) | Catches obvious abuse and low-quality inputs |
| Relevance classifier | LLM (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)
| Layer | Description |
|---|---|
| Story format | Validates all stories have non-trivial persona, goal, and benefit (>=2 chars each) |
| AC coverage | Each story should have >=2 acceptance criteria, with at least one covering negative/edge/error scenarios |
| Sprint capacity | No sprint exceeds team velocity |
| Unrealistic loads | Flags 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:
| Provider | Env Var | Key Format | Value |
|---|---|---|---|
| Anthropic (default) | ANTHROPIC_API_KEY | sk-ant-... | anthropic |
| OpenAI | OPENAI_API_KEY | sk-... | openai |
GOOGLE_API_KEY | AIza... | google | |
| AWS Bedrock | AWS_REGION | IAM 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