Skip to content

About

No description, website, or topics provided.

Resources

Stars

10 stars

Watchers

0 watching

Forks

Latest commit

 

History

7 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

🐶 Cleveland Browns Emotional Support Agent

Demo agent for a 30-minute PyOhio talk, built with the Strands Agents SDK. Each stage adds one building block. The agent is unshakably optimistic — it genuinely believes this is the Browns' year, every single year.

Setup

python3 -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt
aws configure   # needs Bedrock model access (Claude Sonnet)

# One-time, for the guardrail stage (and the frontend/full agent, optionally):
python setup_guardrail.py
export BROWNS_GUARDRAIL_ID=<printed id>

# Local memory for stages 7 and 8 (keep Ollama running):
ollama pull nomic-embed-text:v1.5
python setup_memory.py

Stages

# File Concept The moment
1 01_minimal_agent.py Agent in a few lines Interactive chat, pure belief, no tools
2 02_agent_loop_trace.py The agent loop Watch model → tool → model in agent.messages
3 03_tools.py Tools (@tool) Real nflverse stats: 5-12 spun as "underestimated"
4 04_hooks_and_interrupts.py State + hooks + interrupts DEFCON DAWG POUND → "Authorize Emergency Bar Protocol?"
5 05_guardrails.py Bedrock Guardrails Sadness passes, trash talk gets BLOCKED
6 06_context_mgmt.py Conversation management A season of suffering, summarized
7 07_persistent_memory.py Local Mem0 memory Quit, restart — it still remembers your hot takes
8 08_strands_harness.py Strands Harness Same tools, personality, and Mem0; built-in context and sessions
9 09_agentcore.py AgentCore Runtime Hosted Harness with default Bedrock, AgentCore Memory, and AWS telemetry
— full_agent.py Everything composed Interactive terminal REPL

Run any stage: python 04_hooks_and_interrupts.py

Stage 9 is deployed separately: follow the AgentCore demo instructions. Its deployment bundle excludes local Mem0 data and Logfire credentials. Configure your own AWS target from the supplied example; actual account configuration and deployment state stay local and are ignored by Git.

Stage 8: Strands Harness

source .venv/bin/activate
pip install -r requirements.txt
python 08_strands_harness.py

# Or use the existing Browns frontend with the Harness agent:
streamlit run frontend/app.py -- --harness

The assembly is in 08_strands_harness.py: get_model(), get_memory(), and one create_harness() call. It imports the same seven tools and preserves the Browns personality and PainMonitorHook approval flow. That hook is the demo's business logic; there is no extra logging hook or manual conversation manager. Stage 7 uses Agent with the same native Mem0 store. The frontend without --harness still uses its original agent assembly.

Harness provides automatic context management, built-in tools, and conversation session files in .agent/sessions. Drop skills into .agent/skills/<skill-name>/SKILL.md for automatic discovery. Run from the repository root so these directories stay in this project.

Both stages use the official mem0-strands integration with local Mem0:

  • get_memory() creates a Mem0MemoryStore, wrapped by Strands' MemoryManager.
  • The model saves facts through Strands' native add_memory tool. Automatic background extraction is off, so a second model does not reprocess each turn.
  • Mem0 embeds those facts using nomic-embed-text:v1.5 in Ollama and stores them in embedded Qdrant under .agent/mem0. No Qdrant server or Mem0 API key is needed.
  • The memory manager automatically searches for relevant facts on later turns.
  • Both stages use the same fan ID and database: a fact saved in stage 7 can be recalled in stage 8. Run one CLI/frontend process at a time; embedded Qdrant exclusively locks its database directory. The frontend shares its Mem0 connection across browser sessions while keeping their agents separate.

Mem0 holds lasting facts, not the conversation transcript. Stage 7 starts a new in-memory conversation each run. Stage 8 also gets Harness's default file session storage; its Mem0 store replaces the default file-based long-term memory. The configured Mem0 LLM is local Ollama too, but extraction=False means these explicit fact writes do not call it.

Both stages default to Bedrock, with no model argument. To use local Gemma, uncomment this single line in that stage's build_agent():

model=get_model(),

get_model() returns the Ollama model. Comment the line again to return to Bedrock. For Ollama, have gemma4:latest downloaded as well. Gemma stays loaded for 30 minutes between requests. Its first turn can take longer while Ollama loads the model and processes the initial prompt.

To demonstrate persistence:

python 07_persistent_memory.py
# Say: "My lucky jersey is number 73. Remember that for next time."
# Then quit.
python 07_persistent_memory.py --memories
python 08_strands_harness.py
# Ask: "What is my lucky jersey number?"

Use --memories with either stage to inspect the actual saved records without calling Bedrock. These local records do not appear in the hosted Mem0 dashboard.

In the Harness frontend, open Saved memories beneath the header to inspect the actual stored facts, timestamps, and raw Mem0 records. The panel refreshes after a conversation turn; Refresh memories also reloads it. Agent save calls shows the add_memory inputs and results available in the current chat, so an attempted write is distinguishable from a fact actually stored in Mem0. The panel uses the agent's existing database connection and works while the frontend is running. Background extraction remains off.

Explaining memory during the demo

“The model chooses a useful fact to remember. Mem0 turns that fact into a vector and stores it. When I ask a related question later, we retrieve relevant facts and give them to the model alongside my question.”

  1. Save: The agent calls add_memory. Mem0 embeds each fact using Ollama's nomic-embed-text:v1.5 (768 dimensions), then stores the vector, original text, and metadata in local Qdrant, scoped to the fan's user ID.
  2. Retrieve: Before the model handles a new user message, Strands' MemoryManager uses that message as a search query. Mem0 embeds the query, searches Qdrant by cosine similarity within that fan's memories, and ranks the results. The current injection configuration includes up to five facts.
  3. Inject: Strands appends the retrieved text in a <memory> block to a temporary copy of the user message sent to the model. The original conversation history is not changed, and the model's weights are not updated.

For example, “Which number should I put on my jersey?” can retrieve “The fan's favorite jersey number is 76” without an exact text match. This is automatic retrieval and injection; background extraction is separate and currently disabled. The agent explicitly chooses what to save.

Warnings for the three missing optional spaCy/fastembed features are filtered in both stages. Semantic vector search stays enabled; database failures and other warnings/errors remain visible.

Native tracing needs only environment configuration:

OTEL_TRACES_EXPORTER=console python 08_strands_harness.py

For Logfire, copy .env.example to .env and supply your project's write token. Stage 8 loads .env before creating the agent, including when launched through the frontend with --harness. Harness wires Strands' built-in OTLP exporter; no custom spans or Logfire SDK are needed. The service appears as browns-agent. The ignored .env contains the token; never commit it. Restart Streamlit after changing telemetry configuration. Traces can include prompts, replies, tool arguments/results, and memory content. MEM0_TELEMETRY=false disables Mem0's optional PostHog usage analytics. Strands' Logfire tracing remains controlled separately by the OTEL_* settings.

Try “My lucky jersey is number 73. Remember that for next time,” then “I think the Steelers are a better team and I might switch sides.” The second prompt should trigger the existing pain escalation and bar approval. The terminal asks for approval; the frontend still auto-authorizes it for the demonstration. Bar recommendations remain fictional demo data.

Frontend

export BROWNS_GUARDRAIL_ID=<id>   # optional — enables the guardrail in the UI
streamlit run frontend/app.py

Browns-themed chat with a live pain gauge (sidebar, with sparkline). When pain peaks past the threshold, the frontend auto-authorizes the Emergency Bar Location Protocol: a banner and map drop into the chat with the nearest Browns Backers bar pinned, the gauge holds at the peak ("💀 DEFCON DAWG POUND") for the crisis turn, then vitals stabilize. Casual "where should I watch the game" requests get a quiet approval — no sirens.

Architecture

  • Model knowledge handles Browns history, trivia, and coping — no tool needed. (It also gets today's date and the NFL season convention injected into the system prompt, so "last season" resolves correctly.)
  • Tools return plain data; the personality lives in the system prompt. Real stats via nflverse (nflreadpy — games/plays back to 1999, players, draft; free, no key, cached locally so it works offline), NWS weather (real API, no key), bar lookup (fictional bars), and pain reporting.
  • The model decides the pain delta each turn via update_pain_index — it understands context better than keyword matching. The tool stores it in agent state (shared with hooks, costs zero context tokens).
  • The hook (hooks/pain_monitor_hook.py) shows both hook superpowers in ~30 lines:
    • AfterToolCallEvent — when pain crosses the threshold, append an emergency directive to the pain tool's result (the model sees it and calls the bar tool); once the bar is delivered, reset the pain index — the intervention works.
    • BeforeToolCallEvent — every bar dispatch pauses the loop with an interrupt for human sign-off. Terminal demos ask y/N; the frontend auto-authorizes.
  • The guardrail (browns-loyalty-guardrail, STANDARD tier) blocks insults aimed at the Browns while letting fan grief through. The blocked-input message does the comedy: "This is a SAFE SPACE for BELIEVERS."
  • Memory (stages 7 and 8) uses local Mem0 through the native Strands memory manager. The agent saves fan facts with add_memory; relevant memories are retrieved automatically across restarts. Stage 8 also has Harness session files.

Flow: Model reports → Tool stores → Hook escalates → Model reacts → Hook interrupts → Human decides

Demo pacing notes

  • Pain climbs over a few turns: calm questions score ~0, bad news +1/+2, real trauma (The Drive / The Fumble / Red Right 88) or despair +3 to +5. Two heavy turns reliably cross the threshold. Tune PAIN_THRESHOLD in hooks/pain_monitor_hook.py if needed.
  • Stats are real (nflverse) and cached locally after first load — run each stage once before the talk so the parquet cache is warm and offline-safe. The weather tool hits the live NWS API (which throws the occasional 500 — rehearse it). Bars are intentionally fictional.
  • Guardrail note: the topic definition needs the explicit "sadness is NOT this topic" carve-out and the STANDARD tier, or it blocks sad-but-loyal fans too.

Powered by Strands Agents SDK | Go Dawgs 🐶

About

No description, website, or topics provided.

Resources

Stars

10 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages