Full toolkit for running an AI agent service built with LangGraph, FastAPI and Streamlit
This report summarizes scanner evidence for JoshuaC215/agent-service-toolkit, a Python project with 4,517 GitHub stars. SynthScan examined 14,031 lines of code across 115 source files, recording 277 pattern matches distributed across 16 syntactic categories. The legacy adjusted pattern signal is 29.1 (Elevated pattern signal); it is not an authorship estimate.
Use the findings to ask whether maintainers can explain intent, verify dependencies, simplify unnecessary scaffolding, and focus review effort. Risk Payload v1 adds ownership, supply-chain, over-engineering, and review-effort vectors when available. These are prompts for human verification, not probabilities, security findings, or measured maintenance costs.
Use each vector to guide manual verification of the underlying findings.
Can maintainers explain intent, assumptions, and trade-offs? 181 finding(s).
Are imports declared and package/API names verified? 31 finding(s).
Are wrappers, guards, and repeated structures necessary? 201 finding(s).
Which changes need focused human review? 277 finding(s).
This chart tracks the legacy adjusted pattern score across scan runs. Changes can reflect source changes, scan coverage, or detector/scoring versions; they do not measure AI adoption or prove that a team refactored code. Compare runs only when their scoring versions are compatible. The dashed line tracks the raw pattern-hit count.
Groups findings by configured severity weight. Severity is not a confidence estimate, security rating, or proof that a package is missing. Verify imports against the project's manifests and inspect each finding in its local design context.
This horizontal bar chart shows where configured pattern scores accumulate across directories. Treat the bars as locations to inspect, not as evidence of authorship or code quality. Use the file-level findings to decide whether a directory needs a focused ownership, dependency, complexity, or review check.
The scanner identified 277 distinct pattern matches across 16 syntactic categories. Each entry below represents a discrete location in the source code where the engine recorded a detector match to inspect in context. Expand any category row to inspect the individual file paths, line numbers, code snippets, and the lexical context (CODE, COMMENT, or STRING) in which each match was detected.
Reading the findings table: The Severity column indicates the configured finding weight (CRITICAL / HIGH / MEDIUM / LOW). The Context column identifies whether the match occurred inside executable code, an inline comment, or a string literal — legacy comment-context matches receive a ×1.5 score weight. The ⚡ bolt icon marks clustered matches: three or more patterns within a 10-line window, each receiving an additional ×1.5 density multiplier under the legacy scoring model.
| Severity | File | Line | Snippet | Context |
|---|---|---|---|---|
| LOW | tests/conftest.py | 17 | def pytest_collection_modifyitems(config, items): | CODE |
| LOW | tests/smoke/test_persistence.py | 14 | def test_checkpointer_persists_history(): | CODE |
| LOW | tests/smoke/test_persistence.py | 41 | def test_threads_lists_user_threads(): | CODE |
| LOW | tests/core/test_llm.py | 58 | def test_get_model_groq_guard(): | CODE |
| LOW | tests/core/test_llm.py | 66 | def test_get_model_groq_gpt_oss(): | CODE |
| LOW | tests/core/test_llm.py | 88 | def test_get_model_openrouter(): | CODE |
| LOW | tests/core/test_llm.py | 100 | def test_get_model_openrouter_requires_key(): | CODE |
| LOW⚡ | tests/core/test_settings.py | 32 | def test_settings_default_values(): | CODE |
| LOW⚡ | tests/core/test_settings.py | 40 | def test_settings_no_api_keys(): | CODE |
| LOW⚡ | tests/core/test_settings.py | 47 | def test_settings_with_openai_key(): | CODE |
| LOW⚡ | tests/core/test_settings.py | 55 | def test_settings_with_anthropic_key(): | CODE |
| LOW⚡ | tests/core/test_settings.py | 63 | def test_settings_with_openrouter_key(): | CODE |
| LOW⚡ | tests/core/test_settings.py | 73 | def test_settings_with_vertexai_credentials_file(): | CODE |
| LOW⚡ | tests/core/test_settings.py | 81 | def test_settings_with_multiple_api_keys(): | CODE |
| LOW | tests/core/test_settings.py | 101 | def test_settings_use_fake_model(): | CODE |
| LOW | tests/core/test_settings.py | 108 | def test_settings_use_fake_model_wins_over_ambient_real_keys(): | CODE |
| LOW | tests/core/test_settings.py | 127 | def test_settings_explicit_default_model_overrides_fake(): | CODE |
| LOW | tests/core/test_settings.py | 151 | def test_settings_with_azure_openai_key(): | CODE |
| LOW | tests/core/test_settings.py | 167 | def test_settings_with_both_openai_and_azure(): | CODE |
| LOW | tests/core/test_settings.py | 189 | def test_settings_azure_deployment_names(): | CODE |
| LOW | tests/core/test_settings.py | 194 | def test_settings_azure_missing_deployment_names(): | CODE |
| LOW | tests/core/test_settings.py | 207 | def test_settings_azure_deployment_map(): | CODE |
| LOW | tests/core/test_settings.py | 224 | def test_settings_azure_invalid_deployment_map(): | CODE |
| LOW | tests/core/test_settings.py | 238 | def test_settings_azure_openai(): | CODE |
| LOW⚡ | tests/core/test_settings.py | 264 | def test_settings_log_level_default(): | CODE |
| LOW⚡ | tests/core/test_settings.py | 272 | def test_settings_log_level_from_env(): | CODE |
| LOW⚡ | tests/core/test_settings.py | 280 | def test_settings_log_level_invalid(): | CODE |
| LOW | tests/app/test_streamlit_app.py | 12 | def test_app_simple_non_streaming(mock_agent_client): | CODE |
| LOW | tests/app/test_streamlit_app.py | 75 | def test_app_thread_id_history(mock_agent_client): | CODE |
| LOW | tests/app/test_streamlit_app.py | 100 | def test_app_resume_with_agent_param(mock_agent_client): | CODE |
| LOW | tests/app/test_streamlit_app.py | 339 | async def test_app_streaming_single_sub_agent(mock_agent_client, multi_agent_messages): | CODE |
| LOW | tests/app/test_streamlit_app.py | 409 | async def test_app_streaming_sequential_sub_agents(mock_agent_client, multi_agent_messages): | CODE |
| LOW | tests/app/test_streamlit_app.py | 499 | async def test_app_streaming_nested_sub_agents(mock_agent_client, multi_agent_messages): | CODE |
| LOW | tests/app/test_streamlit_app.py | 604 | def test_app_thread_caching_sidebar(mock_agent_client, mock_threads_data): | CODE |
| LOW | tests/app/test_streamlit_app.py | 621 | def test_app_thread_click_loads_history(mock_agent_client, mock_threads_data): | CODE |
| LOW | tests/app/test_streamlit_app.py | 650 | def test_app_thread_click_history_error(mock_agent_client, mock_threads_data): | CODE |
| LOW | tests/app/test_streamlit_app.py | 667 | def test_app_thread_fetch_error_shows_caption(mock_agent_client): | CODE |
| LOW | tests/integration/test_docker_e2e.py | 8 | def test_service_with_fake_model(): | CODE |
| LOW⚡ | tests/agents/test_lazy_agent.py | 43 | def test_get_graph_before_load(self): | CODE |
| LOW⚡ | tests/agents/test_lazy_agent.py | 49 | def test_get_graph_after_load(self): | CODE |
| LOW⚡ | tests/agents/test_lazy_agent.py | 58 | def test_get_graph_no_graph_created(self): | CODE |
| LOW | tests/agents/test_agent_loading.py | 15 | async def test_load_agent_static_agent(self): | CODE |
| LOW | tests/agents/test_agent_loading.py | 22 | async def test_load_agent_lazy_agent(self): | CODE |
| LOW⚡ | tests/agents/test_agent_loading.py | 34 | async def test_load_agent_nonexistent(self): | CODE |
| LOW⚡ | tests/agents/test_agent_loading.py | 39 | def test_get_agent_static_agent(self): | CODE |
| LOW⚡ | tests/agents/test_agent_loading.py | 44 | def test_get_agent_lazy_agent_not_loaded(self): | CODE |
| LOW | tests/agents/test_agent_loading.py | 55 | def test_get_agent_lazy_agent_loaded(self): | CODE |
| LOW | tests/agents/test_agent_loading.py | 68 | def test_get_agent_nonexistent(self): | CODE |
| LOW | tests/agents/test_github_mcp_agent.py | 22 | async def test_load_without_github_pat(self): | CODE |
| LOW | tests/agents/test_github_mcp_agent.py | 35 | async def test_load_with_github_pat(self): | CODE |
| LOW | tests/agents/test_github_mcp_agent.py | 118 | def test_get_graph_not_loaded(self): | CODE |
| LOW | tests/agents/test_safeguard.py | 8 | def test_safeguard_skipped_under_fake_model(): | CODE |
| LOW | tests/schema/test_models.py | 14 | def test_model_enums_are_all_discovered(): | CODE |
| LOW | tests/schema/test_models.py | 19 | def test_model_values_are_unique_across_enums(): | CODE |
| LOW | tests/schema/test_models.py | 32 | def test_vertexai_models_do_not_collide_with_google(model): | CODE |
| LOW | tests/schema/test_models.py | 37 | def test_model_table_covers_every_model(): | CODE |
| LOW⚡ | tests/voice/test_tts.py | 11 | def test_init_with_openai_provider(mock_openai_client): | CODE |
| LOW⚡ | tests/voice/test_tts.py | 19 | def test_init_with_invalid_provider(): | CODE |
| LOW⚡ | tests/voice/test_tts.py | 26 | def test_init_with_unimplemented_provider(): | CODE |
| LOW⚡ | tests/voice/test_tts.py | 33 | def test_from_env_provider_not_set(): | CODE |
| 76 more matches not shown… | ||||
| Severity | File | Line | Snippet | Context |
|---|---|---|---|---|
| MEDIUM | tests/service/test_service.py | 38 | # Create a separate mock for the default agent | COMMENT |
| MEDIUM | tests/client/test_client.py | 191 | # Create an async iterator for the events | COMMENT |
| MEDIUM | scripts/create_chroma_db.py | 71 | # Create the Chroma database | COMMENT |
| MEDIUM | src/streamlit_app.py | 450 | # Create a status container for each tool call and store the | COMMENT |
| MEDIUM | src/streamlit_app.py | 625 | # Create a nested status container for the sub-agent | COMMENT |
| MEDIUM | src/agents/rag_assistant.py | 95 | # Define the graph | STRING |
| MEDIUM | src/agents/interrupt_agent.py | 220 | # Define the graph | COMMENT |
| MEDIUM | src/agents/safeguard.py | 107 | # Create a formatted history string to evaluate | STRING |
| MEDIUM | src/agents/tools.py | 51 | # Create the embedding function for our project description database | COMMENT |
| MEDIUM | src/agents/command_agent.py | 16 | # Define the nodes | COMMENT |
| MEDIUM⚡ | src/agents/knowledge_base_agent.py | 18 | # Define the state | COMMENT |
| MEDIUM⚡ | src/agents/knowledge_base_agent.py | 27 | # Create the retriever | COMMENT |
| MEDIUM⚡ | src/agents/knowledge_base_agent.py | 35 | # Create the retriever with the specified Knowledge Base ID | COMMENT |
| MEDIUM | src/agents/knowledge_base_agent.py | 157 | # Define the graph | COMMENT |
| MEDIUM | src/agents/research_assistant.py | 99 | # Define the graph | STRING |
| MEDIUM | src/agents/github_mcp_agent/github_mcp_agent.py | 101 | # Create the agent instance | COMMENT |
| MEDIUM | src/agents/bg_task_agent/bg_task_agent.py | 53 | # Define the graph | COMMENT |
| Severity | File | Line | Snippet | Context |
|---|---|---|---|---|
| LOW | scripts/check_live_models.py | 88 | except Exception as e: | CODE |
| LOW | scripts/e2e_ui_tests.py | 90 | except Exception: | CODE |
| LOW | scripts/e2e_ui_tests.py | 437 | except Exception as e: | CODE |
| LOW | scripts/e2e_ui_tests.py | 461 | except Exception: | CODE |
| MEDIUM | scripts/e2e_ui_tests.py | 87 | def launch_browser(p) -> Browser: | CODE |
| LOW⚡ | scripts/smoke_live_app.py | 52 | except Exception as e: | CODE |
| LOW⚡ | scripts/smoke_live_app.py | 60 | except Exception: | CODE |
| LOW⚡ | scripts/smoke_live_app.py | 70 | except Exception: | CODE |
| LOW⚡ | scripts/smoke_live_app.py | 78 | except Exception as e: | CODE |
| LOW⚡ | scripts/smoke_live_app.py | 83 | except Exception: | CODE |
| LOW | scripts/smoke_live_app.py | 124 | except Exception: | CODE |
| LOW | src/streamlit_app.py | 167 | except Exception as e: | CODE |
| LOW | src/core/settings.py | 242 | except Exception as e: | CODE |
| LOW | src/agents/interrupt_agent.py | 117 | except Exception as e: | CODE |
| LOW | src/agents/interrupt_agent.py | 163 | except Exception as e: | CODE |
| LOW | src/agents/tools.py | 34 | except Exception as e: | CODE |
| LOW | src/agents/tools.py | 54 | except Exception as e: | CODE |
| LOW | src/agents/knowledge_base_agent.py | 119 | except Exception as e: | CODE |
| LOW | src/agents/github_mcp_agent/github_mcp_agent.py | 80 | except Exception as e: | CODE |
| LOW | src/voice/tts.py | 150 | except Exception as e: | CODE |
| LOW | src/voice/stt.py | 142 | except Exception as e: | CODE |
| LOW | src/voice/providers/openai_tts.py | 112 | except Exception as e: | CODE |
| LOW | src/voice/providers/openai_stt.py | 54 | except Exception as e: | CODE |
| LOW | src/service/service.py | 106 | except Exception as e: | CODE |
| LOW | src/service/service.py | 116 | except Exception as e: | CODE |
| LOW | src/service/service.py | 238 | except Exception as e: | CODE |
| LOW | src/service/service.py | 324 | except Exception as e: | CODE |
| LOW | src/service/service.py | 349 | except Exception as e: | CODE |
| LOW | src/service/service.py | 445 | except Exception as e: | CODE |
| LOW | src/service/service.py | 470 | except Exception as e: | CODE |
| LOW | src/service/service.py | 487 | except Exception as e: | CODE |
| LOW | src/client/client.py | 187 | except Exception as e: | CODE |
| LOW | src/client/client.py | 194 | except Exception as e: | CODE |
| Severity | File | Line | Snippet | Context |
|---|---|---|---|---|
| LOW | src/core/__init__.py | 1 | CODE | |
| LOW | src/core/__init__.py | 2 | CODE | |
| LOW | src/agents/__init__.py | 1 | CODE | |
| LOW | src/agents/__init__.py | 1 | CODE | |
| LOW | src/agents/__init__.py | 1 | CODE | |
| LOW | src/agents/__init__.py | 1 | CODE | |
| LOW | src/agents/__init__.py | 1 | CODE | |
| LOW | src/agents/__init__.py | 1 | CODE | |
| LOW | src/schema/__init__.py | 1 | CODE | |
| LOW | src/schema/__init__.py | 2 | CODE | |
| LOW | src/schema/__init__.py | 2 | CODE | |
| LOW | src/schema/__init__.py | 2 | CODE | |
| LOW | src/schema/__init__.py | 2 | CODE | |
| LOW | src/schema/__init__.py | 2 | CODE | |
| LOW | src/schema/__init__.py | 2 | CODE | |
| LOW | src/schema/__init__.py | 2 | CODE | |
| LOW | src/schema/__init__.py | 2 | CODE | |
| LOW | src/schema/__init__.py | 2 | CODE | |
| LOW | src/schema/__init__.py | 2 | CODE | |
| LOW | src/schema/__init__.py | 2 | CODE | |
| LOW | src/schema/__init__.py | 2 | CODE | |
| LOW | src/voice/__init__.py | 33 | CODE | |
| LOW | src/voice/__init__.py | 34 | CODE | |
| LOW | src/voice/__init__.py | 35 | CODE | |
| LOW | src/voice/providers/__init__.py | 3 | CODE | |
| LOW | src/voice/providers/__init__.py | 4 | CODE | |
| LOW | src/service/__init__.py | 1 | CODE | |
| LOW | src/client/__init__.py | 1 | CODE | |
| LOW | src/client/__init__.py | 1 | CODE |
| Severity | File | Line | Snippet | Context |
|---|---|---|---|---|
| HIGH | src/agents/rag_assistant.py | 0 | `total=false` is pep589 specs. documentation: https://typing.readthedocs.io/en/latest/spec/typeddict.html#totality | STRING |
| HIGH | src/agents/interrupt_agent.py | 0 | `total=false` is pep589 specs. documentation: https://typing.readthedocs.io/en/latest/spec/typeddict.html#totality | STRING |
| HIGH | src/agents/command_agent.py | 0 | `total=false` is pep589 specs. documentation: https://typing.readthedocs.io/en/latest/spec/typeddict.html#totality | STRING |
| HIGH | src/agents/research_assistant.py | 0 | `total=false` is pep589 specs. documentation: https://typing.readthedocs.io/en/latest/spec/typeddict.html#totality | STRING |
| HIGH | src/agents/bg_task_agent/bg_task_agent.py | 0 | `total=false` is pep589 specs. documentation: https://typing.readthedocs.io/en/latest/spec/typeddict.html#totality | STRING |
| Severity | File | Line | Snippet | Context |
|---|---|---|---|---|
| LOW | src/core/__init__.py | 4 | __all__ = ["settings", "get_model"] | CODE |
| LOW | src/memory/mongodb.py | 11 | logger = logging.getLogger(__name__) | CODE |
| LOW | src/memory/__init__.py | 40 | __all__ = ["initialize_database", "initialize_store"] | CODE |
| LOW | src/memory/postgres.py | 11 | logger = logging.getLogger(__name__) | CODE |
| LOW | src/agents/interrupt_agent.py | 18 | logger = logging.getLogger(__name__) | CODE |
| LOW | src/agents/__init__.py | 10 | __all__ = [ | CODE |
| LOW⚡ | src/agents/knowledge_base_agent.py | 15 | logger = logging.getLogger(__name__) | CODE |
| LOW | src/agents/github_mcp_agent/github_mcp_agent.py | 15 | logger = logging.getLogger(__name__) | CODE |
| LOW | src/schema/__init__.py | 17 | __all__ = [ | CODE |
| LOW | src/voice/tts.py | 11 | logger = logging.getLogger(__name__) | CODE |
| LOW | src/voice/__init__.py | 37 | __all__ = ["VoiceManager", "SpeechToText", "TextToSpeech"] | CODE |
| LOW | src/voice/manager.py | 15 | logger = logging.getLogger(__name__) | CODE |
| LOW | src/voice/stt.py | 11 | logger = logging.getLogger(__name__) | CODE |
| LOW | src/voice/providers/openai_tts.py | 7 | logger = logging.getLogger(__name__) | CODE |
| LOW | src/voice/providers/__init__.py | 10 | __all__ = ["OpenAISTT", "OpenAITTS"] | CODE |
| LOW | src/voice/providers/openai_stt.py | 8 | logger = logging.getLogger(__name__) | CODE |
| LOW | src/service/service.py | 57 | logger = logging.getLogger(__name__) | CODE |
| LOW | src/service/agui.py | 29 | logger = logging.getLogger(__name__) | CODE |
| LOW | src/service/__init__.py | 3 | __all__ = ["app"] | CODE |
| LOW | src/service/threads.py | 17 | logger = logging.getLogger(__name__) | CODE |
| LOW | src/client/client.py | 77 | def update_agent(self, agent: str, verify: bool = True) -> None: | CODE |
| LOW | src/client/__init__.py | 3 | __all__ = ["AgentClient", "AgentClientError"] | CODE |
| Severity | File | Line | Snippet | Context |
|---|---|---|---|---|
| HIGH | scripts/e2e_ui_tests.py | 461 | except Exception: | CODE |
| HIGH | scripts/smoke_live_app.py | 60 | except Exception: | CODE |
| HIGH | scripts/smoke_live_app.py | 70 | except Exception: | CODE |
| HIGH | scripts/smoke_live_app.py | 83 | except Exception: | CODE |
| Severity | File | Line | Snippet | Context |
|---|---|---|---|---|
| HIGH | .claude/skills/maintainer-response/SKILL.md | 197 | ### Be skeptical of automated / AI-generated PRs | COMMENT |
| HIGH | .claude/skills/maintainer-response/SKILL.md | 303 | layer that *unconditionally* adds a "with Claude" badge and a "Generated by Claude Code" | CODE |
| Severity | File | Line | Snippet | Context |
|---|---|---|---|---|
| MEDIUM | CLAUDE.md | 11 | ## Maintainer scaffolding vs. template content | COMMENT |
| MEDIUM | docs/RAG_Assistant.md | 1 | # Creating a RAG assistant | COMMENT |
| MEDIUM | docs/RAG_Assistant.md | 20 | ## Configuring the RAG assistant | COMMENT |
| MEDIUM | .github/workflows/template-cleanup.yml | 4 | # downstream repo. It strips maintainer-only scaffolding, commits the result, then | COMMENT |
| Severity | File | Line | Snippet | Context |
|---|---|---|---|---|
| LOW | scripts/create_chroma_db.py | 81 | # Display results | COMMENT |
| LOW | src/streamlit_app.py | 34 | # Check if user_id exists in session state | COMMENT |
| LOW | src/streamlit_app.py | 623 | # Check if this is a nested transfer/delegate | COMMENT |
| LOW⚡ | src/agents/interrupt_agent.py | 93 | # Check if we already have the birthdate in the store for this user | COMMENT |
| LOW⚡ | src/agents/interrupt_agent.py | 98 | if result: # Check if anything was returned | CODE |
| LOW⚡ | src/agents/interrupt_agent.py | 100 | if result: # Check if list is not empty | CODE |
| LOW | src/agents/knowledge_base_agent.py | 66 | # Check if documents were retrieved | STRING |
| LOW | src/voice/manager.py | 99 | # Check if transcription succeeded | COMMENT |
| Severity | File | Line | Snippet | Context |
|---|---|---|---|---|
| HIGH | src/agents/safeguard.py | 60 | Answer: {"violation": 0, "category": null, "rationale": "Legitimate question about capabilities"} | CODE |
| HIGH | src/agents/safeguard.py | 64 | Answer: {"violation": 0, "category": null, "rationale": "Normal programming assistance request"} | CODE |
| Severity | File | Line | Snippet | Context |
|---|---|---|---|---|
| HIGH | src/voice/tts.py | 78 | Load the appropriate TTS provider implementation. Args: provider: Provider name api_key | STRING |
| HIGH | src/voice/stt.py | 76 | Load the appropriate STT provider implementation. Args: provider: Provider name api_key | STRING |
| Severity | File | Line | Snippet | Context |
|---|---|---|---|---|
| LOW | scripts/e2e_ui_tests.py | 145 | CODE | |
| LOW | scripts/e2e_ui_tests.py | 400 | CODE | |
| LOW | scripts/smoke_live_app.py | 117 | CODE | |
| LOW | src/streamlit_app.py | 67 | CODE | |
| LOW | src/streamlit_app.py | 369 | CODE | |
| LOW | src/streamlit_app.py | 558 | CODE | |
| LOW | src/agents/interrupt_agent.py | 77 | CODE | |
| LOW | src/service/service.py | 243 | CODE | |
| LOW | src/client/client.py | 204 | CODE |
| Severity | File | Line | Snippet | Context |
|---|---|---|---|---|
| MEDIUM | tests/service/test_threads.py | 236 | CODE |
| Severity | File | Line | Snippet | Context |
|---|---|---|---|---|
| LOW | scripts/smoke_test.sh | 1 | #!/usr/bin/env bash | COMMENT |
| LOW | scripts/smoke_test.sh | 21 | # Only databases run in Docker; the service itself runs on the host via uv, | COMMENT |
| Severity | File | Line | Snippet | Context |
|---|---|---|---|---|
| LOW | scripts/smoke_test.sh | 7 | # Usage: | COMMENT |