This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
AgentShip is a production-ready framework for building, deploying, and operating AI agents. Built on Google ADK and LangGraph, it provides REST API, session management, observability, streaming, and deployment tools.
Key Technologies:
- Python 3.13+ with pipenv for dependency management
- FastAPI (REST API server on port 7001)
- Google ADK & LangGraph (dual execution engines)
- PostgreSQL (session storage)
- LiteLLM (multi-provider LLM support)
- Docker & Docker Compose (containerization)
- OPIK (observability)
- MCP (Model Context Protocol for tool integration)
- Auto Tool Documentation (automatic prompt generation from schemas)
make docker-setup # First-time setup (creates .env, builds, starts containers)
make docker-up # Start containers (API: localhost:7001)
make docker-down # Stop containers
make docker-restart # Quick restart
make docker-reload # Hard reload (rebuild + restart)
make docker-logs # View logsPorts:
- API/Swagger/Docs/AgentShip Studio:
localhost:7001 - PostgreSQL (external):
localhost:5433(inside Docker:postgres:5432)
make dev # Start dev server (localhost:7001)
pipenv run uvicorn src.service.main:app --reload --host 0.0.0.0 --port 7001make test # Run all tests
make test-cov # Run tests with coverage report
pipenv run pytest tests/ -v
pipenv run pytest tests/unit/ -v # Unit tests only
pipenv run pytest tests/integration/ -v # Integration tests only
pipenv run pytest tests/unit/test_file.py -v # Single test filemake lint # Run flake8 linter
make format # Format code with black
make type-check # Run mypy type checkingmake docs-build # Build Sphinx documentation
make docs-serve # Build docs + start server (localhost:7001/docs)make heroku-deploy # Deploy to Heroku (one command)make clean # Clean caches (__pycache__, .pytest_cache, etc.)
pipenv install # Install production dependencies
pipenv install --dev # Install development dependenciessrc/
βββ agent_framework/ # Core framework (engines, tools, sessions, config)
β βββ configs/ # Agent & LLM configuration loaders
β βββ core/ # BaseAgent, I/O, types
β βββ engines/ # ADK & LangGraph execution engines
β β βββ adk/ # Google ADK engine
β β βββ langgraph/ # LangGraph engine with LiteLLM
β βββ factories/ # Engine, memory, observability, tool factories
β βββ mcp/ # MCP (Model Control Plane) integration
β βββ middleware/ # Middleware system
β βββ observability/ # OPIK observability integration
β βββ registry/ # Agent discovery and registration
β βββ session/ # Session storage (ADK, LangGraph adapters)
β βββ tools/ # Tool system (adapters, base tool, domains)
β βββ utils/ # Utilities (path, PDF, Azure)
βββ all_agents/ # Agent implementations (auto-discovered)
β βββ base_agent.py # Public import surface (BaseAgent, AgentType)
β βββ single_agent_pattern/
β βββ orchestrator_pattern/
β βββ tool_pattern/
β βββ ...
βββ service/ # FastAPI REST API layer
β βββ main.py # FastAPI app + routes
β βββ routers/ # API routers (REST, SSE, WebSocket)
βββ log_settings.py # Logging configuration
tests/
βββ unit/ # Unit tests
βββ integration/ # Integration tests
debug_ui/ # AgentShip Studio (localhost:7001/studio)
docs_sphinx/ # Sphinx documentation source
agent_store_deploy/ # Database setup scripts
service_cloud_deploy/ # Heroku deployment scripts
Agents are defined using two files:
- main_agent.yaml: Configuration (LLM, temperature, instructions, tools, MCP)
- main_agent.py: Python class inheriting from
BaseAgent
Example: Creating a New Agent
# 1. Create directory
mkdir -p src/all_agents/my_agent
# 2. Create main_agent.yaml
cat > src/all_agents/my_agent/main_agent.yaml << EOF
agent_name: my_agent
llm_provider_name: openai
llm_model: gpt-4o
temperature: 0.4
execution_engine: adk # or langgraph
streaming_mode: none # or token_by_token
description: My helpful assistant
instruction_template: |
You are a helpful assistant.
EOF
# 3. Create main_agent.py
cat > src/all_agents/my_agent/main_agent.py << EOF
from src.all_agents.base_agent import BaseAgent
from src.service.models.base_models import TextInput, TextOutput
from src.agent_framework.utils.path_utils import resolve_config_path
class MyAgent(BaseAgent):
def __init__(self):
super().__init__(
config_path=resolve_config_path(relative_to=__file__),
input_schema=TextInput,
output_schema=TextOutput
)
EOFAgents are auto-discovered on server restart (no manual registration needed).
AgentShip supports two pluggable engines:
- ADK Engine (
execution_engine: adk): Google ADK-based execution - LangGraph Engine (
execution_engine: langgraph): LangGraph with LiteLLM, supports token-by-token streaming
Set execution_engine in YAML. Default is adk.
- Agents in
src/all_agents/are auto-discovered at startup - Discovery looks for classes inheriting from
BaseAgent - Registry key =
agent_namefrommain_agent.yaml(not class name or directory name) - Only
main_agent.yamlfiles create discoverable agents; sub-agent YAMLs in orchestrator dirs are not independently registered - Registry:
src/agent_framework/registry/discovery.py
- Docker: PostgreSQL at
postgres:5432(container),localhost:5433(host) - Local: PostgreSQL at
localhost:5432 - Database name:
agentship_session_store - Adapters: ADK (
session/adapters/adk.py), LangGraph (session/adapters/langgraph.py)
- Tools can be defined in YAML (
tools:section) or Python - MCP Integration: Full Model Context Protocol support for both ADK and LangGraph
- MCP Configuration:
- Global:
.mcp.settings.json(JSON format compatible with Cursor IDE) - Per-agent:
mcp.serversin YAML
- Global:
- Tool adapters for ADK and LangGraph in
src/agent_framework/tools/adapters/ - MCP adapters:
src/agent_framework/mcp/adapters/(ADK and LangGraph) - Tool discovery:
src/agent_framework/mcp/tool_discovery.py - Auto Tool Documentation: Schemas automatically generate LLM prompts (
src/agent_framework/prompts/tool_documentation.py)
- OPIK integration for monitoring and evaluation
- Configured via environment variables
- Adapters for ADK and LangGraph in
src/agent_framework/observability/opik/adapters/
AgentShip has production-ready MCP (Model Context Protocol) support for both ADK and LangGraph engines.
- β STDIO Transport - Connect to local MCP servers via stdin/stdout
- β HTTP/OAuth Transport - Connect to remote services (GitHub, Slack) via OAuth 2.0
- β Auto Tool Discovery - Tools discovered automatically from MCP servers
- β Auto Documentation - Tool schemas β LLM prompts (zero manual work)
- β Dual Engine Support - Works with both ADK and LangGraph
- β Per-Agent Client Isolation - OAuth servers get separate client per agent
- β
Env Var Resolution -
${VAR}tokens in command args resolved at load time - β Event Loop Safe - Automatic reconnection when event loop changes
- β Configuration-First - Define servers in JSON or YAML
| Transport | Client Class | Use Case | Auth |
|---|---|---|---|
stdio |
StdioMCPClient |
Local servers (npx, Python) | Env vars |
http/sse |
SSEMCPClient |
Remote APIs | OAuth 2.0 (requires AGENTSHIP_AUTH_DB_URI) |
Agent YAML
β
MCP Registry (.mcp.settings.json)
β
MCPClientManager (singleton, per-agent cache)
β
StdioMCPClient / SSEMCPClient
β
Tool Discovery (fetches tool schemas)
β
Tool Adapters (ADK or LangGraph)
β
Tool Documentation Generator (extracts schemas)
β
Prompt Builder (injects into system prompt)
β
Agent uses tools (MCP calls handled automatically)
STDIO servers are shared (all agents share one client). OAuth/HTTP servers are isolated per agent to prevent token cross-contamination:
# STDIO: shared client (safe to share)
client = manager.get_client(postgres_config)
# HTTP/OAuth: per-agent client
client = manager.get_client(github_config, owner="my_agent")Cache key: "{server_id}" for shared, "{server_id}:{agent_name}" for per-agent.
Global Config (.mcp.settings.json):
{
"servers": {
"postgres": {
"transport": "stdio",
"command": "npx",
"args": [
"-y",
"@modelcontextprotocol/server-postgres",
"${AGENT_SESSION_STORE_URI}"
]
},
"github": {
"transport": "http",
"url": "https://api.githubcopilot.com/mcp/",
"auth": {
"type": "oauth",
"provider": "github",
"client_id_env": "GITHUB_OAUTH_CLIENT_ID",
"client_secret_env": "GITHUB_OAUTH_CLIENT_SECRET",
"authorize_url": "https://github.com/login/oauth/authorize",
"token_url": "https://github.com/login/oauth/access_token",
"scopes": ["repo"]
}
}
}
}Agent YAML:
agent_name: database_agent
execution_engine: adk # or langgraph - both work!
mcp:
servers:
- postgres # STDIO: shared client
- github # HTTP/OAuth: per-agent clientTool schemas are automatically converted to LLM-friendly documentation:
Tool Schema (from MCP):
{
"name": "query",
"description": "Execute SQL query",
"inputSchema": {
"type": "object",
"properties": {
"sql": {"type": "string", "description": "SQL query to execute"}
},
"required": ["sql"]
}
}Generated Documentation (injected into prompt):
## Available Tools
### query
**Description:** Execute SQL query
**Parameters:**
- `sql` (string, **required**): SQL query to execute
**Example:**
```json
{"sql": "SELECT * FROM table"}
This is **automatically injected** into the agent's system prompt. No manual work needed!
### Key Files
- **Registry**: `src/agent_framework/mcp/registry.py` - MCP server configuration, env var resolution
- **Client Manager**: `src/agent_framework/mcp/client_manager.py` - Singleton, per-agent cache
- **STDIO Client**: `src/agent_framework/mcp/clients/stdio.py` - Process spawning
- **SSE Client**: `src/agent_framework/mcp/clients/sse.py` - HTTP/OAuth transport
- **Tool Discovery**: `src/agent_framework/mcp/tool_discovery.py` - Schema fetching
- **ADK Adapter**: `src/agent_framework/mcp/adapters/adk.py` - ADK tool wrapper
- **LangGraph Adapter**: `src/agent_framework/mcp/adapters/langgraph.py` - LangGraph wrapper
- **Tool Doc Generator**: `src/agent_framework/prompts/tool_documentation.py` - Schema β prompt
- **Tool Manager**: `src/agent_framework/tools/tool_manager.py` - Unified tool creation
- **Token Encryption**: `src/agent_framework/mcp/token_encryption.py` - OAuth token storage
- **DB Operations**: `src/agent_framework/mcp/db_operations.py` - CRUD for OAuth tokens
- **CLI**: `src/cli/` - `agentship mcp connect/list-connections/disconnect`
### Event Loop Handling
MCP clients detect event loop changes and automatically reconnect:
```python
# Agent initialization (one event loop)
agent = PostgresMcpAgent()
# Web request (different event loop)
result = await agent.chat(request) # β
Auto-reconnects!
Logs show:
[WARNING] Event loop changed for postgres, reconnecting...
[INFO] Connecting to STDIO MCP server postgres...
[INFO] MCP STDIO client connected and initialized
This is normal and expected in production web servers.
AgentShip works with any MCP server:
- PostgreSQL -
@modelcontextprotocol/server-postgres - Filesystem -
@modelcontextprotocol/server-filesystem - GitHub -
@modelcontextprotocol/server-github - Google Drive -
@modelcontextprotocol/server-gdrive - Brave Search -
@modelcontextprotocol/server-brave-search
See MCP Servers for full list.
# Unit tests (no external deps)
pipenv run pytest tests/unit/agent_framework/test_mcp/ -v
# Integration tests
pipenv run pytest tests/integration/test_mcp_infrastructure.py -v # no external deps
pipenv run pytest tests/integration/test_mcp_http_agents.py -v # no external deps
pipenv run pytest tests/integration/test_mcp_stdio_agents.py -v # requires Docker postgressrc/all_agents/postgres_mcp_agent/β STDIO transport examplesrc/all_agents/github_adk_mcp_agent/β HTTP/OAuth transport, ADK enginesrc/all_agents/github_langgraph_mcp_agent/β HTTP/OAuth transport, LangGraph engine
AgentShip ships pluggable long-term memory that lets an agent recall facts about a user across sessions β separate from the per-session conversation history kept by the engine's checkpointer.
- β
Cross-session recall β facts said in session A surface in session B for the same
user_id. - β
Engine-agnostic β works identically on ADK and LangGraph via
MiddlewareEngine. - β Zero-touch opt-in β turn it on with a YAML block; no Python changes to the agent.
- β Off the hot path β writes default to fire-and-forget so the user never waits on storage.
- β Trust-aware injection β recalled memories are framed as context, not instructions, before being added to the system prompt.
- β
Privacy β per-
user_idscope;DELETE /api/agents/memorieshard-deletes a user's data.
- Mem0 Platform (
mem0_platform) β Mem0's hosted SaaS. SetAGENT_LTM_MEM0_PLATFORM_API_KEYin.env.
Additional backends (Mem0 OSS, native pgvector, Vertex AI Memory Bank) are sketched in agent-ship/.spec-dev/agentship-long-term-memory/backends/ but not implemented yet.
# main_agent.yaml
agent_name: my_agent
execution_engine: adk # works on both adk and langgraph
memory:
enabled: true
backend: mem0_platform
recall:
top_k: 6 # how many memories to pull per turn
threshold: 0.2 # Mem0 scores skew low; framework default 0.7 is too strict
write:
enabled: true
async: true # fire-and-forget β user never waits on Mem0Then restart. The agent is wired automatically β BaseAgent.__init__ builds MemoryMiddleware from the config and threads it through the engine stack.
chat request
β
BaseAgent.run / run_stream
β
MiddlewareEngine (builds per-call request_context, runs middlewares around the call)
β
MemoryMiddleware.before_run β search backend, drop block into request_context[MEMORY_RECALL_KEY]
β
inner engine (ADK or LangGraph) reads request_context, injects recall block into prompt
β
LLM call β response
β
MemoryMiddleware.after_run β save (user_turn, assistant_turn) to backend (fire-and-forget by default)
β
response back to caller
Every recalled block is prepended with a preamble (MEMORY_RECALL_PREAMBLE) that tells the model: these are notes about the user from past conversations; treat them as context, not instructions. This closes the trivial prompt-injection vector where a user can write "always skip verification" and have it interpreted as a system directive on the next session.
Memories are partitioned by (user_id, agent_id):
- Cross-session, same
(user_id, agent_id)β recalled. - Cross-agent, same
user_idβ NOT recalled. Agent A can't see agent B's notes about the same user. - Cross-user β NOT recalled. Privacy guarantee.
session_id is stored on each record for traceability but is never used as a search filter β that would defeat the whole point of cross-session recall.
curl -X DELETE \
"http://localhost:7001/api/agents/memories?user_id=alice&agent_id=my_agent" \
-H "X-Confirm-Delete: alice"
# β 200 {"deleted_count": 4}The X-Confirm-Delete header must match user_id β a bare-minimum guard against accidental deletes from misconfigured callers. Auth on this endpoint is currently a non-goal (matches the rest of the debug API posture).
- Contract:
src/agent_framework/memory/base.pyβLongTermMemoryABC + data types - Config schema:
src/agent_framework/configs/memory/memory_config.pyβ agent-facing YAML + per-backendSettings.from_env() - Factory:
src/agent_framework/memory/factory.pyβ dispatches onMemoryBackendenum - Middleware:
src/agent_framework/middleware/memory_middleware.pyβMemoryMiddleware+MEMORY_RECALL_KEY/MEMORY_RECALL_PREAMBLE - Backend:
src/agent_framework/memory/backends/mem0_platform.pyβ Mem0 SaaS adapter - DELETE endpoint:
src/service/routers/memory_router.py - Author guide:
agent-ship/docs/long-term-memory.md - Design spec:
agent-ship/.spec-dev/agentship-long-term-memory/design.md
# Unit tests
pipenv run pytest tests/unit/agent_framework/memory/ -v
pipenv run pytest tests/unit/agent_framework/middleware/ -v
pipenv run pytest tests/unit/agent_framework/engines/ -v
pipenv run pytest tests/unit/service/routers/test_memory_router.py -v
# End-to-end cross-session recall (real Mem0 β gated on AGENT_LTM_MEM0_PLATFORM_API_KEY)
docker exec agentship-api python -m pytest tests/integration/test_memory_cross_session.py -v- INFO
AdkEngine.run: ... memory_recall=True/Falseβ one per call; tells you at a glance whether recall fired. - INFO
LangGraphEngine.run: ... memory_recall=True/Falseβ same. - WARNING
memory.recall.failed/memory.write.failedβ backend errors are swallowed (graceful degrade) but always logged. - At DEBUG level: per-skip reasons, per-write outcomes, recall hit counts (
memory.recall.hit found=N injected_chars=M).
Copy env.example to .env and configure:
Required:
- At least one LLM API key:
OPENAI_API_KEY,ANTHROPIC_API_KEY, orGOOGLE_API_KEY
Optional:
AGENT_SESSION_STORE_URI: PostgreSQL connection stringAGENT_SHORT_TERM_MEMORY: Session storage type (DatabaseorInMemory)LOG_LEVEL: Logging level (INFO,DEBUG)ENVIRONMENT: Environment name (local,docker,production)
Docker Networking:
- Docker containers use
postgres:5432(service name) - Local development uses
localhost:5432 docker-compose.ymlautomatically overridesAGENT_SESSION_STORE_URIfor Docker
The repository includes example patterns:
- Single Agent (
single_agent_pattern/): Simple one-agent flow - Orchestrator (
orchestrator_pattern/): Main agent + sub-agents (flight, hotel, summary) - Tool Pattern (
tool_pattern/): Agent with custom tools - File Analysis (
file_analysis_agent/): PDF/document analysis - MCP Demo (
mcp_demo_agent/): MCP integration example
- Use
pipenv run pytest(not barepytest) - Tests marked with
@pytest.mark.unitor@pytest.mark.integration - Async tests supported (
asyncio_mode = autoin pytest.ini) - Coverage reports in
htmlcov/index.html - Test structure mirrors
src/structure
project_root_cwd()β context manager, required when callingdiscover_agents("src/all_agents")reset_mcp_singletonsβ autouse, resetsMCPClientManagerandMCPServerRegistrybetween testsreset_agent_instance_cacheβ autouse, clears the agent registry singletonmock_langgraph_llm(response_text)β patchessrc.agent_framework.engines.langgraph.engine.acompletionrequire_postgres()β returnspytest.mark.skipifwhenAGENT_SESSION_STORE_URIis not set
# ADK engine access (MiddlewareEngine wraps actual engine)
agent.engine._inner.runner # NOT agent.engine.runner
# LangGraph LLM mock
with mock_langgraph_llm("response text"):
events = [e async for e in agent.chat_stream(request)]
# MCP singletons reset between tests
MCPClientManager.reset_instance()
MCPServerRegistry._instance = NoneWhen server is running (port 7001):
- Swagger UI: http://localhost:7001/swagger
- Documentation: http://localhost:7001/docs
- AgentShip Studio: http://localhost:7001/studio (legacy
/debug-uiβ 301 redirect) - Health: http://localhost:7001/health
- Agent Chat: POST http://localhost:7001/api/agents/chat
| Method | Path | Description |
|---|---|---|
| GET | /api/debug/agents |
List all registered agents with schemas |
| GET | /api/debug/agents/{name}/schema |
Input/output schema for an agent |
| GET | /api/debug/agents/{name}/config |
Engine, model, provider, streaming mode |
| POST | /api/debug/chat |
Non-streaming chat (returns full response) |
| POST | /api/debug/chat/stream |
SSE streaming chat (thinking, content, tool_call, tool_result, done) |
| POST | /api/debug/feedback |
Record thumbs up/down feedback |
| GET | /api/debug/sessions |
List sessions |
| POST | /api/debug/sessions |
Create session |
| DELETE | /api/debug/sessions/{id} |
Delete session |
| Environment | Command | Database | Access |
|---|---|---|---|
| Docker | make docker-up |
agentship_session_store |
postgres:5432 (container), localhost:5433 (host) |
| Local | make dev |
agentship_session_store |
localhost:5432 |
| Heroku | Auto-provisioned | Heroku PostgreSQL | DATABASE_URL env var |
Important: Docker and local use separate databases. Data does not sync.
-
Start Development
- Docker:
make docker-setup(first time), thenmake docker-up - Local:
make dev
- Docker:
-
Create New Agent
- Create directory in
src/all_agents/my_new_agent/ - Add
main_agent.yamlandmain_agent.py - Restart server (auto-discovery)
- Create directory in
-
Test Agent
- Use AgentShip Studio: http://localhost:7001/studio
- Or use Swagger: http://localhost:7001/swagger
- Or curl/Postman
-
Run Tests
make testorpipenv run pytest tests/ -v
-
Code Quality
- Format:
make format - Lint:
make lint - Type check:
make type-check
- Format:
- Agent auto-discovery: Agents in
src/all_agents/are automatically discovered on server start - Hot-reload: Code changes auto-reload in Docker (
docker-compose.ymlmountssrc/) - Python version: Requires Python 3.13+ (see
runtime.txt) - Dependency management: Uses pipenv (not pip directly)
- MCP integration:
- Works with both ADK and LangGraph engines
- Configure servers in
.mcp.settings.json(global) or agent YAML (per-agent) - Tool documentation auto-generated from schemas
- Event loop safe (auto-reconnects when needed)
- STDIO transport (local npx/Python) and HTTP/OAuth transport (GitHub, Slack, etc.) both supported
- Auto Tool Documentation:
- Tool schemas are single source of truth
- Documentation auto-generated and injected into prompts
- Works for all tool types (functions, MCP tools, agent tools)
- Zero maintenance required
- Streaming: LangGraph engine supports token-by-token streaming (
streaming_mode: token_based) - Observability: OPIK integration available for both ADK and LangGraph engines
- Memory optimization: Target is 512MB memory limit (see health check endpoint)
from src.all_agents.base_agent import BaseAgent, AgentTypefrom src.agent_framework.utils.path_utils import resolve_config_path
config_path=resolve_config_path(relative_to=__file__)from pydantic import BaseModel
class CustomInput(BaseModel):
field1: str
field2: int
class CustomOutput(BaseModel):
result: str# In BaseAgent subclass
self.agent_config.agent_name
self.agent_config.llm_model
self.agent_config.temperatureProject-level skills live in .claude/commands/ and are available to anyone who opens this repo in Claude Code.
| Skill | Invoke | What it does |
|---|---|---|
/new-agent |
/new-agent weather_agent |
Scaffolds a new agent (YAML + Python) with prompts for engine, model, instruction |
/add-mcp |
/add-mcp postgres |
Adds an MCP server to .mcp.settings.json and wires it to an agent |
/run-tests |
/run-tests unit |
Runs the right tests for changed code with correct flags |
/studio |
/studio |
Reference for Studio URLs, debug API endpoints, and troubleshooting |
- Logs: Check
dev_app.logormake docker-logs - AgentShip Studio: http://localhost:7001/studio for interactive testing
- Health check: http://localhost:7001/health shows memory usage
- PostgreSQL: Connect with
psql -h localhost -p 5433 -U agentship_user -d agentship_session_store(Docker) or port 5432 (local)