"Where sabers rest, stories float, and agents run the bar."
Scummbar AI is an open-source, hands-on study repository that teaches how to design, orchestrate, and operate a complex multi-agent conversational application using Google Agent Development Kit (ADK), Gemini and DeepSeek, delivered through Telegram (multi-player group chat) and a Streamlit web RPG (single-player adventure).
The repository is intentionally structured didactically: every architectural choice, every file, and every integration is documented so you can understand why the system is built this way โ not just what it does.
- ๐บ ScummBar AI โ A Collaborative Multi-Agent Study Project
- ๐บ๏ธ Table of Contents
- ๐ฏ Project Purpose
- ๐ The Story & The Characters
- ๐ Quick Start (Run First, Learn Later)
- ๐๏ธ The ADK Core: Architecture & How It Works
- 4.1 Core Overview Diagram
- 4.2 End-to-End Execution Sequence
- 4.3 Multi-Agent Coordination (Router-Delegate)
- 4.4 Agent Configuration
- 4.5 ADK Skills (Auto-Discovery)
- 4.6 ADK Function Tools
- 4.7 Time Management (Real Atmosphere)
- 4.8 World Context & Narration Rules
- 4.9 The Captain's Log (Tavern Journal)
- 4.10 Model Factory & Dual Authentication
- 4.11 Sessions, Compaction & Context Caching
- ๐ก Telegram Frontend (Multi-Player)
- ๐ฎ Streamlit Frontend (Single-Player RPG)
- ๐ฌ Observability: Logging, Metrics & Tracing
- ๐ค Pi-Agent: AI-Assisted Development & System Skills
- ๐งญ Project Structure
- โ๏ธ Environment Configuration (.env)
Scummbar AI is a didactic laboratory to explore the Google ADK ecosystem and multi-agent architectures in depth โ without sacrificing a fun and immersive user experience.
The main goals are:
| Goal | How It Is Addressed |
|---|---|
| Multi-Agent Orchestration | A root_agent coordinator that delegates to 4 specialized sub-agents (Router-Delegate pattern) |
| Tool Calling | ADK Function Tools for persistent memory, artifacts, images, and live RSS feeds |
| Modular Skills | ADK Skill auto-discovery: adding a capability = creating a SKILL.md folder, zero code |
| Dynamic Prompting | An InstructionProvider that refreshes the tavern atmosphere on every turn |
| Multi-Model | Switch between Gemini and DeepSeek by changing a single line in .env |
| Isolated Dual Auth | API Key โ Vertex AI Service Account, with fully isolated image auth |
| Multi-Frontend | Same shared ADK core, two frontends: Telegram (multi-player group) and Streamlit (single-player RPG) |
| Persistence & Compaction | Shared SQLite WAL + automatic LLM compaction of long sessions |
| Persistent Storytelling | The Captain's Log turns the chat into a first-person tale, updated incrementally |
| Observability | Local-first Logging, Metrics & Tracing (SQLite + OpenTelemetry) with a Streamlit cockpit |
| AI-Assisted Development | A Pi-Agent Skills system with a local hybrid RAG engine for autonomous documentation |
The Scummbar is a legendary Caribbean pirate tavern. It is a shared multi-agent environment where AI-powered characters live, listen, and interact with patrons in real time.
| Character | Role | Didactic Purpose | Personality |
|---|---|---|---|
| ๐บ Barnaby | Bartender | Tool Calling (read/write memory, text artifacts), Skills Auto-Discovery | Empathetic, quiet, knows every pirate's secret, mixes unforgettable custom grogs |
| ๐ฑ Barnacle | Tavern Cat | Shared read-only memory, Telegram ephemeral messages (whispers) | Crotchety, speaks rarely, sleeps on ammo crates |
| ๐ฎ Isolde | Fortune Teller | Independent multi-auth, multimodal image generation (Gemini Flash Image + PIL fallback) | Cryptic, majestic, sits in the Shadow Corner |
| ๐งญ Balthazar | Navigator & Cartographer | Live RSS feeds + comedic translation, vintage nautical map generation (demands grog before drawing), text artifacts (portolans) | Eccentric, theatrically solemn, turns real news into maritime lore |
- Python 3.11+
- Astral
uv(brew install uvorcurl -LsSf https://astral.sh/uv/install.sh) - A Google Gemini API Key (Google AI Studio) or a Vertex AI Service Account
- A Telegram Bot Token from @BotFather (optional, Telegram only)
git clone https://github.com/goldfix/ScummBarAI.git
cd ScummBarAI
# Initialize and create the virtual environment
bash py_env.sh init_py
# Activate the environment
source py-env/bin/activate # or: source py_env.sh activeCreate the file src/scummbar_chat/.env (see the Environment Configuration section for the full reference).
| Frontend | Command | Description |
|---|---|---|
| ADK Web | ./start.sh |
ADK Web UI with SQLite persistence |
| Telegram | python telegram_bot.py --debug |
Telegram bot for groups |
| Streamlit | ./start_streamlit.sh |
Single-player RPG on http://localhost:8501 |
The heart of the application lives in src/scummbar_chat/. Everything else (Telegram, Streamlit, Pi-Agent skills) is a delivery layer or support around this nucleus.
The complete chronological workflow of a single patron turn โ from user input, contextual routing, root coordination, and tool execution down to telemetry flush and incremental Captain's Log writing โ is illustrated in the PlantUML Sequence diagram below:
Instead of a single monolithic prompt, Scummbar uses the ADK Hierarchical Router-Delegate pattern:
root_agent(agent.py): a coordinatorAgentthat never answers directly โ it reads the routing prefix and delegates to the correct sub-agent.- 4 sub-agents registered in
sub_agents=[barnaby_agent, barnacle_agent, isolde_agent, balthazar_agent].
Routing happens at two priority levels (see 5.2 Semantic Routing):
- Explicit @mention (e.g.
@balthazar) โ always wins - Keyword matching (e.g.
grogโ Barnaby,tarocchiโ Isolde)
Once resolved, the router prepends [Risponde NOME] to the text, which the coordinator interprets to delegate.
Each agent lives in bots/<name>/ with two files: agent.py (ADK config) and persona.md (Italian, channel-agnostic prompt).
| Agent | Model | Skills | Tools | Read-Only? |
|---|---|---|---|---|
| ๐บ barnaby | MODEL |
โ grog + menu (auto-discovery) | recall, memorize, write_secret_scroll | No |
| ๐ฑ barnacle | MODEL |
โ grog + menu | recall (smell/read only) | Yes โ cannot write memory |
| ๐ฎ isolde | MODEL |
โ (no skills) | recall, draw_tarot_card | No |
| ๐งญ balthazar | MODEL |
โ grog + menu | recall, memorize, write_secret_scroll, draw_nautical_map, consult_barnaby (AgentTool), consult_barnacle (AgentTool), fetch_news_feed | No |
| ๐ chronicler | COMPACTION_LLM |
โ (internal scribe) | โ (dedicated diary generator) | Yes โ writes to Captain's Log .md |
Base agent configuration (Barnaby example):
barnaby_agent = Agent(
name="barnaby",
model=MODEL,
description="Barnaby, il barista dello Scummbar.",
instruction=_PERSONA, # persona.md
generate_content_config=THINKING_CONFIG, # thinking_level=medium
retry_config=DEFAULT_RETRY_CONFIG, # retry with backoff
tools=[_barnaby_toolset, recall_patron_tool, memorize_patron_tool,
write_secret_scroll_tool],
)Skills live in src/scummbar_chat/skills/ as folders containing SKILL.md:
skills/
โโโ grog/ โ SKILL.md + references/ (dynamic grog preparation)
โโโ menu/ โ SKILL.md (two-level galley menu)
How auto-discovery works: utils.load_all_skills() scans the skills/ folder at runtime and instantiates a SkillToolset for every SKILL.md found. Adding a new skill = creating a folder, zero Python code.
Skills are self-contained: all content (rules, examples, references) lives inside SKILL.md, loaded on demand by the model.
All tools are defined in tools.py and wrapped with FunctionTool(...). They receive the user identity exclusively from tool_context.user_id (never from the LLM, for safety).
| Tool | Function | Description |
|---|---|---|
recall_patron_memory |
Memory read | Retrieves the patron's traits and summaries from patron_memories (SQLite) |
memorize_patron_chat |
Memory write | Updates stable traits (max 10) and chat summary (max 300 chars) |
write_secret_scroll |
Text artifacts | Generates scrolls/recipes/portolans .txt via InMemoryArtifactService |
draw_tarot_card |
Multimodal images | Generates tarot cards with gemini-3.1-flash-lite-image (isolated IMAGE_* auth, 1:1), PIL fallback, PNG/JPEG detection via byte headers |
draw_nautical_map |
Multimodal images | Generates vintage 17th-century nautical charts and archipelago maps (gemini-3.1-flash-lite-image, 4:3), PIL fallback |
consult_barnaby / consult_barnacle |
Infra-Agent AgentTool |
Single-turn peer consultations: Balthazar asks Barnaby/Barnacle for advice to enrich map details |
fetch_news_feed |
Live RSS feeds | ANSA Politica + Google News USA, 2 categories (IT & US politics), strictly sorted chronologically (freshest first) with HTML links |
time_context.py maps the system clock into 6 Caribbean moments of the day:
| Time | Moment | Atmosphere |
|---|---|---|
| 07โ09 | ๐ Dawn | Bar opens, silence, first pink light |
| 09โ12 | โ๏ธ Morning | The bar wakes up, first serious customers |
| 12โ14 | ๐บ Noon | Peak activity, crowd at the counter |
| 14โ16 | ๐ด Afternoon | Sleepy post-lunch calm |
| 16โ18 | ๐ Sunset | Golden light, lanterns lit |
| 18โโ | ๐ Night | The bar never closes, night pirates |
How it is used: agent.py exposes _time_instruction_provider(context) โ an ADK InstructionProvider bound to global_instruction. On every model turn, the atmospheric description of the current moment is regenerated and injected into the context. This is cache-friendly for Gemini and guarantees the bots always know whether it is dawn or deep night.
world/scummbar.md is the tavern's master prompt: geography, ambient rules, character relationships, and the Narratore rules (environmental description injection every 3 turns).
It is loaded with load_md() and passed as static_instruction to the root_agent, so every sub-agent inherits the context without duplicating it.
Key rule: all .md files are channel-agnostic โ no references to Telegram or Streamlit. Visual rendering is exclusively the frontends' responsibility.
diary.py turns the conversation into a first-person tale ("I"), as if the patron himself were writing down his deeds, powered by a dedicated chronicler_agent ("Il Cronista dello Scummbar").
| Aspect | Detail |
|---|---|
| File | data/scummbar_chat/diaries/Diary_<Pirate_Name>.md (one per patron) |
| Tracking | HTML comment at the top of the file: <!-- DIARY_METADATA: {"last_saved_index": N} --> |
| Incremental update | Reads last_saved_index, extracts only new messages (messages[last_saved_index:]), generates a chapter, appends it |
| Idempotency | No new messages โ returns without consuming tokens |
| Chronicler Agent | Dedicated agent (bots/chronicler/) with its own persona.md system prompt specialized in first-person pirate prose |
| Generated Assets (Images & Scrolls) | All generated assets (tarot cards, nautical maps, text scrolls/recipes) are automatically saved to data/scummbar_chat/diaries/assets/ and embedded directly into the diary prose with clean relative Markdown links ( or [๐ ...](assets/filename.txt)) |
| Automatic trigger | In Streamlit, every 10 total session messages (with confirmation toast) |
| Manual trigger | "๐ Compila / Aggiorna Diario ORA" button in the Captain's Log tab |
| Download | "๐ฅ Scarica Diario (.md)" button |
| Dual-provider | Generation uses chronicler_agent.model (COMPACTION_LLM) โ works seamlessly with Gemini and DeepSeek |
utils.py centralizes model creation:
_build_model_instance(model_name, is_main_model)โ returnsGemini(native ADK) if the name has no prefix,LiteLlm(DeepSeek) if it starts withdeepseek/. For the main model it enablesthinking+reasoning_effort=high.get_gemini_client_kwargs(prefix="")โ parameterizes authentication between:- API Key (Google AI Studio): forces
vertexai=False, clearsproject/location - Vertex AI / Service Account: loads credentials in RAM as a
Credentialsobject (without mutatingos.environ, thread-safe) - With the
IMAGE_prefix it fully isolates image-generation authentication
- API Key (Google AI Studio): forces
Key rules: no temperature/top_p/top_k for Gemini 3.x models; thinking_level=medium (Gemini chat) / reasoning_effort=high (DeepSeek); image generation uses dedicated IMAGE_MODEL with independent IMAGE_* auth and its own IMAGE_THINKING_LEVEL=high; include_thoughts=False with thought part filtering.
| Mechanism | Configuration | Detail |
|---|---|---|
| Persistence | DatabaseSessionService โ data/scummbar_chat/sessions.db |
WAL mode + busy_timeout=10000 for multi-frontend concurrency |
| Compaction | EventsCompactionConfig + LlmEventSummarizer |
Every COMPACTION_INTERVAL=30 events, summarizes the past with COMPACTION_MODEL, keeps COMPACTION_OVERLAP=2 events verbatim |
| Context Caching | ContextCacheConfig (Gemini 2.0+) |
min_tokens=2048, ttl=600s, cache_intervals=5; automatically skipped for DeepSeek (server-side KV caching) |
| Artifacts | InMemoryArtifactService |
Scrolls, portolans, and images saved into the ADK session |
| Session Pruning | purge_old_sessions(hours=24) + hourly cron |
Keeps the DB clean by deleting events older than 24h |
Telegram provides the multi-player experience: a shared group where the 4 bots respond in real time.
| File | Role |
|---|---|
telegram_bot.py |
Pre-flight env checks (Gemini/DeepSeek auth), rotating logs (bot.log, errors.log), signal trapping, delegates to adapter.main() |
telegram/adapter.py |
Long-polling engine via aiohttp (no extra libraries). DM redirect to the group, routing, concurrency locks, Narratore injection every 3 messages, artifact upload via sendDocument/sendPhoto, Barnacle's ephemeral whispers, pruning cron |
telegram/formatter.py |
Converts raw markdown to Telegram-safe HTML: speech โ text, *action* โ <i>action</i>, _narration_ โ <blockquote><i>narration</i></blockquote>; escapes <, >, & |
telegram/runner.py |
Initializes App(root_agent) with DatabaseSessionService, EventsCompactionConfig, ContextCacheConfig, InMemoryArtifactService. Exposes run_agent() and purge_old_sessions() |
The _resolve_intent() function applies two priority levels:
- Explicit @mention โ
@barnaby,@barnacle,@isolde,@balthazar(always wins) - Keyword matching on
_INTENT_MAP(e.g.grog/birra/ordinareโ Barnaby;tarocchi/carte/predizioneโ Isolde;mappa/rotta/bussolaโ Balthazar;gatto/fusaโ Barnacle)
- Per-bot locks:
asyncio.Lockwith a 15s timeout (asyncio.wait_for) โ if a bot is busy, the user receives "รจ occupato" instead of waiting forever. - Barnacle ephemeral: the cat's messages use
sendMessagewith ephemeral/reply mode if the bot is admin in the group; public fallback with a๐ฑnote. - Pruning:
_session_cleaner_cron()every hour removes events older than 24 hours (direct DELETE on the ADKeventstable, wrapped in try/except).
The format_response() pipeline applies a 3-tier hierarchy:
| Element | Source pattern | Telegram HTML output |
|---|---|---|
| Environmental narration | full lines _text_ |
<blockquote><i>text</i></blockquote> |
| Character actions | inline *action* |
<i>action</i> |
| Spoken dialogue | plain text | unstyled text |
While Telegram is a real-time group chat, the Streamlit Web App (src/scummbar_chat/streamlit/) turns the Scummbar into a single-player narrative RPG.
Zero business-logic duplication: app.py uses the same run_agent() as Telegram and the same routing functions (_resolve_intent()).
| File | Role |
|---|---|
streamlit/app.py |
Entry point. Manages session, automatic routing, Narratore trigger, history restoration, Chat tab + Captain's Log tab, automatic diary trigger every 10 messages |
streamlit/components.py |
Avatars, 3-tier narrative formatting, artifact rendering, sidebar (mandatory patron name, character legend, game management) |
start_streamlit.sh |
streamlit run src/scummbar_chat/streamlit/app.py |
On startup the field is empty and the chat is locked (st.chat_input(disabled=True)) with a pirate warning: the user must enter their name to enter.
Same engine as Telegram: maga/veggente โ Isolde, navigatore/mappe โ Balthazar, gatto/micio โ Barnacle, barista/grog โ Barnaby.
When the player enters their name:
- Stable
user_idcomputed viasha256(patron_name) - Deterministic
session_id=st_session_{user_id} load_session_chat_history()queries the SQLiteeventstable and instantly restores the pirate's entire past story
narrator_counter: every 3 turns it appends [NOTA DI SISTEMA: ร il momento del Narratore...] to the prompt, like in Telegram.
"๐ Captain's Log" view with: registered/total message stats, "๐ Compila / Aggiorna Diario ORA" button, "๐ฅ Scarica Diario (.md)" button, live Markdown preview, and automatic trigger every 10 messages with confirmation toast.
The frontend uses st.segmented_control (not st.tabs) to alternate between Chat Tavern โ Captain's Log. This keeps st.chat_input at top level, preserving the sticky bottom anchoring (known st.chat_input-inside-st.tabs bug).
Telegram and Streamlit share the same sessions.db. To avoid database is locked:
PRAGMA journal_mode=WAL;PRAGMA busy_timeout=10000;+sqlite3.connect(timeout=10.0)
format_streamlit_narrative() applies a 3-tier visual hierarchy:
| Element | Source pattern | Streamlit visual output |
|---|---|---|
| Environmental narration | full lines _text_ |
Italic between guillemets ยซ...ยป on a light gray box (#e9ecef) |
| Character actions | inline *action* |
Italic between guillemets ยซ...ยป on a light gray badge |
| Spoken dialogue | plain text | Plain sans-serif text (maximum readability) |
Scummbar ships with a complete, local-first observability stack built on the three pillars of telemetry: Logging (what happened), Metrics (how long did it take), and Tracing (how the operations relate hierarchically in time). Everything is stored locally in a dedicated SQLite database and visualized inside the Streamlit UI โ no external servers (Jaeger, Prometheus, Datadog, ...) are required.
All telemetry code lives in the src/scummbar_chat/telemetry/ package:
src/scummbar_chat/telemetry/
โโโ __init__.py # Public API (setup, recorders, queries, renderers)
โโโ context.py # contextvars correlation (channel, session, user, agent, turn)
โโโ logging.py # rotating file handlers + console + HTML log viewer
โโโ db.py # observability.db schema & connection management (WAL)
โโโ metrics.py # recorders + @measure_tool decorator
โโโ queries.py # analytical SQL aggregations for the dashboard
โโโ tracing.py # OpenTelemetry init, per-turn span isolation, SQLite flush
โโโ viewer.py # HTML/CSS waterfall (Gantt) renderer
Every log line, metric row, and trace span is correlated with the same context variables โ channel (telegram/streamlit), session_id, user_id, agent_name, and turn_id โ injected automatically via Python's contextvars (PEP 567). This makes it trivial to reconstruct exactly what happened during any single user turn, in any frontend.
Purpose: a detailed, correlated narrative of who did what, when, and what went wrong.
| Aspect | Detail |
|---|---|
| Files | data/scummbar_chat/logs/app.log (all levels, 10 MB ร 5 rotating) and errors.log (WARNING+, 5 MB ร 3 rotating) |
| Console | same formatted output on stdout/stderr for live debugging |
| Context prefix | e.g. [tg:balthazar:u:123:t:45] or [st:barnaby:u:8741:t:7] โ injected automatically by a ContextualFilter |
| Levels | DEBUG (full prompts, raw params), INFO (lifecycle: routing, tool execution, artifacts, delivery), WARNING/ERROR (fallbacks, timeouts, exceptions with full tracebacks) |
| API | setup_logging(debug=..., force=...), log_context(channel=..., session_id=..., ...) in src/scummbar_chat/telemetry/logging.py and context.py |
Streamlit viewer: the ๐ชต Log di Sistema view renders the raw log files as a syntax-highlighted dark terminal with:
- file selector (
app.log/errors.log), level filter (DEBUGโCRITICAL), line count selector (50โ1000); - instant text search (e.g.
balthazar,draw_nautical_map,error); - semantic colors per severity (
DEBUGblue,INFOgreen,WARNINGamber,ERROR/CRITICALred).
Purpose: aggregated quantitative answers to how often and how fast things happen, per iteration, per agent, and per tool.
Stored in data/scummbar_chat/observability.db (SQLite WAL, dedicated from sessions.db):
| Table | Records | Key columns |
|---|---|---|
turn_metrics |
end-to-end user turns | turn_id, channel, target_agent, total_duration_ms, prompt_length, response_length, artifacts_count, workflow_steps, is_error, input/output/total_tokens |
tool_metrics |
individual FunctionTool calls | tool_name, duration_ms, success, error_type, artifact_filename, metadata_json |
agent_metrics |
granular agent/coordinator/chronicler times | agent_name, duration_ms, model_name, status |
Instrumentation is automatic and low-friction:
@measure_tool(tool_name)decorator wraps every tool intools.py(memory recall, scroll writing, tarot cards, nautical maps, RSS feeds) โ it measures latency, detects generated artifacts, and records success/errors.- Turn duration & token usage are captured in
telegram/adapter.pyandstreamlit/app.pyafter everyrun_agent()call (tokens come from ADKevent.usage_metadata). - Chronicler timings are recorded in
diary.pywhen the Captain's Log chapter is generated.
Purpose: a hierarchical, time-ordered waterfall of a single request as it travels through agents, tools, and models โ showing exactly where the time was spent.
Built on OpenTelemetry GenAI Semantic Conventions (fully supported by Google ADK):
invoke_agent:root_agent โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ 5200 ms
โโโ invoke_agent:balthazar โโโโโโโโโโโโโโโโโโโโโโโโโโโโ 5150 ms
โโโ generate_content (gemini-3.5-flash-lite) โโโโโโโ 650 ms
โโโ execute_tool:consult_barnaby โโโโโโโโโโโโโโโโโโโ 820 ms
โ โโโ generate_content (gemini-3.5-flash-lite) โโโ 800 ms
โโโ execute_tool:draw_nautical_map โโโโโโโโโโโโโโโโ 3100 ms
โ โโโ generate_content (gemini-3.1-flash-lite-image) 3050 ms
โโโ generate_content (final response) โโโโโโโโโโโโโ 580 ms
| Aspect | Detail |
|---|---|
| Engine | google.adk.telemetry.setup.maybe_set_otel_providers() with a fan-out InMemorySpanExporter |
| Per-turn isolation | turn_tracing() (in tracing.py) starts an anchoring root span; only the spans sharing its trace_id are persisted โ concurrent turns (different Telegram bots or parallel Streamlit sessions) never steal each other's spans |
| Flush | at turn end (finally in runner.py) all spans are bulk-inserted into the trace_spans table |
| Schema | span_id, trace_id, parent_span_id, turn_id, name, start_time_ns/end_time_ns, duration_ms, status_code, attributes_json, events_json |
| Semantics | spans include standard attributes such as gen_ai.agent.name, gen_ai.tool.name, gen_ai.request.model, gen_ai.usage.input_tokens/output_tokens |
| Zero-server | no OTLP collector required; spans are persisted locally. Standard OTEL_EXPORTER_OTLP_TRACES_ENDPOINT env vars remain supported if you later want Jaeger/Tempo/GCP |
The ๐ Metriche & Performance view provides:
- KPI cards: average turn latency (min/max), total turns, tool executions with success rate %, artifacts produced;
- Charts: average latency per agent (bar chart + table), tool performance with success rate;
- Trend: line chart of the last 50 turn latencies;
- Turn log: expandable per-turn details with prompt/response sizes, workflow steps, token usage, and the exact tools executed with their durations and generated files.
The ๐ Traces & Waterfall view provides:
- a turn selector (channel, agent, patron, duration, span count);
- a summary card with
trace_id, total duration, and span count; - an interactive waterfall Gantt chart with proportional bars and semantic colors (๐ฃ agents, ๐ tools, ๐ต LLM text, ๐ข image generation, ๐ด errors);
- a per-span inspector with timestamps, status, and the full JSON of OpenTelemetry GenAI attributes.
| Artifact | Path |
|---|---|
| Rotating logs | data/scummbar_chat/logs/app.log, errors.log |
| Metrics & trace database | data/scummbar_chat/observability.db (tables turn_metrics, tool_metrics, agent_metrics, trace_spans) |
This repository is designed to be developed, refactored, and maintained in collaboration with an AI assistant (Pi-Agent). To this end, an autonomous Agent Skills system is configured directly in the codebase.
A Pi-Agent skill is a self-contained package in .agents/skills/, consisting of SKILL.md (full instructions) + optional Python scripts. Pi-Agent scans this folder at startup and learns the capabilities from the description; the full instructions are loaded on-demand when the skill is invoked.
This implements Progressive Disclosure: it keeps the AI's context window clean, saving tokens and improving reasoning focus.
| Aspect | Detail |
|---|---|
| Purpose | Semantic + keyword search across 908 text documents in docs/ (Markdown, AsciiDoc, YAML, source code) |
| Engine | Local hybrid RAG: FTS5 BM25 + Vector Cosine Similarity with sqlite-vec |
| Embeddings | Google gemini-embedding-2 (768 dimensions) |
| Fusion | Reciprocal Rank Fusion (RRF) between the two rankings |
| Cleanup | The indexer compares DB vs disk and automatically removes orphan documents |
| DB | .agents/skills/scummbar-docs-analyzer/data/docs_rag.db (908 docs, 15,430 chunks) |
# Hybrid search (semantic + keyword)
PYTHONPATH=.agents/skills/scummbar-docs-analyzer python3 \
.agents/skills/scummbar-docs-analyzer/rag/search.py "context compaction" --top_k 5
# Incremental re-indexing after adding new docs
PYTHONPATH=.agents/skills/scummbar-docs-analyzer python3 \
.agents/skills/scummbar-docs-analyzer/rag/indexer.py| Aspect | Detail |
|---|---|
| Purpose | Keep MEMORY.md, README.md, and AGENTS.md in sync |
| Rules | Standardizes how to document: session logs, roadmap, architectural decisions |
| Validator | Automatic Python script that verifies no Markdown code block is left open (balanced fence markers) |
| Key rule | Pure documentation additions (docs imports) do not auto-update MEMORY.md |
| Aspect | Detail |
|---|---|
| Purpose | Convert web pages into clean Markdown inside docs/ |
| Pipeline | beautifulsoup4 + html2text |
| Extras | Automatic updates, relativeโabsolute link resolution, UTF-8 emoji preservation, overwrite protection |
| Post-conversion | Automatic trigger of the RAG indexer to update the vector database |
| Aspect | Detail |
|---|---|
| Purpose | Generate vector diagrams and infographics via Kroki.io |
| Encoder | zlib deflate (level 9) + Base64 URL-safe encoding |
| Default Style | C4-PlantUML (c4plantuml) for architectural C4 diagrams |
| Supported Types | Excalidraw, Mermaid, PlantUML, Graphviz/DOT, D2, BPMN, BlockDiag, etc. |
| CLI Script | .agents/skills/scummbar-kroki-diagrams/scripts/kroki_generator.py |
| SVG Localization | --localize downloads remote Kroki SVGs into assets/ and rewrites Markdown links to local files (offline-ready) |
| Target | Used by Pi-Agent for documentation & README diagrams (not in runtime ADK app) |
# 1. Search the documentation (hybrid RAG FTS5 + Cosine)
PYTHONPATH=.agents/skills/scummbar-docs-analyzer python3 \
.agents/skills/scummbar-docs-analyzer/rag/search.py "agent evaluation" --top_k 5
# 2. Convert a web page to Markdown and index it into the RAG
python3 .agents/skills/scummbar-web-to-markdown/scripts/convert.py \
"https://adk.dev/evaluate/" "docs/google-api/"
PYTHONPATH=.agents/skills/scummbar-docs-analyzer python3 \
.agents/skills/scummbar-docs-analyzer/rag/indexer.py
# 3. Generate a C4-PlantUML diagram (default) and localize it as local SVG in assets/
python3 .agents/skills/scummbar-kroki-diagrams/scripts/kroki_generator.py \
"Scummbar System\nBarnaby Bartender" --markdown
python3 .agents/skills/scummbar-kroki-diagrams/scripts/kroki_generator.py --localize README.md
# 4. Update memory, roadmap, and check fence-marker health
/skill:scummbar-memory-updaterscummbar/
โโโ .agents/skills/ # ๐ค Pi-Agent system skills
โ โโโ scummbar-docs-analyzer/ # Hybrid RAG (FTS5 + sqlite-vec + gemini-embedding-2)
โ โโโ scummbar-kroki-diagrams/ # Kroki diagram generator (C4-PlantUML, Excalidraw, Mermaid...)
โ โโโ scummbar-memory-updater/ # MEMORY/README/AGENTS update rules
โ โโโ scummbar-web-to-markdown/ # Web โ Markdown converter
โโโ docs/ # ๐ Technical docs (ADK, DeepSeek, Telegram, Streamlit...)
โโโ data/
โ โโโ scummbar_chat/
โ โโโ sessions.db # SQLite session database (ADK)
โ โโโ observability.db # ๐ฌ Telemetry: turn_metrics, tool_metrics, agent_metrics, trace_spans
โ โโโ diaries/ # ๐ Patron Captain's Logs (Diary_Name.md)
โ โ โโโ assets/ # Generated assets (maps, tarot cards, scrolls .txt)
โ โโโ logs/ # app.log + errors.log (rotating)
โโโ src/scummbar_chat/ # ๐ค Main application (Google ADK)
โ โโโ agent.py # root_agent + temporal InstructionProvider
โ โโโ utils.py # config, model factory, dual auth, load_md/load_all_skills
โ โโโ time_context.py # real clock โ tavern atmosphere
โ โโโ tools.py # ADK FunctionTool (memory, scrolls, tarot, maps, news)
โ โโโ diary.py # ๐ Captain's Log (incremental first-person generation via Chronicler)
โ โโโ telemetry/ # ๐ฌ Observability: context, logging, db, metrics, queries, tracing, viewer
โ โโโ .env # โ ๏ธ DO NOT commit โ tokens and API keys
โ โโโ world/scummbar.md # world context + Narratore rules
โ โโโ bots/ # barnaby, barnacle, isolde, balthazar, chronicler (agent.py + persona.md)
โ โโโ skills/ # grog/, menu/ (auto-discovery)
โ โโโ telegram/ # adapter.py, formatter.py, runner.py
โ โโโ streamlit/ # app.py, components.py
โโโ telegram_bot.py # Telegram entry point (--debug flag)
โโโ start.sh # ADK web with SQLite persistence
โโโ start_streamlit.sh # Streamlit RPG launcher
โโโ py_env.sh # Environment setup (uv + venv)
โโโ pyproject.toml # PEP 621 dependencies (uv)
โโโ ruff.toml # Linter & formatter config
โโโ AGENTS.md # Instructions for AI agents
โโโ MEMORY.md # Live project memory
โโโ README.md # This file
The src/scummbar_chat/.env file is divided into 6 logical sections:
# ===========================================================================
# ๐ SECTION 1: GEMINI AUTHENTICATION (Chat & Compaction)
# ===========================================================================
# Choose ONE of the two options (A or B).
# --- OPTION A: Google AI Studio (API Key) ---
GEMINI_API_KEY=your-api-key-here
# --- OPTION B: Vertex AI / Google Cloud (Service Account) ---
# GOOGLE_CLOUD_PROJECT=your-gcp-project-id
# GOOGLE_CLOUD_LOCATION=global
# GOOGLE_GENAI_USE_VERTEXAI=True
# GOOGLE_APPLICATION_CREDENTIALS=/absolute/path/to/key.json
# ===========================================================================
# ๐ฌ SECTION 2: CONVERSATION MODEL
# ===========================================================================
LLM_MODEL=gemini-3.5-flash-lite
LLM_THINKING_LEVEL=medium
# LLM_MODEL=deepseek/deepseek-v4-flash # DeepSeek alternative
# ===========================================================================
# ๐๏ธ SECTION 3: CONTEXT COMPACTION
# ===========================================================================
COMPACTION_MODEL=gemini-3.5-flash-lite
COMPACTION_INTERVAL=30
COMPACTION_OVERLAP=2
# ===========================================================================
# ๐พ SECTION 3b: EXPLICIT CONTEXT CACHING (Gemini 2.0+)
# ===========================================================================
CONTEXT_CACHE_ENABLED=true
CONTEXT_CACHE_MIN_TOKENS=2048
CONTEXT_CACHE_TTL_SECONDS=600
CONTEXT_CACHE_INTERVALS=5
# ===========================================================================
# ๐ฎ SECTION 4: IMAGE GENERATION (Isolde & Balthazar โ INDEPENDENT AUTH)
# ===========================================================================
IMAGE_MODEL=gemini-3.1-flash-lite-image
IMAGE_THINKING_LEVEL=high
IMAGE_GEMINI_API_KEY=your-dedicated-image-api-key-here
# IMAGE_GOOGLE_CLOUD_PROJECT=another-gcp-project-id
# IMAGE_GOOGLE_CLOUD_LOCATION=europe-west1
# IMAGE_GOOGLE_GENAI_USE_VERTEXAI=True
# IMAGE_GOOGLE_APPLICATION_CREDENTIALS=/absolute/path/to/another-key.json
# ===========================================================================
# ๐ง SECTION 5: DEEPSEEK PROVIDER (Optional)
# ===========================================================================
DEEPSEEK_API_KEY=your-deepseek-api-key-here
DEEPSEEK_REASONING_EFFORT=high
# ===========================================================================
# ๐ก SECTION 6: TELEGRAM (Optional)
# ===========================================================================
TELEGRAM_BOT_TOKEN=your-telegram-bot-token
TELEGRAM_BOT_USERNAME=your_bot_username
TELEGRAM_GROUP_LINK=https://t.me/your-group-linkInspired by the Scumm Bar from Monkey Island. Built for learning and study purposes. No commercial affiliation.
