# Ads Research Agents Source: https://docs.shannon.run/en/api/agents/ads-research Competitive advertising intelligence and landing page analysis **Shannon Cloud Only**: These agents require a Shannon Cloud subscription and use paid external APIs: * **SerpAPI** (Google Search ads) * **SearchAPI.io** (Meta Ad Library) * **Playwright service** (browser automation) * **Vision LLM** (landing page analysis) ## Overview Shannon provides **10 specialized agents** for competitive advertising research, from ad discovery to creative analysis. **Use cases:** * Monitor competitor advertising strategies * Discover winning ad creative patterns * Analyze landing page messaging and design * Track multi-platform ad campaigns (Google, Yahoo JP, Meta) *** ## Available Agents ### serp-ads Extract paid ads from Google Search results for given keywords. **Agent ID:** `serp-ads` **Input Schema:** | Field | Type | Required | Default | Description | | --------------- | ------- | -------- | -------------- | ------------------------------------------ | | `keywords` | string | Yes | - | Search keywords to find ads for | | `country` | string | No | `"us"` | Country code (us, jp, uk, etc.) | | `location` | string | No | - | Specific location for geo-targeted ads | | `language` | string | No | `"en"` | Language code (en, ja, zh, etc.) | | `device` | string | No | `"desktop"` | Device type: `desktop`, `mobile`, `tablet` | | `google_domain` | string | No | `"google.com"` | Google domain to query | | `max_results` | integer | No | `10` | Maximum number of ads to return | **Example Request:** ```bash theme={null} curl -X POST http://localhost:8080/api/v1/agents/serp-ads \ -H "X-API-Key: sk_your_api_key" \ -H "Content-Type: application/json" \ -d '{ "input": { "keywords": "best credit cards", "country": "us", "device": "mobile", "max_results": 5 } }' ``` **Output Structure:** ```json theme={null} { "keywords": "best credit cards", "location": "us", "total_ads": 5, "ads": [ { "position": 1, "title": "Best Credit Cards 2026", "description": "Compare top credit cards and find the best rewards...", "link": "https://example.com/cards", "domain": "example.com" } ], "cost_usd": 0.015, "timestamp": "2026-02-15T10:30:00Z" } ``` *** ### yahoo-jp-ads Extract sponsored ads from Yahoo Japan search results. **Agent ID:** `yahoo-jp-ads` **No API key required** - Uses web scraping. Free, but Cloud-only due to IP rotation requirements. **Input Schema:** | Field | Type | Required | Description | | ---------- | ------ | -------- | ------------------------------------ | | `keywords` | string | Yes | Search keywords (Japanese preferred) | **Example Request:** ```bash theme={null} curl -X POST http://localhost:8080/api/v1/agents/yahoo-jp-ads \ -H "X-API-Key: sk_your_api_key" \ -H "Content-Type: application/json" \ -d '{ "input": { "keywords": "クレジットカード おすすめ" } }' ``` **Output Structure:** ```json theme={null} { "keywords": "クレジットカード おすすめ", "total_ads": 3, "ads": [ { "position": 1, "title": "セブンカード・プラス 公式サイト", "description": "年会費無料でnanaco搭載...", "link": "https://www.7card.co.jp/...", "domain": "7card.co.jp", "source": "yahoo_jp" } ], "cost_usd": 0, "timestamp": "2026-02-15T10:30:00Z" } ``` *** ### meta-ad-library Search Meta Ad Library for Facebook and Instagram ads. **Agent ID:** `meta-ad-library` **Input Schema:** | Field | Type | Required | Default | Description | | ---------- | ------ | -------- | ------- | ------------------------------------------------ | | `query` | string | Yes | - | Search query (brand name, keywords) | | `country` | string | No | `"US"` | Country code (US, JP, UK, etc.) | | `platform` | string | No | All | Filter: `facebook`, `instagram`, or omit for all | **Example Request:** ```bash theme={null} curl -X POST http://localhost:8080/api/v1/agents/meta-ad-library \ -H "X-API-Key: sk_your_api_key" \ -H "Content-Type: application/json" \ -d '{ "input": { "query": "nike running shoes", "country": "US", "platform": "instagram" } }' ``` **Output Structure:** ```json theme={null} { "search_params": { "query": "nike running shoes", "country": "US", "source": "meta_ad_library" }, "summary": { "total_ads_found": 245, "active_ads": 18, "platforms": ["FACEBOOK", "INSTAGRAM"], "unique_advertisers": 5 }, "competitors": [ { "name": "Nike Official", "domain": "nike.com", "page_id": "123456789", "ad_count": 18, "platforms": ["INSTAGRAM"], "sample_ads": [ { "title": "Run Further. Run Faster.", "description": "Discover the new Nike Air Zoom...", "link": "https://www.nike.com/running", "cta": "Shop Now", "platforms": ["INSTAGRAM"], "images": ["https://..."], "display_format": "image" } ], "source": "meta_ad_library" } ], "cost_usd": 0.004, "timestamp": "2026-02-15T10:30:00Z" } ``` *** ### competitor-discover Discover competitor advertisers for given keywords across devices and locations. **Agent ID:** `competitor-discover` **Input Schema:** | Field | Type | Required | Default | Description | | ---------------------- | ---------------- | -------- | ----------------------- | ------------------------------------------------ | | `keywords` | array of strings | Yes | - | Search keywords to find competitors | | `country` | string | No | `"us"` | Country code | | `language` | string | No | `"en"` | Language code | | `location` | string | No | - | Specific location (e.g., "Tokyo, Japan") | | `devices` | array of strings | No | `["desktop", "mobile"]` | Devices to search | | `transparency_top_n` | integer | No | `5` | Top N competitors to fetch transparency data for | | `max_competitors` | integer | No | `20` | Maximum competitors to return | | `include_transparency` | boolean | No | `false` | Fetch Google Ads Transparency data | | `organic_fallback` | boolean | No | `true` | Use organic results when no paid ads | **Example Request:** ```bash theme={null} curl -X POST http://localhost:8080/api/v1/agents/competitor-discover \ -H "X-API-Key: sk_your_api_key" \ -H "Content-Type: application/json" \ -d '{ "input": { "keywords": ["running shoes", "athletic footwear"], "country": "us", "devices": ["desktop", "mobile"], "include_transparency": true, "transparency_top_n": 3 } }' ``` **Output Structure:** ```json theme={null} { "competitors": [ { "domain": "nike.com", "ad_count": 15, "devices_found": ["desktop", "mobile"], "sample_ads": [ { "title": "Nike Running Shoes", "description": "Find your perfect running shoe...", "link": "https://nike.com/running", "device": "desktop" } ], "transparency_data": { "advertiser_id": "AR12345678", "total_ads": 523, "platforms": ["SEARCH", "YOUTUBE"] } } ], "total_ads_found": 45, "api_cost_usd": 0.03 } ``` *** ### ads-transparency Multi-platform ad transparency: Google Transparency Center, Yahoo JP ads, Meta Ad Library. **Agent ID:** `ads-transparency` **Input Schema:** | Field | Type | Required | Description | | ----------------- | ------ | -------- | ------------------------------------------------------------------ | | `advertiser_id` | string | No | Google Ads advertiser ID (preferred for Google) | | `domain` | string | No | Domain name (Google fallback, Yahoo keyword, Meta query) | | `country` | string | No | Country code (e.g., 'jp', 'us') - controls auto-platform selection | | `platforms` | array | No | Override auto-selection: `['google', 'yahoo', 'meta']` | | `platform` | string | No | Google platform filter: `SEARCH`, `YOUTUBE`, `SHOPPING`, etc. | | `meta_platform` | string | No | Meta platform filter: `facebook`, `instagram` | | `creative_format` | string | No | Google ad format: `TEXT`, `IMAGE`, `VIDEO` | | `start_date` | string | No | Start date (YYYY-MM-DD) | | `end_date` | string | No | End date (YYYY-MM-DD) | **Example Request (Japan market, all platforms):** ```bash theme={null} curl -X POST http://localhost:8080/api/v1/agents/ads-transparency \ -H "X-API-Key: sk_your_api_key" \ -H "Content-Type: application/json" \ -d '{ "input": { "domain": "7card.co.jp", "country": "jp" } }' ``` **Output Structure:** ```json theme={null} { "google": { "advertiser_id": "AR12345678", "total_ads": 523, "platforms": ["SEARCH", "YOUTUBE"], "sample_ads": [...] }, "yahoo_jp": { "total_ads": 3, "ads": [...] }, "meta": { "total_ads": 18, "platforms": ["FACEBOOK", "INSTAGRAM"], "advertisers": [...] }, "platforms_queried": ["google", "yahoo_jp", "meta"], "country": "jp", "errors": {} } ``` *** ### lp-visual-analyze Screenshot and analyze a landing page using Playwright + Vision LLM. **Agent ID:** `lp-visual-analyze` **Input Schema:** | Field | Type | Required | Default | Description | | ------------------ | ------- | -------- | ------------- | ---------------------------------------------- | | `url` | string | Yes | - | Landing page URL to analyze | | `language` | string | No | `"en"` | Language for OCR (ja, zh, ko enable OCR) | | `full_page` | boolean | No | `true` | Capture full scrolled page | | `analysis_prompt` | string | No | - | Custom analysis prompt | | `include_markdown` | boolean | No | `true` | Include extracted text content | | `enable_ocr` | boolean | No | Auto | Force enable/disable OCR | | `capture_mode` | string | No | `"full_page"` | `full_page` or `sections` | | `device` | string | No | `"desktop"` | Device viewport: `desktop`, `mobile`, `tablet` | | `max_sections` | integer | No | `8` | Max sections in sections mode | **Example Request (Full page analysis):** ```bash theme={null} curl -X POST http://localhost:8080/api/v1/agents/lp-visual-analyze \ -H "X-API-Key: sk_your_api_key" \ -H "Content-Type: application/json" \ -d '{ "input": { "url": "https://www.7card.co.jp/lp/plus/", "language": "ja", "full_page": true, "include_markdown": true } }' ``` **Output Structure (full\_page mode):** ```json theme={null} { "url": "https://www.7card.co.jp/lp/plus/", "screenshot_url": "https://storage.firecrawl.dev/...", "vision_analysis": { "success": true, "structured": { "above_the_fold": { "main_headline": "セブンカード・プラス", "primary_cta": {"text": "今すぐ申し込む", "color": "orange"} }, "page_sections": [ {"name": "Hero", "position": 1, "key_content": "Main headline and CTA"} ], "pricing": {"visible": true, "currency": "JPY"}, "trust_elements": {"testimonials": {"count": 3}}, "visual_design": {"primary_colors": ["orange", "white"], "style": "modern"} }, "cost_usd": { "vision": 0.0016, "scrape": 0.0053, "ocr": 0.0015, "total": 0.0084 } } } ``` **Large Binary Data**: Screenshots >500KB are stored in Redis and replaced with blob references. Use `/api/v1/blob/{key}` to retrieve the actual image data. See [Blob Storage API](/en/api/rest/agents#blob-storage) for details. *** ### lp-batch-analyze Analyze multiple landing pages in parallel. **Agent ID:** `lp-batch-analyze` **Input Schema:** | Field | Type | Required | Default | Description | | ------------------ | ---------------- | -------- | ----------- | ---------------------------------- | | `urls` | array of strings | Yes | - | List of landing page URLs (max 10) | | `language` | string | No | `"en"` | Language code | | `full_page` | boolean | No | `false` | Capture full page | | `device` | string | No | `"desktop"` | Device viewport | | `analysis_prompt` | string | No | - | Custom analysis prompt | | `include_markdown` | boolean | No | `false` | Include extracted text | **Example Request:** ```bash theme={null} curl -X POST http://localhost:8080/api/v1/agents/lp-batch-analyze \ -H "X-API-Key: sk_your_api_key" \ -H "Content-Type: application/json" \ -d '{ "input": { "urls": [ "https://competitor1.com/landing", "https://competitor2.com/offer", "https://competitor3.com/product" ], "device": "mobile", "language": "en" } }' ``` **Output Structure:** ```json theme={null} { "batch_id": "batch-abc123", "total_urls": 3, "successful": 3, "failed": 0, "results": [ { "url": "https://competitor1.com/landing", "success": true, "vision_analysis": { "structured": {"above_the_fold": {"main_headline": "..."}}, "cost_usd": {"total": 0.008} } } ], "cost_usd": { "firecrawl": 0.0159, "vision": 0.0045, "total": 0.0204 } } ``` *** ### ad-creative-analyze Analyze ad copy patterns, persuasion techniques, and messaging gaps. **Agent ID:** `ad-creative-analyze` **Input Schema:** | Field | Type | Required | Default | Description | | ------------- | ---------------- | -------- | ----------- | -------------------------------------------------------------- | | `ads` | array of objects | Yes | - | Ad objects to analyze | | `competitors` | array of objects | No | - | Optional competitor objects | | `industry` | string | No | `"general"` | Industry preset: `ecommerce`, `shoes`, `finance`, `saas`, etc. | | `language` | string | No | `"en"` | Language of ad copy | **Example Request:** ```bash theme={null} curl -X POST http://localhost:8080/api/v1/agents/ad-creative-analyze \ -H "X-API-Key: sk_your_api_key" \ -H "Content-Type: application/json" \ -d '{ "input": { "ads": [ { "title": "Best Running Shoes 2026", "description": "Ultra-lightweight design with maximum cushioning...", "domain": "nike.com" }, { "title": "Run Like a Pro", "description": "Professional-grade performance for everyday runners...", "domain": "adidas.com" } ], "industry": "shoes", "language": "en" } }' ``` **Output Structure:** ```json theme={null} { "headline_patterns": [ "Year-based urgency (2026)", "Superlative positioning (Best, Ultra)" ], "persuasion_techniques": [ "Social proof (Pro endorsement)", "Performance benefits (lightweight, cushioning)" ], "cta_patterns": [ "Direct action (Shop Now)", "Information seeking (Learn More)" ], "emotional_triggers": [ "Aspiration (Run Like a Pro)", "Safety (maximum cushioning)" ], "competitive_gaps": [ "No price messaging in competitor ads", "Limited warranty/guarantee mentions" ], "recommendations": [ "Test price-focused headlines", "Add money-back guarantee in copy" ], "api_cost_usd": 0.02 } ``` *** ### keyword-extract Extract search keywords from a natural language query. **Agent ID:** `keyword-extract` **Open Source Compatible**: Works in Shannon OSS with your LLM provider API key. **Input Schema:** | Field | Type | Required | Default | Description | | -------------- | ------- | -------- | ------------- | --------------------------------------------- | | `query` | string | Yes | - | Natural language query or product description | | `country` | string | No | `"us"` | Target country for keyword localization | | `language` | string | No | Auto-detected | Preferred language for keywords | | `max_keywords` | integer | No | `5` | Maximum keywords to extract | **Example Request:** ```bash theme={null} curl -X POST http://localhost:8080/api/v1/agents/keyword-extract \ -H "X-API-Key: sk_your_api_key" \ -H "Content-Type: application/json" \ -d '{ "input": { "query": "I need shoes for marathon training in winter conditions", "country": "us", "max_keywords": 3 } }' ``` **Output Structure:** ```json theme={null} { "keywords": [ "marathon training shoes", "winter running shoes", "cold weather athletic footwear" ], "detected_language": "en", "detected_country": "us", "api_cost_usd": 0.001 } ``` *** ### browser-screenshot Navigate to a URL and capture a screenshot using Playwright. **Agent ID:** `browser-screenshot` **Input Schema:** | Field | Type | Required | Default | Description | | ------------ | ------- | -------- | -------------------- | ------------------------------------------------------------------ | | `url` | string | Yes | - | URL to capture | | `full_page` | boolean | No | `false` | Capture full scrolled page | | `wait_until` | string | No | `"domcontentloaded"` | When navigation is done: `load`, `domcontentloaded`, `networkidle` | | `timeout_ms` | integer | No | `30000` | Navigation timeout (milliseconds) | **Example Request:** ```bash theme={null} curl -X POST http://localhost:8080/api/v1/agents/browser-screenshot \ -H "X-API-Key: sk_your_api_key" \ -H "Content-Type: application/json" \ -d '{ "input": { "url": "https://www.nike.com", "full_page": true } }' ``` **Output Structure:** ```json theme={null} { "screenshot": "iVBORw0KGgoAAAANSUhEUgA...", // Base64 PNG "url": "https://www.nike.com", "title": "Nike. Just Do It." } ``` **Base64 encoding**: Screenshot is returned as base64-encoded PNG. For large images (>500KB), a blob reference is returned instead. See [Blob Storage API](#blob-storage). *** ## Blob Storage API For responses containing large base64 data (screenshots >500KB), the data is stored in Redis and replaced with a reference object. **Response Format with Blob Reference:** ```json theme={null} { "screenshot_b64": null, "screenshot_b64_ref": { "redis_key": "shannon:blob:task-abc123:screenshot_b64", "size": 1500000, "ttl_days": 7 } } ``` **Retrieving Blob Data:** ```bash theme={null} curl -H "X-API-Key: sk_your_api_key" \ "http://localhost:8080/api/v1/blob/shannon:blob:task-abc123:screenshot_b64" ``` **Returns:** Raw base64 string (Content-Type: text/plain) **Blob Properties:** * TTL: 7 days (refreshed on each access) * Cache-Control: 7-day client caching * Affected fields: `screenshot_b64`, `popup_screenshot_b64`, `data_base64`, `image_base64`, `screenshot` *** ## Common Workflows ### Workflow 1: Competitor Ad Discovery ```python theme={null} import httpx client = httpx.Client( base_url="http://localhost:8080", headers={"X-API-Key": "sk_your_api_key"} ) # Step 1: Discover competitors competitors = client.post("/api/v1/agents/competitor-discover", json={ "input": { "keywords": ["running shoes", "athletic footwear"], "include_transparency": True } }).json() # Step 2: Analyze their ads for competitor in competitors["competitors"][:3]: ads_analysis = client.post("/api/v1/agents/ad-creative-analyze", json={ "input": { "ads": competitor["sample_ads"], "industry": "shoes" } }).json() print(f"{competitor['domain']}: {ads_analysis['recommendations']}") ``` ### Workflow 2: Landing Page Competitive Analysis ```python theme={null} # Step 1: Get competitor domains competitors = client.post("/api/v1/agents/serp-ads", json={ "input": {"keywords": "credit cards", "max_results": 5} }).json() # Step 2: Batch analyze their landing pages lp_urls = [ad["link"] for ad in competitors["ads"]] lp_analysis = client.post("/api/v1/agents/lp-batch-analyze", json={ "input": { "urls": lp_urls, "device": "mobile", "language": "en" } }).json() # Step 3: Compare results for result in lp_analysis["results"]: print(f"{result['url']}: {result['vision_analysis']['structured']['above_the_fold']}") ``` ### Workflow 3: Multi-Platform Ad Tracking ```python theme={null} # Track a single brand across Google, Yahoo JP, Meta transparency = client.post("/api/v1/agents/ads-transparency", json={ "input": { "domain": "7card.co.jp", "country": "jp" } }).json() print("Platforms found:") for platform in transparency["platforms_queried"]: data = transparency[platform] print(f" {platform}: {data.get('total_ads', 0)} ads") ``` *** ## Related Complete API endpoint documentation Stock news and sentiment analysis agents Retrieve agent execution results Full agent catalog and availability matrix # Financial Research Agents Source: https://docs.shannon.run/en/api/agents/financial Stock news, SEC filings, and sentiment analysis **Mixed Availability**: * ✅ **Open Source**: `sec-filings` (free SEC EDGAR API) * ✅ **Shannon Cloud**: All agents with managed API access * ✅\* **OSS with your API keys**: `alpaca-news` (Alpaca free tier), `twitter-sentiment` (xAI API) ## Overview Shannon provides **4 specialized agents** for financial research and equity analysis, combining free public data (SEC filings) with premium sentiment analysis (Twitter/xAI) and news aggregation (Alpaca, Benzinga). **Use cases:** * Event monitoring (8-K material events, insider trading) * Social sentiment tracking (Twitter mentions, influencer analysis) * News aggregation (multi-source news with sentiment scoring) * Equity research workflows *** ## Available Agents ### sec-filings **Open Source Compatible**: Free SEC EDGAR API. Works in both OSS and Cloud with no API key required. Get recent SEC filings (8-K, 10-K, 10-Q, etc.) for a stock ticker. **Agent ID:** `sec-filings` **Input Schema:** | Field | Type | Required | Default | Description | | ----------- | ------- | -------- | -------------------------- | ------------------------------------------ | | `ticker` | string | Yes | - | Stock ticker symbol (e.g., 'NVDA', 'TSLA') | | `days_back` | integer | No | `30` | Days of history to search (1-365) | | `forms` | string | No | `"8-K,10-K,10-Q,4,SC 13D"` | Comma-separated form types | **Supported Form Types:** * **8-K**: Material events (acquisitions, executive changes, etc.) * **10-K**: Annual report * **10-Q**: Quarterly report * **4**: Insider trading (officers, directors) * **SC 13D**: Major shareholder filings (>5% ownership) * **DEF 14A**: Proxy statement * **S-1**: IPO registration **Example Request:** ```bash theme={null} curl -X POST http://localhost:8080/api/v1/agents/sec-filings \ -H "X-API-Key: sk_your_api_key" \ -H "Content-Type: application/json" \ -d '{ "input": { "ticker": "NVDA", "days_back": 90, "forms": "8-K,10-K,10-Q" } }' ``` **Output Structure:** ```json theme={null} { "source": "sec_edgar", "ticker": "NVDA", "company_name": "NVIDIA CORP", "cik": "0001045810", "filings": [ { "form_type": "8-K", "filed_date": "2026-01-15", "description": "Current Report", "filing_url": "https://www.sec.gov/Archives/edgar/data/1045810/...", "is_material": true }, { "form_type": "10-Q", "filed_date": "2025-11-20", "description": "Quarterly Report", "filing_url": "https://www.sec.gov/Archives/edgar/data/1045810/...", "is_material": false } ], "has_material_events": true, "total_count": 5 } ``` **Use Case Example:** ```python theme={null} # Monitor for material events import httpx def check_material_events(ticker): response = httpx.post( "http://localhost:8080/api/v1/agents/sec-filings", headers={"X-API-Key": "sk_your_api_key"}, json={"input": {"ticker": ticker, "forms": "8-K"}} ).json() # Wait for task completion task_id = response["task_id"] # ... poll for result ... if result["has_material_events"]: print(f"⚠️ Material events for {ticker}:") for filing in result["filings"]: if filing["is_material"]: print(f" - {filing['filed_date']}: {filing['description']}") ``` *** ### twitter-sentiment **Shannon Cloud Only**: Uses xAI API for Twitter/X sentiment analysis. Analyze X/Twitter sentiment for a stock using xAI's Grok model. **Agent ID:** `twitter-sentiment` **Input Schema:** | Field | Type | Required | Default | Description | | ----------- | ------- | -------- | ------- | ------------------------------------------ | | `ticker` | string | Yes | - | Stock ticker symbol (e.g., 'NVDA', 'TSLA') | | `days_back` | integer | No | `1` | Days of history to search (1-7) | **Example Request:** ```bash theme={null} curl -X POST http://localhost:8080/api/v1/agents/twitter-sentiment \ -H "X-API-Key: sk_your_api_key" \ -H "Content-Type: application/json" \ -d '{ "input": { "ticker": "NVDA", "days_back": 3 } }' ``` **Output Structure:** ```json theme={null} { "ticker": "NVDA", "analysis": "Recent Twitter sentiment for NVIDIA is predominantly bullish. Key influencers are discussing strong Q4 earnings and AI chip demand. Notable mentions include:\n- @analyst123: 'NVDA crushing expectations'\n- @investor_daily: 'AI demand shows no signs of slowing'", "sentiment": "bullish", "citations": [ "https://x.com/analyst123/status/123456789", "https://x.com/investor_daily/status/987654321" ], "cost_usd": 0.30 } ``` **Sentiment Values:** * `bullish` - Positive sentiment, buying interest * `bearish` - Negative sentiment, selling pressure * `mixed` - Conflicting signals * `neutral` - No clear directional sentiment **API Cost**: Twitter sentiment uses xAI's Grok model, which costs approximately \$0.30 per analysis. Monitor usage to control costs. *** ### alpaca-news **Open Source Compatible**: Alpaca Markets offers a free tier. Works in OSS with your Alpaca API key. Get recent stock news from Alpaca Markets API (powered by Benzinga). **Agent ID:** `alpaca-news` **Input Schema:** | Field | Type | Required | Default | Description | | ------------ | ------- | -------- | ------- | ----------------------------------------------------------- | | `symbols` | string | Yes | - | Comma-separated stock symbols (e.g., 'NVDA' or 'NVDA,AAPL') | | `hours_back` | integer | No | `24` | Hours of history to search (1-168) | | `limit` | integer | No | `10` | Maximum number of news items to return (1-50) | **Example Request:** ```bash theme={null} curl -X POST http://localhost:8080/api/v1/agents/alpaca-news \ -H "X-API-Key: sk_your_api_key" \ -H "Content-Type: application/json" \ -d '{ "input": { "symbols": "NVDA,AMD", "hours_back": 48, "limit": 20 } }' ``` **Output Structure:** ```json theme={null} { "symbols": "NVDA,AMD", "news": [ { "headline": "NVIDIA announces new AI chip breakthrough", "summary": "NVIDIA Corporation unveiled its next-generation AI accelerator...", "author": "Tech News Daily", "created_at": "2026-02-15T09:30:00Z", "updated_at": "2026-02-15T09:30:00Z", "url": "https://example.com/nvidia-ai-chip", "symbols": ["NVDA"], "source": "benzinga" }, { "headline": "AMD gains market share in datacenter GPUs", "summary": "Advanced Micro Devices reported increased datacenter revenue...", "author": "Market Watch", "created_at": "2026-02-15T08:15:00Z", "updated_at": "2026-02-15T08:15:00Z", "url": "https://example.com/amd-datacenter", "symbols": ["AMD"], "source": "benzinga" } ], "total_count": 18 } ``` **Alpaca Free Tier:** * 200 news requests per month * Real-time news feed * Benzinga news source *** ### news-aggregator **Shannon Cloud Only**: Combines multiple premium sources (Alpaca, SEC, xAI). Fetch comprehensive news and sentiment from multiple sources (Alpaca, SEC, Twitter) for a stock. **Agent ID:** `news-aggregator` **Input Schema:** | Field | Type | Required | Default | Description | | ----------------- | ------- | -------- | ------- | ------------------------------------ | | `ticker` | string | Yes | - | Stock ticker symbol (e.g., 'NVDA') | | `include_filings` | boolean | No | `true` | Include SEC filings in results | | `include_twitter` | boolean | No | `true` | Include Twitter sentiment in results | **Example Request:** ```bash theme={null} curl -X POST http://localhost:8080/api/v1/agents/news-aggregator \ -H "X-API-Key: sk_your_api_key" \ -H "Content-Type: application/json" \ -d '{ "input": { "ticker": "NVDA", "include_filings": true, "include_twitter": true } }' ``` **Output Structure:** ```json theme={null} { "ticker": "NVDA", "as_of": "2026-02-15T10:30:00Z", "sources_used": ["alpaca", "sec", "twitter"], "overall_sentiment": { "score": 0.72, "label": "bullish" }, "alpaca": { "articles": [ { "headline": "NVIDIA announces new AI chip breakthrough", "summary": "...", "created_at": "2026-02-15T09:30:00Z", "url": "https://..." } ], "total_count": 18, "heuristic_sentiment_score": 0.65 }, "sec": { "filings": [ { "form_type": "8-K", "filed_date": "2026-01-15", "description": "Current Report", "filing_url": "https://www.sec.gov/...", "is_material": true } ], "has_material_events": true, "total_count": 3 }, "twitter": { "analysis": "Recent Twitter sentiment is predominantly bullish...", "sentiment": "bullish", "citations": ["https://x.com/..."] } } ``` **Overall Sentiment Calculation:** The aggregator calculates a weighted sentiment score: * **Alpaca news**: 40% weight (heuristic keyword analysis) * **Twitter**: 40% weight (xAI sentiment) * **SEC filings**: 20% weight (material events indicator) **Score Ranges:** * `0.6 - 1.0`: Bullish * `0.4 - 0.6`: Neutral * `0.0 - 0.4`: Bearish *** ## Common Workflows ### Workflow 1: Daily Event Monitoring Monitor multiple tickers for material SEC events: ```python theme={null} import httpx tickers = ["NVDA", "AAPL", "MSFT", "GOOGL", "AMZN"] client = httpx.Client( base_url="http://localhost:8080", headers={"X-API-Key": "sk_your_api_key"} ) for ticker in tickers: response = client.post("/api/v1/agents/sec-filings", json={ "input": { "ticker": ticker, "days_back": 1, "forms": "8-K" } }).json() task_id = response["task_id"] # ... poll for result ... if result.get("has_material_events"): print(f"⚠️ {ticker}: {len(result['filings'])} new 8-K filings") ``` ### Workflow 2: Comprehensive Stock Analysis Combine all sources for a single ticker: ```python theme={null} # Get aggregated view aggregated = client.post("/api/v1/agents/news-aggregator", json={ "input": {"ticker": "NVDA"} }).json() task_id = aggregated["task_id"] # ... poll for result ... print(f"Overall sentiment: {result['overall_sentiment']['label']}") print(f"Sources: {', '.join(result['sources_used'])}") print(f"News articles: {result['alpaca']['total_count']}") print(f"Material events: {result['sec']['has_material_events']}") ``` ### Workflow 3: Sentiment Comparison Across Peers Compare sentiment for multiple tickers: ```python theme={null} peer_group = ["NVDA", "AMD", "INTC"] sentiments = {} for ticker in peer_group: response = client.post("/api/v1/agents/twitter-sentiment", json={ "input": {"ticker": ticker, "days_back": 1} }).json() task_id = response["task_id"] # ... poll for result ... sentiments[ticker] = result["sentiment"] print("Peer sentiment comparison:") for ticker, sentiment in sentiments.items(): print(f" {ticker}: {sentiment}") ``` ### Workflow 4: News-Driven Trading Alerts Monitor news for specific keywords: ```python theme={null} response = client.post("/api/v1/agents/alpaca-news", json={ "input": { "symbols": "NVDA", "hours_back": 1, "limit": 50 } }).json() task_id = response["task_id"] # ... poll for result ... keywords = ["breakthrough", "partnership", "acquisition", "lawsuit"] for article in result["news"]: headline_lower = article["headline"].lower() if any(kw in headline_lower for kw in keywords): print(f"🚨 Alert: {article['headline']}") print(f" URL: {article['url']}") ``` *** ## Self-Hosted OSS Setup To use financial agents in Shannon OSS: ### sec-filings (No API Key Required) Works out of the box - SEC EDGAR is a free public API. ```bash theme={null} # No configuration needed docker compose up -d ``` ### alpaca-news (Free Tier Available) 1. **Sign up for Alpaca** (free tier): [https://alpaca.markets](https://alpaca.markets) 2. **Get API keys** from dashboard 3. **Add to .env**: ```bash theme={null} # .env ALPACA_API_KEY=your_api_key_here ALPACA_SECRET_KEY=your_secret_key_here ``` 4. **Restart services**: ```bash theme={null} docker compose restart llm-service ``` ### twitter-sentiment (Paid API) 1. **Sign up for xAI** (requires payment): [https://x.ai](https://x.ai) 2. **Get API key** 3. **Add to .env**: ```bash theme={null} # .env XAI_API_KEY=your_xai_api_key_here ``` 4. **Restart services**: ```bash theme={null} docker compose restart llm-service ``` **xAI Costs**: Twitter sentiment analysis costs approximately \$0.30 per request. Monitor your usage carefully. *** ## Cost Comparison | Agent | OSS (Self-Hosted) | Shannon Cloud | | ------------------- | ---------------------------- | ----------------------- | | `sec-filings` | Free (SEC EDGAR) | Free (included) | | `alpaca-news` | Free tier (200/month) | Managed (included) | | `twitter-sentiment` | \~\$0.30/call (your xAI key) | \~\$0.30/call (managed) | | `news-aggregator` | Free + \$0.30 (combined) | \~\$0.35/call (managed) | **Shannon Cloud Benefits:** * ✅ Managed API keys (no signup needed) * ✅ Cost accounting per tenant * ✅ Rate limiting and quotas * ✅ Infrastructure (no server maintenance) *** ## Related Complete API endpoint documentation Competitive advertising intelligence agents Retrieve agent execution results Full agent catalog and availability matrix # Agents Overview Source: https://docs.shannon.run/en/api/agents/overview Single-purpose agents for specialized tasks ## What are Shannon Agents? Shannon Agents are **single-purpose, deterministic tools** that execute specific tasks without LLM orchestration. Each agent wraps one underlying tool and returns structured results. **Key characteristics:** * ✅ **Deterministic** - Same input always produces same output * ✅ **Fast** - No AI planning overhead, direct tool execution * ✅ **Structured** - Validated input/output schemas * ✅ **Cost-effective** - Only pay for the tool, not orchestration ## Agents vs Tasks | Aspect | Agents API | Tasks API | | ------------- | ------------------------ | -------------------- | | **Execution** | Direct tool call | Multi-step workflow | | **LLM Use** | Only if tool requires it | Planning + execution | | **Latency** | Seconds | Seconds to minutes | | **Use Case** | Single data retrieval | Complex analysis | **When to use Agents:** * You need specific data (ads, news, SEC filings) * You know exactly which tool to use * You want predictable, structured output **When to use Tasks:** * You need AI to plan the approach * Multiple tools may be needed * You want natural language interaction *** ## Feature Availability Shannon provides agents in two deployment models: **Shannon Open Source**: Self-hosted deployment with core platform features **Shannon Cloud**: Managed SaaS platform with enterprise features and integrations ### Availability Matrix | Feature Category | Agent/Tool | Shannon OSS | Shannon Cloud | | ---------------------- | ------------------- | ----------- | ------------- | | **Ads Research** | | | | | | serp-ads | ❌ | ✅ | | | yahoo-jp-ads | ❌ | ✅ | | | meta-ad-library | ❌ | ✅ | | | competitor-discover | ❌ | ✅ | | | ads-transparency | ❌ | ✅ | | | lp-visual-analyze | ❌ | ✅ | | | lp-batch-analyze | ❌ | ✅ | | | ad-creative-analyze | ❌ | ✅ | | | keyword-extract | ✅\* | ✅ | | | browser-screenshot | ❌ | ✅ | | **Financial Research** | | | | | | sec-filings | ✅ | ✅ | | | twitter-sentiment | ❌ | ✅ | | | alpaca-news | ✅\* | ✅ | | | news-aggregator | ❌ | ✅ | | **Platform APIs** | | | | | | Agents API | ✅ | ✅ | | | Blob Storage API | ✅ | ✅ | | | Role Presets | ✅ | ✅ | | | Skills System | ✅ | ✅ | | | Research Workflows | ✅ | ✅ | | | Swarm Coordination | ✅ | ✅ | **Legend:** * ✅ Available * ❌ Not available * ✅\* Available if you provide your own API keys *** ## Why Some Features are Cloud-Only Many agents use commercial APIs that require billing accounts: * **SerpAPI** - Google search ads discovery * **SearchAPI.io** - Meta Ad Library access * **xAI API** - Twitter sentiment analysis Shannon Cloud provides managed API keys and cost accounting. Self-hosted deployments would need individual API subscriptions. Some agents require specialized infrastructure: * **Playwright service** - Browser automation for screenshots and LP analysis * **Vision LLM services** - Image analysis with GPT-4V or Claude 3 * **IP rotation** - Yahoo JP scraping requires distributed IP pools These services are provisioned and maintained in Shannon Cloud. Shannon Cloud provides: * Per-tenant usage tracking * Rate limiting and quotas * Detailed cost breakdowns * Billing integration Self-hosted deployments would need to implement these features separately. *** ## Using OSS Features in Self-Hosted For agents marked ✅\*, you can use them in Shannon OSS by providing your own API keys: ```bash theme={null} # .env for OSS deployment SERPAPI_API_KEY=your_key_here SEARCHAPI_API_KEY=your_key_here XAI_API_KEY=your_key_here ALPACA_API_KEY=your_key_here ``` **Available for self-hosting with your API keys:** * `keyword-extract` - Uses your LLM provider (OpenAI/Anthropic) * `sec-filings` - Free SEC EDGAR API (no key required) * `alpaca-news` - Alpaca Markets free tier available **API Costs**: When providing your own API keys, you are responsible for API usage costs. Monitor your usage to avoid unexpected bills. *** ## Agent Categories Shannon agents are organized into functional categories: ### Ads Research **Shannon Cloud Only**: These agents require Shannon Cloud subscription and use paid external APIs (SerpAPI, SearchAPI.io, Playwright service). **10 agents** for competitive advertising analysis, landing page research, and creative insights. Extract competitor ads from Google, Yahoo JP, Meta (Facebook/Instagram), analyze landing pages, and discover creative patterns. **Use cases:** * Competitor ad discovery * Landing page analysis * Creative messaging research * Market intelligence *** ### Financial Research **Mixed Availability**: Some agents work in OSS (sec-filings), others require Shannon Cloud (twitter-sentiment, news-aggregator). **4 agents** for stock news, SEC filings, sentiment analysis, and multi-source aggregation. Access SEC filings, Twitter sentiment, stock news, and aggregated financial data for equity research. **Use cases:** * Equity research * Event monitoring (8-K filings) * Social sentiment tracking * News aggregation *** ## Quick Start ### 1. List Available Agents ```bash theme={null} curl http://localhost:8080/api/v1/agents \ -H "X-API-Key: sk_your_api_key" ``` ### 2. Get Agent Schema ```bash theme={null} curl http://localhost:8080/api/v1/agents/serp-ads \ -H "X-API-Key: sk_your_api_key" ``` ### 3. Execute Agent ```bash theme={null} curl -X POST http://localhost:8080/api/v1/agents/serp-ads \ -H "X-API-Key: sk_your_api_key" \ -H "Content-Type: application/json" \ -d '{ "input": { "keywords": "running shoes", "country": "us" } }' ``` ### 4. Retrieve Results ```bash theme={null} # Use task_id from step 3 curl http://localhost:8080/api/v1/tasks/{task_id} \ -H "X-API-Key: sk_your_api_key" ``` *** ## API Reference Complete API reference for agent endpoints Ads research agent catalog Financial research agent catalog Retrieve agent execution results *** ## Common Patterns ### Pattern 1: Sequential Agent Calls Execute multiple agents in sequence, passing results between them: ```python theme={null} import httpx client = httpx.Client( base_url="http://localhost:8080", headers={"X-API-Key": "sk_your_api_key"} ) # Step 1: Extract keywords keywords_resp = client.post("/api/v1/agents/keyword-extract", json={ "input": {"query": "Find shoes for marathon training"} }).json() # Wait for completion task_id = keywords_resp["task_id"] # ... poll for result ... # Step 2: Use extracted keywords for ad search ads_resp = client.post("/api/v1/agents/serp-ads", json={ "input": {"keywords": extracted_keywords} }).json() ``` ### Pattern 2: Parallel Agent Execution Execute multiple agents concurrently for faster results: ```python theme={null} import asyncio import httpx async def execute_agent(client, agent_id, input_data): response = await client.post( f"/api/v1/agents/{agent_id}", json={"input": input_data} ) return response.json() async def main(): async with httpx.AsyncClient( base_url="http://localhost:8080", headers={"X-API-Key": "sk_your_api_key"} ) as client: # Execute 3 agents in parallel results = await asyncio.gather( execute_agent(client, "serp-ads", {"keywords": "shoes"}), execute_agent(client, "sec-filings", {"ticker": "NKE"}), execute_agent(client, "alpaca-news", {"symbols": "NKE"}) ) print("All agents submitted:", [r["task_id"] for r in results]) ``` ### Pattern 3: Batch Processing Process multiple items using batch-enabled agents: ```python theme={null} # Analyze multiple landing pages in one call response = httpx.post( "http://localhost:8080/api/v1/agents/lp-batch-analyze", headers={"X-API-Key": "sk_your_api_key"}, json={ "input": { "urls": [ "https://competitor1.com/product", "https://competitor2.com/landing", "https://competitor3.com/offer" ], "device": "mobile", "language": "en" } } ).json() ``` *** ## Next Steps Complete endpoint documentation Explore ads research agents Explore financial research agents Use agents with Python SDK # Authentication Source: https://docs.shannon.run/en/api/authentication API key authentication and authorization in Shannon ## Overview For HTTP header details and request examples, see [/en/api/rest/authentication](/en/api/rest/authentication). Shannon supports API key authentication to secure access to the orchestration platform. Authentication is **disabled by default** for easy local development and can be enabled for production deployments. ## Authentication Modes ### Development Mode (Default) ```bash theme={null} # In .env file GATEWAY_SKIP_AUTH=1 ``` In development mode: * No API key required * All requests are accepted * Useful for local testing and development ### Production Mode ```bash theme={null} # In .env file GATEWAY_SKIP_AUTH=0 ``` In production mode: * API key required for all requests * Invalid keys return 401 Unauthorized * Rate limiting enforced per key ## API Key Format Shannon uses prefixed API keys: ``` sk_test_1234567890abcdef # Test keys sk_live_1234567890abcdef # Production keys ``` Never commit API keys to version control or share them publicly. ## Creating API Keys ### Via Command Line ```bash theme={null} # Create a test API key (run from repo root) make seed-api-key # Output ✅ Test API key created. Use 'sk_test_123456' for testing. Note: Authentication is disabled by default (GATEWAY_SKIP_AUTH=1) ``` ### Via API ```bash theme={null} curl -X POST http://localhost:8080/api/v1/auth/api-keys \ -H "Authorization: Bearer " \ -d '{ "name": "My Application", "user_id": "user-123" }' ``` ## Rate Limiting Shannon enforces rate limits per API key using a fixed-window counter: ### Default Limits | Limit Type | Default Value | | ------------------- | ------------- | | Requests per minute | 60 | | Burst allowance | 10 | Token budgets and concurrent task limits are enforced at the workflow level by the orchestrator, not at the gateway layer. ### Rate Limit Headers Responses include current window limits. On 429, `Retry-After` is set: ```http theme={null} X-RateLimit-Limit: 60 X-RateLimit-Remaining: 45 X-RateLimit-Reset: 1730001234 ``` ### Rate Limit Exceeded When you exceed the rate limit: ```bash theme={null} HTTP/1.1 429 Too Many Requests Retry-After: 30 { "error": "Rate limit exceeded", "message": "Too many requests. Please retry after the rate limit window resets." } ``` ### Handling Rate Limits Implement exponential backoff: ```python theme={null} import time import requests from requests.exceptions import HTTPError def submit_with_retry(query, max_retries=3): for attempt in range(max_retries): try: response = requests.post( "http://localhost:8080/api/v1/tasks", headers={"X-API-Key": "sk_test_123456"}, json={"query": query} ) response.raise_for_status() return response.json() except HTTPError as e: if e.response.status_code == 429: retry_after = int(e.response.headers.get('Retry-After', 60)) print(f"Rate limited. Retrying after {retry_after}s...") time.sleep(retry_after) else: raise raise Exception("Max retries exceeded") ``` ## Multi-Tenancy Shannon supports multi-tenant deployments with tenant isolation: ### Tenant ID Include tenant ID in requests: ```bash theme={null} curl -X POST http://localhost:8080/api/v1/tasks \ -H "X-API-Key: sk_test_123456" \ -H "X-Tenant-ID: org-acme" \ -H "Content-Type: application/json" \ -d '{"query": "Process data"}' ``` ### Tenant Isolation Each tenant has: * Isolated session storage * Per-tenant isolation via payload filters (`tenant_id`) within shared Qdrant collections * Independent budget tracking * Dedicated metrics ## OPA Policy Enforcement Shannon uses [Open Policy Agent](https://www.openpolicyagent.org/) for fine-grained access control: ### Policy Structure ```rego theme={null} package shannon.auth import future.keywords.if # Default deny default allow = false # Allow if user has valid API key and appropriate permissions allow if { input.api_key_valid input.user.team in ["engineering", "data-science"] input.task.mode in allowed_modes[input.user.team] } # Team-specific allowed modes allowed_modes := { "engineering": ["simple", "standard", "complex"], "data-science": ["standard", "complex"] } # Token budget limits by team max_tokens[team] := 50000 if team == "data-science" max_tokens[team] := 10000 if team == "engineering" ``` ### Policy Modes Configure in `config/shannon.yaml`: ```yaml theme={null} policy: enabled: true mode: "enforce" # dry-run, enforce, off path: "/app/config/opa/policies" fail_closed: false # Allow on policy errors ``` **Modes:** * `enforce`: Deny requests that violate policies * `dry-run`: Log violations but allow requests * `off`: Disable policy enforcement ### Example: Restricting Models ```rego theme={null} package shannon.models # Only allow specific models per team allowed_models[user.team] contains model if { user.team == "cost-sensitive" model in ["gpt-5-mini", "claude-haiku"] } allowed_models[user.team] contains model if { user.team == "premium" model in ["gpt-5.1", "claude-opus", "gpt-5-mini", "claude-sonnet"] } ``` ## Security Best Practices ### 1. Rotate Keys Regularly ```bash theme={null} # Create new key make seed-api-key # Update applications with new key # Revoke old key after migration ``` ### 2. Use Environment Variables ```python theme={null} import os from shannon import ShannonClient client = ShannonClient( base_url=os.environ["SHANNON_BASE_URL"], api_key=os.environ["SHANNON_API_KEY"], ) ``` ### 3. Enable HTTPS in Production ```yaml theme={null} # config/shannon.yaml gateway: tls: enabled: true cert_file: "/path/to/cert.pem" key_file: "/path/to/key.pem" ``` ### 4. Monitor API Key Usage Track usage in Prometheus metrics: ```promql theme={null} # Requests per API key sum by (api_key_id) (rate(shannon_gateway_requests_total[5m])) # Errors by API key sum by (api_key_id) (rate(shannon_gateway_errors_total[5m])) ``` ### 5. Implement IP Whitelisting ```yaml theme={null} # config/shannon.yaml gateway: ip_whitelist: enabled: true allowed_ips: - "10.0.0.0/8" - "192.168.1.100" ``` ## Troubleshooting **Cause**: Missing or invalid API key **Solution**: ```bash theme={null} # Verify auth is enabled grep GATEWAY_SKIP_AUTH .env # If enabled, check your API key curl -H "X-API-Key: sk_test_123456" \ http://localhost:8080/api/v1/tasks ``` **Cause**: Valid API key but insufficient permissions (OPA policy) **Solution**: Check OPA policy logs: ```bash theme={null} docker compose logs orchestrator | grep "policy" ``` **Cause**: Exceeded rate limits **Solution**: Implement retry logic with exponential backoff: ```python theme={null} retry_after = response.headers.get('Retry-After', 60) time.sleep(int(retry_after)) ``` **Cause**: Key may be expired or revoked **Solution**: Create a new test key: ```bash theme={null} make seed-api-key ``` ## Next Steps Learn how to submit tasks with authentication Understand rate limits and quotas Use SDK for automatic authentication Complete REST API documentation # Event Types Catalog Source: https://docs.shannon.run/en/api/event-types Complete reference for Shannon streaming event types ## Overview Shannon emits **real-time events** via Server-Sent Events (SSE) to provide visibility into task execution. This document catalogs the **37 event types** actually emitted by the platform, their structure, and when they occur. Events provide: * **Real-time progress** - Track task execution as it happens * **Debugging insights** - LLM prompts, tool invocations, agent reasoning * **Cost monitoring** - Track token usage and costs in real-time * **Multi-agent coordination** - Observe team formation and collaboration * **Error recovery** - Monitor error handling and recovery attempts ## Event Structure All events follow this base structure: ```json theme={null} { "workflow_id": "wf-550e8400-e29b-41d4-a716-446655440000", "type": "AGENT_THINKING", "agent_id": "agent-001", "message": "Analyzing task complexity...", "timestamp": "2024-10-27T10:00:00Z", "seq": 42, "stream_id": "stream-abc123", "payload": {} } ``` ### Base Fields | Field | Type | Description | | ------------- | -------- | ------------------------------------------------------------------ | | `workflow_id` | string | Unique task/workflow identifier | | `type` | string | Event type (see catalog below) | | `agent_id` | string | Agent that emitted the event | | `message` | string | Human-readable event description | | `timestamp` | ISO 8601 | When the event occurred | | `seq` | integer | Sequence number for ordering | | `stream_id` | string | Stream identifier for reconnection | | `payload` | object | Event-specific payload (optional; serialized as `payload` in JSON) | *** ## Event Categories Events are organized into logical categories: 1. **Workflow Events** - Task lifecycle 2. **Agent Events** - Agent execution 3. **Tool Events** - Tool invocations 4. **Pattern Events** - Cognitive pattern execution 5. **Team Events** - Multi-agent coordination 6. **LLM Events** - Language model interactions 7. **Progress Events** - Task progress and status 8. **System Events** - Errors and system state *** ## Event Type Quick Reference Authoritative list of event types emitted by Shannon: | Event Type | Category | Description | | -------------------------- | ---------------- | ---------------------------------------------------------------- | | `WORKFLOW_STARTED` | Workflow | Task begins execution | | `WORKFLOW_COMPLETED` | Workflow | Task completes successfully | | `WORKFLOW_PAUSING` | Workflow control | Pause requested; workflow preparing to pause | | `WORKFLOW_PAUSED` | Workflow control | Workflow paused at a checkpoint | | `WORKFLOW_RESUMED` | Workflow control | Workflow resumed after pause | | `WORKFLOW_CANCELLING` | Workflow control | Cancel requested; workflow preparing to terminate | | `WORKFLOW_CANCELLED` | Workflow control | Workflow cancelled at a checkpoint | | `AGENT_STARTED` | Agent | Agent begins processing | | `AGENT_THINKING` | Agent | Agent reasoning/planning | | `AGENT_COMPLETED` | Agent | Agent finishes successfully | | `AGENT_FAILED` | Agent | Agent encountered an error | | `TOOL_INVOKED` | Tool | Tool is called | | `TOOL_OBSERVATION` | Tool | Agent observes tool result | | `TEAM_RECRUITED` | Team | Multi-agent team assembled | | `TEAM_RETIRED` | Team | Team disbanded | | `TEAM_STATUS` | Team | Team coordination update | | `DEPENDENCY_SATISFIED` | Team | Dependencies resolved | | `DELEGATION` | Team | Task delegated to another agent | | `MESSAGE_SENT` | Message | Agent sends message | | `MESSAGE_RECEIVED` | Message | Agent receives message | | `LLM_PROMPT` | LLM | Prompt sent to LLM | | `LLM_PARTIAL` | LLM | Incremental LLM output | | `LLM_OUTPUT` | LLM | Final LLM output | | `PROGRESS` | Progress | General progress update | | `DATA_PROCESSING` | Progress | Processing/analyzing data | | `WAITING` | Progress | Waiting for resources | | `ERROR_OCCURRED` | System | Error occurred | | `ERROR_RECOVERY` | System | Error recovery attempt | | `APPROVAL_REQUESTED` | System | Human approval needed | | `APPROVAL_DECISION` | System | Approval decision made | | `WORKSPACE_UPDATED` | System | Memory/context updated | | `ROLE_ASSIGNED` | System | Agent role assigned | | `STATUS_UPDATE` | System | General status update | | `BUDGET_THRESHOLD` | System/Progress | Budget warning threshold reached for a task | | `THREAD_MESSAGE_DELTA` | Stream | Incremental message content chunk | | `THREAD_MESSAGE_COMPLETED` | Stream | Message content fully delivered | | `STREAM_END` | Stream | Explicit end-of-stream signal (no more events for this workflow) | **Total**: 37 event types Note: Events such as `WORKFLOW_FAILED`, `TASK_COMPLETED`, `TOOL_COMPLETED`, `TOOL_FAILED`, and `BUDGET_UPDATE` are not emitted by the streaming API. Failures are surfaced via `ERROR_OCCURRED`; completion is indicated by `WORKFLOW_COMPLETED`. `STREAM_END` is emitted as a lifecycle signal after completion/termination when streaming ends. *** ## Workflow Events Events related to the overall task workflow. ### WORKFLOW\_STARTED **Emitted**: When a task begins execution **Data**: ```json theme={null} { "type": "WORKFLOW_STARTED", "message": "Workflow started", "data": { "query": "Analyze Q4 sales data", "mode": "STANDARD", "session_id": "sess-123", "estimated_complexity": 0.75 } } ``` **Fields**: * `query`: Original task query * `mode`: Execution mode (SIMPLE, STANDARD, COMPLEX) * `session_id`: Session identifier * `estimated_complexity`: Complexity score (0.0-1.0) *** ### WORKFLOW\_COMPLETED **Emitted**: When a task completes successfully **Data**: ```json theme={null} { "type": "WORKFLOW_COMPLETED", "message": "Workflow completed successfully", "data": { "result": "Analysis complete. Key findings: ...", "duration_ms": 45000, "total_tokens": 5420, "total_cost_usd": 0.0814, "agents_used": 3, "tools_invoked": 7 } } ``` **Fields**: * `result`: Final task result * `duration_ms`: Total execution time * `total_tokens`: Cumulative token usage * `total_cost_usd`: Total cost * `agents_used`: Number of agents invoked * `tools_invoked`: Number of tool calls *** ## Selected Examples ### AGENT\_THINKING ```json theme={null} { "type": "AGENT_THINKING", "agent_id": "researcher", "message": "Analyzing data structure...", "timestamp": "2025-01-20T10:00:02Z" } ``` ### TOOL\_INVOKED / TOOL\_OBSERVATION ```json theme={null} { "type": "TOOL_INVOKED", "message": "web_search: q=\"LLM caching\"" } { "type": "TOOL_OBSERVATION", "message": "web_search: 12 results" } ``` ### LLM\_OUTPUT ```json theme={null} { "type": "LLM_OUTPUT", "message": "Final summary of findings..." } ``` ### ERROR\_OCCURRED ```json theme={null} { "type": "ERROR_OCCURRED", "message": "Provider rate limit exceeded", "data": { "error": "RATE_LIMIT", "retry_after": 30 } } ``` ### APPROVAL\_REQUESTED ```json theme={null} { "type": "APPROVAL_REQUESTED", "message": "Delete temporary files older than 30 days?", "data": { "approval_id": "appr-456" } } ``` **Common Error Types**: * `BUDGET_EXCEEDED` - Cost/token limit reached * `TIMEOUT` - Execution timeout * `TOOL_EXECUTION_FAILED` - Tool error * `LLM_ERROR` - LLM provider error * `INVALID_INPUT` - Malformed request *** ## Agent Events Events related to individual agent execution. ### AGENT\_STARTED **Emitted**: When an agent begins processing **Data**: ```json theme={null} { "type": "AGENT_STARTED", "agent_id": "data-analyst", "message": "Agent started", "data": { "role": "data-analyst", "subtask": "Analyze revenue trends", "tools_available": ["csv_loader", "pandas", "matplotlib"] } } ``` *** ### AGENT\_THINKING **Emitted**: Agent is reasoning/processing (most frequent event) **Data**: ```json theme={null} { "type": "AGENT_THINKING", "agent_id": "data-analyst", "message": "Analyzing data structure...", "data": { "thought": "I need to first load the CSV and examine column types", "next_action": "invoke_tool", "confidence": 0.85 } } ``` **Usage**: Display as progress indicator to user *** ### AGENT\_COMPLETED **Emitted**: Agent finished its subtask **Data**: ```json theme={null} { "type": "AGENT_COMPLETED", "agent_id": "data-analyst", "message": "Agent completed successfully", "data": { "result": "Revenue increased 15% YoY", "tokens_used": 1200, "cost_usd": 0.018, "duration_ms": 8000 } } ``` *** ### AGENT\_FAILED **Emitted**: Agent encountered an error **Data**: ```json theme={null} { "type": "AGENT_FAILED", "agent_id": "data-analyst", "message": "Agent failed: Tool execution error", "data": { "error": "TOOL_EXECUTION_FAILED", "error_message": "CSV file not found", "recoverable": true } } ``` *** ## Tool Events Events related to tool invocations. ### TOOL\_INVOKED **Emitted**: When a tool is called **Data**: ```json theme={null} { "type": "TOOL_INVOKED", "message": "Invoking tool: csv_loader", "data": { "tool_name": "csv_loader", "tool_args": { "file_path": "sales_q4.csv" }, "timeout_seconds": 30 } } ``` *** ### TOOL\_OBSERVATION **Emitted**: Agent observes a tool result **Data**: ```json theme={null} { "type": "TOOL_OBSERVATION", "message": "Tool result: csv_loader", "data": { "tool_name": "csv_loader", "result": { "rows": 15000, "columns": 12, "sample": ["date", "product", "revenue", "..."] }, "duration_ms": 450, "truncated": false } } ``` **Fields**: * `tool_name`: Name of the tool that was invoked * `result`: Tool output (structured data or text) * `duration_ms`: Tool execution time * `truncated`: Whether result was truncated (true if > 2000 chars) **Note**: Large tool results are automatically truncated to 2000 characters with UTF-8 safety to prevent overwhelming the streaming connection. The `truncated` field indicates if this occurred. Full results are always available in the task completion response. *** *** ## Pattern Events Pattern selection and decomposition events are not part of the public streaming schema and are omitted for brevity. *** ## Team Events Multi-agent team coordination and management. ### TEAM\_RECRUITED **Emitted**: When a team of agents is assembled for execution **Data**: ```json theme={null} { "type": "TEAM_RECRUITED", "message": "Team recruited: 3 agents", "data": { "team_size": 3, "agents": [ { "agent_id": "data-analyst", "role": "analyst", "capabilities": ["data_loading", "analysis"] }, { "agent_id": "visualizer", "role": "visualization", "capabilities": ["charts", "graphs"] }, { "agent_id": "writer", "role": "report_generation", "capabilities": ["summarization", "writing"] } ] } } ``` *** ### TEAM\_RETIRED **Emitted**: When a team is disbanded after task completion **Data**: ```json theme={null} { "type": "TEAM_RETIRED", "message": "Team retired after task completion", "data": { "team_size": 3, "duration_ms": 45000, "total_tokens": 8000, "reason": "task_completed" } } ``` *** ### TEAM\_STATUS **Emitted**: Periodic updates on multi-agent team coordination **Data**: ```json theme={null} { "type": "TEAM_STATUS", "message": "Team progress update", "data": { "active_agents": 2, "idle_agents": 1, "tasks_completed": 5, "tasks_remaining": 3, "coordination_mode": "parallel" } } ``` *** ### DEPENDENCY\_SATISFIED **Emitted**: When task dependencies are resolved and execution can proceed **Data**: ```json theme={null} { "type": "DEPENDENCY_SATISFIED", "message": "Dependencies satisfied for subtask-3", "data": { "subtask_id": "subtask-3", "satisfied_dependencies": ["subtask-1", "subtask-2"], "can_proceed": true } } ``` *** ## Message Events Agent-to-agent communication. ### MESSAGE\_SENT **Emitted**: Agent sends message to another agent **Data**: ```json theme={null} { "type": "MESSAGE_SENT", "agent_id": "supervisor", "message": "Message sent to data-analyst", "data": { "to": "data-analyst", "content": "Please analyze revenue trends", "message_type": "DELEGATION" } } ``` *** ### MESSAGE\_RECEIVED **Emitted**: Agent receives message **Data**: ```json theme={null} { "type": "MESSAGE_RECEIVED", "agent_id": "data-analyst", "message": "Message received from supervisor", "data": { "from": "supervisor", "content": "Please analyze revenue trends", "acknowledged": true } } ``` *** ## LLM Events Language model interaction events for debugging and monitoring. ### LLM\_PROMPT **Emitted**: When a prompt is sent to the LLM (sanitized for privacy) **Data**: ```json theme={null} { "type": "LLM_PROMPT", "message": "Sending prompt to LLM", "data": { "model": "gpt-5", "prompt_length": 1200, "max_tokens": 2000, "temperature": 0.7, "sanitized_prompt": "Analyze the provided data..." } } ``` *** ### LLM\_PARTIAL **Emitted**: Incremental LLM output chunk during streaming **Data**: ```json theme={null} { "type": "LLM_PARTIAL", "message": "Received partial LLM output", "data": { "chunk": "Based on the analysis", "chunk_index": 5, "total_tokens_so_far": 50 } } ``` *** ### LLM\_OUTPUT **Emitted**: Final LLM output for a step **Data**: ```json theme={null} { "type": "LLM_OUTPUT", "message": "LLM output complete", "data": { "output": "Analysis complete. Revenue increased 15% YoY...", "model": "gpt-5", "provider": "openai", "usage": { "total_tokens": 350, "input_tokens": 200, "output_tokens": 150 }, "cost_usd": 0.0105, "duration_ms": 2000 } } ``` **Fields**: * `output`: Complete LLM response text * `model`: Model used (canonical name) * `provider`: LLM provider (openai, anthropic, google, xai, etc.) * `usage`: OpenAI-compatible usage object containing: * `total_tokens`: Total tokens (input + output) * `input_tokens`: Input/prompt tokens * `output_tokens`: Generated tokens * `cost_usd`: Estimated cost in USD * `duration_ms`: Request duration in milliseconds **Note**: Usage metadata follows OpenAI's standard format and is now available for all providers including OpenAI, Anthropic, Google, Groq, xAI, and OpenAI-compatible endpoints. The `usage` object structure matches OpenAI's streaming response format for seamless integration. See the [OpenAI-Compatible API](/en/api/rest/openai-compatible) for details on using Shannon with OpenAI SDKs. *** ## Progress Events Task progress and status updates for user feedback. ### PROGRESS **Emitted**: General progress update during execution **Data**: ```json theme={null} { "type": "PROGRESS", "message": "Progress: 60% complete", "data": { "percentage": 60, "current_step": 3, "total_steps": 5, "current_task": "Generating visualizations" } } ``` *** ### DATA\_PROCESSING **Emitted**: Agent is processing or analyzing data **Data**: ```json theme={null} { "type": "DATA_PROCESSING", "message": "Processing sales data", "data": { "operation": "data_analysis", "records_processed": 15000, "total_records": 15000, "processing_stage": "aggregation" } } ``` *** ### WAITING **Emitted**: Agent is waiting for resources or responses **Data**: ```json theme={null} { "type": "WAITING", "message": "Waiting for dependencies", "data": { "waiting_for": "subtask-2", "wait_reason": "dependency_not_satisfied", "estimated_wait_seconds": 10 } } ``` *** ## System Events System-level events and errors. ### ERROR\_OCCURRED **Emitted**: System error during execution **Data**: ```json theme={null} { "type": "ERROR_OCCURRED", "message": "Database connection failed", "data": { "error_type": "DATABASE_ERROR", "error_message": "Connection timeout", "recoverable": true, "retry_count": 2, "max_retries": 3 } } ``` *** ### ERROR\_RECOVERY **Emitted**: System is recovering from an error **Data**: ```json theme={null} { "type": "ERROR_RECOVERY", "message": "Recovering from database connection error", "data": { "error_type": "DATABASE_ERROR", "recovery_action": "retry_connection", "attempt": 2, "max_attempts": 3, "success": true } } ``` *** ### APPROVAL\_REQUESTED **Emitted**: Human approval needed to proceed **Data**: ```json theme={null} { "type": "APPROVAL_REQUESTED", "message": "Approval requested for file system access", "data": { "approval_id": "appr-123", "tool_name": "file_system", "operation": "write", "risk_level": "HIGH", "timeout_seconds": 1800, "details": { "file_path": "/data/critical.db", "action": "delete" } } } ``` *** ### APPROVAL\_DECISION **Emitted**: Human has made an approval decision **Data**: ```json theme={null} { "type": "APPROVAL_DECISION", "message": "Approval granted", "data": { "approval_id": "appr-123", "decision": "approved", "approved_by": "user-456", "timestamp": "2024-10-27T10:05:00Z", "comments": "Verified action is necessary" } } ``` **Decision Values**: * `approved` - Action allowed to proceed * `denied` - Action blocked * `timeout` - No decision within timeout period *** ### WORKSPACE\_UPDATED **Emitted**: Working memory/context updated **Data**: ```json theme={null} { "type": "WORKSPACE_UPDATED", "message": "Workspace updated", "data": { "key": "loaded_datasets", "value": ["sales_q4.csv"], "action": "ADD" } } ``` *** ### ROLE\_ASSIGNED **Emitted**: Agent role assigned during execution **Data**: ```json theme={null} { "type": "ROLE_ASSIGNED", "agent_id": "agent-002", "message": "Role assigned: data-analyst", "data": { "role": "data-analyst", "capabilities": ["data_loading", "analysis", "visualization"], "tools": ["csv_loader", "pandas", "matplotlib"] } } ``` *** ### STATUS\_UPDATE **Emitted**: General status update for the task or workflow **Data**: ```json theme={null} { "type": "STATUS_UPDATE", "message": "Task processing update", "data": { "status": "string", "details": "string" } } ``` *** ### THREAD\_MESSAGE\_DELTA **Emitted**: Incremental content chunk during streaming response generation **Data**: ```json theme={null} { "type": "THREAD_MESSAGE_DELTA", "message": "Content chunk", "data": { "delta": "string", "index": 0 } } ``` *** ### THREAD\_MESSAGE\_COMPLETED **Emitted**: Full message content has been delivered **Data**: ```json theme={null} { "type": "THREAD_MESSAGE_COMPLETED", "message": "Message complete", "data": { "content": "string", "total_tokens": 0 } } ``` *** ### BUDGET\_THRESHOLD **Emitted**: Token budget reaches a warning threshold (typically 80% of limit) **Data**: ```json theme={null} { "type": "BUDGET_THRESHOLD", "message": "Task budget at 85.0% (threshold: 80.0%)", "data": { "usage_percent": 85.0, "threshold_percent": 80.0, "tokens_used": 8500, "tokens_budget": 10000, "level": "warning", "budget_type": "task" } } ``` **Fields**: * `usage_percent`: Current usage as a percentage (e.g., 85.0) * `threshold_percent`: Warning threshold percentage (e.g., 80.0) * `tokens_used`: Cumulative tokens consumed so far * `tokens_budget`: Maximum allowed tokens for the task * `level`: Severity level (`"warning"`) * `budget_type`: Type of budget that triggered the event (`"task"`) **Usage**: Monitor this event to warn users before hitting hard budget limits, allowing graceful degradation or early termination decisions. *** ## Event Ordering Events are **strictly ordered** by sequence number (`seq`): ```json theme={null} {"seq": 1, "type": "WORKFLOW_STARTED"} {"seq": 2, "type": "AGENT_STARTED"} {"seq": 3, "type": "AGENT_THINKING"} {"seq": 4, "type": "TOOL_INVOKED"} {"seq": 5, "type": "LLM_OUTPUT"} {"seq": 6, "type": "WORKFLOW_COMPLETED"} ``` **Properties**: * Sequence numbers are monotonically increasing * No gaps in sequence (every number from 1 to N) * Events from same workflow always ordered correctly *** ## Typical Event Flow (Simplified) ``` 1. WORKFLOW_STARTED 2. AGENT_STARTED 3. AGENT_THINKING 4. TOOL_INVOKED (optional) 5. LLM_OUTPUT (final answer) 6. WORKFLOW_COMPLETED ``` ## Event Persistence Events are stored in: * **PostgreSQL**: Permanent event log * **Redis**: Recent events (hot cache) * **Real-time**: SSE stream **Retrieving Historical Events**: ```bash theme={null} # Get all events for a task GET /api/v1/tasks/{task_id}/events?limit=1000 ``` *** ## Event Reliability and Guarantees ### Ordering Guarantees Shannon provides **strict ordering** within a single workflow: * Events are numbered sequentially (`seq` field) * No gaps in sequence numbers (1, 2, 3, ...) * Events from the same workflow always arrive in order * Events from different workflows may be interleaved ### Delivery Guarantees * **At-least-once delivery**: Events may be delivered multiple times (use `seq` for deduplication) * **Event persistence**: All events stored in PostgreSQL `event_logs` table * **Hot cache**: Recent events cached in Redis for fast retrieval * **Historical access**: Query past events via REST API ### Stream Reconnection If SSE connection drops: ```python theme={null} # Reconnect and resume from last sequence number last_seq = 42 # Last received event for event in client.stream_events(workflow_id, from_seq=last_seq + 1): process_event(event) ``` ### Event Retention | Storage | Retention Period | Purpose | Event Types | | ---------- | ------------------- | ------------------- | ---------------------- | | Redis | 24 hours | Real-time streaming | All events | | PostgreSQL | 90 days (default) | Historical queries | Critical events only\* | | Archival | 1+ years (optional) | Long-term audit | Configurable | **PostgreSQL Selective Persistence**: To optimize database performance, only critical events are persisted to PostgreSQL, including: `WORKFLOW_COMPLETED`, `AGENT_COMPLETED`, `TOOL_INVOKED`, `LLM_OUTPUT`, and `ERROR_OCCURRED`. Ephemeral events like `LLM_PARTIAL`, `HEARTBEAT`, and `AGENT_THINKING` are excluded from database writes (reducing write load by \~92%) but remain fully available via real-time SSE streaming and Redis cache. See [Database Schema](/en/architecture/database-schema#event-logs-table) for event storage details. *** ## Related Topics SSE and WebSocket streaming SDK streaming guide View task history Debug streaming issues # Quick Start Examples Source: https://docs.shannon.run/en/api/getting-started/quick-start-examples Get started with Shannon API in minutes - complete examples for your first API calls ## Your First API Call The basic Shannon workflow is simple: submit a task, get a task ID, check the status, and retrieve the result. ### Complete Example (cURL) ```bash theme={null} # 1. Submit a task RESPONSE=$(curl -sS -X POST http://localhost:8080/api/v1/tasks \ -H "X-API-Key: sk_test_123456" \ -H "Content-Type: application/json" \ -d '{ "query": "What is the capital of France?" }') # 2. Extract task ID TASK_ID=$(echo $RESPONSE | jq -r '.task_id') echo "Task ID: $TASK_ID" # 3. Poll for result while true; do STATUS=$(curl -sS http://localhost:8080/api/v1/tasks/$TASK_ID \ -H "X-API-Key: sk_test_123456") STATE=$(echo $STATUS | jq -r '.status') if [ "$STATE" = "TASK_STATUS_COMPLETED" ]; then echo $STATUS | jq -r '.result' break elif [ "$STATE" = "TASK_STATUS_FAILED" ]; then echo "Error: $(echo $STATUS | jq -r '.error')" exit 1 fi sleep 2 done ``` **Expected Output**: ``` Task ID: task_01HQZX3Y9K8M2P4N5S7T9W2V The capital of France is Paris. ``` ## Common API Patterns ### 1. Submit → Poll → Retrieve The standard workflow for any task: ```python Python theme={null} import httpx import time # Submit task response = httpx.post( "http://localhost:8080/api/v1/tasks", headers={"X-API-Key": "sk_test_123456"}, json={"query": "What is the capital of France?"} ) task_id = response.json()["task_id"] print(f"Task submitted: {task_id}") # Poll until complete while True: status_resp = httpx.get( f"http://localhost:8080/api/v1/tasks/{task_id}", headers={"X-API-Key": "sk_test_123456"} ) status = status_resp.json() if status["status"] == "TASK_STATUS_COMPLETED": print("Result:", status["result"]) break elif status["status"] == "TASK_STATUS_FAILED": print("Error:", status["error"]) break time.sleep(2) ``` ```javascript JavaScript/Node.js theme={null} const axios = require('axios'); async function runTask() { // Submit task const submitResp = await axios.post( 'http://localhost:8080/api/v1/tasks', { query: 'What is the capital of France?' }, { headers: { 'X-API-Key': 'sk_test_123456' } } ); const taskId = submitResp.data.task_id; console.log('Task submitted:', taskId); // Poll until complete while (true) { const statusResp = await axios.get( `http://localhost:8080/api/v1/tasks/${taskId}`, { headers: { 'X-API-Key': 'sk_test_123456' } } ); const { status, result, error } = statusResp.data; if (status === 'TASK_STATUS_COMPLETED') { console.log('Result:', result); break; } else if (status === 'TASK_STATUS_FAILED') { console.log('Error:', error); break; } await new Promise(resolve => setTimeout(resolve, 2000)); } } runTask().catch(console.error); ``` ```bash cURL theme={null} #!/bin/bash API_KEY="sk_test_123456" # Submit TASK_ID=$(curl -sS -X POST http://localhost:8080/api/v1/tasks \ -H "X-API-Key: $API_KEY" \ -H "Content-Type: application/json" \ -d '{"query": "What is the capital of France?"}' | jq -r '.task_id') echo "Task ID: $TASK_ID" # Poll while true; do STATUS=$(curl -sS http://localhost:8080/api/v1/tasks/$TASK_ID \ -H "X-API-Key: $API_KEY" | jq -r '.status') if [ "$STATUS" = "TASK_STATUS_COMPLETED" ]; then curl -sS http://localhost:8080/api/v1/tasks/$TASK_ID \ -H "X-API-Key: $API_KEY" | jq -r '.result' break fi sleep 2 done ``` ### 2. Multi-Turn Conversations (Sessions) Use the same `session_id` to maintain context across multiple tasks: ```python Python theme={null} import httpx API_KEY = "sk_test_123456" SESSION_ID = "user-123-chat" # Turn 1 resp1 = httpx.post( "http://localhost:8080/api/v1/tasks", headers={"X-API-Key": API_KEY}, json={ "query": "What is Python?", "session_id": SESSION_ID } ) print("Task 1:", resp1.json()["task_id"]) # Turn 2 (references previous context) resp2 = httpx.post( "http://localhost:8080/api/v1/tasks", headers={"X-API-Key": API_KEY}, json={ "query": "What are its main advantages?", "session_id": SESSION_ID # Same session } ) print("Task 2:", resp2.json()["task_id"]) ``` ```javascript JavaScript theme={null} const axios = require('axios'); const API_KEY = 'sk_test_123456'; const SESSION_ID = 'user-123-chat'; async function multiTurnConversation() { // Turn 1 const resp1 = await axios.post( 'http://localhost:8080/api/v1/tasks', { query: 'What is Python?', session_id: SESSION_ID }, { headers: { 'X-API-Key': API_KEY } } ); console.log('Task 1:', resp1.data.task_id); // Turn 2 (references previous context) const resp2 = await axios.post( 'http://localhost:8080/api/v1/tasks', { query: 'What are its main advantages?', session_id: SESSION_ID // Same session }, { headers: { 'X-API-Key': API_KEY } } ); console.log('Task 2:', resp2.data.task_id); } multiTurnConversation(); ``` ```bash cURL theme={null} API_KEY="sk_test_123456" SESSION_ID="user-123-chat" # Turn 1 curl -X POST http://localhost:8080/api/v1/tasks \ -H "X-API-Key: $API_KEY" \ -H "Content-Type: application/json" \ -d "{\"query\": \"What is Python?\", \"session_id\": \"$SESSION_ID\"}" # Turn 2 (references previous context) curl -X POST http://localhost:8080/api/v1/tasks \ -H "X-API-Key: $API_KEY" \ -H "Content-Type: application/json" \ -d "{\"query\": \"What are its main advantages?\", \"session_id\": \"$SESSION_ID\"}" ``` ### 3. Real-Time Streaming (SSE) Get live updates instead of polling: ```python Python theme={null} import httpx # Submit task response = httpx.post( "http://localhost:8080/api/v1/tasks", headers={"X-API-Key": "sk_test_123456"}, json={"query": "Write a haiku about coding"} ) task_id = response.json()["task_id"] # Stream events with httpx.stream( "GET", f"http://localhost:8080/api/v1/tasks/{task_id}/stream", headers={"X-API-Key": "sk_test_123456"} ) as stream: for line in stream.iter_lines(): if line.startswith("data: "): event_data = line[6:] # Remove "data: " prefix print(event_data) ``` ```javascript JavaScript theme={null} const EventSource = require('eventsource'); // Submit task first const axios = require('axios'); const response = await axios.post( 'http://localhost:8080/api/v1/tasks', { query: 'Write a haiku about coding' }, { headers: { 'X-API-Key': 'sk_test_123456' } } ); const taskId = response.data.task_id; // Stream events const eventSource = new EventSource( `http://localhost:8080/api/v1/tasks/${taskId}/stream`, { headers: { 'X-API-Key': 'sk_test_123456' } } ); eventSource.onmessage = (event) => { console.log(event.data); }; eventSource.onerror = (error) => { console.error('Stream error:', error); eventSource.close(); }; ``` ```bash cURL theme={null} # Submit task TASK_ID=$(curl -sS -X POST http://localhost:8080/api/v1/tasks \ -H "X-API-Key: sk_test_123456" \ -H "Content-Type: application/json" \ -d '{"query": "Write a haiku about coding"}' | jq -r '.task_id') # Stream events curl -N http://localhost:8080/api/v1/tasks/$TASK_ID/stream \ -H "X-API-Key: sk_test_123456" ``` ### 4. Error Handling Always handle errors gracefully: ```python Python theme={null} import httpx try: response = httpx.post( "http://localhost:8080/api/v1/tasks", headers={"X-API-Key": "sk_test_123456"}, json={"query": "Analyze this data"}, timeout=30.0 ) response.raise_for_status() task = response.json() print(f"Success: {task['task_id']}") except httpx.HTTPStatusError as e: if e.response.status_code == 401: print("Error: Invalid API key") elif e.response.status_code == 429: retry_after = e.response.headers.get("Retry-After", "60") print(f"Rate limited. Retry after {retry_after}s") else: print(f"HTTP error: {e.response.status_code}") print(e.response.json()) except httpx.TimeoutException: print("Request timed out") except Exception as e: print(f"Unexpected error: {e}") ``` ```javascript JavaScript theme={null} const axios = require('axios'); async function submitWithErrorHandling(query) { try { const response = await axios.post( 'http://localhost:8080/api/v1/tasks', { query }, { headers: { 'X-API-Key': 'sk_test_123456' }, timeout: 30000 } ); console.log('Success:', response.data.task_id); return response.data; } catch (error) { if (error.response) { // Server responded with error status switch (error.response.status) { case 401: console.error('Invalid API key'); break; case 429: const retryAfter = error.response.headers['retry-after'] || 60; console.error(`Rate limited. Retry after ${retryAfter}s`); break; default: console.error('HTTP error:', error.response.status); console.error(error.response.data); } } else if (error.request) { // No response received console.error('No response from server'); } else { // Request setup error console.error('Error:', error.message); } throw error; } } submitWithErrorHandling('Analyze this data'); ``` ```bash cURL theme={null} #!/bin/bash API_KEY="sk_test_123456" RESPONSE=$(curl -sS -w "\n%{http_code}" -X POST http://localhost:8080/api/v1/tasks \ -H "X-API-Key: $API_KEY" \ -H "Content-Type: application/json" \ -d '{"query": "Analyze this data"}') HTTP_CODE=$(echo "$RESPONSE" | tail -n1) BODY=$(echo "$RESPONSE" | head -n-1) case $HTTP_CODE in 200) echo "Success: $(echo $BODY | jq -r '.task_id')" ;; 401) echo "Error: Invalid API key" exit 1 ;; 429) echo "Error: Rate limited" exit 1 ;; *) echo "Error: HTTP $HTTP_CODE" echo $BODY | jq exit 1 ;; esac ``` ## Authentication Setup ### Getting Your API Key For local testing, you can disable authentication: ```bash theme={null} # In .env file GATEWAY_SKIP_AUTH=1 ``` Then use any placeholder key: ```bash theme={null} curl -H "X-API-Key: dev" http://localhost:8080/api/v1/tasks ``` Generate an API key from your Shannon dashboard: 1. Log in to Shannon Cloud 2. Navigate to Settings → API Keys 3. Click "Create API Key" 4. Copy the key (starts with `sk_`) Store it securely (e.g., environment variable): ```bash theme={null} export SHANNON_API_KEY="sk_live_abc123..." ``` ### Including the API Key Always include your API key in the `X-API-Key` header: ```bash theme={null} # ✅ Correct curl -H "X-API-Key: sk_test_123456" http://localhost:8080/api/v1/tasks # ❌ Wrong - Authorization header doesn't work for API keys curl -H "Authorization: Bearer sk_test_123456" http://localhost:8080/api/v1/tasks ``` **Never commit API keys to version control**. Use environment variables or secret management tools. ## Complete Workflow Example Here's a real-world example that combines all patterns: ```python Python theme={null} import httpx import time import os class ShannonClient: def __init__(self, base_url: str, api_key: str): self.base_url = base_url self.api_key = api_key def submit_task(self, query: str, session_id: str = None): """Submit a task and return task ID.""" response = httpx.post( f"{self.base_url}/api/v1/tasks", headers={"X-API-Key": self.api_key}, json={ "query": query, "session_id": session_id } ) response.raise_for_status() return response.json()["task_id"] def wait_for_completion(self, task_id: str, timeout: int = 300): """Poll until task completes.""" start_time = time.time() while True: if time.time() - start_time > timeout: raise TimeoutError(f"Task timeout after {timeout}s") response = httpx.get( f"{self.base_url}/api/v1/tasks/{task_id}", headers={"X-API-Key": self.api_key} ) status = response.json() if status["status"] == "TASK_STATUS_COMPLETED": return status["result"] elif status["status"] == "TASK_STATUS_FAILED": raise Exception(f"Task failed: {status['error']}") time.sleep(2) def run_task(self, query: str, session_id: str = None): """Submit and wait for result in one call.""" task_id = self.submit_task(query, session_id) return self.wait_for_completion(task_id) # Usage client = ShannonClient( base_url="http://localhost:8080", api_key=os.getenv("SHANNON_API_KEY", "sk_test_123456") ) # Single task result = client.run_task("What is the capital of France?") print(result) # Multi-turn conversation session_id = "chat-123" result1 = client.run_task("What is Python?", session_id) result2 = client.run_task("What are its advantages?", session_id) print(result1) print(result2) ``` ```javascript JavaScript theme={null} const axios = require('axios'); class ShannonClient { constructor(baseUrl, apiKey) { this.baseUrl = baseUrl; this.apiKey = apiKey; } async submitTask(query, sessionId = null) { const response = await axios.post( `${this.baseUrl}/api/v1/tasks`, { query, session_id: sessionId }, { headers: { 'X-API-Key': this.apiKey } } ); return response.data.task_id; } async waitForCompletion(taskId, timeout = 300000) { const startTime = Date.now(); while (true) { if (Date.now() - startTime > timeout) { throw new Error(`Task timeout after ${timeout}ms`); } const response = await axios.get( `${this.baseUrl}/api/v1/tasks/${taskId}`, { headers: { 'X-API-Key': this.apiKey } } ); const { status, result, error } = response.data; if (status === 'TASK_STATUS_COMPLETED') { return result; } else if (status === 'TASK_STATUS_FAILED') { throw new Error(`Task failed: ${error}`); } await new Promise(resolve => setTimeout(resolve, 2000)); } } async runTask(query, sessionId = null) { const taskId = await this.submitTask(query, sessionId); return await this.waitForCompletion(taskId); } } // Usage const client = new ShannonClient( 'http://localhost:8080', process.env.SHANNON_API_KEY || 'sk_test_123456' ); async function main() { // Single task const result = await client.runTask('What is the capital of France?'); console.log(result); // Multi-turn conversation const sessionId = 'chat-123'; const result1 = await client.runTask('What is Python?', sessionId); const result2 = await client.runTask('What are its advantages?', sessionId); console.log(result1); console.log(result2); } main().catch(console.error); ``` ## Next Steps Now that you know the basics, explore more advanced features: Complete documentation for all endpoints Pre-built agents for specific tasks Official Python client library Real-time event streaming with SSE ## Common Questions Poll the `GET /api/v1/tasks/{id}` endpoint until `status` is `TASK_STATUS_COMPLETED` or `TASK_STATUS_FAILED`. For long-running tasks, use streaming instead of polling. * `result` is the raw text output from the LLM * `response` is only present if `result` contains valid JSON (parsed automatically) Most of the time, you'll use `result`. Sessions persist for 30 days by default. All tasks with the same `session_id` share conversation history. Yes, use `POST /api/v1/tasks/{id}/cancel`. The task will stop at the next safe checkpoint. Streaming provides real-time updates with lower latency and less server load. Use it for long-running tasks or when you need immediate feedback. ## Troubleshooting ### 401 Unauthorized * Check your API key is correct * Verify you're using the `X-API-Key` header (not `Authorization`) * In dev mode, ensure `GATEWAY_SKIP_AUTH=1` is set ### 429 Rate Limited * Check the `Retry-After` header * Implement exponential backoff * Upgrade your API tier for higher limits ### Task stuck in RUNNING * Wait longer (complex tasks take time) * Use streaming to see progress * Check Temporal UI for detailed workflow status ### Empty result field * Task might still be running (check `status`) * Task failed (check `error` field) * Use streaming to see intermediate output # GET /providers/models Source: https://docs.shannon.run/en/api/models/overview List available LLM models and configurations ## Endpoint ``` GET http://localhost:8000/providers/models ``` ## Description Returns all models currently configured in Shannon, organized by provider. This endpoint queries the Python LLM service directly and reflects the models defined in `config/models.yaml`. ## Authentication **Required**: No (internal service endpoint) For production deployments, access should be restricted to internal networks only. ## Request ### Query Parameters | Parameter | Type | Required | Description | | --------- | ------ | -------- | --------------------------------------------- | | `tier` | string | No | Filter by tier: `small`, `medium`, or `large` | ### Headers None required for internal access. ## Response ### Success Response **Status**: `200 OK` **Body**: ```json theme={null} { "openai": [ { "id": "gpt-5-nano-2025-08-07", "name": "gpt-5-nano-2025-08-07", "tier": "small", "context_window": 128000, "cost_per_1k_prompt_tokens": 0.0001, "cost_per_1k_completion_tokens": 0.0004, "supports_tools": true, "supports_streaming": true, "available": true } ], "anthropic": [ { "id": "claude-sonnet-4-5-20250929", "name": "claude-sonnet-4-5-20250929", "tier": "medium", "context_window": 200000, "cost_per_1k_prompt_tokens": 0.003, "cost_per_1k_completion_tokens": 0.015, "supports_tools": true, "supports_streaming": true, "available": true } ] } ``` ### Response Structure Response is organized by provider, with each provider returning an array of model objects: | Field | Type | Description | | ------------------------------- | ------- | ---------------------------------------- | | `id` | string | Model identifier (canonical name) | | `name` | string | Display name (same as id) | | `tier` | string | Size tier: `small`, `medium`, or `large` | | `context_window` | integer | Maximum context length in tokens | | `cost_per_1k_prompt_tokens` | float | Cost per 1K input tokens (USD) | | `cost_per_1k_completion_tokens` | float | Cost per 1K output tokens (USD) | | `supports_tools` | boolean | Function calling support | | `supports_streaming` | boolean | Real-time streaming support | | `available` | boolean | Currently available for use | ## Examples ### List All Models ```bash theme={null} curl http://localhost:8000/providers/models | jq ``` ### Filter by Tier ```bash theme={null} # Small models only curl "http://localhost:8000/providers/models?tier=small" | jq # Large models only curl "http://localhost:8000/providers/models?tier=large" | jq ``` ### Python Example ```python theme={null} import httpx # Get all models response = httpx.get("http://localhost:8000/providers/models") models = response.json() # Print OpenAI models for model in models.get("openai", []): print(f"{model['id']} - {model['tier']} - ${model['cost_per_1k_prompt_tokens']:.4f}/1K") # Filter small tier response = httpx.get("http://localhost:8000/providers/models?tier=small") small_models = response.json() ``` ## Model Tiers Models are organized into three tiers based on capability and cost: ### Small Tier (Priority for 50% of workload) Fast, cost-optimized models for basic tasks: * **OpenAI**: gpt-5-nano-2025-08-07 * **Anthropic**: claude-haiku-4-5-20251001 * **xAI**: grok-3-mini * **Google**: gemini-2.5-flash-lite * **DeepSeek**: deepseek-chat ### Medium Tier (Priority for 40% of workload) Balanced capability/cost models: * **OpenAI**: gpt-5-mini-2025-08-07 * **Anthropic**: claude-sonnet-4-5-20250929 * **xAI**: grok-4-fast-non-reasoning * **Google**: gemini-2.5-flash * **Meta**: llama-4-scout ### Large Tier (Priority for 10% of workload) Heavy reasoning models for complex tasks: * **OpenAI**: gpt-4.1-2025-04-14, gpt-5-pro-2025-10-06 * **Anthropic**: claude-opus-4-1-20250805 * **Google**: gemini-2.5-pro * **DeepSeek**: deepseek-r1 * **xAI**: grok-4-fast-reasoning ## Configuration Source Models are defined in `config/models.yaml` under `model_catalog`: ```yaml theme={null} model_catalog: openai: gpt-5-nano-2025-08-07: model_id: gpt-5-nano-2025-08-07 tier: small context_window: 128000 max_tokens: 4096 supports_functions: true supports_streaming: true ``` Pricing is centralized under `pricing.models`: ```yaml theme={null} pricing: models: openai: gpt-5-nano-2025-08-07: input_per_1k: 0.0001 output_per_1k: 0.0004 ``` ## Use Cases **1. Discover Available Models** ```bash theme={null} curl http://localhost:8000/providers/models | jq 'keys' # ["anthropic", "openai", "google", "xai", ...] ``` **2. Check Pricing** ```bash theme={null} curl http://localhost:8000/providers/models | \ jq '.openai[] | {id, input: .cost_per_1k_prompt_tokens, output: .cost_per_1k_completion_tokens}' ``` **3. Verify API Key Configuration** ```bash theme={null} # If a provider returns empty array, API key may be missing curl http://localhost:8000/providers/models | jq '.anthropic | length' ``` **4. Build Model Selector UI** ```javascript theme={null} const response = await fetch('http://localhost:8000/providers/models?tier=small'); const models = await response.json(); // Populate dropdown Object.entries(models).forEach(([provider, modelList]) => { modelList.forEach(model => { dropdown.add(new Option(`${provider}: ${model.id}`, model.id)); }); }); ``` ## Notes * **Static Configuration**: Models are loaded from `config/models.yaml`, not dynamically discovered from provider APIs * **Hot Reload**: Changes to `models.yaml` require service restart to take effect * **Empty Providers**: If a provider returns `[]`, check that the API key is set in `.env` * **Pricing Centralization**: All costs come from `pricing` section in YAML, ensuring consistency across Go/Rust/Python services * **Internal Endpoint**: The `/providers/models` endpoint is on the LLM service (port 8000). For external access, use the Gateway's OpenAI-compatible `/v1/models` endpoint (port 8080) — see [OpenAI-Compatible API](/en/api/rest/openai-compatible) ## Environment Variables Override model selections with environment variables: ```bash theme={null} # Stage-specific overrides COMPLEXITY_MODEL_ID=gpt-5-mini-2025-08-07 DECOMPOSITION_MODEL_ID=gpt-5-mini-2025-08-07 DEFAULT_MODEL_TIER=small ``` See [Configuration Guide](/en/quickstart/configuration) for full list. ## Troubleshooting **Empty provider arrays** * Verify API key is set: `OPENAI_API_KEY`, `ANTHROPIC_API_KEY`, etc. * Check `config/models.yaml` has entries under `model_catalog.` **Missing models** * Ensure `MODELS_CONFIG_PATH` points to correct file * Verify YAML syntax is valid * Check for typos in model IDs **Incorrect pricing** * Pricing comes from `pricing.models.` section * Update `config/models.yaml` and restart services * Verify Go/Rust services also read same config file ## Related Documentation How tier routing and fallback works Environment variables and config files Use model\_tier or model\_override Pricing architecture details # API Overview Source: https://docs.shannon.run/en/api/overview Shannon's REST and gRPC APIs for AI agent orchestration ## Introduction Shannon provides HTTP REST and gRPC APIs for submitting tasks, streaming results, and managing AI agent workflows. **Base URL**: `http://localhost:8080` (development) ## Quick Start ```bash theme={null} # Submit a task curl -X POST http://localhost:8080/api/v1/tasks \ -H "Content-Type: application/json" \ -d '{"query": "Analyze sentiment: Shannon makes AI simple"}' # Response { "task_id": "task-dev-1234567890", "status": "submitted", "created_at": "2025-01-20T10:00:00Z" } # Stream real-time events curl -N "http://localhost:8080/api/v1/stream/sse?workflow_id=task-dev-1234567890" ``` ## API Endpoints ### Core Task Operations | Endpoint | Method | Description | | ----------------------------- | ------ | ------------------------------------ | | `/api/v1/tasks` | POST | Submit a new task | | `/api/v1/tasks/stream` | POST | Submit task and get stream URL (201) | | `/api/v1/tasks` | GET | List tasks with filters | | `/api/v1/tasks/{id}` | GET | Get task status and result | | `/api/v1/tasks/{id}/stream` | GET | Stream events for a specific task | | `/api/v1/tasks/{id}/events` | GET | Get task event history | | `/api/v1/tasks/{id}/timeline` | GET | Get Temporal execution timeline | ### Task Control | Endpoint | Method | Description | | ---------------------------------- | ------ | --------------------------------------- | | `/api/v1/tasks/{id}/cancel` | POST | Cancel a running task | | `/api/v1/tasks/{id}/pause` | POST | Pause task at next checkpoint | | `/api/v1/tasks/{id}/resume` | POST | Resume a paused task | | `/api/v1/tasks/{id}/control-state` | GET | Get task control state (paused/running) | ### Real-Time Streaming | Endpoint | Method | Description | | ------------------------------------- | ------ | ------------------------- | | `/api/v1/stream/sse?workflow_id={id}` | GET | Server-Sent Events stream | | `/api/v1/stream/ws?workflow_id={id}` | GET | WebSocket stream | ### Session Management | Endpoint | Method | Description | | -------------------------------------- | ------ | -------------------------------------------- | | `/api/v1/sessions` | GET | List sessions with pagination | | `/api/v1/sessions/{sessionId}` | GET | Get session details | | `/api/v1/sessions/{sessionId}/history` | GET | Get session chat history | | `/api/v1/sessions/{sessionId}/events` | GET | Get session events (grouped by turn) | | `/api/v1/sessions/{sessionId}` | PATCH | Update session title (UUID or external\_id) | | `/api/v1/sessions/{sessionId}` | DELETE | Delete session (soft delete; 204 idempotent) | ### Schedules (Recurring Tasks) | Endpoint | Method | Description | | ------------------------------- | ------ | ------------------------------ | | `/api/v1/schedules` | POST | Create a new schedule | | `/api/v1/schedules` | GET | List schedules with filters | | `/api/v1/schedules/{id}` | GET | Get schedule details | | `/api/v1/schedules/{id}/runs` | GET | Get schedule execution history | | `/api/v1/schedules/{id}` | PUT | Update schedule configuration | | `/api/v1/schedules/{id}/pause` | POST | Pause a schedule | | `/api/v1/schedules/{id}/resume` | POST | Resume a paused schedule | | `/api/v1/schedules/{id}` | DELETE | Delete a schedule | ### Authentication | Endpoint | Method | Description | Auth Required | | ---------------------------- | ------ | ------------------------ | ------------- | | `/api/v1/auth/register` | POST | Register a new user | No | | `/api/v1/auth/login` | POST | Login and get JWT tokens | No | | `/api/v1/auth/refresh` | POST | Refresh JWT access token | No | | `/api/v1/auth/me` | GET | Get current user info | Yes | | `/api/v1/auth/refresh-key` | POST | Regenerate API key | Yes | | `/api/v1/auth/api-keys` | GET | List user's API keys | Yes | | `/api/v1/auth/api-keys` | POST | Create new API key | Yes | | `/api/v1/auth/api-keys/{id}` | DELETE | Revoke an API key | Yes | ### Approvals | Endpoint | Method | Description | | ---------------------------- | ------ | ---------------------------------------------------- | | `/api/v1/approvals/decision` | POST | Submit approval decision for human-in-the-loop tasks | ### OpenAI-Compatible API Shannon provides an OpenAI-compatible API for easy integration with existing OpenAI SDKs and tools. See the [OpenAI-Compatible API Reference](/en/api/rest/openai-compatible) for full details. | Endpoint | Method | Description | | ---------------------- | ------ | -------------------------------------- | | `/v1/chat/completions` | POST | Chat completions (streaming supported) | | `/v1/models` | GET | List available models | | `/v1/models/{model}` | GET | Get specific model details | ### Health & Observability | Endpoint | Method | Description | Auth Required | | --------------- | ------ | ---------------- | ------------- | | `/health` | GET | Health check | No | | `/readiness` | GET | Readiness probe | No | | `/openapi.json` | GET | OpenAPI 3.0 spec | No | ## Authentication **Development Default**: Authentication is **disabled** (`GATEWAY_SKIP_AUTH=1`). Enable for production. When enabled, pass API key via header: ```bash theme={null} curl -H "X-API-Key: sk_test_123456" \ http://localhost:8080/api/v1/tasks ``` SSE Streaming: Browser `EventSource` cannot send custom headers. * Development: set `GATEWAY_SKIP_AUTH=1` to skip auth. * Production: initiate SSE from your backend (or edge) and inject `X-API-Key` or `Authorization: Bearer` headers. * For SSE endpoints, the `api_key` query parameter is supported as a fallback when headers cannot be sent. ## Response Format All endpoints return JSON with consistent error format: **Success (200)**: ```json theme={null} { "task_id": "task-dev-1234567890", "status": "COMPLETED", "result": "Sentiment: Positive" } ``` **Error (400/401/404/429/500)**: ```json theme={null} { "error": "Task not found" } ``` ## Rate Limiting * **Default**: 60 requests/minute per API key * **Headers**: `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` * **429 Response**: Includes `Retry-After` header ## Event Streaming Shannon emits 33 event types for real-time monitoring: **Core Events**: * `WORKFLOW_STARTED`, `WORKFLOW_COMPLETED` * `AGENT_STARTED`, `AGENT_COMPLETED` * `STATUS_UPDATE`, `DATA_PROCESSING` * `ERROR_OCCURRED` **LLM Events**: * `LLM_PROMPT`, `LLM_OUTPUT`, `LLM_PARTIAL` **Tool Events**: * `TOOL_INVOKED`, `TOOL_OBSERVATION` See [Event Types Reference](/en/api/rest/streaming#event-types) for the complete list. Python SDK: use the `EventType` enum for filtering and checks. See [SDK Streaming](/en/sdk/python/streaming). ## Client Access Use the Gateway REST API or the Python SDK (HTTP-only): * REST endpoints: `http://localhost:8080/api/v1/*` * Python SDK: `ShannonClient(base_url="http://localhost:8080")` Note: gRPC services are internal and not part of the public SDK surface. The OpenAI-compatible `/v1/chat/completions` endpoint is intended for compatibility. For full Shannon features (skills, session workspaces, research strategies), use `/api/v1/tasks` and related endpoints. ## Best Practices ### 1. Use Session IDs for Context ```json theme={null} { "query": "What about Q2?", "session_id": "user-123-analytics" } ``` ### 2. Stream Events for Long Tasks ```python theme={null} import requests r = requests.get( f"http://localhost:8080/api/v1/stream/sse?workflow_id={task_id}", stream=True ) for line in r.iter_lines(): if line: event = parse_sse(line) print(event['type']) ``` ### 3. Handle Rate Limits ```python theme={null} import time def submit_with_retry(query, max_retries=3): for attempt in range(max_retries): response = requests.post(url, json={"query": query}) if response.status_code == 429: retry_after = int(response.headers.get("Retry-After", 60)) time.sleep(retry_after) continue return response ``` ### 4. Use Idempotency Keys ```bash theme={null} curl -X POST http://localhost:8080/api/v1/tasks \ -H "Idempotency-Key: $(uuidgen)" \ -d '{"query": "Process once"}' ``` ## SDKs Official Python client with streaming support Use any HTTP client library ## Next Steps Task submission API Real-time event streaming Session management API keys and JWT authentication Pause, resume, and cancel tasks Get started with Python # Agents API Source: https://docs.shannon.run/en/api/rest/agents Execute single-purpose agents via direct API calls ## Overview The Agents API provides direct execution of single-purpose agents (also called "Quick Tools") without LLM orchestration. Each agent wraps a specific tool and returns structured results. **Key Differences from Task API:** * **Agents API**: Direct tool execution, no AI orchestration, asynchronous results * **Task API**: Multi-step workflows, LLM planning, asynchronous execution All agent executions are **asynchronous** (return task\_id), even though agents perform single operations. Use the [Task Status API](/en/api/rest/get-status) to retrieve results. ## Base URL ``` http://localhost:8080/api/v1/agents ``` Production: `https://your-domain.com/api/v1/agents` ## Authentication **Required**: Yes Include API key in header: ``` X-API-Key: sk_your_api_key ``` **Development Default**: Authentication is disabled when `GATEWAY_SKIP_AUTH=1` is set. *** ## Endpoints ### List Agents **GET /api/v1/agents** Returns all available agents with their schemas and metadata. #### Request ```bash theme={null} curl http://localhost:8080/api/v1/agents \ -H "X-API-Key: sk_test_123456" ``` #### Response ```json theme={null} { "agents": [ { "id": "serp-ads", "name": "SERP Ads Extract", "description": "Extract paid ads from Google Search results for given keywords", "category": "ads_research", "tool": "ads_serp_extract", "input_schema": { "required": ["keywords"], "properties": { "keywords": { "type": "string", "description": "Search keywords to find ads for" }, "country": { "type": "string", "default": "us", "description": "Country code (us, jp, uk, etc.)" } } }, "cost_per_call": 0.015 } ], "count": 14 } ``` #### Response Fields | Field | Type | Description | | -------- | ------- | ------------------------- | | `agents` | array | List of agent definitions | | `count` | integer | Total number of agents | **Agent Object:** | Field | Type | Description | | --------------- | ------ | ----------------------------------------------- | | `id` | string | Agent identifier (used in execute endpoint) | | `name` | string | Human-readable name | | `description` | string | What the agent does | | `category` | string | Agent category (ads\_research, financial, etc.) | | `tool` | string | Underlying tool name | | `input_schema` | object | JSON schema for input validation | | `cost_per_call` | number | Estimated cost per execution (USD) | *** ### Get Agent Details **GET /api/v1/agents/** Returns details for a specific agent, including its input schema. #### Request ```bash theme={null} curl http://localhost:8080/api/v1/agents/serp-ads \ -H "X-API-Key: sk_test_123456" ``` #### Response ```json theme={null} { "id": "serp-ads", "name": "SERP Ads Extract", "description": "Extract paid ads from Google Search results for given keywords", "category": "ads_research", "tool": "ads_serp_extract", "input_schema": { "required": ["keywords"], "properties": { "keywords": { "type": "string", "description": "Search keywords to find ads for" }, "country": { "type": "string", "default": "us" }, "device": { "type": "string", "enum": ["desktop", "mobile", "tablet"], "default": "desktop" } } }, "cost_per_call": 0.015 } ``` #### Error Responses **404 Not Found** - Agent does not exist: ```json theme={null} { "error": "Agent not found: invalid-agent-id" } ``` *** ### Execute Agent **POST /api/v1/agents/** Executes a specific agent with provided input. Returns a task ID immediately; the agent runs asynchronously. #### Request Headers | Header | Required | Description | | -------------- | -------- | -------------------------- | | `X-API-Key` | Yes | API authentication key | | `Content-Type` | Yes | Must be `application/json` | #### Request Body | Parameter | Type | Required | Description | | ------------ | ------- | -------- | ------------------------------------------------------------------ | | `input` | object | Yes | Agent-specific input parameters (validated against `input_schema`) | | `session_id` | string | No | Session identifier for tracking (auto-generated if omitted) | | `stream` | boolean | No | Enable streaming (reserved for future use) | #### Example: Execute SERP Ads Agent ```bash theme={null} curl -X POST http://localhost:8080/api/v1/agents/serp-ads \ -H "X-API-Key: sk_test_123456" \ -H "Content-Type: application/json" \ -d '{ "session_id": "user-session-123", "input": { "keywords": "best credit cards", "country": "us", "device": "desktop" } }' ``` #### Response **Status**: `202 Accepted` **Headers**: * `X-Workflow-ID`: Temporal workflow identifier * `X-Session-ID`: Session identifier **Body**: ```json theme={null} { "task_id": "task-abc123", "agent_id": "serp-ads", "status": "STATUS_CODE_OK", "created_at": "2026-02-15T10:30:00Z" } ``` #### Retrieve Results Use the [Get Task Status](/en/api/rest/get-status) endpoint with the returned `task_id`: ```bash theme={null} curl http://localhost:8080/api/v1/tasks/task-abc123 \ -H "X-API-Key: sk_test_123456" ``` **Response when completed:** ```json theme={null} { "task_id": "task-abc123", "status": "TASK_STATUS_COMPLETED", "result": "{\"keywords\":\"best credit cards\",\"total_ads\":5,\"ads\":[...]}", "response": { "keywords": "best credit cards", "total_ads": 5, "ads": [ { "position": 1, "title": "Best Credit Cards 2026", "description": "Compare top credit cards...", "link": "https://example.com/cards", "domain": "example.com" } ], "cost_usd": 0.015, "timestamp": "2026-02-15T10:30:00Z" }, "model_used": "claude-sonnet-4-20250514", "provider": "anthropic", "usage": { "total_tokens": 1250, "input_tokens": 800, "output_tokens": 450, "estimated_cost": 0.018 } } ``` #### Error Responses **400 Bad Request** - Invalid input: ```json theme={null} { "error": "input validation failed: missing required field: keywords" } ``` **404 Not Found** - Agent does not exist: ```json theme={null} { "error": "Agent not found: invalid-agent" } ``` **429 Too Many Requests** - Rate limit exceeded: ```json theme={null} { "error": "Rate limit exceeded" } ``` *** ## Input Validation All agent inputs are validated against the agent's `input_schema` before execution. **Validation Rules:** 1. **Required fields** must be present and non-null 2. **Type checking** - strings, integers, booleans, arrays, objects 3. **Enum validation** - values must be in allowed list 4. **Unknown fields** - rejected for security (not in schema) **Example Schema:** ```json theme={null} { "required": ["keywords"], "properties": { "keywords": { "type": "string", "description": "Search keywords" }, "device": { "type": "string", "enum": ["desktop", "mobile", "tablet"], "default": "desktop" }, "max_results": { "type": "integer", "default": 10 } } } ``` **Valid input:** ```json theme={null} { "keywords": "best shoes", "device": "mobile", "max_results": 5 } ``` **Invalid input (missing required field):** ```json theme={null} { "device": "mobile" } ``` Error: `"input validation failed: missing required field: keywords"` **Invalid input (unknown field):** ```json theme={null} { "keywords": "shoes", "unknown_field": "value" } ``` Error: `"input validation failed: unknown field: unknown_field (not defined in agent schema)"` *** ## Available Agents Shannon provides **14+ specialized agents** across multiple categories. For a complete catalog of available agents with detailed schemas and examples, see: 10 agents for competitive advertising analysis 4 agents for stock news and sentiment analysis ### Quick Reference by Category **Ads Research** (10 agents): * `serp-ads` - Extract Google paid ads * `yahoo-jp-ads` - Extract Yahoo Japan sponsored ads * `meta-ad-library` - Search Meta Ad Library (Facebook/Instagram) * `competitor-discover` - Find competitor advertisers * `ads-transparency` - Multi-platform ad transparency data * `lp-visual-analyze` - Screenshot and analyze landing pages * `lp-batch-analyze` - Batch analyze multiple landing pages * `ad-creative-analyze` - Analyze ad copy patterns * `keyword-extract` - Extract search keywords from text * `browser-screenshot` - Capture webpage screenshots **Financial Tools** (4 agents): * `sec-filings` - SEC EDGAR filings lookup * `twitter-sentiment` - X/Twitter sentiment via xAI * `alpaca-news` - Stock news from Alpaca Markets * `news-aggregator` - Multi-source news aggregation *** ## Unified Task API Alternative You can also execute agents through the **unified Task API** using the `context.agent` parameter: ```bash theme={null} curl -X POST http://localhost:8080/api/v1/tasks \ -H "X-API-Key: sk_test_123456" \ -H "Content-Type: application/json" \ -d '{ "query": "NVDA", "session_id": "user-session-123", "context": { "agent": "sec-filings", "agent_input": { "ticker": "NVDA", "days_back": 90 } } }' ``` **Both approaches are equivalent**: * Dedicated endpoint: `POST /api/v1/agents/{id}` * Unified endpoint: `POST /api/v1/tasks` with `context.agent` *** ## Best Practices ### 1. Validate Inputs Before Submission Use the GET endpoint to retrieve the agent's schema, then validate your input client-side: ```python theme={null} import httpx # Get schema agent = httpx.get( "http://localhost:8080/api/v1/agents/serp-ads", headers={"X-API-Key": "sk_test_123456"} ).json() # Validate required fields required = agent["input_schema"]["required"] input_data = {"keywords": "shoes", "device": "mobile"} for field in required: if field not in input_data: raise ValueError(f"Missing required field: {field}") ``` ### 2. Handle Async Results All agents return task IDs immediately. Poll for results: ```python theme={null} import time # Execute agent response = httpx.post( "http://localhost:8080/api/v1/agents/serp-ads", headers={"X-API-Key": "sk_test_123456"}, json={"input": {"keywords": "shoes"}} ).json() task_id = response["task_id"] # Poll for completion while True: status = httpx.get( f"http://localhost:8080/api/v1/tasks/{task_id}", headers={"X-API-Key": "sk_test_123456"} ).json() if status["status"] == "TASK_STATUS_COMPLETED": print(status["response"]) break time.sleep(2) ``` ### 3. Use Sessions for Context Reuse `session_id` across related agent calls: ```python theme={null} session_id = "user-123-analysis" # First agent call httpx.post(..., json={ "session_id": session_id, "input": {"keywords": "shoes"} }) # Second agent call (same session) httpx.post(..., json={ "session_id": session_id, "input": {"urls": ["https://example.com"]} }) ``` ### 4. Check Cost Estimates Before executing expensive agents, check `cost_per_call`: ```python theme={null} agent = httpx.get( "http://localhost:8080/api/v1/agents/twitter-sentiment", headers={"X-API-Key": "sk_test_123456"} ).json() if agent["cost_per_call"] > 0.10: print(f"Warning: High cost agent (${agent['cost_per_call']})") ``` *** ## Code Examples ### Python with httpx ```python theme={null} import httpx # List all agents agents = httpx.get( "http://localhost:8080/api/v1/agents", headers={"X-API-Key": "sk_test_123456"} ).json() print(f"Found {agents['count']} agents") # Execute specific agent response = httpx.post( "http://localhost:8080/api/v1/agents/serp-ads", headers={ "X-API-Key": "sk_test_123456", "Content-Type": "application/json" }, json={ "session_id": "my-session", "input": { "keywords": "running shoes", "country": "us", "device": "mobile" } } ).json() print(f"Task ID: {response['task_id']}") ``` ### JavaScript/Node.js ```javascript theme={null} const axios = require('axios'); // Get agent details const agent = await axios.get( 'http://localhost:8080/api/v1/agents/serp-ads', { headers: { 'X-API-Key': 'sk_test_123456' } } ); console.log('Agent schema:', agent.data.input_schema); // Execute agent const response = await axios.post( 'http://localhost:8080/api/v1/agents/serp-ads', { session_id: 'my-session', input: { keywords: 'running shoes', country: 'us' } }, { headers: { 'X-API-Key': 'sk_test_123456', 'Content-Type': 'application/json' } } ); console.log('Task ID:', response.data.task_id); ``` ### Go ```go theme={null} package main import ( "bytes" "encoding/json" "fmt" "net/http" ) type AgentExecuteRequest struct { SessionID string `json:"session_id,omitempty"` Input map[string]interface{} `json:"input"` } func executeAgent(agentID string, input map[string]interface{}) (string, error) { req := AgentExecuteRequest{ SessionID: "my-session", Input: input, } body, _ := json.Marshal(req) httpReq, _ := http.NewRequest( "POST", fmt.Sprintf("http://localhost:8080/api/v1/agents/%s", agentID), bytes.NewBuffer(body), ) httpReq.Header.Set("X-API-Key", "sk_test_123456") httpReq.Header.Set("Content-Type", "application/json") client := &http.Client{} resp, err := client.Do(httpReq) if err != nil { return "", err } defer resp.Body.Close() var result map[string]interface{} json.NewDecoder(resp.Body).Decode(&result) return result["task_id"].(string), nil } func main() { taskID, _ := executeAgent("serp-ads", map[string]interface{}{ "keywords": "running shoes", "country": "us", }) fmt.Printf("Task ID: %s\n", taskID) } ``` *** ## Related Endpoints Unified task submission with AI orchestration Retrieve agent execution results Complete ads research agent catalog Financial research agent catalog # POST /api/v1/approvals/decision Source: https://docs.shannon.run/en/api/rest/approve-task Submit a human approval decision for workflows requiring approval ## Endpoint ``` POST http://localhost:8080/api/v1/approvals/decision ``` ## Description Submits an approval or rejection for a workflow that paused for human approval. On success, the decision is signaled to the workflow and execution proceeds (or terminates). ## Authentication **Required**: Yes Include API key in header: ``` X-API-Key: sk_test_123456 ``` ## Request ### Headers | Header | Required | Description | | -------------- | -------- | ---------------------- | | `X-API-Key` | Yes | API authentication key | | `Content-Type` | Yes | `application/json` | | `traceparent` | No | W3C trace context | ### Body Parameters | Parameter | Type | Required | Description | | ----------------- | ------- | -------- | ------------------------------ | | `workflow_id` | string | Yes | Target workflow ID | | `approval_id` | string | Yes | Approval identifier | | `approved` | boolean | Yes | Approve or reject | | `feedback` | string | No | Optional feedback/comment | | `modified_action` | string | No | Optional modified action | | `run_id` | string | No | Specific run ID (optional) | | `approved_by` | string | No | Defaults to authenticated user | ### Request Body Schema ```json theme={null} { "workflow_id": "task-123", "approval_id": "appr-456", "approved": true, "feedback": "Looks good", "modified_action": "", "run_id": "" } ``` ## Response ### 200 OK ```json theme={null} { "status": "sent", "success": true, "message": "Approval appr-456 processed successfully", "workflow_id": "task-123", "run_id": "", "approval_id": "appr-456" } ``` ### 400 / 401 / 403 / 404 ```json theme={null} { "error": "..." } ``` ## Examples ### Approve (curl) ```bash theme={null} curl -X POST "http://localhost:8080/api/v1/approvals/decision" \ -H "X-API-Key: $API_KEY" \ -H "Content-Type: application/json" \ -d '{ "workflow_id": "task-123", "approval_id": "appr-456", "approved": true, "feedback": "Proceed" }' ``` ### Reject (Python / httpx) ```python theme={null} import httpx httpx.post( "http://localhost:8080/api/v1/approvals/decision", headers={"X-API-Key": api_key, "Content-Type": "application/json"}, json={"workflow_id": wid, "approval_id": appr, "approved": False, "feedback": "Not safe"} ) ``` ## Notes * Replaces the legacy admin endpoint at `http://localhost:8081/approvals/decision` (deprecated). # Authentication & Headers Source: https://docs.shannon.run/en/api/rest/authentication Authentication methods and header reference for Shannon REST API See platform-level auth: [/en/api/authentication](/en/api/authentication) ## Authentication Shannon Gateway supports two authentication methods for all protected endpoints. ### API Key Authentication Include your API key in the `X-API-Key` header: ```bash theme={null} curl -H "X-API-Key: sk_test_123456" \ http://localhost:8080/api/v1/tasks ``` ### Bearer Token Authentication Alternatively, use the `Authorization: Bearer` header with an API key or JWT token: ```bash theme={null} curl -H "Authorization: Bearer sk_test_123456" \ http://localhost:8080/api/v1/tasks ``` Both methods are equivalent — use whichever fits your tooling. ### Authentication Errors **401 Unauthorized** - Missing or invalid credentials: ```json theme={null} // Missing auth header { "error": "Authentication required" } // Invalid API key { "error": "Invalid API key" } // Expired JWT token { "error": "Invalid or expired token" } ``` **Common causes**: * Missing `X-API-Key` or `Authorization` header * Invalid API key format * Disabled or expired API key * Expired JWT token ## Request Headers ### Required Headers #### X-API-Key **Purpose**: Authentication **Required**: Yes (unless `GATEWAY_SKIP_AUTH=1`) **Format**: String ```bash theme={null} X-API-Key: sk_test_123456 ``` #### Content-Type (POST requests) **Purpose**: Specify request body format **Required**: Yes for POST requests **Format**: `application/json` ```bash theme={null} Content-Type: application/json ``` ### Optional Headers #### Idempotency-Key **Purpose**: Prevent duplicate task submissions **Required**: No (recommended for critical operations) **Format**: UUID or unique string **Cache Duration**: 24 hours ```bash theme={null} Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000 ``` **Example**: ```bash theme={null} # First request - creates task curl -X POST http://localhost:8080/api/v1/tasks \ -H "X-API-Key: sk_test_123456" \ -H "Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000" \ -H "Content-Type: application/json" \ -d '{"query": "Process payment #12345"}' # Duplicate request - returns cached response curl -X POST http://localhost:8080/api/v1/tasks \ -H "X-API-Key: sk_test_123456" \ -H "Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000" \ -H "Content-Type: application/json" \ -d '{"query": "Process payment #12345"}' ``` #### traceparent **Purpose**: W3C distributed tracing **Required**: No (recommended for observability) **Format**: `{version}-{trace-id}-{parent-id}-{flags}` ```bash theme={null} traceparent: 00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01 ``` **Format Specification**: * **version**: `00` (current version) * **trace-id**: 32 hex characters (128 bits) * **parent-id**: 16 hex characters (64 bits) * **flags**: 2 hex characters (sampled: `01`, not sampled: `00`) **Example**: ```python theme={null} import uuid def generate_traceparent(): trace_id = uuid.uuid4().hex + uuid.uuid4().hex # 32 chars parent_id = uuid.uuid4().hex[:16] # 16 chars return f"00-{trace_id}-{parent_id}-01" traceparent = generate_traceparent() # "00-4bf92f3577b34da6a3ce929d0e0e47360011223344556677-00f067aa0ba902b7-01" ``` #### tracestate **Purpose**: Vendor-specific trace context **Required**: No **Format**: Comma-separated key-value pairs ```bash theme={null} tracestate: shannon=task_abc123,vendor=value ``` #### Cache-Control **Purpose**: Control caching behavior **Required**: No **Format**: Standard HTTP cache directives ```bash theme={null} Cache-Control: no-cache Cache-Control: max-age=300 ``` #### Last-Event-ID (SSE only) **Purpose**: Resume SSE stream from a specific event **Required**: No **Format**: Event ID string — either a Redis stream ID (e.g., `1700000000000-0`) or a numeric sequence (e.g., `42`) ```bash theme={null} Last-Event-ID: 1700000000000-0 ``` Used for SSE reconnection: ```javascript theme={null} const eventSource = new EventSource(url, { headers: { 'X-API-Key': 'sk_test_123456', 'Last-Event-ID': '1700000000000-0' // Resume from this stream ID (or use a numeric seq) } }); ``` ## Response Headers ### Standard Response Headers #### X-Workflow-ID **Purpose**: Temporal workflow identifier **Present In**: POST /api/v1/tasks, GET /api/v1/tasks/ **Format**: String (same as task\_id) ```bash theme={null} X-Workflow-ID: task_01HQZX3Y9K8M2P4N5S7T9W2V ``` **Use Case**: Track workflow execution in Temporal UI ```bash theme={null} WORKFLOW_ID=$(curl -s -X POST ... | grep -i "X-Workflow-ID" | cut -d: -f2) echo "Monitor at: http://localhost:8088/workflows/$WORKFLOW_ID" ``` #### X-Session-ID **Purpose**: Session identifier for multi-turn conversations **Present In**: POST /api/v1/tasks **Format**: UUID string ```bash theme={null} X-Session-ID: user-123-chat-session ``` #### Content-Type **Purpose**: Response body format **Present In**: All JSON responses **Format**: `application/json` ```bash theme={null} Content-Type: application/json ``` For SSE: ```bash theme={null} Content-Type: text/event-stream ``` ### Rate Limiting Headers #### X-RateLimit-Limit **Purpose**: Maximum requests allowed per window **Present In**: All authenticated requests **Format**: Integer ```bash theme={null} X-RateLimit-Limit: 100 ``` #### X-RateLimit-Remaining **Purpose**: Remaining requests in current window **Present In**: All authenticated requests **Format**: Integer ```bash theme={null} X-RateLimit-Remaining: 95 ``` #### X-RateLimit-Reset **Purpose**: Unix timestamp when rate limit resets **Present In**: All authenticated requests **Format**: Unix timestamp (seconds) ```bash theme={null} X-RateLimit-Reset: 1609459200 ``` **Example - Check Rate Limit**: ```bash theme={null} curl -v http://localhost:8080/api/v1/tasks \ -H "X-API-Key: sk_test_123456" 2>&1 | grep -i "X-RateLimit" # Output: # < X-RateLimit-Limit: 100 # < X-RateLimit-Remaining: 95 # < X-RateLimit-Reset: 1609459200 ``` #### Retry-After **Purpose**: Seconds to wait before retrying (429 responses) **Present In**: 429 Too Many Requests responses **Format**: Integer (seconds) ```bash theme={null} Retry-After: 60 ``` **Example - Handle Rate Limit**: ```python theme={null} import time import httpx response = httpx.post(...) if response.status_code == 429: retry_after = int(response.headers.get("Retry-After", 60)) print(f"Rate limited, waiting {retry_after}s...") time.sleep(retry_after) # Retry request... ``` ### CORS Headers #### Access-Control-Allow-Origin **Purpose**: Allowed origins for CORS **Present In**: All responses (development mode) **Format**: Domain or `*` ```bash theme={null} Access-Control-Allow-Origin: * ``` **Production**: Configure specific domains: ```go theme={null} // gateway/main.go w.Header().Set("Access-Control-Allow-Origin", "https://app.example.com") ``` #### Access-Control-Allow-Methods **Purpose**: Allowed HTTP methods **Present In**: CORS preflight responses **Format**: Comma-separated methods ```bash theme={null} Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS ``` #### Access-Control-Allow-Headers **Purpose**: Allowed request headers **Present In**: CORS preflight responses **Format**: Comma-separated headers ```bash theme={null} Access-Control-Allow-Headers: Content-Type, Authorization, X-API-Key, Idempotency-Key, traceparent ``` ## Header Examples ### Minimal Request (GET) ```bash theme={null} curl http://localhost:8080/api/v1/tasks \ -H "X-API-Key: sk_test_123456" ``` ### Full Request (POST) ```bash theme={null} curl -X POST http://localhost:8080/api/v1/tasks \ -H "X-API-Key: sk_test_123456" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000" \ -H "traceparent: 00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01" \ -d '{"query": "Analyze data"}' ``` ### Python - All Headers ```python theme={null} import httpx import uuid def generate_traceparent(): trace_id = uuid.uuid4().hex + uuid.uuid4().hex parent_id = uuid.uuid4().hex[:16] return f"00-{trace_id}-{parent_id}-01" response = httpx.post( "http://localhost:8080/api/v1/tasks", headers={ "X-API-Key": "sk_test_123456", "Content-Type": "application/json", "Idempotency-Key": str(uuid.uuid4()), "traceparent": generate_traceparent() }, json={"query": "Analyze data"} ) # Extract response headers workflow_id = response.headers.get("X-Workflow-ID") session_id = response.headers.get("X-Session-ID") rate_limit = response.headers.get("X-RateLimit-Remaining") print(f"Workflow: {workflow_id}") print(f"Session: {session_id}") print(f"Rate limit remaining: {rate_limit}") ``` ### JavaScript - Fetch API ```javascript theme={null} const response = await fetch('http://localhost:8080/api/v1/tasks', { method: 'POST', headers: { 'X-API-Key': 'sk_test_123456', 'Content-Type': 'application/json', 'Idempotency-Key': crypto.randomUUID() }, body: JSON.stringify({ query: 'Analyze data' }) }); // Read response headers const workflowId = response.headers.get('X-Workflow-ID'); const rateLimit = response.headers.get('X-RateLimit-Remaining'); console.log('Workflow:', workflowId); console.log('Rate limit remaining:', rateLimit); ``` ## Security Best Practices ### 1. Protect API Keys **Never commit API keys to version control**: ```bash theme={null} # ✅ Good - Use environment variables export SHANNON_API_KEY="sk_test_123456" curl -H "X-API-Key: $SHANNON_API_KEY" ... # ❌ Bad - Hardcoded in scripts curl -H "X-API-Key: sk_test_123456" ... ``` **Store in secure configuration**: ```python theme={null} import os API_KEY = os.environ.get("SHANNON_API_KEY") if not API_KEY: raise ValueError("SHANNON_API_KEY not set") ``` ### 2. Use HTTPS in Production ```python theme={null} # ✅ Production BASE_URL = "https://shannon.example.com" # ⚠️ Development only BASE_URL = "http://localhost:8080" ``` ### 3. Rotate API Keys Regularly ```sql theme={null} -- Disable old key UPDATE auth.api_keys SET enabled = false WHERE key = 'sk_test_old'; -- Create new key INSERT INTO auth.api_keys (key, user_id, tenant_id, name, enabled) VALUES ('sk_test_new', 'user-uuid', 'tenant-uuid', 'Rotated Key', true); ``` ### 4. Implement Key Expiration ```sql theme={null} -- Add expiration to keys UPDATE auth.api_keys SET expires_at = NOW() + INTERVAL '90 days'; -- Check for expired keys SELECT * FROM auth.api_keys WHERE expires_at < NOW(); ``` ### 5. Monitor API Key Usage ```python theme={null} def track_api_usage(api_key: str): """Log API key usage for monitoring.""" response = httpx.post(...) # Log usage logger.info("API request", extra={ "api_key": api_key[:10] + "...", # Partial key only "endpoint": "/api/v1/tasks", "status": response.status_code, "rate_limit_remaining": response.headers.get("X-RateLimit-Remaining") }) ``` ## Troubleshooting ### Authentication Failures **Problem**: Getting 401 Unauthorized **Solutions**: 1. Check API key is included: ```bash theme={null} curl -v http://localhost:8080/api/v1/tasks 2>&1 | grep "X-API-Key" ``` 2. Verify API key format: ```bash theme={null} # Should start with sk_ echo $API_KEY | grep -E "^sk_" ``` 3. Check if auth is disabled: ```bash theme={null} docker compose exec gateway env | grep GATEWAY_SKIP_AUTH ``` 4. Verify key in database: ```sql theme={null} SELECT key, enabled, expires_at FROM auth.api_keys WHERE key = 'sk_test_123456'; ``` ### Rate Limit Issues **Problem**: Getting 429 Too Many Requests **Solutions**: 1. Check rate limit headers: ```bash theme={null} curl -v ... 2>&1 | grep "X-RateLimit" ``` 2. Implement exponential backoff: ```python theme={null} if response.status_code == 429: retry_after = int(response.headers.get("Retry-After", 60)) time.sleep(retry_after) ``` 3. Increase rate limits (if needed): ```bash theme={null} # In .env RATE_LIMIT_RPM=200 RATE_LIMIT_BURST=50 ``` ### Idempotency Issues **Problem**: Duplicate tasks created **Solutions**: 1. Always include Idempotency-Key: ```python theme={null} idempotency_key = str(uuid.uuid4()) headers["Idempotency-Key"] = idempotency_key ``` 2. Store idempotency keys: ```python theme={null} # Store key with request db.requests.insert({ "idempotency_key": idempotency_key, "task_id": task_id, "created_at": datetime.now() }) ``` 3. Check Redis cache: ```bash theme={null} docker compose exec redis redis-cli KEYS "idempotency:*" ``` ## Related Documentation Complete API reference POST /api/v1/tasks Rate limit details Tracing setup # POST /api/v1/tasks/{id}/cancel Source: https://docs.shannon.run/en/api/rest/cancel-task Cancel a queued or running task (idempotent) ## Endpoint ``` POST http://localhost:8080/api/v1/tasks/{id}/cancel ``` ## Description Cancels a queued or running task. Returns `202 Accepted` when the cancellation is accepted for processing. If the task has already reached a terminal state, returns `409 Conflict`. ## Authentication **Required**: Yes Include API key in header: ``` X-API-Key: sk_test_123456 ``` ## Request ### Headers | Header | Required | Description | | -------------- | -------- | ----------------------------------- | | `X-API-Key` | Yes | API authentication key | | `Content-Type` | No | `application/json` if body provided | | `traceparent` | No | W3C trace context | ### Path Parameters | Parameter | Type | Required | Description | | --------- | ------ | -------- | ------------------------------ | | `id` | string | Yes | Task ID (Temporal workflow ID) | ### Body Parameters | Parameter | Type | Required | Description | | --------- | ------ | -------- | ------------------------------ | | `reason` | string | No | Optional human-readable reason | ### Request Body Schema ```json theme={null} { "reason": "Stopped by user" } ``` ## Response ### 202 Accepted ```json theme={null} { "success": true, "message": "Task cancelled successfully" } ``` ### 409 Conflict ```json theme={null} { "success": false, "message": "Task is already in terminal state: COMPLETED", "status": "COMPLETED" } ``` ### 404 Not Found ```json theme={null} { "error": "Task not found" } ``` ### 401 / 403 ```json theme={null} { "error": "Unauthorized" } ``` or ```json theme={null} { "error": "Forbidden" } ``` ## Examples ### curl ```bash theme={null} curl -X POST "http://localhost:8080/api/v1/tasks/${TASK_ID}/cancel" \ -H "X-API-Key: $API_KEY" \ -H "Content-Type: application/json" \ -d '{"reason":"user request"}' ``` ### Python (httpx) ```python theme={null} import httpx resp = httpx.post( f"http://localhost:8080/api/v1/tasks/{task_id}/cancel", headers={"X-API-Key": api_key}, json={"reason": "cleanup"} ) print(resp.status_code, resp.json()) ``` ### JavaScript (axios) ```javascript theme={null} await axios.post( `http://localhost:8080/api/v1/tasks/${taskId}/cancel`, { reason: "not needed" }, { headers: { "X-API-Key": apiKey } } ); ``` ## Notes * Idempotent: repeated calls return `202` or `409` depending on current state. * Cancellation is propagated to child workflows (graceful via `REQUEST_CANCEL`). # Channels API Source: https://docs.shannon.run/en/api/rest/channels Connect Shannon to external messaging platforms like Slack and LINE ## Overview The Channels API lets you integrate Shannon with external messaging platforms. Create channels to connect Slack workspaces or LINE accounts, and Shannon will automatically process incoming messages through its agent system. ## Features * **Slack integration** with event subscription and bot messaging * **LINE integration** with webhook-based messaging * **HMAC signature verification** for secure inbound webhooks * **Agent routing** via `agent_name` config to direct messages to specific agents * **Per-user isolation** with unique constraint on type + name per user * **Credential security** — credentials are never exposed in API responses ## Create Channel Create a new messaging channel. ```bash curl theme={null} curl -X POST http://localhost:8080/api/v1/channels \ -H "Content-Type: application/json" \ -H "Authorization: Bearer " \ -d '{ "type": "slack", "name": "My Slack Channel", "credentials": { "signing_secret": "your-slack-signing-secret", "bot_token": "xoxb-your-bot-token", "app_id": "A0123456789" }, "config": { "agent_name": "research-agent" } }' ``` ```python Python SDK theme={null} from shannon import ShannonClient client = ShannonClient() channel = client.create_channel( type="slack", name="My Slack Channel", credentials={ "signing_secret": "your-slack-signing-secret", "bot_token": "xoxb-your-bot-token", "app_id": "A0123456789", }, config={"agent_name": "research-agent"}, ) print(f"Channel created: {channel.id}") ``` ### Request Body | Field | Type | Required | Description | | ------------- | ------ | -------- | ------------------------------------------- | | `type` | string | Yes | Channel type: `"slack"` or `"line"` | | `name` | string | Yes | Channel display name | | `credentials` | object | Yes | Platform-specific credentials (see below) | | `config` | object | No | Optional configuration (e.g., `agent_name`) | ### Response ```json theme={null} { "id": "550e8400-e29b-41d4-a716-446655440000", "user_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7", "type": "slack", "name": "My Slack Channel", "config": { "agent_name": "research-agent" }, "enabled": true, "created_at": "2026-03-10T10:00:00Z", "updated_at": "2026-03-10T10:00:00Z" } ``` The `credentials` field is **never** returned in API responses for security. Store your credentials securely on your side. ## List Channels List all channels for the authenticated user. ```bash curl theme={null} curl http://localhost:8080/api/v1/channels \ -H "Authorization: Bearer " ``` ```python Python SDK theme={null} from shannon import ShannonClient client = ShannonClient() channels = client.list_channels() for ch in channels: print(f"{ch.name} ({ch.type}) — enabled: {ch.enabled}") ``` ### Response ```json theme={null} [ { "id": "550e8400-e29b-41d4-a716-446655440000", "user_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7", "type": "slack", "name": "My Slack Channel", "config": { "agent_name": "research-agent" }, "enabled": true, "created_at": "2026-03-10T10:00:00Z", "updated_at": "2026-03-10T10:00:00Z" } ] ``` ## Get Channel Get details of a specific channel. ```bash curl theme={null} curl http://localhost:8080/api/v1/channels/{id} \ -H "Authorization: Bearer " ``` ```python Python SDK theme={null} from shannon import ShannonClient client = ShannonClient() channel = client.get_channel("550e8400-e29b-41d4-a716-446655440000") print(f"{channel.name} — type: {channel.type}") ``` ### Response ```json theme={null} { "id": "550e8400-e29b-41d4-a716-446655440000", "user_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7", "type": "slack", "name": "My Slack Channel", "config": { "agent_name": "research-agent" }, "enabled": true, "created_at": "2026-03-10T10:00:00Z", "updated_at": "2026-03-10T10:00:00Z" } ``` ## Update Channel Update an existing channel. All fields are optional — only provided fields will be updated. ```bash curl theme={null} curl -X PUT http://localhost:8080/api/v1/channels/{id} \ -H "Content-Type: application/json" \ -H "Authorization: Bearer " \ -d '{ "name": "Renamed Slack Channel", "config": { "agent_name": "financial-agent" }, "enabled": false }' ``` ```python Python SDK theme={null} from shannon import ShannonClient client = ShannonClient() channel = client.update_channel( "550e8400-e29b-41d4-a716-446655440000", name="Renamed Slack Channel", config={"agent_name": "financial-agent"}, enabled=False, ) print(f"Updated: {channel.name}, enabled: {channel.enabled}") ``` ### Request Body | Field | Type | Required | Description | | ------------- | ------- | -------- | ----------------------------- | | `name` | string | No | New channel name | | `credentials` | object | No | New platform credentials | | `config` | object | No | New configuration | | `enabled` | boolean | No | Enable or disable the channel | ### Response Returns the updated channel object (same schema as [Get Channel](#get-channel)). ## Delete Channel Delete a channel permanently. ```bash curl theme={null} curl -X DELETE http://localhost:8080/api/v1/channels/{id} \ -H "Authorization: Bearer " ``` ```python Python SDK theme={null} from shannon import ShannonClient client = ShannonClient() client.delete_channel("550e8400-e29b-41d4-a716-446655440000") print("Channel deleted") ``` ### Response ```json Response theme={null} { "message": "Channel deleted" } ``` (HTTP 200 OK with JSON body) ## Inbound Webhook Receive messages from external platforms. This endpoint is called by Slack or LINE when a user sends a message. ```bash theme={null} POST /api/v1/channels/{channel_id}/webhook ``` This endpoint requires **no authentication**. Instead, each platform uses its own HMAC signature verification to ensure requests are genuine. ### Response ```json theme={null} { "status": "dispatched" } ``` The webhook validates the inbound request, extracts the message, and dispatches it to Shannon's agent system asynchronously. The response is returned immediately. ## Credential Formats ### Slack Credentials ```json theme={null} { "signing_secret": "your-slack-signing-secret", "bot_token": "xoxb-your-bot-token", "app_id": "A0123456789" } ``` | Field | Description | | ---------------- | ---------------------------------------------------- | | `signing_secret` | Found in your Slack app's **Basic Information** page | | `bot_token` | Bot user OAuth token (starts with `xoxb-`) | | `app_id` | Your Slack app ID | ### LINE Credentials ```json theme={null} { "channel_secret": "your-line-channel-secret", "channel_access_token": "your-line-channel-access-token" } ``` | Field | Description | | ---------------------- | ----------------------------------------------------------- | | `channel_secret` | Found in your LINE Developer Console channel settings | | `channel_access_token` | Long-lived channel access token from LINE Developer Console | ## Webhook Behavior ### Slack * **Signature verification**: Validates `X-Slack-Request-Timestamp` and `X-Slack-Signature` headers using HMAC-SHA256 with the channel's `signing_secret` * **Clock skew**: Requests older than 5 minutes are rejected * **URL verification**: Automatically responds to Slack's `url_verification` challenge during app setup * **Bot filtering**: Messages from bots are ignored to prevent loops * **Supported events**: `message` and `app_mention` ### LINE * **Signature verification**: Validates `X-Line-Signature` header using HMAC-SHA256 (base64-encoded) with the channel's `channel_secret` * **Reply behavior**: Sends a "Thinking..." reply immediately using the reply token * **Message handling**: Processes the first text message from the event payload ### Agent Routing Set the `agent_name` field in the channel's `config` to route incoming messages to a specific agent: ```json theme={null} { "config": { "agent_name": "research-agent" } } ``` If `agent_name` is not set, messages are handled by Shannon's default agent routing. ## Channel Response Object | Field | Type | Description | | ------------ | ----------------- | ----------------------------------- | | `id` | string (UUID) | Unique channel identifier | | `user_id` | string (UUID) | Owner user ID (optional) | | `type` | string | Channel type: `"slack"` or `"line"` | | `name` | string | Channel display name | | `config` | object | Channel configuration | | `enabled` | boolean | Whether the channel is active | | `created_at` | string (ISO 8601) | Creation timestamp | | `updated_at` | string (ISO 8601) | Last update timestamp | ## Error Responses | Status | Error | Description | | ------ | --------------------- | ----------------------------------------------- | | 400 | Invalid request | Missing required fields or invalid channel type | | 401 | Unauthorized | Missing or invalid authentication | | 403 | Forbidden | Cannot access another user's channel | | 404 | Channel not found | Invalid channel ID | | 409 | Conflict | Duplicate channel (same user + type + name) | | 500 | Internal server error | Server-side failure | All errors follow the standard format: ```json theme={null} { "error": "error message here" } ``` ## Next Steps Submit tasks directly via API Configure agents for channel routing # Daemon WebSocket API Source: https://docs.shannon.run/en/api/rest/daemon-websocket Real-time bidirectional messaging between daemon clients and Shannon via WebSocket ## Overview The Daemon WebSocket API provides a persistent, bidirectional connection for daemon clients to receive and process messages in real time. Unlike the REST API where clients poll for updates, the WebSocket connection enables Shannon to push incoming messages (from Slack, LINE, or system events) directly to connected daemons. The core protocol revolves around a **claim-based message dispatch** model: Shannon broadcasts messages to eligible connections, and daemons race to claim exclusive processing rights before replying. ## Endpoint ``` GET /v1/ws/messages ``` Upgrade to WebSocket with standard auth headers. ## Authentication Authentication is performed **before** the WebSocket upgrade using the same middleware as REST endpoints. | Method | Header | | ---------- | ------------------------------- | | JWT Bearer | `Authorization: Bearer ` | | API Key | `X-API-Key: ` | ```bash websocat theme={null} websocat "ws://localhost:8080/v1/ws/messages" \ -H "Authorization: Bearer " ``` ```javascript JavaScript theme={null} const ws = new WebSocket("ws://localhost:8080/v1/ws/messages", { headers: { "Authorization": "Bearer " } }); ``` ```python Python theme={null} import websockets async with websockets.connect( "ws://localhost:8080/v1/ws/messages", additional_headers={"Authorization": "Bearer "} ) as ws: async for message in ws: print(message) ``` ## Connection Lifecycle Client sends `GET /v1/ws/messages` with authentication headers. The server validates credentials before upgrading. Server upgrades to WebSocket (gorilla/websocket, 4KB read/write buffers, CheckOrigin allows all origins). Server sends a `connected` message to confirm the connection is ready. ```json theme={null} {"type": "connected"} ``` Both sides exchange JSON messages. The server dispatches incoming messages; the client claims, processes, and replies. Server sends WebSocket pings every **20 seconds**. Client must respond with a pong within **60 seconds** or the connection is closed. ### Connection Parameters | Parameter | Value | | ------------------ | ----- | | Ping interval | 20s | | Pong timeout | 60s | | Max message size | 64 KB | | Write timeout | 10s | | Read/write buffers | 4 KB | ## Message Envelope All messages (both directions) follow a consistent envelope format: ```json theme={null} { "type": "", "message_id": "", "payload": {} } ``` | Field | Type | Description | | ------------ | ------------- | -------------------------------------------------------------------- | | `type` | string | Message type identifier | | `message_id` | string (UUID) | Unique message identifier (omitted for `connected` and `disconnect`) | | `payload` | object | Type-specific data | ## Server-to-Client Messages ### `connected` Sent once immediately after the WebSocket connection is established. ```json theme={null} { "type": "connected" } ``` ### `message` An inbound message dispatched for processing. This is the primary message type — it carries messages from channel webhooks (Slack, LINE) or system events. ```json theme={null} { "type": "message", "message_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "payload": { "channel": "slack", "thread_id": "C07ABCDEF-1234567890.123456", "sender": "user@example.com", "text": "Hello, can you help me?", "agent_name": "research-agent", "timestamp": "2026-03-10T10:00:00Z" } } ``` #### MessagePayload Fields | Field | Type | Description | | ------------ | ----------------- | --------------------------------------------------- | | `channel` | string | Originating channel type: `"slack"`, `"line"`, etc. | | `thread_id` | string | Thread identifier for the conversation | | `sender` | string | Sender identifier (email, user ID, etc.) | | `text` | string | The message content | | `agent_name` | string | Target agent for processing | | `timestamp` | string (ISO 8601) | When the message was received | ### `system` System-level notification from Shannon. ```json theme={null} { "type": "system", "message_id": "f7e8d9c0-b1a2-3456-7890-abcdef123456", "payload": { "text": "Agent research-agent is now available" } } ``` ### `claim_ack` Response to a client's `claim` request, indicating whether the claim was granted. ```json theme={null} { "type": "claim_ack", "message_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "payload": { "granted": true } } ``` | Field | Type | Description | | --------- | ------- | ------------------------------------------------------------- | | `granted` | boolean | `true` if this client was granted exclusive processing rights | ## Client-to-Server Messages ### `claim` Claim exclusive processing rights for a message. Only one client can successfully claim a given message. ```json theme={null} { "type": "claim", "message_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890" } ``` ### `progress` Send a heartbeat/progress update while processing a claimed message. This extends the claim lease, preventing timeout. ```json theme={null} { "type": "progress", "message_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "payload": { "status": "processing", "percent": 50 } } ``` ### `reply` Send the completed response for a claimed message. Shannon routes this back to the originating channel (Slack, LINE, etc.). ```json theme={null} { "type": "reply", "message_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "payload": { "channel": "slack", "thread_id": "C07ABCDEF-1234567890.123456", "text": "Here is my response...", "format": "text" } } ``` #### ReplyPayload Fields | Field | Type | Description | | ----------- | ------ | --------------------------------------- | | `channel` | string | Target channel type | | `thread_id` | string | Thread to reply in | | `text` | string | Response content | | `format` | string | Output format: `"text"` or `"markdown"` | ### `disconnect` Gracefully close the connection. ```json theme={null} { "type": "disconnect" } ``` ## Claim Flow The claim flow is the core protocol for distributed message processing. It ensures exactly one daemon processes each message, even when multiple daemons are connected. ```mermaid theme={null} sequenceDiagram participant Slack as Slack / LINE participant GW as Shannon Gateway participant D1 as Daemon A participant D2 as Daemon B participant Redis Slack->>GW: Inbound webhook message GW->>D1: {"type":"message","message_id":"abc",...} GW->>D2: {"type":"message","message_id":"abc",...} D1->>GW: {"type":"claim","message_id":"abc"} D2->>GW: {"type":"claim","message_id":"abc"} GW->>Redis: Atomic claim (SETNX) Redis-->>GW: D1 wins GW->>D1: {"type":"claim_ack","payload":{"granted":true}} GW->>D2: {"type":"claim_ack","payload":{"granted":false}} D1->>GW: {"type":"progress","message_id":"abc",...} D1->>GW: {"type":"reply","message_id":"abc",...} GW->>Slack: Route reply to originating channel ``` When a message arrives (via channel webhook or system), the Gateway dispatches it to **all** eligible WebSocket connections indexed by `tenant:user`. Each daemon that wants to process the message sends a `claim` request with the `message_id`. The Gateway atomically claims the message in Redis (`SETNX`). The first client wins; all others receive `{"granted": false}`. The winning daemon processes the message. It can optionally send `progress` messages to extend the claim lease and signal activity. The daemon sends a `reply` with the completed response. Shannon routes it back to the originating channel. ### Claim Metadata When a message is claimed, the Gateway stores metadata in Redis with a **60-second TTL**: | Field | Description | | ----------------- | --------------------------------------------------- | | `conn_id` | WebSocket connection identifier | | `channel_id` | Originating channel ID | | `channel_type` | Channel type (`slack`, `line`, etc.) | | `thread_id` | Conversation thread ID | | `reply_token` | Platform-specific reply token (if applicable) | | `timestamp` | Claim timestamp | | `workflow_id` | Associated Temporal workflow ID (if applicable) | | `workflow_run_id` | Associated Temporal workflow run ID (if applicable) | Pending message metadata has a **90-second TTL**. If a claimed message is not replied to within 60 seconds, the claim expires and the message can be re-dispatched. ## Hub Architecture The WebSocket Hub manages all active connections with these routing strategies: * **Tenant-user indexing** — Connections are indexed by `"tenant:user"` key for targeted dispatch * **Sticky thread routing** — Messages from the same thread (`"channel_type:thread_id"`) are routed to the same connection when possible * **Redis-backed claims** — Distributed claim resolution ensures consistency across multiple Gateway instances ## Reply Routing When the Gateway receives a `reply` from a daemon, it routes the response based on claim metadata: 1. **Workflow reply** — If `workflow_id` exists in claim metadata, the Gateway signals the associated Temporal workflow with the reply content 2. **Channel reply** — Otherwise, the reply is routed back to the originating channel (Slack post, LINE push message, etc.) ## Error Handling | Scenario | Behavior | | ----------------------- | -------------------------------------------- | | Auth failure on upgrade | HTTP 401 returned, WebSocket not established | | Message exceeds 64 KB | Connection closed | | Pong timeout (60s) | Connection closed by server | | Write timeout (10s) | Message dropped, connection may close | | Claim expired (60s TTL) | Message eligible for re-dispatch | | Invalid JSON | Message ignored | ## Next Steps Manage channel integrations for Slack and LINE Server-Sent Events for task streaming # GET /api/v1/tasks/{id}/control-state Source: https://docs.shannon.run/en/api/rest/get-control-state Query the pause/cancel control state for a task ## Endpoint ```http theme={null} GET http://localhost:8080/api/v1/tasks/{id}/control-state ``` ## Description Returns the **current control state** for a workflow: whether it is paused, cancelled, and metadata about who triggered the action and when. This is useful for: * Building UIs that show **Pause / Resume** button states * Polling from backend services to coordinate other systems ## Authentication **Required**: Yes Include API key in header: ```http theme={null} X-API-Key: sk_test_123456 ``` ## Request ### Headers | Header | Required | Description | | ------------- | -------- | ---------------------- | | `X-API-Key` | Yes | API authentication key | | `traceparent` | No | W3C trace context | ### Path Parameters | Parameter | Type | Required | Description | | --------- | ------ | -------- | ------------------------------ | | `id` | string | Yes | Task ID (Temporal workflow ID) | ## Response ### 200 OK ```json theme={null} { "is_paused": true, "is_cancelled": false, "paused_at": "2025-10-29T06:00:01Z", "pause_reason": "User requested pause", "paused_by": "user-123", "cancel_reason": "", "cancelled_by": "" } ``` Notes: * `paused_at` is an empty string when the workflow has never been paused. * `is_cancelled` reflects logical cancel state from control signals, not just Temporal termination. ### 404 Not Found ```json theme={null} { "error": "Task not found" } ``` ### 401 / 403 ```json theme={null} { "error": "Unauthorized" } ``` or ```json theme={null} { "error": "Forbidden" } ``` # GET /api/v1/tasks/{id} Source: https://docs.shannon.run/en/api/rest/get-status Get task status and result ## Endpoint ``` GET http://localhost:8080/api/v1/tasks/{id} ``` ## Description Retrieves the current status, result, and metadata for a specific task. Use this endpoint to check task progress or retrieve final results. ## Authentication **Required**: Yes Include API key in header: ``` X-API-Key: sk_test_123456 ``` ## Request ### Path Parameters | Parameter | Type | Required | Description | | --------- | ------ | -------- | ------------------------------------ | | `id` | string | Yes | Task ID (also serves as workflow ID) | ### Headers | Header | Required | Description | | ----------- | -------- | ------------------ | | `X-API-Key` | Yes | Authentication key | ## Response ### Success Response **Status**: `200 OK` **Headers**: * `X-Workflow-ID`: Temporal workflow identifier (same as task ID) **Body**: ```json theme={null} { "task_id": "string", "workflow_id": "string", "status": "string", "result": "string", "response": {}, "metadata": {}, "error": "string", "created_at": "timestamp", "updated_at": "timestamp", "query": "string", "session_id": "string", "context": {}, "unified_response": {}, "mode": "string", "model_used": "string", "provider": "string", "usage": { "total_tokens": 0, "input_tokens": 0, "output_tokens": 0, "estimated_cost": 0.0 } } ``` ### Response Fields | Field | Type | Description | | ------------------ | --------- | ---------------------------------------------------------------------------------------------------- | | `task_id` | string | Unique task identifier | | `workflow_id` | string | Workflow identifier (same as task\_id) | | `status` | string | Current task status | | `result` | string | Raw LLM output (plain text or JSON string) | | `response` | object | Parsed JSON (only present if `result` contains valid JSON) | | `metadata` | object | Task metadata (citations, verification, unified\_response, extracted\_facts, model\_breakdown, etc.) | | `error` | string | Error message (empty if no error) | | `created_at` | timestamp | Task creation time | | `updated_at` | timestamp | Last update time | | `query` | string | Original task query | | `session_id` | string | Session identifier | | `context` | object | Task context (budget info, date, parameters) | | `unified_response` | object | Structured unified response with parsed result, performance metrics, and metadata | | `mode` | string | Execution mode used | | `model_used` | string | Primary model used (e.g., `gpt-5-mini-2025-08-07`) | | `provider` | string | Provider name (e.g., `openai`, `anthropic`) | | `usage` | object | Token usage and cost: `{ total_tokens, input_tokens?, output_tokens?, estimated_cost? }` | ### Status Values * `TASK_STATUS_UNSPECIFIED` - Status unknown * `TASK_STATUS_QUEUED` - Waiting to execute * `TASK_STATUS_RUNNING` - Currently executing * `TASK_STATUS_COMPLETED` - Successfully completed * `TASK_STATUS_FAILED` - Failed with error * `TASK_STATUS_PAUSED` - Paused by user or HITL review * `TASK_STATUS_CANCELLED` - Cancelled by user * `TASK_STATUS_TIMEOUT` - Exceeded timeout limit ### Execution Mode Values * `EXECUTION_MODE_SIMPLE` - Single LLM call, no tools * `EXECUTION_MODE_STANDARD` - Multi-step with tools * `EXECUTION_MODE_COMPLEX` - Advanced reasoning patterns ## Examples ### Check Task Status ```bash theme={null} curl -X GET "http://localhost:8080/api/v1/tasks/task_01HQZX3Y9K8M2P4N5S7T9W2V" \ -H "X-API-Key: sk_test_123456" ``` **Response (Queued)**: ```json theme={null} { "task_id": "task_01HQZX3Y9K8M2P4N5S7T9W2V", "status": "TASK_STATUS_QUEUED", "response": null, "error": "", "created_at": "2025-10-22T10:30:00Z", "updated_at": "2025-10-22T10:30:00Z", "query": "What is the capital of France?", "session_id": "user-123-session", "mode": "EXECUTION_MODE_SIMPLE" } ``` **Response (Running)**: ```json theme={null} { "task_id": "task_01HQZX3Y9K8M2P4N5S7T9W2V", "status": "TASK_STATUS_RUNNING", "response": null, "error": "", "created_at": "2025-10-22T10:30:00Z", "updated_at": "2025-10-22T10:30:02Z", "query": "What is the capital of France?", "session_id": "user-123-session", "mode": "EXECUTION_MODE_SIMPLE" } ``` **Response (Completed)**: ```json theme={null} { "task_id": "task_01HQZX3Y9K8M2P4N5S7T9W2V", "status": "TASK_STATUS_COMPLETED", "result": "The capital of France is Paris. Paris has been the capital since 987 AD and is located in the north-central part of the country.", "error": "", "created_at": "2025-10-22T10:30:00Z", "updated_at": "2025-10-22T10:30:05Z", "query": "What is the capital of France?", "session_id": "user-123-session", "mode": "EXECUTION_MODE_SIMPLE", "model_used": "gpt-5-mini-2025-08-07", "provider": "openai", "usage": { "total_tokens": 300, "input_tokens": 200, "output_tokens": 100, "estimated_cost": 0.006 } } ``` **Response (Failed)**: ```json theme={null} { "task_id": "task_01HQZX3Y9K8M2P4N5S7T9W2V", "status": "TASK_STATUS_FAILED", "response": null, "error": "LLM service unavailable: connection timeout", "created_at": "2025-10-22T10:30:00Z", "updated_at": "2025-10-22T10:30:10Z", "query": "What is the capital of France?", "session_id": "user-123-session", "mode": "EXECUTION_MODE_SIMPLE" } ``` ## Deep Research Response Payload When a task is submitted with `force_research: true`, the completed response includes additional metadata fields with structured research data. ### Deep Research Metadata Fields For Deep Research tasks, the `metadata` object contains: | Field | Type | Description | | ------------------ | ------ | ----------------------------------------------------------- | | `unified_response` | object | Consolidated response with all metadata | | `citations` | array | Structured citation data from research | | `verification` | object | Claim verification results (if `enable_verification: true`) | | `extracted_facts` | array | Structured facts (if `enable_fact_extraction: true`) | | `fact_summary` | object | Summary statistics for extracted facts | ### Example: Deep Research Completed Response ```json theme={null} { "task_id": "task-abc123-research", "status": "TASK_STATUS_COMPLETED", "result": "# AI Trends in 2025\n\nThe artificial intelligence landscape in 2025 is characterized by...[1][2]\n\n## Sources\n[1] MIT Technology Review (https://...) - 2025-01-15\n[2] Nature AI (https://...) - 2025-01-10", "metadata": { "unified_response": { "task_id": "task-abc123-research", "session_id": "session-xyz", "status": "completed", "result": "The artificial intelligence landscape in 2025...", "metadata": { "model": "claude-sonnet-4-5-20250929", "execution_mode": "EXECUTION_MODE_COMPLEX", "complexity_score": 0.72, "agents_used": 3 }, "usage": { "input_tokens": 12100, "output_tokens": 3320, "total_tokens": 15420, "cost_usd": 0.0462 }, "performance": { "execution_time_ms": 45200 }, "stop_reason": "completed", "error": null, "timestamp": "2025-01-20T10:00:00Z" }, "citations": [ { "title": "AI Breakthrough Report 2025", "url": "https://www.technologyreview.com/2025/01/ai-report", "source": "MIT Technology Review", "credibility_score": 0.92, "quality_score": 0.88 }, { "title": "Nature AI Special Issue", "url": "https://www.nature.com/articles/ai-2025", "source": "Nature", "credibility_score": 0.95, "quality_score": 0.91 } ], "verification": { "overall_confidence": 0.87, "total_claims": 12, "supported_claims": 10, "unsupported_claims": ["Claim about future predictions"], "conflicts": [], "claim_details": [ { "claim": "AI models have achieved 95% accuracy on benchmark X", "supporting_citations": [1, 2], "conflicting_citations": [], "confidence": 0.92 } ] } }, "model_used": "claude-sonnet-4-5-20250929", "provider": "anthropic", "usage": { "total_tokens": 15420, "input_tokens": 12100, "output_tokens": 3320, "estimated_cost": 0.0462 } } ``` ### Extracted Facts (Optional) When `enable_fact_extraction: true` is set in the request context: ```json theme={null} { "metadata": { "extracted_facts": [ { "statement": "GPT-5 achieved 95% accuracy on MMLU benchmark", "confidence": 0.92, "source_citation": [1, 3], "category": "performance", "entity_mentions": ["GPT-5", "MMLU"], "temporal_marker": "2025", "is_quantitative": true } ], "fact_summary": { "total_facts": 24, "high_confidence": 18, "categorized_facts": { "performance": 8, "market": 6, "research": 10 }, "contradiction_count": 1 } } } ``` ### Citation Object Schema | Field | Type | Description | | ------------------- | ------ | ---------------------------- | | `url` | string | Source URL | | `title` | string | Article/page title | | `source` | string | Publisher/domain name | | `credibility_score` | float | Source credibility (0.0-1.0) | | `quality_score` | float | Content quality (0.0-1.0) | The position of each citation in the `metadata.citations` array corresponds to the `[n]` index used in inline references. ### Verification Object Schema | Field | Type | Description | | -------------------- | ------- | -------------------------------------------- | | `overall_confidence` | float | Aggregate verification confidence (0.0-1.0) | | `total_claims` | integer | Number of factual claims extracted | | `supported_claims` | integer | Claims with supporting citations | | `unsupported_claims` | array | List of claim texts without citation support | | `conflicts` | array | Detected conflicting information | | `claim_details` | array | Per-claim verification details | **Accessing Deep Research Data**: The `metadata.citations` array and `metadata.verification` object are only populated for research workflows (`force_research: true`). For simple tasks, these fields will be absent from the response. ## Error Responses ### 401 Unauthorized ```json theme={null} { "error": "Unauthorized" } ``` ### 404 Not Found ```json theme={null} { "error": "Task not found" } ``` ### 500 Internal Server Error ```json theme={null} { "error": "Failed to get task status: database error" } ``` ## Code Examples ### Python - Simple Status Check ```python theme={null} import httpx def get_task_status(task_id: str, api_key: str): """Get task status.""" response = httpx.get( f"http://localhost:8080/api/v1/tasks/{task_id}", headers={"X-API-Key": api_key} ) if response.status_code == 404: return None return response.json() status = get_task_status("task_abc123", "sk_test_123456") if status: print(f"Status: {status['status']}") if status['status'] == 'TASK_STATUS_COMPLETED': print(f"Result: {status['result']}") ``` ### Python - Poll Until Completion ```python theme={null} import httpx import time def wait_for_completion(task_id: str, api_key: str, timeout: int = 300): """Poll task status until completion or timeout.""" start_time = time.time() while True: response = httpx.get( f"http://localhost:8080/api/v1/tasks/{task_id}", headers={"X-API-Key": api_key} ) status = response.json() current_status = status["status"] # Check if terminal state if current_status == "TASK_STATUS_COMPLETED": return status.get("result") elif current_status == "TASK_STATUS_FAILED": raise Exception(f"Task failed: {status['error']}") elif current_status == "TASK_STATUS_TIMEOUT": raise Exception("Task timed out") elif current_status == "TASK_STATUS_CANCELLED": raise Exception("Task was cancelled") # Check timeout if time.time() - start_time > timeout: raise TimeoutError(f"Polling timeout after {timeout}s") # Wait before next poll time.sleep(2) # Usage try: result = wait_for_completion("task_abc123", "sk_test_123456") print("Result:", result) except Exception as e: print("Error:", e) ``` ### JavaScript/Node.js ```javascript theme={null} const axios = require('axios'); async function getTaskStatus(taskId) { try { const response = await axios.get( `http://localhost:8080/api/v1/tasks/${taskId}`, { headers: { 'X-API-Key': 'sk_test_123456' } } ); const status = response.data; console.log('Status:', status.status); if (status.status === 'TASK_STATUS_COMPLETED') { console.log('Result:', status.result); } else if (status.status === 'TASK_STATUS_FAILED') { console.error('Error:', status.error); } return status; } catch (error) { if (error.response?.status === 404) { console.error('Task not found'); } else { console.error('Error:', error.response?.data || error.message); } throw error; } } getTaskStatus('task_abc123'); ``` ### JavaScript - Poll with Async/Await ```javascript theme={null} async function waitForCompletion(taskId, timeout = 300000) { const startTime = Date.now(); const pollInterval = 2000; // 2 seconds while (true) { const response = await axios.get( `http://localhost:8080/api/v1/tasks/${taskId}`, { headers: { 'X-API-Key': 'sk_test_123456' } } ); const { status, result, error } = response.data; if (status === 'TASK_STATUS_COMPLETED') { return result; } else if (status === 'TASK_STATUS_FAILED') { throw new Error(`Task failed: ${error}`); } else if (status === 'TASK_STATUS_TIMEOUT') { throw new Error('Task timed out'); } // Check timeout if (Date.now() - startTime > timeout) { throw new Error(`Polling timeout after ${timeout}ms`); } // Wait before next poll await new Promise(resolve => setTimeout(resolve, pollInterval)); } } // Usage waitForCompletion('task_abc123') .then(result => console.log('Result:', result)) .catch(error => console.error('Error:', error.message)); ``` ### Go ```go theme={null} package main import ( "encoding/json" "fmt" "net/http" "time" ) type TaskStatusResponse struct { TaskID string `json:"task_id"` WorkflowID string `json:"workflow_id"` Status string `json:"status"` Result string `json:"result"` Response map[string]interface{} `json:"response"` Error string `json:"error"` Query string `json:"query"` SessionID string `json:"session_id"` Mode string `json:"mode"` Context map[string]interface{} `json:"context"` ModelUsed string `json:"model_used"` Provider string `json:"provider"` CreatedAt string `json:"created_at"` UpdatedAt string `json:"updated_at"` Usage map[string]interface{} `json:"usage"` Metadata map[string]interface{} `json:"metadata"` UnifiedResponse map[string]interface{} `json:"unified_response"` } func getTaskStatus(taskID string) (*TaskStatusResponse, error) { url := fmt.Sprintf("http://localhost:8080/api/v1/tasks/%s", taskID) req, _ := http.NewRequest("GET", url, nil) req.Header.Set("X-API-Key", "sk_test_123456") client := &http.Client{} resp, err := client.Do(req) if err != nil { return nil, err } defer resp.Body.Close() if resp.StatusCode == 404 { return nil, fmt.Errorf("task not found") } var status TaskStatusResponse json.NewDecoder(resp.Body).Decode(&status) return &status, nil } func waitForCompletion(taskID string, timeout time.Duration) (map[string]interface{}, error) { start := time.Now() for { status, err := getTaskStatus(taskID) if err != nil { return nil, err } switch status.Status { case "TASK_STATUS_COMPLETED": return status.Response, nil case "TASK_STATUS_FAILED": return nil, fmt.Errorf("task failed: %s", status.Error) case "TASK_STATUS_TIMEOUT": return nil, fmt.Errorf("task timed out") } if time.Since(start) > timeout { return nil, fmt.Errorf("polling timeout") } time.Sleep(2 * time.Second) } } func main() { result, err := waitForCompletion("task_abc123", 5*time.Minute) if err != nil { fmt.Println("Error:", err) return } fmt.Println("Result:", result) } ``` ### Bash - Monitor Task Progress ```bash theme={null} #!/bin/bash API_KEY="sk_test_123456" TASK_ID="$1" if [ -z "$TASK_ID" ]; then echo "Usage: $0 " exit 1 fi echo "Monitoring task: $TASK_ID" echo "" while true; do RESPONSE=$(curl -s "http://localhost:8080/api/v1/tasks/$TASK_ID" \ -H "X-API-Key: $API_KEY") STATUS=$(echo $RESPONSE | jq -r '.status') echo "[$(date +%T)] Status: $STATUS" case $STATUS in "TASK_STATUS_COMPLETED") echo "" echo "✓ Task completed!" echo "" echo $RESPONSE | jq -r '.result' exit 0 ;; "TASK_STATUS_FAILED") echo "" echo "✗ Task failed!" ERROR=$(echo $RESPONSE | jq -r '.error') echo "Error: $ERROR" exit 1 ;; "TASK_STATUS_TIMEOUT") echo "" echo "✗ Task timed out!" exit 1 ;; "TASK_STATUS_CANCELLED") echo "" echo "✗ Task cancelled!" exit 1 ;; esac sleep 2 done ``` ## Use Cases ### 1. Submit and Wait Pattern ```python theme={null} import httpx import time def submit_and_wait(query: str, api_key: str): """Submit task and wait for result.""" # Submit submit_response = httpx.post( "http://localhost:8080/api/v1/tasks", headers={"X-API-Key": api_key}, json={"query": query} ) task_id = submit_response.json()["task_id"] print(f"Task submitted: {task_id}") # Wait while True: status_response = httpx.get( f"http://localhost:8080/api/v1/tasks/{task_id}", headers={"X-API-Key": api_key} ) status = status_response.json() if status["status"] == "TASK_STATUS_COMPLETED": return status["result"] elif status["status"] == "TASK_STATUS_FAILED": raise Exception(status["error"]) time.sleep(2) result = submit_and_wait("What is Python?", "sk_test_123456") print(result) ``` ### 2. Dashboard Status Widget ```python theme={null} def get_task_summary(task_id: str, api_key: str): """Get task summary for dashboard.""" response = httpx.get( f"http://localhost:8080/api/v1/tasks/{task_id}", headers={"X-API-Key": api_key} ) status = response.json() return { "id": task_id, "query": status["query"][:50] + "...", "status": status["status"].replace("TASK_STATUS_", ""), "mode": status["mode"].replace("EXECUTION_MODE_", ""), "created": status["created_at"] } # Display in UI summary = get_task_summary("task_abc123", "sk_test_123456") print(f"{summary['status']}: {summary['query']}") ``` ### 3. Batch Status Check ```python theme={null} def check_multiple_tasks(task_ids: list, api_key: str): """Check status of multiple tasks.""" results = {} for task_id in task_ids: try: response = httpx.get( f"http://localhost:8080/api/v1/tasks/{task_id}", headers={"X-API-Key": api_key}, timeout=5.0 ) results[task_id] = response.json()["status"] except Exception as e: results[task_id] = f"ERROR: {e}" return results # Check 5 tasks task_ids = ["task_1", "task_2", "task_3", "task_4", "task_5"] statuses = check_multiple_tasks(task_ids, "sk_test_123456") for task_id, status in statuses.items(): print(f"{task_id}: {status}") ``` ## Best Practices ### 1. Use Streaming Instead of Polling For long-running tasks, use SSE streaming instead of polling: ```python theme={null} # ❌ Bad - Polls every 2 seconds while True: status = httpx.get(f".../{task_id}") if status["status"] == "COMPLETED": break time.sleep(2) # ✅ Good - Use streaming for event in client.stream(task_id): print(event.type, event.message) if event.type == "WORKFLOW_COMPLETED": break ``` ### 2. Handle All Status States ```python theme={null} status = get_task_status(task_id, api_key) match status["status"]: case "TASK_STATUS_QUEUED": print("Task is queued...") case "TASK_STATUS_RUNNING": print("Task is running...") case "TASK_STATUS_COMPLETED": result = status["result"] print(f"Result: {result}") case "TASK_STATUS_FAILED": print(f"Failed: {status['error']}") case "TASK_STATUS_TIMEOUT": print("Task timed out") case "TASK_STATUS_CANCELLED": print("Task was cancelled") ``` ### 3. Implement Exponential Backoff ```python theme={null} import time def poll_with_backoff(task_id, api_key, max_wait=60): """Poll with exponential backoff.""" wait_time = 1 while True: status = get_task_status(task_id, api_key) if status["status"] in ["TASK_STATUS_COMPLETED", "TASK_STATUS_FAILED"]: return status time.sleep(wait_time) wait_time = min(wait_time * 2, max_wait) # Cap at 60s ``` ### 4. Cache Status Responses ```python theme={null} from functools import lru_cache import time @lru_cache(maxsize=1000) def get_cached_status(task_id: str, api_key: str, timestamp: int): """Cache status for 5 seconds.""" return get_task_status(task_id, api_key) # Usage current_time = int(time.time() / 5) # 5-second buckets status = get_cached_status("task_abc123", "sk_test_123456", current_time) ``` ### 5. Extract Metadata ```python theme={null} def extract_task_info(task_id: str, api_key: str): """Extract useful metadata.""" status = get_task_status(task_id, api_key) return { "task_id": status["task_id"], "query": status["query"], "status": status["status"], "mode": status["mode"], "session_id": status["session_id"], "has_result": status["response"] is not None, "has_error": bool(status["error"]), "workflow_url": f"http://localhost:8088/workflows/{task_id}" } ``` ## Related Endpoints POST /api/v1/tasks Real-time monitoring Use client.get\_status() ## Notes **Don't Poll in Production**: For long-running tasks, use streaming endpoints instead of polling status. Polling creates unnecessary load and adds latency. **Session Tracking**: The `session_id` field allows you to track which session a task belongs to, useful for multi-turn conversations and cost attribution. # HITL Review API Source: https://docs.shannon.run/en/api/rest/hitl-review API reference for Human-in-the-Loop research plan review endpoints ## Overview The HITL (Human-in-the-Loop) review API provides endpoints for reviewing, refining, and approving AI-generated research plans before execution begins. These endpoints interact with the review state stored in Redis and coordinate with the Temporal workflow via gRPC signals. For a tutorial on using the HITL review system, see [Human-in-the-Loop Review](/tutorials/hitl-review). ## Authentication **Required**: Yes Include API key in header: ```http theme={null} X-API-Key: sk_test_123456 ``` Ownership is enforced: only the user who submitted the task can interact with its review. *** ## Get Review State ```http theme={null} GET http://localhost:8080/api/v1/tasks/{workflow_id}/review ``` ### Description Returns the current review conversation state for a workflow. Use this to retrieve the initial research plan and track the review cycle. ### Path Parameters | Parameter | Type | Required | Description | | ------------- | ------ | -------- | ------------------------------------------- | | `workflow_id` | string | Yes | Workflow ID (from task submission response) | ### Headers | Header | Required | Description | | ----------- | -------- | ---------------------- | | `X-API-Key` | Yes | API authentication key | ### Response #### 200 OK ```json theme={null} { "status": "reviewing", "round": 1, "version": 1, "current_plan": "Based on your query, I propose researching...", "rounds": [ { "role": "assistant", "message": "Based on your query, I propose researching...", "timestamp": "2025-01-15T10:30:00Z" } ], "query": "Research the competitive landscape of AI agent frameworks" } ``` **Response Headers:** | Header | Description | | ------ | -------------------------------------------------------------------- | | `ETag` | Current version number (use with `If-Match` for concurrency control) | **Response Fields:** | Field | Type | Description | | -------------------- | ------- | -------------------------------------------------------------------------------------------------------------------- | | `status` | string | `"reviewing"` (waiting for input) or `"approved"` (plan accepted) | | `round` | integer | Current conversation round (starts at 1) | | `version` | integer | Monotonic version for optimistic concurrency | | `current_plan` | string | Latest actionable plan (set when LLM intent is `"ready"`). May be empty if LLM is still asking clarifying questions. | | `rounds` | array | Full conversation history | | `rounds[].role` | string | `"assistant"` (LLM) or `"user"` (human feedback) | | `rounds[].message` | string | Message content | | `rounds[].timestamp` | string | RFC 3339 timestamp | | `query` | string | Original task query | #### 401 Unauthorized ```json theme={null} { "error": "Unauthorized" } ``` #### 403 Forbidden ```json theme={null} { "error": "Forbidden: not the task owner" } ``` #### 404 Not Found ```json theme={null} { "error": "Review session not found or expired" } ``` ### Example ```bash curl theme={null} curl -s http://localhost:8080/api/v1/tasks/task-abc123/review \ -H "X-API-Key: $API_KEY" | jq ``` ```python Python SDK theme={null} from shannon import ShannonClient client = ShannonClient(base_url="http://localhost:8080") state = client.get_review_state("task-abc123") print(state.status, state.round, state.version) ``` *** ## Submit Feedback ```http theme={null} POST http://localhost:8080/api/v1/tasks/{workflow_id}/review ``` ### Description Sends feedback to refine the research plan. The gateway forwards the feedback to the LLM service, which generates an updated plan incorporating the user's input. The conversation round and version are incremented. A distributed Redis lock prevents concurrent feedback requests from racing during the LLM call. ### Path Parameters | Parameter | Type | Required | Description | | ------------- | ------ | -------- | ----------- | | `workflow_id` | string | Yes | Workflow ID | ### Headers | Header | Required | Description | | -------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------- | | `X-API-Key` | Yes | API authentication key | | `Content-Type` | Yes | `application/json` | | `If-Match` | No | Version number for optimistic concurrency. If provided, the request is rejected with 409 if the current version does not match. | ### Body Parameters | Parameter | Type | Required | Description | | --------- | ------ | -------- | ------------------------- | | `action` | string | Yes | Must be `"feedback"` | | `message` | string | Yes | Feedback text (max 10 KB) | ### Request Body ```json theme={null} { "action": "feedback", "message": "Focus on open-source frameworks and include pricing comparisons" } ``` ### Response #### 200 OK ```json theme={null} { "status": "reviewing", "plan": { "message": "I've updated the research plan to focus on...", "round": 2, "version": 2, "intent": "ready" } } ``` **Response Headers:** | Header | Description | | ------ | ---------------------- | | `ETag` | Updated version number | **Response Fields:** | Field | Type | Description | | -------------- | ------- | --------------------------------------------------------------------------- | | `status` | string | Always `"reviewing"` for feedback responses | | `plan.message` | string | Updated plan from the LLM | | `plan.round` | integer | Current round number | | `plan.version` | integer | Updated version (for next `If-Match`) | | `plan.intent` | string | `"feedback"` (LLM asking questions) or `"ready"` (actionable plan proposed) | #### 400 Bad Request ```json theme={null} { "error": "message is required for feedback" } ``` ```json theme={null} { "error": "message exceeds maximum length (10KB)" } ``` ```json theme={null} { "error": "action must be 'feedback' or 'approve'" } ``` #### 404 Not Found ```json theme={null} { "error": "Review session not found or expired" } ``` #### 409 Conflict Version mismatch: ```json theme={null} { "error": "Conflict: state has been modified" } ``` Another feedback request in progress: ```json theme={null} { "error": "Another feedback request is in progress. Please wait." } ``` Maximum rounds exceeded: ```json theme={null} { "error": "Maximum review rounds reached. Please approve the plan." } ``` #### 502 Bad Gateway LLM service unavailable: ```json theme={null} { "error": "Failed to generate updated plan" } ``` ### Example ```bash curl theme={null} curl -X POST http://localhost:8080/api/v1/tasks/task-abc123/review \ -H "Content-Type: application/json" \ -H "X-API-Key: $API_KEY" \ -H "If-Match: 1" \ -d '{ "action": "feedback", "message": "Include a section on pricing and add LangGraph to the comparison" }' ``` ```python Python SDK theme={null} updated = client.submit_review_feedback( "task-abc123", "Include a section on pricing and add LangGraph to the comparison", version=1, ) print(updated.version, updated.current_plan) ``` *** ## Approve Plan ```http theme={null} POST http://localhost:8080/api/v1/tasks/{workflow_id}/review ``` ### Description Approves the current research plan, unblocking the workflow to proceed with execution. The gateway sends a Temporal Signal to the waiting workflow, injecting the confirmed plan and review conversation into the task context. ### Path Parameters | Parameter | Type | Required | Description | | ------------- | ------ | -------- | ----------- | | `workflow_id` | string | Yes | Workflow ID | ### Headers | Header | Required | Description | | -------------- | -------- | ----------------------------------------- | | `X-API-Key` | Yes | API authentication key | | `Content-Type` | Yes | `application/json` | | `If-Match` | No | Version number for optimistic concurrency | ### Body Parameters | Parameter | Type | Required | Description | | --------- | ------ | -------- | ------------------- | | `action` | string | Yes | Must be `"approve"` | ### Request Body ```json theme={null} { "action": "approve" } ``` ### Response #### 200 OK ```json theme={null} { "status": "approved", "message": "Research started" } ``` #### 400 Bad Request No plan available to approve: ```json theme={null} { "error": "No research plan to approve. Please provide feedback to generate a plan first." } ``` #### 409 Conflict Version mismatch: ```json theme={null} { "error": "Conflict: plan has been updated. Please review the latest version." } ``` Feedback in progress: ```json theme={null} { "error": "A feedback request is in progress. Please wait and try again." } ``` #### 502 Bad Gateway ```json theme={null} { "error": "Failed to approve review" } ``` ### Example ```bash curl theme={null} curl -X POST http://localhost:8080/api/v1/tasks/task-abc123/review \ -H "Content-Type: application/json" \ -H "X-API-Key: $API_KEY" \ -H "If-Match: 2" \ -d '{"action": "approve"}' ``` ```python Python SDK theme={null} result = client.approve_review("task-abc123", version=2) print(result) # {"status": "approved", "message": "Research started"} ``` *** ## Behavior Notes * **Redis TTL**: Review state is stored in Redis with a TTL equal to the review timeout plus a 5-minute buffer (default: 20 minutes). After expiry, the review session is no longer accessible. * **Maximum rounds**: 10 feedback rounds are permitted. At the final round, the LLM is instructed to produce a definitive plan. Beyond this limit, only approval is accepted. * **Ownership**: Only the user who submitted the task can access its review state (enforced via `owner_user_id` stored in Redis). * **Concurrency**: Both feedback and approval acquire a distributed Redis lock. If a feedback request is in progress (holding the lock), approval will fail fast with 409. * **SSE events**: The review cycle emits `RESEARCH_PLAN_READY`, `REVIEW_USER_FEEDBACK`, `RESEARCH_PLAN_UPDATED`, and `RESEARCH_PLAN_APPROVED` events to the Redis event stream. These appear in the SSE stream and are persisted for session history. * **Token tracking**: LLM token usage during review (feedback rounds) is recorded via `RecordTokenUsage` gRPC for accurate cost accounting. # GET /api/v1/tasks Source: https://docs.shannon.run/en/api/rest/list-tasks List and filter tasks ## Endpoint ``` GET http://localhost:8080/api/v1/tasks ``` ## Description Retrieves a paginated list of tasks with optional filtering by status and session. ## Authentication **Required**: Yes Include API key in header: ``` X-API-Key: sk_test_123456 ``` ## Request ### Headers | Header | Required | Description | | ----------- | -------- | ------------------ | | `X-API-Key` | Yes | Authentication key | ### Query Parameters | Parameter | Type | Required | Description | Default | | ------------ | ------- | -------- | --------------------------------- | ------- | | `limit` | integer | No | Number of tasks to return (1-100) | `20` | | `offset` | integer | No | Number of tasks to skip | `0` | | `session_id` | string | No | Filter by session ID | - | | `status` | string | No | Filter by task status | - | ### Status Filter Values * `QUEUED` - Tasks waiting to execute * `RUNNING` - Tasks currently executing * `COMPLETED` - Successfully completed tasks * `FAILED` - Failed tasks * `CANCELLED` or `CANCELED` - Cancelled tasks * `TIMEOUT` - Tasks that exceeded timeout Filter values use short form (e.g., `RUNNING`). Response status fields include the `TASK_STATUS_` prefix (e.g., `TASK_STATUS_RUNNING`). ## Response ### Success Response **Status**: `200 OK` **Body**: ```json theme={null} { "tasks": [ { "task_id": "string", "query": "string", "status": "string", "mode": "string", "created_at": "timestamp", "completed_at": "timestamp", "total_token_usage": { "total_tokens": 0, "cost_usd": 0.0, "prompt_tokens": 0, "completion_tokens": 0 } } ], "total_count": 0 } ``` ### Response Fields | Field | Type | Description | | ------------- | ------- | -------------------------------------- | | `tasks` | array | Array of task summaries | | `total_count` | integer | Total number of tasks matching filters | ### Task Summary Fields | Field | Type | Description | | ------------------- | --------- | --------------------------------------------------------------------------------------------- | | `task_id` | string | Unique task identifier | | `query` | string | Original task query | | `status` | string | Current task status | | `mode` | string | Execution mode (`EXECUTION_MODE_SIMPLE`, `EXECUTION_MODE_STANDARD`, `EXECUTION_MODE_COMPLEX`) | | `created_at` | timestamp | Task creation time (ISO 8601) | | `completed_at` | timestamp | Task completion time (null if not completed) | | `total_token_usage` | object | Token usage and cost metrics | The `mode` field uses the `EXECUTION_MODE_` prefix in list responses (e.g., `EXECUTION_MODE_STANDARD`). The single-task GET endpoint returns the short form (e.g., `standard`). ### Token Usage Fields | Field | Type | Description | | ------------------- | ------- | --------------------- | | `total_tokens` | integer | Total tokens consumed | | `cost_usd` | float | Total cost in USD | | `prompt_tokens` | integer | Tokens in prompts | | `completion_tokens` | integer | Tokens in completions | ## Examples ### List All Tasks ```bash theme={null} curl -X GET "http://localhost:8080/api/v1/tasks" \ -H "X-API-Key: sk_test_123456" ``` **Response**: ```json theme={null} { "tasks": [ { "task_id": "task_01HQZX3Y9K8M2P4N5S7T9W2V", "query": "What is the capital of France?", "status": "TASK_STATUS_COMPLETED", "mode": "EXECUTION_MODE_SIMPLE", "created_at": "2025-10-22T10:30:00Z", "completed_at": "2025-10-22T10:30:05Z", "total_token_usage": { "total_tokens": 150, "cost_usd": 0.0023, "prompt_tokens": 45, "completion_tokens": 105 } }, { "task_id": "task_01HQZX4Y9K8M2P4N5S7T9W2W", "query": "Analyze sales trends for Q4", "status": "TASK_STATUS_RUNNING", "mode": "EXECUTION_MODE_STANDARD", "created_at": "2025-10-22T10:31:00Z", "completed_at": null, "total_token_usage": null } ], "total_count": 2 } ``` ### Filter by Status ```bash theme={null} # Get only running tasks curl -X GET "http://localhost:8080/api/v1/tasks?status=RUNNING" \ -H "X-API-Key: sk_test_123456" ``` ```bash theme={null} # Get completed tasks curl -X GET "http://localhost:8080/api/v1/tasks?status=COMPLETED" \ -H "X-API-Key: sk_test_123456" ``` ```bash theme={null} # Get failed tasks curl -X GET "http://localhost:8080/api/v1/tasks?status=FAILED" \ -H "X-API-Key: sk_test_123456" ``` ### Filter by Session ```bash theme={null} curl -X GET "http://localhost:8080/api/v1/tasks?session_id=user-123-chat" \ -H "X-API-Key: sk_test_123456" ``` **Response**: ```json theme={null} { "tasks": [ { "task_id": "task_abc123", "query": "What is Python?", "status": "TASK_STATUS_COMPLETED", "created_at": "2025-10-22T10:00:00Z", "completed_at": "2025-10-22T10:00:03Z" }, { "task_id": "task_def456", "query": "What are its main advantages?", "status": "TASK_STATUS_COMPLETED", "created_at": "2025-10-22T10:01:00Z", "completed_at": "2025-10-22T10:01:04Z" } ], "total_count": 2 } ``` ### Pagination ```bash theme={null} # First page (20 tasks) curl -X GET "http://localhost:8080/api/v1/tasks?limit=20&offset=0" \ -H "X-API-Key: sk_test_123456" # Second page curl -X GET "http://localhost:8080/api/v1/tasks?limit=20&offset=20" \ -H "X-API-Key: sk_test_123456" # Third page curl -X GET "http://localhost:8080/api/v1/tasks?limit=20&offset=40" \ -H "X-API-Key: sk_test_123456" ``` ### Combined Filters ```bash theme={null} # Get completed tasks in a session, paginated curl -X GET "http://localhost:8080/api/v1/tasks?session_id=user-123&status=COMPLETED&limit=10&offset=0" \ -H "X-API-Key: sk_test_123456" ``` ## Error Responses ### 401 Unauthorized ```json theme={null} { "error": "Unauthorized" } ``` ### 429 Too Many Requests ```json theme={null} { "error": "Rate limit exceeded" } ``` ### 500 Internal Server Error ```json theme={null} { "error": "Failed to list tasks: database error" } ``` ## Code Examples ### Python with httpx ```python theme={null} import httpx response = httpx.get( "http://localhost:8080/api/v1/tasks", headers={"X-API-Key": "sk_test_123456"}, params={ "status": "COMPLETED", "limit": 50, "offset": 0 } ) data = response.json() print(f"Total: {data['total_count']} tasks") for task in data["tasks"]: print(f"- {task['task_id']}: {task['status']}") if task['total_token_usage']: print(f" Cost: ${task['total_token_usage']['cost_usd']:.4f}") ``` ### Python - List All Tasks (Pagination) ```python theme={null} import httpx def list_all_tasks(api_key: str, status: str = None): """List all tasks with pagination.""" all_tasks = [] offset = 0 limit = 100 while True: response = httpx.get( "http://localhost:8080/api/v1/tasks", headers={"X-API-Key": api_key}, params={ "status": status, "limit": limit, "offset": offset } ) data = response.json() tasks = data["tasks"] all_tasks.extend(tasks) # Check if we've retrieved all tasks if len(tasks) < limit: break offset += limit return all_tasks # Usage completed_tasks = list_all_tasks("sk_test_123456", status="COMPLETED") print(f"Found {len(completed_tasks)} completed tasks") ``` ### JavaScript/Node.js ```javascript theme={null} const axios = require('axios'); async function listTasks(filters = {}) { try { const response = await axios.get( 'http://localhost:8080/api/v1/tasks', { headers: { 'X-API-Key': 'sk_test_123456' }, params: { status: filters.status, session_id: filters.sessionId, limit: filters.limit || 20, offset: filters.offset || 0 } } ); console.log(`Total tasks: ${response.data.total_count}`); response.data.tasks.forEach(task => { console.log(`${task.task_id}: ${task.status}`); }); return response.data; } catch (error) { console.error('Error:', error.response?.data || error.message); throw error; } } // List running tasks listTasks({ status: 'RUNNING' }); // List tasks in a session listTasks({ sessionId: 'user-123-chat', limit: 50 }); ``` > Note: The `session_id` filter accepts a UUID or an `external_id`. ### Bash Script - Monitor Running Tasks ```bash theme={null} #!/bin/bash API_KEY="sk_test_123456" BASE_URL="http://localhost:8080" echo "Monitoring running tasks..." while true; do clear echo "=== Running Tasks ===" echo "" RESPONSE=$(curl -s "$BASE_URL/api/v1/tasks?status=RUNNING" \ -H "X-API-Key: $API_KEY") TOTAL=$(echo $RESPONSE | jq -r '.total_count') echo "Total running: $TOTAL" echo "" echo $RESPONSE | jq -r '.tasks[] | "\(.task_id): \(.query)"' sleep 5 done ``` ### Go ```go theme={null} package main import ( "encoding/json" "fmt" "net/http" "net/url" ) type ListTasksResponse struct { Tasks []TaskSummary `json:"tasks"` TotalCount int32 `json:"total_count"` } type TaskSummary struct { TaskID string `json:"task_id"` Query string `json:"query"` Status string `json:"status"` Mode string `json:"mode"` CreatedAt string `json:"created_at"` CompletedAt *string `json:"completed_at"` } func listTasks(status string, limit, offset int) (*ListTasksResponse, error) { baseURL := "http://localhost:8080/api/v1/tasks" // Build query parameters params := url.Values{} if status != "" { params.Add("status", status) } params.Add("limit", fmt.Sprintf("%d", limit)) params.Add("offset", fmt.Sprintf("%d", offset)) reqURL := baseURL + "?" + params.Encode() req, _ := http.NewRequest("GET", reqURL, nil) req.Header.Set("X-API-Key", "sk_test_123456") client := &http.Client{} resp, err := client.Do(req) if err != nil { return nil, err } defer resp.Body.Close() var result ListTasksResponse json.NewDecoder(resp.Body).Decode(&result) return &result, nil } func main() { tasks, err := listTasks("COMPLETED", 10, 0) if err != nil { fmt.Println("Error:", err) return } fmt.Printf("Total tasks: %d\n", tasks.TotalCount) for _, task := range tasks.Tasks { fmt.Printf("- %s: %s\n", task.TaskID, task.Status) } } ``` ## Use Cases ### 1. Dashboard - Show Recent Tasks ```python theme={null} def get_recent_tasks(api_key: str, limit: int = 10): """Get most recent tasks for dashboard.""" response = httpx.get( "http://localhost:8080/api/v1/tasks", headers={"X-API-Key": api_key}, params={"limit": limit, "offset": 0} ) return response.json()["tasks"] recent = get_recent_tasks("sk_test_123456", limit=5) for task in recent: print(f"{task['created_at']}: {task['query'][:50]}...") ``` ### 2. Cost Tracking ```python theme={null} def calculate_session_cost(api_key: str, session_id: str): """Calculate total cost for a session.""" response = httpx.get( "http://localhost:8080/api/v1/tasks", headers={"X-API-Key": api_key}, params={"session_id": session_id, "limit": 100} ) total_cost = 0 for task in response.json()["tasks"]: if task["total_token_usage"]: total_cost += task["total_token_usage"]["cost_usd"] return total_cost cost = calculate_session_cost("sk_test_123456", "user-123-chat") print(f"Session cost: ${cost:.4f}") ``` ### 3. Failure Monitoring ```python theme={null} import time def monitor_failures(api_key: str, interval: int = 60): """Monitor for failed tasks.""" while True: response = httpx.get( "http://localhost:8080/api/v1/tasks", headers={"X-API-Key": api_key}, params={"status": "FAILED", "limit": 10} ) failed_tasks = response.json()["tasks"] if failed_tasks: print(f"⚠️ {len(failed_tasks)} failed tasks:") for task in failed_tasks: print(f" - {task['task_id']}: {task['query']}") # Get details details = httpx.get( f"http://localhost:8080/api/v1/tasks/{task['task_id']}", headers={"X-API-Key": api_key} ) error = details.json().get("error", "Unknown error") print(f" Error: {error}") time.sleep(interval) monitor_failures("sk_test_123456") ``` ## Implementation Notes ### Rate Limiting This endpoint is rate limited. Default limits: * **60 requests/minute** per API key * **20 requests/second** burst ### Performance * **Indexed queries**: Filtering by `status` and `session_id` is fast * **Pagination**: Use `limit` and `offset` to avoid large responses * **Total count**: Includes all matching tasks, not just returned page ### Ordering Tasks are returned in **reverse chronological order** (newest first). ## Best Practices ### 1. Use Pagination Always paginate for large result sets: ```python theme={null} def fetch_page(offset, limit=20): return httpx.get( "http://localhost:8080/api/v1/tasks", headers={"X-API-Key": "sk_test_123456"}, params={"limit": limit, "offset": offset} ).json() # Fetch first 3 pages for page in range(3): tasks = fetch_page(offset=page * 20, limit=20) process_tasks(tasks) ``` ### 2. Filter by Status Don't fetch all tasks if you only need specific statuses: ```python theme={null} # ✅ Good - filter server-side running = httpx.get(..., params={"status": "RUNNING"}) # ❌ Bad - filter client-side all_tasks = httpx.get(...) running = [t for t in all_tasks if t["status"] == "RUNNING"] ``` ### 3. Cache Results Cache task lists for dashboards: ```python theme={null} import time cache = {"data": None, "timestamp": 0} def get_cached_tasks(): now = time.time() if cache["data"] is None or now - cache["timestamp"] > 60: # Refresh every 60 seconds response = httpx.get(...) cache["data"] = response.json() cache["timestamp"] = now return cache["data"] ``` ## Related Endpoints POST /api/v1/tasks GET /api/v1/tasks/ Real-time monitoring Use the SDK instead ## Notes **SDK Alternative**: The Python SDK does not expose a `list_tasks()` method. This endpoint is REST-only. For SDK usage, store task IDs and query status individually using `client.get_status(task_id)`. # OpenAI-Compatible API Source: https://docs.shannon.run/en/api/rest/openai-compatible Use Shannon with OpenAI SDKs and tools via the /v1/chat/completions endpoint ## Overview Shannon exposes an OpenAI-compatible API layer that lets you use existing OpenAI SDKs, tools, and integrations to interact with Shannon's agent orchestration platform. The compatibility layer translates OpenAI chat completion requests into Shannon tasks and streams the results back in OpenAI format. This means you can point the OpenAI Python or Node.js SDK at Shannon and get access to multi-agent research, tool use, and deep analysis -- all through a familiar interface. The OpenAI-compatible API is designed for compatibility with existing tooling. For full Shannon features (skills, session workspaces, research strategies, task control), use the native [`/api/v1/tasks`](/en/api/rest/submit-task) endpoints. ## Endpoints | Method | Path | Description | | ------ | ---------------------- | ------------------------------------------------------ | | `POST` | `/v1/chat/completions` | Create a chat completion (streaming and non-streaming) | | `GET` | `/v1/models` | List available models | | `GET` | `/v1/models/{model}` | Get model details | **Base URL**: `http://localhost:8080` (development) ## Authentication The OpenAI-compatible endpoints use the same authentication as other Shannon APIs. ```bash theme={null} # Bearer token (OpenAI SDK default) Authorization: Bearer sk_your_api_key # Or X-API-Key header X-API-Key: sk_your_api_key ``` **Development Default**: Authentication is disabled when `GATEWAY_SKIP_AUTH=1` is set. Enable authentication for production deployments. ## Available Models Shannon maps model names to different workflow modes and strategies. Select a model to control how your request is processed. | Model | Workflow Mode | Description | Default Max Tokens | Availability | | --------------------------- | ------------- | ------------------------------------------- | ------------------ | ---------------------- | | `shannon-chat` | Simple | General chat completion (default) | 4096 | All | | `shannon-standard-research` | Research | Balanced research with moderate depth | 4096 | All | | `shannon-deep-research` | Research | Deep research with iterative refinement | 8192 | All | | `shannon-quick-research` | Research | Fast research for simple queries | 4096 | All | | `shannon-complex` | Supervisor | Multi-agent orchestration for complex tasks | 8192 | All | | `shannon-ads-research` | Ads Research | Multi-platform ads competitor analysis | 8192 | **Shannon Cloud Only** | If no model is specified, `shannon-chat` is used. **Shannon Cloud Only**: The `shannon-ads-research` model is an enterprise feature available only on Shannon Cloud deployments with ads research vendor adapters configured. Models can be customized via `config/openai_models.yaml`. See the Shannon configuration documentation for details on adding custom models. ## Chat Completions ### POST /v1/chat/completions #### Request Body | Parameter | Type | Required | Description | | ------------------- | ------- | -------- | ------------------------------------------------------- | | `model` | string | No | Model name (defaults to `shannon-chat`) | | `messages` | array | Yes | Array of message objects | | `stream` | boolean | No | Enable streaming (default: `false`) | | `max_tokens` | integer | No | Maximum tokens for response (capped at 16384) | | `temperature` | number | No | Sampling temperature 0-2 (default: 0.7) | | `top_p` | number | No | Nucleus sampling parameter | | `n` | integer | No | Number of completions (only `1` is supported) | | `stop` | array | No | Stop sequences | | `presence_penalty` | number | No | Presence penalty -2.0 to 2.0 | | `frequency_penalty` | number | No | Frequency penalty -2.0 to 2.0 | | `user` | string | No | End-user identifier for tracking and session derivation | | `stream_options` | object | No | Streaming options (see below) | **Message Object**: | Field | Type | Required | Description | | --------- | ------ | -------- | --------------------------------- | | `role` | string | Yes | `system`, `user`, or `assistant` | | `content` | string | Yes | Message content (text only) | | `name` | string | No | Optional name for the participant | **Stream Options**: | Field | Type | Description | | --------------- | ------- | ------------------------------------------------ | | `include_usage` | boolean | Include token usage in the final streaming chunk | #### How Messages Are Processed Shannon translates the OpenAI messages array into a Shannon task: * **Last user message** becomes the task query * **First system message** becomes the system prompt * **All other messages** (excluding system and last user) become conversation history * The **model name** determines the workflow mode and research strategy #### Non-Streaming Response ```json theme={null} { "id": "chatcmpl-20250120100000a1b2c3d4", "object": "chat.completion", "created": 1737367200, "model": "shannon-chat", "choices": [ { "index": 0, "message": { "role": "assistant", "content": "The response text from Shannon..." }, "finish_reason": "stop" } ], "usage": { "prompt_tokens": 25, "completion_tokens": 150, "total_tokens": 175 } } ``` Non-streaming requests have a **35-minute timeout** to accommodate deep research and long-running workflows. For very long tasks, prefer streaming mode. #### Streaming Response When `stream: true`, the response is delivered as Server-Sent Events: **First chunk** (includes role): ``` data: {"id":"chatcmpl-...","object":"chat.completion.chunk","created":1737367200,"model":"shannon-chat","choices":[{"index":0,"delta":{"role":"assistant","content":"The"},"finish_reason":null}]} ``` **Content chunks**: ``` data: {"id":"chatcmpl-...","object":"chat.completion.chunk","created":1737367200,"model":"shannon-chat","choices":[{"index":0,"delta":{"content":" response text"},"finish_reason":null}]} ``` **Final chunk** (with finish reason): ``` data: {"id":"chatcmpl-...","object":"chat.completion.chunk","created":1737367200,"model":"shannon-chat","choices":[{"index":0,"delta":{},"finish_reason":"stop"}],"usage":{"prompt_tokens":25,"completion_tokens":150,"total_tokens":175}} ``` **Stream terminator**: ``` data: [DONE] ``` Usage data in the final chunk is only included when `stream_options.include_usage` is set to `true`. ## Shannon Extensions ### shannon\_events Field During streaming, Shannon extends the standard OpenAI chunk format with a `shannon_events` field. This field carries agent lifecycle events that provide visibility into what Shannon's agents are doing behind the scenes. ```json theme={null} { "id": "chatcmpl-...", "object": "chat.completion.chunk", "created": 1737367200, "model": "shannon-deep-research", "choices": [ { "index": 0, "delta": {} } ], "shannon_events": [ { "type": "AGENT_STARTED", "agent_id": "researcher_1", "message": "Starting research on query...", "timestamp": 1737367201, "payload": {} } ] } ``` **ShannonEvent fields**: | Field | Type | Description | | ----------- | ------- | ------------------------------ | | `type` | string | Event type (see list below) | | `agent_id` | string | Agent identifier | | `message` | string | Human-readable description | | `timestamp` | integer | Unix timestamp | | `payload` | object | Additional event-specific data | **Forwarded event types**: | Category | Events | | ----------------- | -------------------------------------------------------------------------------------------------------------------------- | | Workflow | `WORKFLOW_STARTED`, `WORKFLOW_PAUSING`, `WORKFLOW_PAUSED`, `WORKFLOW_RESUMED`, `WORKFLOW_CANCELLING`, `WORKFLOW_CANCELLED` | | Agent | `AGENT_STARTED`, `AGENT_COMPLETED`, `AGENT_THINKING` | | Tool | `TOOL_INVOKED`, `TOOL_OBSERVATION` | | Progress | `PROGRESS`, `DATA_PROCESSING`, `WAITING`, `ERROR_RECOVERY` | | Team | `TEAM_RECRUITED`, `TEAM_RETIRED`, `TEAM_STATUS`, `ROLE_ASSIGNED`, `DELEGATION`, `DEPENDENCY_SATISFIED` | | Budget & Approval | `BUDGET_THRESHOLD`, `APPROVAL_REQUESTED`, `APPROVAL_DECISION` | Standard OpenAI clients ignore unknown fields, so the `shannon_events` field is safe to use with any OpenAI-compatible tooling. Parse it when you want richer progress information. ### X-Session-ID Header Shannon supports multi-turn conversations via the `X-Session-ID` request header. When provided, Shannon maintains conversation context across requests. ```bash theme={null} curl -X POST http://localhost:8080/v1/chat/completions \ -H "Authorization: Bearer sk_your_key" \ -H "X-Session-ID: my-conversation-1" \ -H "Content-Type: application/json" \ -d '{"model": "shannon-chat", "messages": [{"role": "user", "content": "Hello"}]}' ``` If no `X-Session-ID` is provided, Shannon derives a session ID from the conversation content (hash of system message + first user message) or from the `user` field. The response includes `X-Session-ID` and `X-Shannon-Session-ID` headers when a new session is created or a collision is detected. ## Rate Limiting Rate limits are enforced per API key, per model. The default limits are: * **60 requests per minute** per model * **200,000 tokens per minute** per model **Rate limit headers** included in every response: | Header | Description | | -------------------------------- | ---------------------------------------- | | `X-RateLimit-Limit-Requests` | Maximum requests per minute | | `X-RateLimit-Remaining-Requests` | Remaining requests in current window | | `X-RateLimit-Limit-Tokens` | Maximum tokens per minute | | `X-RateLimit-Remaining-Tokens` | Remaining tokens in current window | | `X-RateLimit-Reset-Requests` | Time until request limit resets | | `Retry-After` | Seconds to wait before retrying (on 429) | ## Error Handling Errors follow the OpenAI error response format: ```json theme={null} { "error": { "message": "Model 'invalid-model' not found. Use GET /v1/models to list available models.", "type": "invalid_request_error", "code": "model_not_found" } } ``` **Error types**: | HTTP Status | Type | Code | Description | | ----------- | ----------------------- | --------------------- | -------------------------------------------- | | 400 | `invalid_request_error` | `invalid_request` | Malformed request or missing required fields | | 401 | `authentication_error` | `invalid_api_key` | Invalid or missing API key | | 403 | `permission_error` | `invalid_request` | Insufficient permissions | | 404 | `invalid_request_error` | `model_not_found` | Model does not exist | | 429 | `rate_limit_error` | `rate_limit_exceeded` | Rate limit exceeded | | 500 | `server_error` | `internal_error` | Internal server error | ## List Models ### GET /v1/models Returns all available Shannon models. ```bash theme={null} curl http://localhost:8080/v1/models \ -H "Authorization: Bearer sk_your_key" ``` **Response**: ```json theme={null} { "object": "list", "data": [ { "id": "shannon-chat", "object": "model", "created": 1737367200, "owned_by": "shannon" }, { "id": "shannon-deep-research", "object": "model", "created": 1737367200, "owned_by": "shannon" } ] } ``` ### GET /v1/models/ Returns details for a specific model. The model description is included in the `X-Model-Description` response header. ```bash theme={null} curl http://localhost:8080/v1/models/shannon-deep-research \ -H "Authorization: Bearer sk_your_key" ``` ## Usage with OpenAI SDKs ### Python ```python theme={null} from openai import OpenAI client = OpenAI( base_url="http://localhost:8080/v1", api_key="sk_your_api_key", # or "not-needed" if auth is disabled ) # Non-streaming response = client.chat.completions.create( model="shannon-chat", messages=[ {"role": "system", "content": "You are a helpful assistant."}, {"role": "user", "content": "What is Shannon?"} ], ) print(response.choices[0].message.content) # Ads Research (Shannon Cloud Only) response = client.chat.completions.create( model="shannon-ads-research", messages=[ {"role": "user", "content": "Analyze competitor ads for organic skincare products"} ], ) print(response.choices[0].message.content) # Streaming stream = client.chat.completions.create( model="shannon-deep-research", messages=[ {"role": "user", "content": "Analyze the impact of AI on healthcare"} ], stream=True, stream_options={"include_usage": True}, ) for chunk in stream: if chunk.choices[0].delta.content: print(chunk.choices[0].delta.content, end="", flush=True) # Access Shannon-specific events (if available) if hasattr(chunk, "shannon_events") and chunk.shannon_events: for event in chunk.shannon_events: print(f"\n[{event['type']}] {event.get('message', '')}") ``` ### Node.js / TypeScript ```typescript theme={null} import OpenAI from "openai"; const client = new OpenAI({ baseURL: "http://localhost:8080/v1", apiKey: "sk_your_api_key", }); // Non-streaming const response = await client.chat.completions.create({ model: "shannon-chat", messages: [ { role: "user", content: "What is Shannon?" } ], }); console.log(response.choices[0].message.content); // Streaming const stream = await client.chat.completions.create({ model: "shannon-deep-research", messages: [ { role: "user", content: "Analyze the impact of AI on healthcare" } ], stream: true, }); for await (const chunk of stream) { const content = chunk.choices[0]?.delta?.content; if (content) { process.stdout.write(content); } } ``` ### curl ```bash theme={null} # Non-streaming curl -X POST http://localhost:8080/v1/chat/completions \ -H "Content-Type: application/json" \ -H "Authorization: Bearer sk_your_api_key" \ -d '{ "model": "shannon-chat", "messages": [ {"role": "user", "content": "What is Shannon?"} ] }' # Streaming curl -N -X POST http://localhost:8080/v1/chat/completions \ -H "Content-Type: application/json" \ -H "Authorization: Bearer sk_your_api_key" \ -d '{ "model": "shannon-deep-research", "messages": [ {"role": "user", "content": "Analyze the impact of AI on healthcare"} ], "stream": true, "stream_options": {"include_usage": true} }' # With session ID for multi-turn curl -X POST http://localhost:8080/v1/chat/completions \ -H "Content-Type: application/json" \ -H "Authorization: Bearer sk_your_api_key" \ -H "X-Session-ID: my-session-1" \ -d '{ "model": "shannon-chat", "messages": [ {"role": "system", "content": "You are a data analyst."}, {"role": "user", "content": "Summarize Q4 revenue trends"} ] }' ``` ## Streaming with Shannon Events To build rich UIs that show agent progress, parse the `shannon_events` field from streaming chunks: ```python theme={null} import json import httpx def stream_with_events(query: str, model: str = "shannon-deep-research"): response = httpx.post( "http://localhost:8080/v1/chat/completions", headers={ "Content-Type": "application/json", "Authorization": "Bearer sk_your_api_key", }, json={ "model": model, "messages": [{"role": "user", "content": query}], "stream": True, }, timeout=None, ) for line in response.iter_lines(): if not line.startswith("data: "): continue data = line[6:] if data == "[DONE]": break chunk = json.loads(data) # Print content deltas delta = chunk["choices"][0].get("delta", {}) if delta.get("content"): print(delta["content"], end="", flush=True) # Print Shannon agent events for event in chunk.get("shannon_events", []): print(f"\n [{event['type']}] {event.get('message', '')}") stream_with_events("Research the latest developments in quantum computing") ``` ## Heartbeat and Keepalive During streaming, Shannon sends SSE comment lines (`: keepalive`) every 30 seconds to keep the connection alive. Conforming SSE clients ignore these automatically. This prevents load balancers and proxies from closing idle connections during long-running research tasks. ## Limitations The following OpenAI API features are **not supported**: | Feature | Status | | --------------------------------- | --------------------------------- | | Function calling / tools | Not supported | | Vision / image inputs | Not supported (text content only) | | Audio inputs/outputs | Not supported | | Embeddings API (`/v1/embeddings`) | Not available | | Fine-tuning API | Not available | | `response_format` (JSON mode) | Not supported | | `logprobs` | Not supported | | `seed` | Not supported | | `n` > 1 (multiple completions) | Not supported | The `messages[].content` field only accepts plain text strings. Multipart content (arrays with image\_url objects) is not supported. ## Differences from Standard OpenAI API | Aspect | OpenAI API | Shannon OpenAI-Compatible API | | ------------------ | ------------------------------------------------ | ------------------------------------------------ | | Models | GPT-4, GPT-3.5, etc. | `shannon-chat`, `shannon-deep-research`, etc. | | Processing | Single LLM call | Multi-agent orchestration, tool use, research | | Latency | Seconds | Seconds to minutes (depending on model/strategy) | | Streaming events | Content only | Content + `shannon_events` agent lifecycle | | Session management | Not built-in | `X-Session-ID` header with server-side context | | Rate limits | Per-organization | Per API key, per model | | Finish reasons | `stop`, `length`, `tool_calls`, `content_filter` | `stop` only | ## Related Full Shannon task submission with all features Shannon's native SSE and WebSocket streaming Complete list of Shannon event types Shannon's native Python client # REST API Reference Source: https://docs.shannon.run/en/api/rest/overview Complete REST API reference for Shannon Gateway ## Base URL ``` http://localhost:8080/api/v1 ``` ## Endpoints Summary Shannon provides **40+ REST endpoints** across 8 categories: ### Task Operations (10 endpoints) | Endpoint | Description | | ------------------------------- | ------------------------------------------------------------ | | `POST /tasks` | Submit a new task | | `POST /tasks/stream` | Submit task and receive stream URL | | `GET /tasks` | List tasks with filters (limit, offset, status, session\_id) | | `GET /tasks/{id}` | Get task status and result | | `GET /tasks/{id}/events` | Get persistent event history | | `GET /tasks/{id}/timeline` | Get deterministic Temporal timeline | | `POST /tasks/{id}/pause` | Pause a running task at workflow checkpoints | | `POST /tasks/{id}/resume` | Resume a previously paused task | | `GET /tasks/{id}/control-state` | Get pause/cancel control state for a task | | `POST /tasks/{id}/cancel` | Cancel a queued or running task | ### Streaming (2 endpoints) | Endpoint | Description | | ---------------------------------- | ---------------------------- | | `GET /stream/sse?workflow_id={id}` | Server-Sent Events streaming | | `GET /stream/ws?workflow_id={id}` | WebSocket streaming | ### Session Management (6 endpoints) | Endpoint | Description | | ----------------------------------- | -------------------------------------------- | | `GET /sessions` | List sessions with pagination | | `GET /sessions/{sessionId}` | Get session details | | `GET /sessions/{sessionId}/history` | Get session conversation history | | `GET /sessions/{sessionId}/events` | Get session events (grouped by turn) | | `PATCH /sessions/{sessionId}` | Update session title (UUID or external\_id) | | `DELETE /sessions/{sessionId}` | Delete session (soft delete; 204 idempotent) | ### Schedules (8 endpoints) | Endpoint | Description | | ----------------------------- | ----------------------------- | | `POST /schedules` | Create a new schedule | | `GET /schedules` | List schedules | | `GET /schedules/{id}` | Get schedule details | | `GET /schedules/{id}/runs` | Get schedule run history | | `PUT /schedules/{id}` | Update schedule configuration | | `POST /schedules/{id}/pause` | Pause a schedule | | `POST /schedules/{id}/resume` | Resume a schedule | | `DELETE /schedules/{id}` | Delete a schedule | ### Authentication (8 endpoints) | Endpoint | Description | | ----------------------------- | ----------------------- | | `POST /auth/register` | Register a new user | | `POST /auth/login` | Login and obtain tokens | | `POST /auth/refresh` | Refresh access token | | `GET /auth/me` | Get current user info | | `POST /auth/api-keys/refresh` | Refresh an API key | | `GET /auth/api-keys` | List API keys | | `POST /auth/api-keys` | Create a new API key | | `DELETE /auth/api-keys/{id}` | Revoke an API key | ### Approvals & HITL Review (3 endpoints) | Endpoint | Description | | -------------------------- | -------------------------------- | | `POST /approvals/decision` | Submit a human approval decision | | `GET /tasks/{id}/review` | Get HITL review details | | `POST /tasks/{id}/review` | Submit HITL review decision | ### OpenAI Compatible (3 endpoints) | Endpoint | Description | | --------------------------- | -------------------------------------- | | `POST /v1/chat/completions` | Chat completions (streaming supported) | | `GET /v1/models` | List available models | | `GET /v1/models/{model}` | Get model details | ### Health & Observability (3 endpoints) | Endpoint | Auth Required | Description | | ------------------- | ------------- | ------------------------------------- | | `GET /health` | No | Health check probe | | `GET /readiness` | No | Readiness probe (checks orchestrator) | | `GET /openapi.json` | No | OpenAPI 3.0 specification | ## Authentication **Development**: Authentication is **disabled by default** (`GATEWAY_SKIP_AUTH=1`). **Production**: Set `GATEWAY_SKIP_AUTH=0` and use API keys. ### API Key Header ```bash theme={null} curl -H "X-API-Key: sk_test_123456" \ http://localhost:8080/api/v1/tasks ``` ### SSE Streaming Authentication Browser `EventSource` cannot send custom headers. In production, proxy SSE via your backend and inject `X-API-Key` or Bearer headers. For SSE endpoints, the `api_key` query parameter is supported as a fallback (e.g., `?api_key=sk_...`). ```javascript theme={null} const eventSource = new EventSource( `/api/v1/stream/sse?workflow_id=${id}` ); ``` ## Common Headers ### Request Headers | Header | Required | Description | | ----------------- | ---------- | ------------------------------------------ | | `X-API-Key` | Yes\* | API authentication key | | `Content-Type` | Yes (POST) | `application/json` | | `Idempotency-Key` | No | Prevents duplicate submissions (24h cache) | | `traceparent` | No | W3C trace context for distributed tracing | \*Not required if `GATEWAY_SKIP_AUTH=1` ### Response Headers | Header | Description | | ----------------------- | ----------------------------------------- | | `X-Workflow-ID` | Temporal workflow identifier for tracking | | `X-Session-ID` | Session identifier | | `X-RateLimit-Limit` | Requests allowed per minute | | `X-RateLimit-Remaining` | Requests remaining in current window | | `X-RateLimit-Reset` | Unix timestamp when limit resets | ## Rate Limiting * **Default**: 60 requests/minute per API key * **Status**: `429 Too Many Requests` when exceeded * **Header**: `Retry-After` indicates seconds to wait ```json theme={null} { "error": "Rate limit exceeded", "retry_after": 60 } ``` ## Idempotency Prevent duplicate task submissions using `Idempotency-Key` header: ```bash theme={null} curl -X POST http://localhost:8080/api/v1/tasks \ -H "Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000" \ -H "Content-Type: application/json" \ -d '{"query": "Analyze data"}' ``` **Behavior**: * Same key within 24 hours returns **cached response** * Request body is **ignored** for duplicate keys * Uses Redis for distributed cache ## Error Handling ### HTTP Status Codes | Code | Meaning | Common Causes | | ----- | --------------------- | ------------------------------------- | | `200` | Success | Request completed | | `200` | OK | Task submitted successfully | | `400` | Bad Request | Invalid JSON, missing required fields | | `401` | Unauthorized | Missing or invalid API key | | `404` | Not Found | Task/session not found | | `429` | Too Many Requests | Rate limit exceeded | | `500` | Internal Server Error | Server error | | `502` | Bad Gateway | Orchestrator unavailable | | `503` | Service Unavailable | System overloaded | ### Error Response Format ```json theme={null} { "error": "Task not found" } ``` ## CORS CORS is enabled for development: ``` Access-Control-Allow-Origin: * Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS Access-Control-Allow-Headers: Content-Type, X-API-Key, Idempotency-Key, traceparent ``` **Production**: Configure `Access-Control-Allow-Origin` to specific domains. ## Timeouts | Operation | Timeout | | --------------- | ----------------------------------------------------- | | HTTP Read | 30 seconds | | HTTP Write | 30 seconds | | HTTP Idle | 60 seconds | | Agent Execution | 10 minutes (configurable via `AGENT_TIMEOUT_SECONDS`) | ## Best Practices ### 1. Always Use Idempotency Keys ```python theme={null} import uuid import requests response = requests.post( "http://localhost:8080/api/v1/tasks", headers={ "X-API-Key": "sk_test_123456", "Idempotency-Key": str(uuid.uuid4()) }, json={"query": "Analyze data"} ) ``` ### 2. Implement Exponential Backoff ```python theme={null} import time def submit_with_retry(query, max_retries=3): for attempt in range(max_retries): response = requests.post(url, json={"query": query}) if response.status_code == 429: retry_after = int(response.headers.get("Retry-After", 60)) time.sleep(retry_after) continue return response ``` ### 3. Stream Events for Long Tasks ```python theme={null} import httpx with httpx.stream( "GET", f"http://localhost:8080/api/v1/stream/sse?workflow_id={task_id}", headers={"X-API-Key": "sk_test_123456"} ) as response: for line in response.iter_lines(): if line.startswith("data:"): event = json.loads(line[5:]) print(event["type"]) ``` ### 4. Extract Workflow ID from Response ```python theme={null} response = requests.post( "http://localhost:8080/api/v1/tasks", headers={"X-API-Key": "sk_test_123456"}, json={"query": "Analyze data"} ) workflow_id = response.headers.get("X-Workflow-ID") # Use workflow_id for streaming or Temporal UI ``` ## Quick Example ```bash theme={null} #!/bin/bash API_KEY="sk_test_123456" BASE="http://localhost:8080/api/v1" # Submit task RESPONSE=$(curl -s -X POST "$BASE/tasks" \ -H "X-API-Key: $API_KEY" \ -H "Content-Type: application/json" \ -d '{"query": "What is 2+2?"}') TASK_ID=$(echo $RESPONSE | jq -r '.task_id') echo "Task: $TASK_ID" # Get status curl -s "$BASE/tasks/$TASK_ID" -H "X-API-Key: $API_KEY" | jq # Stream events curl -N "$BASE/stream/sse?workflow_id=$TASK_ID" -H "X-API-Key: $API_KEY" ``` ## Next Steps Task submission endpoint SSE and WebSocket streaming Session management Use the Python SDK # POST /api/v1/tasks/{id}/pause Source: https://docs.shannon.run/en/api/rest/pause-task Pause a running task at the next checkpoint ## Endpoint ```http theme={null} POST http://localhost:8080/api/v1/tasks/{id}/pause ``` ## Description Pauses a running task at the **next workflow checkpoint**. The current activity (LLM call, tool, etc.) is allowed to finish, and then the workflow blocks until it is resumed or cancelled. Use this when you want to temporarily stop long‑running research or supervisor workflows without losing progress. ## Authentication **Required**: Yes Include API key in header: ```http theme={null} X-API-Key: sk_test_123456 ``` ## Request ### Headers | Header | Required | Description | | -------------- | -------- | -------------------------- | | `X-API-Key` | Yes | API authentication key | | `Content-Type` | No | `application/json` if body | | `traceparent` | No | W3C trace context | ### Path Parameters | Parameter | Type | Required | Description | | --------- | ------ | -------- | ------------------------------ | | `id` | string | Yes | Task ID (Temporal workflow ID) | ### Body Parameters | Parameter | Type | Required | Description | | --------- | ------ | -------- | -------------------------- | | `reason` | string | No | Optional pause reason note | ### Request Body Example ```json theme={null} { "reason": "Pause while user reviews intermediate results" } ``` ## Response ### 202 Accepted ```json theme={null} { "success": true, "message": "Pause signal sent", "task_id": "task-123" } ``` ### 404 Not Found ```json theme={null} { "error": "Task not found" } ``` ### 409 Failed Precondition Cannot pause task in invalid state: ```json theme={null} { "error": "cannot pause completed task" } ``` ```json theme={null} { "error": "cannot pause failed task" } ``` ```json theme={null} { "error": "cannot pause cancelled task" } ``` ```json theme={null} { "error": "cannot pause timed out task" } ``` ```json theme={null} { "error": "task is already paused" } ``` ### 401 / 403 ```json theme={null} { "error": "Unauthorized" } ``` or ```json theme={null} { "error": "Forbidden" } ``` ## Notes * Pause takes effect at well‑defined checkpoints (e.g., **pre\_execution**, **pre\_completion**); it does **not** interrupt in‑flight activities. * While paused, SSE streams will emit `workflow.pausing` and `workflow.paused` events with a `checkpoint` field. * Ownership and tenancy are enforced: users can only pause their own tasks. # POST /api/v1/tasks/{id}/resume Source: https://docs.shannon.run/en/api/rest/resume-task Resume a previously paused task ## Endpoint ```http theme={null} POST http://localhost:8080/api/v1/tasks/{id}/resume ``` ## Description Resumes a task that was previously paused via the pause endpoint. The workflow continues from the last checkpoint and proceeds normally. ## Authentication **Required**: Yes Include API key in header: ```http theme={null} X-API-Key: sk_test_123456 ``` ## Request ### Headers | Header | Required | Description | | -------------- | -------- | -------------------------- | | `X-API-Key` | Yes | API authentication key | | `Content-Type` | No | `application/json` if body | | `traceparent` | No | W3C trace context | ### Path Parameters | Parameter | Type | Required | Description | | --------- | ------ | -------- | ------------------------------ | | `id` | string | Yes | Task ID (Temporal workflow ID) | ### Body Parameters | Parameter | Type | Required | Description | | --------- | ------ | -------- | --------------------------- | | `reason` | string | No | Optional resume reason note | ### Request Body Example ```json theme={null} { "reason": "User approved continuation" } ``` ## Response ### 202 Accepted ```json theme={null} { "success": true, "message": "Resume signal sent", "task_id": "task-123" } ``` ### 404 Not Found ```json theme={null} { "error": "Task not found" } ``` ### 409 Failed Precondition Cannot resume task in invalid state: ```json theme={null} { "error": "cannot resume completed task" } ``` ```json theme={null} { "error": "cannot resume failed task" } ``` ```json theme={null} { "error": "cannot resume cancelled task" } ``` ```json theme={null} { "error": "cannot resume timed out task" } ``` ```json theme={null} { "error": "task is not paused" } ``` ### 401 / 403 ```json theme={null} { "error": "Unauthorized" } ``` or ```json theme={null} { "error": "Forbidden" } ``` ## Notes * Only has an effect for tasks that are currently paused; calling `resume` on a non-paused task returns a **409 Failed Precondition** error. * State validation ensures clear error messages for invalid operations (e.g., resuming completed or already-running tasks). * SSE streams emit `workflow.resumed` when a paused workflow continues. # Schedules API Source: https://docs.shannon.run/en/api/rest/schedules Create and manage recurring task schedules ## Overview Shannon supports recurring task execution using Temporal's native Schedule API. Create cron-based schedules that automatically execute tasks at specified intervals. ## Features * **Cron-based scheduling** with timezone support * **Resource limits** to prevent abuse * **Budget control** per execution * **Execution history** with cost tracking * **Pause/Resume/Delete** operations * **Multi-tenant isolation** with user/tenant ownership ## Create Schedule Create a new recurring task schedule. ```bash curl theme={null} curl -X POST http://localhost:8080/api/v1/schedules \ -H "Content-Type: application/json" \ -H "Authorization: Bearer " \ -d '{ "name": "Daily summary", "description": "Generate daily activity summary", "cron_expression": "0 9 * * *", "timezone": "America/New_York", "task_query": "Summarize yesterday'\''s activity", "task_context": { "report_format": "markdown" }, "max_budget_per_run_usd": 5.0, "timeout_seconds": 600 }' ``` ```python Python SDK theme={null} from shannon import ShannonClient client = ShannonClient() schedule = client.create_schedule( name="Daily summary", cron_expression="0 9 * * *", timezone="America/New_York", task_query="Summarize yesterday's activity", max_budget_per_run_usd=5.0 ) print(f"Schedule created: {schedule.id}") ``` ### Request Body | Field | Type | Required | Description | | ------------------------ | ------- | -------- | ---------------------------------------- | | `name` | string | Yes | Schedule name (max 255 chars) | | `description` | string | No | Optional description | | `cron_expression` | string | Yes | Cron schedule (5-field format) | | `timezone` | string | No | IANA timezone (default: UTC) | | `task_query` | string | Yes | Query to execute | | `task_context` | object | No | Additional context for the task | | `max_budget_per_run_usd` | number | No | Max budget per execution (default: 10.0) | | `timeout_seconds` | integer | No | Execution timeout (default: 600) | ### Response ```json theme={null} { "schedule_id": "550e8400-e29b-41d4-a716-446655440000", "message": "Schedule created successfully", "next_run_at": "2025-12-16T09:00:00-05:00" } ``` ## List Schedules List schedules with pagination and filtering. ```bash theme={null} curl "http://localhost:8080/api/v1/schedules?page=1&page_size=50&status=ACTIVE" \ -H "Authorization: Bearer " ``` ### Query Parameters | Parameter | Type | Default | Description | | ----------- | ------- | ------- | --------------------------------- | | `page` | integer | 1 | Page number | | `page_size` | integer | 50 | Items per page (max 100) | | `status` | string | - | Filter by status (ACTIVE, PAUSED) | ### Response ```json theme={null} { "schedules": [ { "id": "550e8400-e29b-41d4-a716-446655440000", "name": "Daily summary", "cron_expression": "0 9 * * *", "timezone": "America/New_York", "status": "ACTIVE", "next_run_at": "2025-12-16T09:00:00-05:00", "total_runs": 45, "successful_runs": 43, "failed_runs": 2 } ], "total": 1, "page": 1, "page_size": 50 } ``` ## Get Schedule Get details of a specific schedule. ```bash theme={null} curl "http://localhost:8080/api/v1/schedules/{id}" \ -H "Authorization: Bearer " ``` ### Response ```json theme={null} { "id": "550e8400-e29b-41d4-a716-446655440000", "name": "Daily summary", "description": "Generate daily activity summary", "cron_expression": "0 9 * * *", "timezone": "America/New_York", "task_query": "Summarize yesterday's activity", "task_context": {"report_format": "markdown"}, "max_budget_per_run_usd": 5.0, "timeout_seconds": 600, "status": "ACTIVE", "created_at": "2025-01-01T00:00:00Z", "updated_at": "2025-01-15T10:00:00Z", "next_run_at": "2025-01-16T09:00:00-05:00", "last_run_at": "2025-01-15T09:00:00-05:00", "total_runs": 45, "successful_runs": 43, "failed_runs": 2 } ``` ## Update Schedule Update an existing schedule. ```bash theme={null} curl -X PUT "http://localhost:8080/api/v1/schedules/{id}" \ -H "Content-Type: application/json" \ -H "Authorization: Bearer " \ -d '{ "cron_expression": "0 10 * * *", "max_budget_per_run_usd": 8.0 }' ``` ### Request Body All fields are optional. Only provided fields will be updated. | Field | Type | Description | | ------------------------ | ------- | ----------------- | | `name` | string | New schedule name | | `description` | string | New description | | `cron_expression` | string | New cron schedule | | `timezone` | string | New timezone | | `task_query` | string | New task query | | `task_context` | object | New context | | `max_budget_per_run_usd` | number | New budget limit | | `timeout_seconds` | integer | New timeout | ## Get Execution History Get execution history for a schedule. ```bash theme={null} curl "http://localhost:8080/api/v1/schedules/{id}/runs?page=1&page_size=20" \ -H "Authorization: Bearer " ``` ### Response ```json theme={null} { "runs": [ { "workflow_id": "wf-abc-123", "query": "Generate daily report", "status": "COMPLETED", "result": "Report generated successfully...", "model_used": "gpt-4o", "provider": "openai", "total_tokens": 12500, "total_cost_usd": 0.45, "duration_ms": 150000, "triggered_at": "2025-01-15T09:00:00Z", "started_at": "2025-01-15T09:00:01Z", "completed_at": "2025-01-15T09:02:30Z" } ], "total_count": 45, "page": 1, "page_size": 20 } ``` ### Response Fields | Field | Type | Description | | ----------------------- | ------- | ----------------------------------------------------- | | `runs` | array | List of schedule run records | | `runs[].workflow_id` | string | Workflow ID for this execution | | `runs[].query` | string | The query that was executed | | `runs[].status` | string | Run status (e.g., COMPLETED, FAILED) | | `runs[].result` | string | Execution result (omitted if empty) | | `runs[].error_message` | string | Error message if failed (omitted if empty) | | `runs[].model_used` | string | LLM model used (omitted if empty) | | `runs[].provider` | string | LLM provider (omitted if empty) | | `runs[].total_tokens` | integer | Total tokens consumed | | `runs[].total_cost_usd` | number | Total cost in USD | | `runs[].duration_ms` | integer | Execution duration in milliseconds (omitted if empty) | | `runs[].triggered_at` | string | When the run was triggered (ISO 8601) | | `runs[].started_at` | string | When execution started (omitted if empty) | | `runs[].completed_at` | string | When execution completed (omitted if empty) | | `total_count` | integer | Total number of runs | | `page` | integer | Current page number | | `page_size` | integer | Number of items per page | ## Pause Schedule Pause a schedule to stop future executions. ```bash theme={null} curl -X POST "http://localhost:8080/api/v1/schedules/{id}/pause" \ -H "Content-Type: application/json" \ -H "Authorization: Bearer " \ -d '{"reason": "Temporary maintenance"}' ``` ### Response ```json theme={null} { "message": "Schedule paused successfully", "paused_at": "2025-01-15T10:00:00Z" } ``` ## Resume Schedule Resume a paused schedule. ```bash theme={null} curl -X POST "http://localhost:8080/api/v1/schedules/{id}/resume" \ -H "Content-Type: application/json" \ -H "Authorization: Bearer " \ -d '{"reason": "Maintenance complete"}' ``` ### Response ```json theme={null} { "message": "Schedule resumed successfully", "next_run_at": "2025-01-16T09:00:00-05:00" } ``` ## Delete Schedule Delete a schedule permanently. ```bash theme={null} curl -X DELETE "http://localhost:8080/api/v1/schedules/{id}" \ -H "Authorization: Bearer " ``` ### Response ```json theme={null} { "message": "Schedule deleted successfully" } ``` ## Cron Expression Format Uses standard cron syntax (5 fields): ``` ┌───────────── minute (0 - 59) │ ┌───────────── hour (0 - 23) │ │ ┌───────────── day of month (1 - 31) │ │ │ ┌───────────── month (1 - 12) │ │ │ │ ┌───────────── day of week (0 - 6) (Sunday to Saturday) │ │ │ │ │ * * * * * ``` ### Common Examples | Expression | Description | | -------------- | ----------------------------- | | `0 9 * * *` | Daily at 9:00 AM | | `0 */4 * * *` | Every 4 hours | | `0 0 * * 1` | Every Monday at midnight | | `30 8 1 * *` | First day of month at 8:30 AM | | `0 12 * * 1-5` | Weekdays at noon | | `0 0 1,15 * *` | 1st and 15th of each month | ## Resource Limits Shannon enforces resource limits to prevent abuse: | Limit | Default | Environment Variable | | ---------------------- | ---------- | ---------------------------- | | Max schedules per user | 50 | `SCHEDULE_MAX_PER_USER` | | Minimum interval | 60 minutes | `SCHEDULE_MIN_INTERVAL_MINS` | | Max budget per run | \$10.00 | `SCHEDULE_MAX_BUDGET_USD` | Schedules that exceed budget limits will be paused automatically. ## Error Responses | Status | Error | Description | | ------ | ----------------------- | ------------------------------- | | 400 | Invalid cron expression | Cron syntax error | | 400 | Interval too short | Below minimum interval | | 403 | Schedule limit reached | Max schedules per user exceeded | | 404 | Schedule not found | Invalid schedule ID | | 409 | Schedule already paused | Cannot pause paused schedule | ## Next Steps One-time task submission Understand workflow execution # Sessions API Source: https://docs.shannon.run/en/api/rest/sessions List, inspect, and delete sessions ## Introduction Manage user sessions used to group related tasks and context. Notes: * Soft delete only. `DELETE` marks a session as deleted (keeps data) and returns 204. * Deleted sessions are excluded from reads and cannot be fetched (404). ## List Sessions ``` GET /api/v1/sessions?limit=20&offset=0 ``` Query params: * `limit` (1–100, default 20) * `offset` (>= 0, default 0) Response (200): ```json theme={null} { "sessions": [ { "session_id": "a0c2b1e2-...", "user_id": "00000000-0000-0000-0000-000000000002", "task_count": 3, "total_tokens_used": 1234, "token_budget": 10000, "created_at": "2025-10-28T06:00:00Z" } ], "total_count": 1 } ``` ## Get Session ``` GET /api/v1/sessions/{sessionId} ``` Path params: * `sessionId` (UUID or external\_id string) Response (200): session metadata including token usage and task count. Errors: 401, 403, 404. ### Session ID Format Shannon supports **dual-ID pattern** for sessions: 1. **UUID Format** (internal): `a0c2b1e2-fd3e-4567-890a-bcdef1234567` 2. **External ID Format** (custom strings): `"user-123-chat"`, `"analytics-session-456"` When you submit a task with a non-UUID `session_id`, Shannon: * Creates an internal UUID for database storage * Stores your custom ID in `context.external_id` * Accepts **either format** in all session API calls **Example**: ```bash theme={null} # Create session with custom ID curl -X POST http://localhost:8080/api/v1/tasks \ -H "X-API-Key: sk_test_123456" \ -d '{"query": "Hello", "session_id": "my-chat-session"}' # Query using same custom ID curl http://localhost:8080/api/v1/sessions/my-chat-session \ -H "X-API-Key: sk_test_123456" ``` This allows natural session naming without managing UUIDs. ## Get Session History ``` GET /api/v1/sessions/{sessionId}/history ``` Path params: * `sessionId` (UUID or external\_id string) Returns all tasks in the session with execution details. Errors: 401, 403, 404. ## Get Session Events (Grouped by Turn) ``` GET /api/v1/sessions/{sessionId}/events?limit=10&offset=0 ``` Path params: * `sessionId` (UUID or external\_id string) Returns chat history grouped by task/turn, including full events per turn (excludes `LLM_PARTIAL`). Query params: * `limit` (1–100, default 10) — number of turns to return * `offset` (>= 0, default 0) — number of turns to skip Response (200): ```json theme={null} { "session_id": "chat-session-123", "count": 2, "turns": [ { "turn": 1, "task_id": "task-001", "user_query": "What is 2+2?", "final_output": "2 + 2 equals 4.", "timestamp": "2025-10-29T06:00:01.513288Z", "events": [ { "workflow_id": "wf-1", "type": "LLM_PROMPT", "message": "...", "timestamp": "..." }, { "workflow_id": "wf-1", "type": "LLM_OUTPUT", "message": "2 + 2 equals 4.", "timestamp": "..." } ], "metadata": { "tokens_used": 150, "execution_time_ms": 8000, "agents_involved": ["planner", "simple-agent"] } } ] } ``` Notes: * `final_output` falls back to the first `LLM_OUTPUT` event if the task result is empty. * `turn` numbers are global (offset+index). * Returns 404 if the session is deleted or not owned by the requester. ## Update Session Title ``` PATCH /api/v1/sessions/{sessionId} ``` Path params: * `sessionId` (UUID or external\_id) Body: ```json theme={null} { "title": "New Title" } ``` Rules: * Title is trimmed; control characters removed. * Max 60 characters (UTF-8 safe). Longer titles are rejected. Responses: * 200 OK with updated title * 400 Invalid request (empty or too long title) * 401 Unauthorized * 403 Forbidden (not the owner) * 404 Not Found ## Delete Session (Soft Delete) ``` DELETE /api/v1/sessions/{sessionId} ``` Path params: * `sessionId` (UUID or external\_id string) Behavior: * Marks the session as deleted (`deleted_at`, `deleted_by`), does not remove data. * Idempotent: always returns 204 for the owner. * Clears session cache to prevent stale reads. Responses: * 204 No Content * 401 Unauthorized * 403 Forbidden (not the owner) * 404 Not Found # Skills API Source: https://docs.shannon.run/en/api/rest/skills List, retrieve, and manage Shannon skills ## Overview The Skills API allows you to list available skills, retrieve skill details, and check version history. Skills are markdown-based workflow definitions that drive single-agent execution with predefined system prompts and configurations. ## Endpoints | Endpoint | Method | Description | | -------------------------------- | ------ | ------------------------- | | `/api/v1/skills` | GET | List all skills | | `/api/v1/skills/{name}` | GET | Get skill details | | `/api/v1/skills/{name}/versions` | GET | Get skill version history | *** ## List Skills ``` GET /api/v1/skills ``` Returns all available skills, optionally filtered by category. ### Query Parameters | Parameter | Type | Required | Description | | ---------- | ------ | -------- | ---------------------------------------------------------------- | | `category` | string | No | Filter by category (e.g., `research`, `development`, `analysis`) | ### Response ```json theme={null} { "skills": [ { "name": "deep-research", "version": "1.0.0", "category": "research", "description": "Comprehensive research with citations and verification", "requires_tools": ["web_search", "web_fetch"], "dangerous": false, "enabled": true }, { "name": "code-review", "version": "1.0.0", "category": "development", "description": "Review code for bugs and improvements", "requires_tools": ["file_read"], "dangerous": false, "enabled": true } ], "count": 2, "categories": ["research", "development", "analysis"] } ``` ### Example ```bash theme={null} # List all skills curl http://localhost:8080/api/v1/skills \ -H "X-API-Key: sk_test_123456" # Filter by category curl "http://localhost:8080/api/v1/skills?category=research" \ -H "X-API-Key: sk_test_123456" ``` *** ## Get Skill ``` GET /api/v1/skills/{name} ``` Returns detailed information about a specific skill, including its content and metadata. ### Path Parameters | Parameter | Type | Required | Description | | --------- | ------ | -------- | ---------------------------------- | | `name` | string | Yes | Skill name (e.g., `deep-research`) | ### Response ```json theme={null} { "skill": { "name": "deep-research", "version": "1.0.0", "category": "research", "description": "Comprehensive research with citations and verification", "content": "# Deep Research Skill\n\nYou are a research agent...", "requires_role": "research", "requires_tools": ["web_search", "web_fetch"], "dangerous": false, "enabled": true, "budget_max": 50000 }, "metadata": { "source_path": "config/skills/research/deep-research.md", "content_hash": "sha256:abc123...", "loaded_at": "2025-01-20T10:00:00Z" } } ``` ### Example ```bash theme={null} curl http://localhost:8080/api/v1/skills/deep-research \ -H "X-API-Key: sk_test_123456" ``` ### Error Responses **404 Not Found**: ```json theme={null} { "error": "Skill not found" } ``` *** ## Get Skill Versions ``` GET /api/v1/skills/{name}/versions ``` Returns all available versions of a skill. ### Path Parameters | Parameter | Type | Required | Description | | --------- | ------ | -------- | ---------------------------------- | | `name` | string | Yes | Skill name (e.g., `deep-research`) | ### Response ```json theme={null} { "name": "deep-research", "versions": [ { "name": "deep-research", "version": "1.0.0", "category": "research", "description": "Comprehensive research with citations", "requires_tools": ["web_search", "web_fetch"], "dangerous": false, "enabled": true }, { "name": "deep-research", "version": "0.9.0", "category": "research", "description": "Beta research skill", "requires_tools": ["web_search"], "dangerous": false, "enabled": false } ], "count": 2 } ``` ### Example ```bash theme={null} curl http://localhost:8080/api/v1/skills/deep-research/versions \ -H "X-API-Key: sk_test_123456" ``` *** ## Using Skills in Tasks To use a skill when submitting a task, include the `skill` parameter: ```bash theme={null} curl -X POST http://localhost:8080/api/v1/tasks \ -H "X-API-Key: sk_test_123456" \ -H "Content-Type: application/json" \ -d '{ "query": "Research the latest developments in quantum computing", "skill": "deep-research" }' ``` ### Version Selection Specify a version using `name@version` syntax: ```bash theme={null} curl -X POST http://localhost:8080/api/v1/tasks \ -H "X-API-Key: sk_test_123456" \ -H "Content-Type: application/json" \ -d '{ "query": "Review this pull request", "skill": "code-review@1.0.0" }' ``` Skills expand into system prompt overrides and optional role assignments. They provide a way to define reusable workflows without modifying code. *** ## Skill Structure Skills are defined as markdown files with YAML frontmatter: ```markdown theme={null} --- name: my-skill version: "1.0.0" category: development description: "Description of what this skill does" requires_role: developer requires_tools: - file_read - file_write dangerous: false enabled: true budget_max: 50000 --- # Skill Instructions You are a specialized agent that... ## Guidelines 1. First, analyze the request 2. Then, execute the appropriate actions 3. Finally, report the results ``` ### Frontmatter Fields | Field | Type | Required | Description | | ---------------- | ------- | -------- | ----------------------------- | | `name` | string | Yes | Skill identifier | | `version` | string | Yes | Semantic version | | `category` | string | No | Grouping category | | `description` | string | No | Human-readable description | | `requires_role` | string | No | Role preset to use | | `requires_tools` | array | No | Required tools | | `dangerous` | boolean | No | Requires elevated permissions | | `enabled` | boolean | No | Whether skill is active | | `budget_max` | integer | No | Max token budget | *** ## Dangerous Skills Skills marked as `dangerous: true` require special authorization: * Admin or Owner role, OR * Explicit `skills:dangerous` scope in API key ```bash theme={null} # Dangerous skill invocation (requires authorization) curl -X POST http://localhost:8080/api/v1/tasks \ -H "X-API-Key: sk_admin_key" \ -H "Content-Type: application/json" \ -d '{ "query": "Execute system maintenance", "skill": "system-admin" }' ``` Dangerous skills can execute privileged operations. Only grant `skills:dangerous` scope to trusted API keys. *** ## Related Use skills when submitting tasks Add tools for skills to use # Streaming Endpoints Source: https://docs.shannon.run/en/api/rest/streaming Real-time event streaming via SSE and WebSocket ## Overview Shannon provides real-time event streaming through Server-Sent Events (SSE) and WebSocket protocols. Use streaming to monitor task execution, display progress, and receive results as they're generated. **Authentication**: Streaming endpoints require the same headers as other APIs. Browsers cannot send custom headers with EventSource. * Development: set `GATEWAY_SKIP_AUTH=1`. * Production: proxy SSE via your backend and inject `X-API-Key` or Bearer headers. For SSE endpoints, the `api_key` query parameter is supported as a fallback (e.g., `?api_key=sk_...`). For all other endpoints, use headers exclusively. **Streaming Limits**: * **Timeout**: Streams automatically close after 5 minutes of inactivity * **Buffer Size**: Maximum 1MB buffered data per connection * **Usage Metadata**: Token counts and costs now available for all LLM providers (OpenAI, Anthropic, Google, Groq, xAI) ## Endpoints | Method | Endpoint | Protocol | Description | | ------ | --------------------------- | --------- | -------------------------------------------- | | `POST` | `/api/v1/tasks/stream` | HTTP+SSE | Submit task and get stream URL (recommended) | | `GET` | `/api/v1/stream/sse` | SSE | Server-Sent Events endpoint | | `GET` | `/api/v1/stream/ws` | WebSocket | WebSocket streaming endpoint | | `GET` | `/api/v1/tasks/{id}/events` | HTTP | Get historical events (paginated) | ## Unified Submit + Stream (Recommended) ### POST /api/v1/tasks/stream The easiest way to submit a task and immediately start streaming its events. This endpoint combines task submission with streaming setup in one call. **Best for frontend applications**: This endpoint is perfect for real-time UIs where you want to show progress immediately after submitting a task. #### Authentication **Required**: Yes ``` X-API-Key: sk_test_123456 ``` Or: ``` Authorization: Bearer YOUR_TOKEN ``` #### Request Body | Parameter | Type | Required | Description | | ------------------- | ------ | -------- | ------------------------------------------------------ | | `query` | string | Yes | Natural language task description | | `session_id` | string | No | Session identifier for multi-turn conversations | | `context` | object | No | Additional context data as key-value pairs | | `model_tier` | string | No | Preferred tier: `small`, `medium`, or `large` | | `model_override` | string | No | Specific model name (canonical; e.g., `gpt-5`) | | `provider_override` | string | No | Force provider (e.g., `openai`, `anthropic`, `google`) | #### Response **Status**: `201 Created` **Body**: ```json theme={"dark"} { "task_id": "task_01HQZX3Y9K8M2P4N5S7T9W2V", "workflow_id": "task_01HQZX3Y9K8M2P4N5S7T9W2V", "stream_url": "/api/v1/stream/sse?workflow_id=task_01HQZX3Y9K8M2P4N5S7T9W2V" } ``` #### Response Fields | Field | Type | Description | | ------------- | ------ | ----------------------------------- | | `task_id` | string | Unique task identifier | | `workflow_id` | string | Task/workflow identifier | | `stream_url` | string | Relative URL to SSE stream endpoint | #### Example: JavaScript/TypeScript ```javascript theme={"dark"} async function submitAndStream(query, onEvent, onComplete, onError) { try { // 1. Submit task and get stream URL const response = await fetch('http://localhost:8080/api/v1/tasks/stream', { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer YOUR_TOKEN' }, body: JSON.stringify({ query: query, session_id: `session-${Date.now()}` }) }); if (!response.ok) { throw new Error(`HTTP ${response.status}: ${response.statusText}`); } const { task_id, workflow_id, stream_url } = await response.json(); console.log(`Task submitted: ${workflow_id}`); // 2. Connect to SSE stream // Note: Browser EventSource does NOT support custom headers. // Use api_key query param, or proxy SSE via your backend. const eventSource = new EventSource( `http://localhost:8080${stream_url}&api_key=${localStorage.getItem('token')}` ); eventSource.onmessage = (e) => { const event = JSON.parse(e.data); onEvent(event); // Check for completion if (event.type === 'WORKFLOW_COMPLETED') { eventSource.close(); onComplete(event); } }; eventSource.onerror = (err) => { console.error('SSE error:', err); eventSource.close(); onError(err); }; return { workflow_id, eventSource }; } catch (error) { onError(error); throw error; } } // Usage submitAndStream( "Analyze Q4 revenue trends", (event) => console.log(`[${event.type}]`, event.message), (final) => console.log("Completed:", final.result), (error) => console.error("Error:", error) ); ``` #### Example: React Hook ```jsx theme={"dark"} import { useState, useCallback, useRef } from 'react'; function useTaskStream(apiUrl = 'http://localhost:8080') { const [events, setEvents] = useState([]); const [isStreaming, setIsStreaming] = useState(false); const [error, setError] = useState(null); const [workflowId, setWorkflowId] = useState(null); const eventSourceRef = useRef(null); const submitTask = useCallback(async (query, sessionId = null) => { setEvents([]); setError(null); setIsStreaming(true); try { // Submit task const response = await fetch(`${apiUrl}/api/v1/tasks/stream`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${localStorage.getItem('token')}` }, body: JSON.stringify({ query, session_id: sessionId || `session-${Date.now()}` }) }); if (!response.ok) throw new Error(`HTTP ${response.status}`); const { task_id, workflow_id, stream_url } = await response.json(); setWorkflowId(workflow_id); // Connect to stream // Note: Browser EventSource does NOT support custom headers. // Use api_key query param, or proxy SSE via your backend. const token = localStorage.getItem('token'); const sseUrl = token ? `${apiUrl}${stream_url}&api_key=${token}` : `${apiUrl}${stream_url}`; const eventSource = new EventSource(sseUrl); eventSourceRef.current = eventSource; eventSource.onmessage = (e) => { const event = JSON.parse(e.data); setEvents(prev => [...prev, event]); if (event.type === 'WORKFLOW_COMPLETED' || event.type === 'ERROR_OCCURRED') { eventSource.close(); setIsStreaming(false); } }; eventSource.onerror = (err) => { setError(err); setIsStreaming(false); eventSource.close(); }; } catch (err) { setError(err); setIsStreaming(false); } }, [apiUrl]); const stopStreaming = useCallback(() => { if (eventSourceRef.current) { eventSourceRef.current.close(); setIsStreaming(false); } }, []); return { submitTask, stopStreaming, events, isStreaming, error, workflowId }; } // Usage in component function TaskStreamDemo() { const { submitTask, stopStreaming, events, isStreaming, error, workflowId } = useTaskStream(); return (
{workflowId &&

Workflow ID: {workflowId}

} {error &&

Error: {error.message}

}
{events.map((event, idx) => (
{event.type}: {event.message || event.agent_id}
))}
{isStreaming && }
); } ``` #### Example: Vue 3 Composition API ```vue theme={"dark"} ``` #### Example: Python ```python theme={"dark"} import httpx import json def submit_and_stream(query: str, api_key: str): """Submit task and stream events.""" # 1. Submit task response = httpx.post( "http://localhost:8080/api/v1/tasks/stream", headers={ "Authorization": f"Bearer {api_key}", "Content-Type": "application/json" }, json={ "query": query, "session_id": f"session-{int(time.time())}" } ) data = response.json() workflow_id = data["workflow_id"] stream_url = data["stream_url"] print(f"Task submitted: {workflow_id}") # 2. Stream events with httpx.stream( "GET", f"http://localhost:8080{stream_url}", headers={"Authorization": f"Bearer {api_key}"}, timeout=None ) as stream_response: for line in stream_response.iter_lines(): if line.startswith("data:"): event = json.loads(line[5:]) print(f"[{event['type']}] {event.get('message', '')}") if event['type'] in ['WORKFLOW_COMPLETED']: return event # Usage result = submit_and_stream("What is the capital of France?", "sk_test_123456") print("Final result:", result.get('result')) ``` **Why use this endpoint?** The unified endpoint ensures you start streaming immediately after submission, preventing any missed events that could occur if you submit and then connect separately. ## Server-Sent Events (SSE) ### GET /api/v1/stream/sse Real-time event streaming using Server-Sent Events. #### Authentication **Required**: Yes ``` X-API-Key: sk_test_123456 ``` #### Query Parameters | Parameter | Type | Required | Description | | --------------- | ------ | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `workflow_id` | string | Yes | Task/workflow identifier | | `types` | string | No | Comma-separated event types to filter | | `last_event_id` | string | No | Resume from specific event ID. Accepts a Redis stream ID (e.g., `1700000000000-0`) or a numeric sequence (e.g., `42`). When numeric, replay includes events with `seq > last_event_id`. | #### Event Format Each event follows SSE specification: ``` id: event: data: ``` #### Example Request ```bash theme={"dark"} curl -N "http://localhost:8080/api/v1/stream/sse?workflow_id=task_abc123" \ -H "X-API-Key: sk_test_123456" ``` #### Example Response ``` id: 1719000000000-0 event: WORKFLOW_STARTED data: {"workflow_id":"task_abc123","type":"WORKFLOW_STARTED","agent_id":"orchestrator","message":"Starting up","timestamp":"2025-10-22T10:30:00Z","seq":1,"stream_id":"1719000000000-0"} id: 1719000001000-0 event: AGENT_THINKING data: {"workflow_id":"task_abc123","type":"AGENT_THINKING","agent_id":"Ryogoku","message":"Analyzing query...","timestamp":"2025-10-22T10:30:01Z","seq":2,"stream_id":"1719000001000-0"} id: 1719000002000-0 event: TOOL_INVOKED data: {"workflow_id":"task_abc123","type":"TOOL_INVOKED","agent_id":"Ryogoku","message":"Calling web_search","payload":{"tool":"web_search","params":{"query":"Python programming"}},"timestamp":"2025-10-22T10:30:02Z","seq":3,"stream_id":"1719000002000-0"} id: 1719000005000-0 event: TOOL_OBSERVATION data: {"workflow_id":"task_abc123","type":"TOOL_OBSERVATION","agent_id":"Ryogoku","message":"web_search completed","payload":{"success":true,"duration_ms":2800},"timestamp":"2025-10-22T10:30:05Z","seq":4,"stream_id":"1719000005000-0"} id: 1719000006000-0 event: thread.message.completed data: {"workflow_id":"task_abc123","agent_id":"simple-agent","response":"Python is a high-level programming language...","metadata":{"model_used":"claude-haiku-4-5-20251001","tokens_used":850,"cost_usd":0.003},"seq":5,"stream_id":"1719000006000-0"} id: 1719000007000-0 event: WORKFLOW_COMPLETED data: {"workflow_id":"task_abc123","type":"WORKFLOW_COMPLETED","message":"Workflow completed","timestamp":"2025-10-22T10:30:07Z","seq":6,"stream_id":"1719000007000-0"} ``` **Event ID format**: The `id` field uses Redis stream IDs (e.g., `1719000000000-0`), NOT simple integers. You must use these exact IDs when reconnecting with `last_event_id`. See [Reconnection](#reconnection) below. ## WebSocket ### GET /api/v1/stream/ws Bidirectional streaming via WebSocket. #### Authentication The gateway authenticates WebSocket connections via headers only (`X-API-Key` or `Authorization`). Browsers cannot set custom headers during the WebSocket handshake. For browser usage: * Run locally with `GATEWAY_SKIP_AUTH=1`, or * Use a reverse proxy that injects the header before forwarding to the gateway. Header-based examples for server environments: Node (ws): ```js theme={"dark"} import WebSocket from 'ws'; const ws = new WebSocket('ws://localhost:8080/api/v1/stream/ws', { headers: { 'X-API-Key': 'sk_test_123456' }, }); ws.on('open', () => { ws.send(JSON.stringify({ type: 'subscribe', workflow_id: 'task_abc123' })); }); ws.on('message', (msg) => { const data = JSON.parse(msg.toString()); console.log('Event:', data.type, data.message); }); ws.on('error', (err) => console.error('WebSocket error:', err)); ``` Python (websockets): ```python theme={"dark"} import asyncio, json, websockets async def main(): async with websockets.connect( 'ws://localhost:8080/api/v1/stream/ws', extra_headers={'X-API-Key': 'sk_test_123456'}, ) as ws: await ws.send(json.dumps({'type': 'subscribe', 'workflow_id': 'task_abc123'})) async for message in ws: data = json.loads(message) print('Event:', data.get('type'), data.get('message')) asyncio.run(main()) ``` Passing the API key in the query string or via an "auth" message after connect is not supported by the gateway. #### Message Types **Client → Server**: ```json theme={"dark"} // Subscribe to workflow { "type": "subscribe", "workflow_id": "task_abc123", "types": ["AGENT_THINKING", "TOOL_INVOKED"] } // Unsubscribe { "type": "unsubscribe", "workflow_id": "task_abc123" } // Ping (keep-alive) { "type": "ping" } ``` **Server → Client**: ```json theme={"dark"} // Event { "type": "AGENT_THINKING", "workflow_id": "task_abc123", "message": "Analyzing query...", "timestamp": "2025-10-22T10:30:00Z" } // Pong (keep-alive response) { "type": "pong" } ``` ## OpenAI-Compatible Streaming Shannon also provides an OpenAI-compatible streaming endpoint at `/v1/chat/completions` that translates Shannon events into the standard OpenAI `chat.completion.chunk` format. This allows you to use OpenAI SDKs directly with Shannon. For complete documentation on the OpenAI-compatible API, including request/response schemas, available models, Shannon-specific extensions (`shannon_events`), and SDK usage examples, see the [OpenAI-Compatible API Reference](/en/api/rest/openai-compatible). ## Event Types ### Core Events | Event Type | Description | When Fired | | -------------------- | ------------------------------ | --------------------- | | `WORKFLOW_STARTED` | Workflow execution began | Start of task | | `WORKFLOW_COMPLETED` | Workflow finished successfully | End of task (success) | ### Agent Events | Event Type | Description | Payload Fields | | ----------------- | ------------------------ | --------------------- | | `AGENT_THINKING` | Agent reasoning/planning | `agent_id`, `message` | | `AGENT_COMPLETED` | Agent finished execution | `agent_id`, `result` | ### Tool Events | Event Type | Description | Payload Fields | | ------------------ | -------------------------- | ---------------- | | `TOOL_INVOKED` | Tool execution started | `tool`, `params` | | `TOOL_OBSERVATION` | Agent observes tool result | `tool`, `result` | ### LLM Events | Event Type | Description | Payload Fields | | -------------------------- | ------------------------- | ------------------------------------------------------------------------------ | | `LLM_PROMPT` | Prompt sent to LLM | `message`, `payload.model`, `payload.provider` | | `thread.message.delta` | Streaming text chunk | `delta`, `agent_id` | | `thread.message.completed` | Final response with usage | `response`, `metadata.model_used`, `metadata.tokens_used`, `metadata.cost_usd` | | `LLM_PARTIAL` | Raw LLM streaming token | `text` | | `LLM_OUTPUT` | Raw LLM final output | `text` | **For most integrations**, listen to `thread.message.delta` (streaming text) and `thread.message.completed` (final result with usage metadata) rather than `LLM_PARTIAL`/`LLM_OUTPUT`. ### Progress & System Events | Event Type | Description | Payload Fields | | ------------------- | -------------------------------- | -------------------------------------------------------------------------------------------- | | `PROGRESS` | Progress update | `progress`, `message` | | `DATA_PROCESSING` | Data processing in progress | `message` | | `WAITING` | Waiting for resources | `message` | | `ERROR_OCCURRED` | Error occurred | `error`, `severity` | | `ERROR_RECOVERY` | Error recovery attempt | `message` | | `WORKSPACE_UPDATED` | Memory/context updated | `message` | | `BUDGET_THRESHOLD` | Budget warning threshold reached | `usage_percent`, `threshold_percent`, `tokens_used`, `tokens_budget`, `level`, `budget_type` | ### Stream Lifecycle Events | Event Type | Description | | ------------ | -------------------------------------------------------------------------------- | | `STREAM_END` | Explicit end-of-stream signal (no more events will be emitted for this workflow) | Over SSE, the `STREAM_END` lifecycle event is delivered as an SSE event named `done` with `data: [DONE]` (plain text, not JSON). Over WebSocket, it appears as a normal JSON event with `"type": "STREAM_END"`. ### Team & Approvals | Event Type | Description | | ---------------------- | -------------------------- | | `TEAM_RECRUITED` | Multi-agent team assembled | | `TEAM_RETIRED` | Team disbanded | | `TEAM_STATUS` | Team coordination update | | `ROLE_ASSIGNED` | Agent role assigned | | `DELEGATION` | Task delegated | | `DEPENDENCY_SATISFIED` | Dependency resolved | | `APPROVAL_REQUESTED` | Human approval needed | | `APPROVAL_DECISION` | Approval decision recorded | ## Code Examples ### Python with httpx (SSE) ```python theme={"dark"} import httpx import json def stream_task_events(task_id: str, api_key: str): """Stream task events using SSE.""" url = f"http://localhost:8080/api/v1/stream/sse?workflow_id={task_id}" with httpx.stream( "GET", url, headers={"X-API-Key": api_key}, timeout=None # No timeout for streaming ) as response: for line in response.iter_lines(): if line.startswith("data:"): data = json.loads(line[5:]) # Remove "data:" prefix yield data # Usage for event in stream_task_events("task_abc123", "sk_test_123456"): print(f"[{event.get('type')}] {event.get('message', '')}") if event.get('type') == 'WORKFLOW_COMPLETED': print("Final result:", event.get('result')) break ``` ### Python - Stream with Event Filtering ```python theme={"dark"} def stream_filtered_events(task_id: str, api_key: str, event_types: list): """Stream only specific event types.""" types_param = ",".join(event_types) url = f"http://localhost:8080/api/v1/stream/sse?workflow_id={task_id}&types={types_param}" with httpx.stream("GET", url, headers={"X-API-Key": api_key}) as response: for line in response.iter_lines(): if line.startswith("data:"): yield json.loads(line[5:]) # Only receive agent thinking and tool events for event in stream_filtered_events( "task_abc123", "sk_test_123456", ["AGENT_THINKING", "TOOL_INVOKED", "TOOL_OBSERVATION"] ): print(f"{event['type']}: {event.get('message', event.get('tool'))}") ``` ### JavaScript/Node.js (SSE) ```javascript theme={"dark"} const EventSource = require('eventsource'); function streamTaskEvents(taskId, apiKey) { const url = `http://localhost:8080/api/v1/stream/sse?workflow_id=${taskId}`; const eventSource = new EventSource(url, { headers: { 'X-API-Key': apiKey } }); eventSource.onmessage = (event) => { const data = JSON.parse(event.data); console.log(`[${data.type}] ${data.message || ''}`); if (data.type === 'WORKFLOW_COMPLETED') { console.log('Final result:', data.result); eventSource.close(); } }; eventSource.onerror = (error) => { console.error('SSE error:', error); eventSource.close(); }; return eventSource; } // Usage const stream = streamTaskEvents('task_abc123', 'sk_test_123456'); // Close manually setTimeout(() => stream.close(), 60000); ``` ### JavaScript/Node.js - WebSocket (ws) ```javascript theme={"dark"} import WebSocket from 'ws'; function connectWebSocket(taskId, apiKey) { const ws = new WebSocket( `ws://localhost:8080/api/v1/stream/ws?workflow_id=${taskId}`, { headers: { 'X-API-Key': apiKey } } ); ws.on('open', () => console.log('✓ Connected')); ws.on('message', (msg) => { const data = JSON.parse(msg.toString()); switch (data.type) { case 'AGENT_THINKING': console.log(`💭 ${data.message}`); break; case 'TOOL_INVOKED': console.log(`🔧 Tool: ${data.tool}`); break; case 'TOOL_OBSERVATION': console.log(`✓ Result: ${data.result}`); break; case 'WORKFLOW_COMPLETED': console.log(`✓ Done: ${data.result}`); ws.close(); break; default: console.log(`[${data.type}] ${data.message || ''}`); } }); ws.on('error', (err) => console.error('❌ Error:', err)); ws.on('close', () => console.log('Connection closed')); return ws; } const ws = connectWebSocket('task_abc123', 'sk_test_123456'); ``` ### Go (SSE) ```go theme={"dark"} package main import ( "bufio" "encoding/json" "fmt" "net/http" "strings" ) type Event struct { Type string `json:"type"` WorkflowID string `json:"workflow_id"` Message string `json:"message,omitempty"` Data map[string]interface{} `json:",inline"` } func streamEvents(taskID, apiKey string) error { url := fmt.Sprintf( "http://localhost:8080/api/v1/stream/sse?workflow_id=%s", taskID, ) req, _ := http.NewRequest("GET", url, nil) req.Header.Set("X-API-Key", apiKey) client := &http.Client{} resp, err := client.Do(req) if err != nil { return err } defer resp.Body.Close() scanner := bufio.NewScanner(resp.Body) for scanner.Scan() { line := scanner.Text() if strings.HasPrefix(line, "data:") { data := strings.TrimPrefix(line, "data:") var event Event json.Unmarshal([]byte(data), &event) fmt.Printf("[%s] %s\n", event.Type, event.Message) if event.Type == "WORKFLOW_COMPLETED" { fmt.Println("✓ Task completed") break } } } return scanner.Err() } func main() { err := streamEvents("task_abc123", "sk_test_123456") if err != nil { fmt.Println("Error:", err) } } ``` ### Bash/curl (SSE) ```bash theme={"dark"} #!/bin/bash API_KEY="sk_test_123456" TASK_ID="$1" curl -N "http://localhost:8080/api/v1/stream/sse?workflow_id=$TASK_ID" \ -H "X-API-Key: $API_KEY" \ | while IFS= read -r line; do if [[ $line == data:* ]]; then # Extract JSON from "data: {...}" JSON="${line#data:}" # Parse and display TYPE=$(echo "$JSON" | jq -r '.type') MESSAGE=$(echo "$JSON" | jq -r '.message // ""') echo "[$(date +%T)] $TYPE: $MESSAGE" # Exit on completion if [[ "$TYPE" == "WORKFLOW_COMPLETED" ]]; then echo "" echo "$JSON" | jq -r '.result' break fi fi done ``` ## Use Cases ### 1. Real-Time Progress Display ```python theme={"dark"} def display_progress(task_id: str, api_key: str): """Display real-time progress to user.""" print(f"Task {task_id} starting...") for event in stream_task_events(task_id, api_key): event_type = event.get('type') if event_type == 'AGENT_THINKING': print(f"💭 {event['message']}") elif event_type == 'TOOL_INVOKED': print(f"🔧 Using tool: {event['tool']}") elif event_type == 'TOOL_OBSERVATION': print(f"✓ Tool completed") elif event_type == 'LLM_PARTIAL': print(event['text'], end='', flush=True) elif event_type == 'WORKFLOW_COMPLETED': print(f"\n\n✓ Done!") return event['result'] ``` ### 2. Log All Events to File ```python theme={"dark"} import json from datetime import datetime def log_events_to_file(task_id: str, api_key: str, log_file: str): """Log all events to JSON Lines file.""" with open(log_file, 'a') as f: for event in stream_task_events(task_id, api_key): # Add timestamp event['logged_at'] = datetime.now().isoformat() # Write as JSON Lines f.write(json.dumps(event) + '\n') f.flush() if event.get('type') == 'WORKFLOW_COMPLETED': break log_events_to_file("task_abc123", "sk_test_123456", "task_events.jsonl") ``` ### 3. Collect Tool Usage Metrics ```python theme={"dark"} def collect_tool_metrics(task_id: str, api_key: str): """Collect metrics about tool usage.""" metrics = { "tools_invoked": 0, "tools_succeeded": 0, "tools_failed": 0, "tool_list": [] } for event in stream_task_events(task_id, api_key): if event.get('type') == 'TOOL_INVOKED': metrics['tools_invoked'] += 1 metrics['tool_list'].append(event['tool']) elif event.get('type') == 'TOOL_OBSERVATION': metrics['tools_succeeded'] += 1 elif event.get('type') == 'ERROR_OCCURRED': metrics['tools_failed'] += 1 elif event.get('type') == 'WORKFLOW_COMPLETED': break return metrics metrics = collect_tool_metrics("task_abc123", "sk_test_123456") print(f"Tools used: {metrics['tool_list']}") print(f"Success rate: {metrics['tools_succeeded']}/{metrics['tools_invoked']}") ``` ### 4. React UI Integration ```javascript theme={"dark"} import { useState, useEffect } from 'react'; function TaskMonitor({ taskId, apiKey }) { const [events, setEvents] = useState([]); const [status, setStatus] = useState('connecting'); useEffect(() => { // Browser EventSource does NOT support custom headers. // Use api_key query param, or proxy SSE via your backend. const eventSource = new EventSource( `http://localhost:8080/api/v1/stream/sse?workflow_id=${taskId}&api_key=${apiKey}` ); eventSource.onopen = () => setStatus('connected'); eventSource.onmessage = (event) => { const data = JSON.parse(event.data); setEvents(prev => [...prev, data]); if (data.type === 'WORKFLOW_COMPLETED') { setStatus('completed'); eventSource.close(); } }; eventSource.onerror = () => { setStatus('error'); eventSource.close(); }; return () => eventSource.close(); }, [taskId, apiKey]); return (

Status: {status}

    {events.map((event, i) => (
  • {event.type}: {event.message || event.tool}
  • ))}
); } ``` ## Deep Research Streaming Deep Research tasks take 2-10 minutes. Use `context.force_research` to trigger Deep Research through the Task API: ```bash theme={"dark"} curl -X POST http://localhost:8080/api/v1/tasks/stream \ -H "Content-Type: application/json" \ -H "X-API-Key: sk_test_123456" \ -d '{ "query": "What are the latest developments in quantum computing?", "session_id": "session-123", "context": {"force_research": true} }' ``` Control research depth with `context.research_strategy`: | Strategy | Depth | Typical Duration | Use Case | | -------------------- | ------- | ---------------- | -------------------------------- | | `quick` | Low | 30s-2min | Simple factual queries | | `standard` (default) | Medium | 2-5min | General research | | `deep` | High | 5-10min | Complex multi-faceted topics | | `academic` | Highest | 5-10min | Citation-heavy academic research | **Chat API vs Task API for Deep Research**: The Chat API (`/v1/chat/completions` with `model: "shannon-deep-research"`) can also trigger Deep Research, but its streaming format does **not** include SSE event IDs — so reconnection is impossible. If your platform has connection time limits (e.g., Vercel 5-min limit) or you need to survive page refreshes, **use the Task API**. ## Heartbeat The server sends a `: ping` SSE comment every 10 seconds to keep connections alive through proxies and load balancers: ``` : ping ``` This is an SSE comment (not a JSON message). If you stop receiving pings, the connection is dead — reconnect immediately. ## Reconnection SSE connections may drop due to network issues, proxy timeouts, or platform limits (e.g., Vercel hobby plan: 5-minute connection limit). Shannon supports resuming from where you left off. **How it works:** 1. Track the `id` field of each received SSE event 2. When disconnected, reconnect with `last_event_id` parameter 3. The server replays all events after that ID (buffered \~256 events, 24h TTL) ```bash theme={"dark"} # Reconnect from where you left off curl -N "http://localhost:8080/api/v1/stream/sse?workflow_id=task_abc123&last_event_id=1719000002000-0" \ -H "X-API-Key: sk_test_123456" ``` ### Proactive Reconnection (Recommended) For platforms with connection time limits, proactively disconnect before the limit: ``` 1. Connect to SSE 2. Start a 4-minute timer (safety margin before 5-min limit) 3. When timer fires: a. Record last received event ID b. Close connection c. Immediately reconnect with last_event_id= 4. Repeat until you receive WORKFLOW_COMPLETED or done event ``` ### Page Refresh / Fallback If the user refreshes the page and you still have the `workflow_id`: 1. Check task status: `GET /api/v1/tasks/{workflow_id}` 2. If `TASK_STATUS_RUNNING` → reconnect to SSE 3. If `TASK_STATUS_COMPLETED` → display the result from the response 4. If `TASK_STATUS_FAILED` → display the error ### Python with Reconnection ```python theme={"dark"} import time def stream_with_reconnect(task_id: str, api_key: str, max_retries: int = 5): """Stream with automatic reconnection using last_event_id.""" last_id = None for attempt in range(max_retries): try: url = f"http://localhost:8080/api/v1/stream/sse?workflow_id={task_id}" if last_id: url += f"&last_event_id={last_id}" with httpx.stream("GET", url, headers={"X-API-Key": api_key}, timeout=None) as response: for line in response.iter_lines(): if line.startswith("id:"): last_id = line[3:].strip() # Track last event ID elif line.startswith("data:"): event = json.loads(line[5:]) yield event if event.get('type') in ('WORKFLOW_COMPLETED', 'STREAM_END'): return except Exception as e: if attempt < max_retries - 1: wait = min(2 ** attempt, 10) print(f"Connection lost, reconnecting in {wait}s (last_id={last_id})...") time.sleep(wait) else: raise ``` ## Best Practices ### 2. Implement Timeout ```python theme={"dark"} import signal def stream_with_timeout(task_id: str, api_key: str, timeout: int = 300): """Stream with timeout.""" def timeout_handler(signum, frame): raise TimeoutError("Stream timeout") signal.signal(signal.SIGALRM, timeout_handler) signal.alarm(timeout) try: for event in stream_task_events(task_id, api_key): yield event if event.get('type') == 'WORKFLOW_COMPLETED': break finally: signal.alarm(0) # Cancel alarm ``` ### 3. Filter Events Client-Side ```python theme={"dark"} def stream_specific_events(task_id: str, api_key: str, event_types: set): """Filter events client-side.""" for event in stream_task_events(task_id, api_key): if event.get('type') in event_types: yield event if event.get('type') == 'WORKFLOW_COMPLETED': yield event break # Only receive agent and tool events for event in stream_specific_events( "task_abc123", "sk_test_123456", {"AGENT_THINKING", "TOOL_INVOKED", "TOOL_OBSERVATION"} ): print(event) ``` ### 4. Resume from Last Event ```python theme={"dark"} def stream_with_resume(task_id: str, api_key: str, last_event_id: str = None): """Resume streaming from specific event.""" url = f"http://localhost:8080/api/v1/stream/sse?workflow_id={task_id}" if last_event_id: url += f"&last_event_id={last_event_id}" # Stream events... ``` Note: `last_event_id` accepts either a Redis stream ID (e.g., `1700000000000-0`) or a numeric sequence (e.g., `42`). When numeric, replay includes events with `seq > last_event_id`. ## Comparison: SSE vs WebSocket vs Polling | Feature | SSE | WebSocket | Polling | | ------------------- | ----------------- | ---------------- | --------------- | | **Direction** | Server → Client | Bidirectional | Client → Server | | **Protocol** | HTTP | WebSocket | HTTP | | **Auto-reconnect** | Yes (browser) | No (manual) | N/A | | **Overhead** | Low | Very Low | High | | **Simplicity** | High | Medium | High | | **Use Case** | Real-time updates | Interactive apps | Simple status | | **Shannon Support** | ✅ Recommended | ✅ Available | ⚠️ Not ideal | ### When to Use Each * **SSE**: Most use cases, real-time monitoring, progress display * **WebSocket**: Interactive applications, bidirectional communication needed * **Polling** (GET /api/v1/tasks/): Legacy systems, no streaming support ## Related Endpoints POST /api/v1/tasks GET /api/v1/tasks/ Use client.stream() ## Notes **Event Retention**: * **Redis**: All events stored for 24 hours (real-time streaming) * **PostgreSQL**: Critical events stored for 90 days (historical queries) * Use `last_event_id` to resume streaming if connection drops **Connection Limits**: * Maximum 100 concurrent streaming connections per API key * 5-minute inactivity timeout (automatic connection close) * 1MB buffer size limit per connection * Consider multiplexing multiple workflows over a single WebSocket # POST /api/v1/tasks Source: https://docs.shannon.run/en/api/rest/submit-task Submit a new task for execution ## Endpoint ``` POST http://localhost:8080/api/v1/tasks ``` ## Description Submits a new task to Shannon for execution. The task is queued immediately and processed asynchronously by the Temporal workflow engine. ## Authentication **Required**: Yes Include API key in header: ``` X-API-Key: sk_test_123456 ``` ## Request ### Headers | Header | Required | Description | Example | | ----------------- | -------- | -------------------------- | -------------------------------------- | | `X-API-Key` | Yes | Authentication key | `sk_test_123456` | | `Content-Type` | Yes | Must be `application/json` | `application/json` | | `Idempotency-Key` | No | Unique key for idempotency | `550e8400-e29b-41d4-a716-446655440000` | | `traceparent` | No | W3C trace context | `00-4bf92f...` | ### Body Parameters | Parameter | Type | Required | Description | | ----------------------- | ------- | -------- | --------------------------------------------------------------------------------------------- | | `query` | string | Yes | Natural language task description | | `session_id` | string | No | Session identifier for multi-turn conversations | | `context` | object | No | Additional context data as key-value pairs | | `mode` | string | No | Workflow routing: `simple`, `standard`, `complex`, or `supervisor` | | `model_tier` | string | No | Preferred tier: `small`, `medium`, or `large` | | `model_override` | string | No | Specific model name (canonical; e.g., `gpt-5`, `claude-sonnet-4-5-20250929`) | | `provider_override` | string | No | Force provider (e.g., `openai`, `anthropic`, `google`) | | `skill` | string | No | Markdown-defined skill to drive single-agent execution ([Skills System](/en/api/rest/skills)) | | `research_strategy` | string | No | Research strategy preset: `quick`, `standard`, `deep`, or `academic` | | `max_iterations` | integer | No | Maximum number of iterations for the task (1-50) | | `max_concurrent_agents` | integer | No | Maximum number of concurrent agents (1-20) | | `enable_verification` | boolean | No | Enable verification step after task completion | ### Request Body Schema **Example 1: General AI-powered execution** ```json theme={null} { "query": "Analyze August website traffic trends", // REQUIRED: Task to execute "session_id": "analytics-session-123", // OPTIONAL: Session ID for multi-turn conversations (auto-generated if omitted) "mode": "supervisor", // OPTIONAL: Workflow routing - "simple", "standard", "complex", or "supervisor" (default: auto-detect) "model_tier": "large", // OPTIONAL: Model size - "small", "medium", or "large" (default: "small") "model_override": "gpt-5", // OPTIONAL: Specific model (canonical id) "provider_override": "openai", // OPTIONAL: Force specific provider "context": { // OPTIONAL: Execution context object "role": "data_analytics", // OPTIONAL: Role preset name (e.g., "analysis", "research", "writer") "system_prompt": "You are a data analyst specializing in website analytics.", // OPTIONAL: Custom system prompt (overrides role preset) "prompt_params": { // OPTIONAL: Arbitrary key-value pairs for prompts/tools/adapters "profile_id": "49598h6e", // EXAMPLE: Custom parameter (passed to tools/adapters) "aid": "fcb1cd29-9104-47b1-b914-31db6ba30c1a", // EXAMPLE: Custom parameter (application ID) "current_date": "2025-10-31" // EXAMPLE: Custom parameter (current date) }, "history_window_size": 75, // OPTIONAL: Max conversation history messages (default: 50) "primers_count": 3, // OPTIONAL: Number of early messages to keep (default: 5) "recents_count": 20, // OPTIONAL: Number of recent messages to keep (default: 15) "compression_trigger_ratio": 0.75, // OPTIONAL: Trigger compression at 75% of window (default: 0.8) "compression_target_ratio": 0.375 // OPTIONAL: Compress to 37.5% of window (default: 0.5) } } ``` **Example 2: Template-only execution (no AI)** ```json theme={null} { "query": "Generate weekly research briefing", // REQUIRED: Task description "session_id": "research-session-456", // OPTIONAL: Session ID "context": { // OPTIONAL: Context object "template": "research_summary", // OPTIONAL: Template name to use "template_version": "1.0.0", // OPTIONAL: Template version (default: latest) "disable_ai": true, // OPTIONAL: Template-only mode, no AI fallback (default: false) "prompt_params": { // OPTIONAL: Parameters for template rendering "week": "2025-W44" // EXAMPLE: Custom parameter for template } } } ``` **Parameter Conflicts to Avoid:** * Don't use both `template` and `template_name` (they're aliases - use `template` only) * **Don't combine `disable_ai: true` with model controls** - Gateway returns 400 error when conflicts detected: * `disable_ai: true` + `model_tier` → 400 * `disable_ai: true` + `model_override` → 400 * `disable_ai: true` + `provider_override` → 400 * Top-level parameters override context equivalents: * Top-level `model_tier` overrides `context.model_tier` * Top-level `model_override` overrides `context.model_override` * Top-level `provider_override` overrides `context.provider_override` * Top-level `skill` overrides `context.skill` * Top-level `research_strategy` overrides `context.research_strategy` ### Context Parameters (`context.*`) Recognized keys: * `role` — role preset (e.g., `analysis`, `research`, `writer`, `ads_research`, `financial_news`, `browser_use`) * `system_prompt` — overrides role prompt; supports `${var}` from `prompt_params` * `prompt_params` — arbitrary parameters for prompts/tools/adapters * `model_tier` — fallback when top‑level not provided * `model_override` — specific model name (canonical; e.g., `gpt-5`, `claude-sonnet-4-5-20250929`) * `provider_override` — force provider (e.g., `openai`, `anthropic`, `google`) * `research_strategy` — *(deprecated: use top-level `research_strategy` instead; context value is ignored when top-level is set)* * `skill` — *(deprecated: use top-level `skill` instead; context value is ignored when top-level is set)* * `template` — template name (alias: `template_name`) * `template_version` — template version * `disable_ai` — template-only mode (no AI fallback) - **cannot be combined with model controls** * Window controls: `history_window_size`, `use_case_preset`, `primers_count`, `recents_count`, `compression_trigger_ratio`, `compression_target_ratio` * **Deep Research 2.0 controls** (when `force_research: true`): * `iterative_research_enabled` — Enable/disable iterative coverage loop (default: `true`) * `iterative_max_iterations` — Max iterations 1-5 (strategy presets seed defaults; otherwise falls back to `3`) * `enable_fact_extraction` — Extract structured facts into metadata (default: `false`) * **Ads Research Platform Toggles** (when `role: "ads_research"`): * `platforms.google` — Enable/disable Google Shopping Ads (default: `true`) * `platforms.yahoo_jp` — Enable/disable Yahoo Japan Ads (default: `true`) * `platforms.meta` — Enable/disable Meta Ad Library (default: `true`) * `platforms.meta_platform` — Meta platform filter: `facebook`, `instagram`, `messenger`, `whatsapp`, or `all` (default: `all`) Rules: * Top-level parameters override context equivalents: `model_tier`, `model_override`, `provider_override`, `skill`, `research_strategy` * `mode` supports: `simple|standard|complex|supervisor` (default: auto-detect) * `model_tier` supports: `small|medium|large` * **Conflict validation**: `disable_ai: true` cannot be combined with `model_tier`, `model_override`, or `provider_override` (returns 400) ### Role Presets Role presets provide specialized system prompts and tool allowlists for different task types. Set via `context.role`: | Role | Description | Available Tools | Use Case | | ----------------------------------------- | ------------------------------------------------------------- | -------------------------------------------------------------------------------- | ----------------------------------------------- | | `generalist` | General-purpose assistant (default) | All tools | Simple queries, general chat | | `analysis` | Analytical assistant with structured reasoning | `web_search`, `file_read` | Data analysis, structured reasoning | | `research` | Research assistant for gathering and synthesizing information | `web_search`, `web_fetch`, `web_subpage_fetch`, `web_crawl` | Information gathering, fact-finding | | `writer` | Technical writer for clear, organized prose | `file_read` | Documentation, content creation | | `critic` | Critical reviewer for identifying flaws and risks | `file_read` | Code review, quality assurance | | `developer` | Developer assistant with filesystem access | `file_read`, `file_write`, `file_list`, `bash`, `python_executor` | Coding, debugging, file operations | | `browser_use` | Browser automation specialist | `browser_*` tools, `web_search` | Web scraping, UI automation, screenshot capture | | `ads_research` **(Shannon Cloud Only)** | Multi-platform ads competitor analysis | `ads_serp_extract`, `ads_transparency_search`, `meta_ad_library`, `yahoo_jp_ads` | Marketing research, competitor ads analysis | | `financial_news` **(Shannon Cloud Only)** | Financial news and sentiment analysis | `news_aggregator`, `alpaca_news`, `sec_filings`, `twitter_sentiment` | Stock news, market sentiment analysis | | `data_analytics` | Data analytics with vendor integrations | Vendor-specific analytics tools | Business intelligence, analytics reporting | **Shannon Cloud Only**: Roles marked as "Shannon Cloud Only" are enterprise features and require a Shannon Cloud deployment with vendor adapter configuration. ## Response ### Success Response **Status**: `200 OK` **Headers**: * `X-Workflow-ID`: Temporal workflow identifier * `X-Session-ID`: Session identifier (auto-generated if not provided) **Body**: ```json theme={null} { "task_id": "string", "status": "string", "message": "string (optional)", "created_at": "timestamp" } ``` ### Response Fields | Field | Type | Description | | ------------ | --------- | ------------------------------------------ | | `task_id` | string | Unique task identifier (also workflow ID) | | `status` | string | Submission status (e.g., `STATUS_CODE_OK`) | | `message` | string | Optional status message | | `created_at` | timestamp | Task creation time (ISO 8601) | ## Examples ### Basic Task Submission ```bash theme={null} curl -X POST http://localhost:8080/api/v1/tasks \ -H "X-API-Key: sk_test_123456" \ -H "Content-Type: application/json" \ -d '{ "query": "What is the capital of France?" }' ``` **Response**: ```json theme={null} { "task_id": "task_01HQZX3Y9K8M2P4N5S7T9W2V", "status": "STATUS_CODE_OK", "message": "Task submitted successfully", "created_at": "2025-10-22T10:30:00Z" } ``` ### Task with Session ID (Multi-Turn) ```bash theme={null} # First turn curl -X POST http://localhost:8080/api/v1/tasks \ -H "X-API-Key: sk_test_123456" \ -H "Content-Type: application/json" \ -d '{ "query": "What is Python?", "session_id": "user-123-chat" }' # Second turn (references previous context) curl -X POST http://localhost:8080/api/v1/tasks \ -H "X-API-Key: sk_test_123456" \ -H "Content-Type: application/json" \ -d '{ "query": "What are its main advantages?", "session_id": "user-123-chat" }' ``` ### Task with Context ```bash theme={null} curl -X POST http://localhost:8080/api/v1/tasks \ -H "X-API-Key: sk_test_123456" \ -H "Content-Type: application/json" \ -d '{ "query": "Summarize this user feedback", "context": { "user_id": "user_12345", "feedback_type": "bug_report", "severity": "high", "product": "mobile_app", "role": "analysis", "model_override": "gpt-5" } }' ``` ### Force Tier (Top‑Level) ```bash theme={null} curl -X POST http://localhost:8080/api/v1/tasks \ -H "X-API-Key: sk_test_123456" \ -H "Content-Type: application/json" \ -d '{ "query": "Complex analysis", "model_tier": "large" }' ``` ### Template‑Only Execution ```bash theme={null} curl -X POST http://localhost:8080/api/v1/tasks \ -H "X-API-Key: sk_test_123456" \ -H "Content-Type: application/json" \ -d '{ "query": "Weekly research briefing", "context": {"template": "research_summary", "template_version": "1.0.0", "disable_ai": true} }' ``` ### Supervisor Mode ```bash theme={null} curl -X POST http://localhost:8080/api/v1/tasks \ -H "X-API-Key: sk_test_123456" \ -H "Content-Type: application/json" \ -d '{ "query": "Assess system reliability", "mode": "supervisor" }' ``` ### Ads Research (Shannon Cloud Only) Multi-platform advertising competitor analysis with platform toggles. ```bash theme={null} # Analyze competitors across all platforms curl -X POST http://localhost:8080/api/v1/tasks \ -H "X-API-Key: sk_test_123456" \ -H "Content-Type: application/json" \ -d '{ "query": "Analyze competitor ads for organic skincare products", "context": { "role": "ads_research" } }' # Google Shopping Ads only curl -X POST http://localhost:8080/api/v1/tasks \ -H "X-API-Key: sk_test_123456" \ -H "Content-Type: application/json" \ -d '{ "query": "Find competitor pricing strategies for wireless earbuds", "context": { "role": "ads_research", "platforms": { "google": true, "yahoo_jp": false, "meta": false } } }' # Meta (Instagram only) + Yahoo Japan curl -X POST http://localhost:8080/api/v1/tasks \ -H "X-API-Key: sk_test_123456" \ -H "Content-Type: application/json" \ -d '{ "query": "Research fashion brand advertising on Instagram and Yahoo Japan", "context": { "role": "ads_research", "platforms": { "google": false, "yahoo_jp": true, "meta": true, "meta_platform": "instagram" } } }' ``` **Platform Defaults**: All platforms are enabled by default. Use `platforms` object to selectively disable platforms or filter Meta by platform (`facebook`, `instagram`, `messenger`, `whatsapp`, `all`). ### Deep Research 2.0 Deep Research 2.0 provides iterative coverage improvement for comprehensive research tasks. ```bash theme={null} # Basic Deep Research (uses default settings) curl -X POST http://localhost:8080/api/v1/tasks \ -H "X-API-Key: sk_test_123456" \ -H "Content-Type: application/json" \ -d '{ "query": "AI trends in 2025", "context": { "force_research": true } }' # Custom iterations (faster, less thorough) curl -X POST http://localhost:8080/api/v1/tasks \ -H "X-API-Key: sk_test_123456" \ -H "Content-Type: application/json" \ -d '{ "query": "Compare major LLM providers", "context": { "force_research": true, "iterative_max_iterations": 2 } }' # Disable iterative mode (use legacy research) curl -X POST http://localhost:8080/api/v1/tasks \ -H "X-API-Key: sk_test_123456" \ -H "Content-Type: application/json" \ -d '{ "query": "Explain machine learning basics", "context": { "force_research": true, "iterative_research_enabled": false } }' ``` **Deep Research 2.0** is enabled by default when `force_research: true`. It uses a multi-stage workflow with coverage evaluation to ensure comprehensive results. Use `iterative_max_iterations` to control depth (1-5, default: 3). ### With Idempotency ```bash theme={null} # Generate idempotency key (use UUID) IDEMPOTENCY_KEY=$(uuidgen) curl -X POST http://localhost:8080/api/v1/tasks \ -H "X-API-Key: sk_test_123456" \ -H "Idempotency-Key: $IDEMPOTENCY_KEY" \ -H "Content-Type: application/json" \ -d '{ "query": "Analyze sales data for Q4" }' ``` ### With Distributed Tracing ```bash theme={null} curl -X POST http://localhost:8080/api/v1/tasks \ -H "X-API-Key: sk_test_123456" \ -H "traceparent: 00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01" \ -H "Content-Type: application/json" \ -d '{ "query": "Research latest AI trends" }' ``` ## Error Responses ### 400 Bad Request **Missing Query**: ```json theme={null} { "error": "Query is required" } ``` **Invalid JSON**: ```json theme={null} { "error": "Invalid request body: unexpected EOF" } ``` ### 401 Unauthorized **Missing API Key**: ```json theme={null} { "error": "Unauthorized" } ``` **Invalid API Key**: ```json theme={null} { "error": "Unauthorized" } ``` ### 429 Too Many Requests ```json theme={null} { "error": "Rate limit exceeded" } ``` **Headers**: * `X-RateLimit-Limit: 100` * `X-RateLimit-Remaining: 0` * `X-RateLimit-Reset: 1609459200` * `Retry-After: 60` ### 500 Internal Server Error ```json theme={null} { "error": "Failed to submit task: database connection failed" } ``` ## Code Examples ### Python with httpx ```python theme={null} import httpx response = httpx.post( "http://localhost:8080/api/v1/tasks", headers={ "X-API-Key": "sk_test_123456", "Content-Type": "application/json" }, json={ "query": "What is the capital of France?" } ) if response.status_code == 200: data = response.json() print(f"Task ID: {data['task_id']}") print(f"Status: {data['status']}") else: print(f"Error: {response.status_code}") print(response.json()) ``` ### Python with requests ```python theme={null} import requests response = requests.post( "http://localhost:8080/api/v1/tasks", headers={ "X-API-Key": "sk_test_123456" }, json={ "query": "Analyze customer sentiment", "context": { "source": "twitter", "date_range": "2025-10-01 to 2025-10-22" } } ) task = response.json() print(f"Task submitted: {task['task_id']}") ``` ### JavaScript/Node.js ```javascript theme={null} const axios = require('axios'); async function submitTask(query) { try { const response = await axios.post( 'http://localhost:8080/api/v1/tasks', { query: query }, { headers: { 'X-API-Key': 'sk_test_123456', 'Content-Type': 'application/json' } } ); console.log('Task ID:', response.data.task_id); console.log('Status:', response.data.status); return response.data; } catch (error) { console.error('Error:', error.response?.data || error.message); throw error; } } submitTask('What is quantum computing?'); ``` ### cURL with Idempotency ```bash theme={null} #!/bin/bash API_KEY="sk_test_123456" IDEMPOTENCY_KEY=$(uuidgen) # Submit task RESPONSE=$(curl -s -X POST http://localhost:8080/api/v1/tasks \ -H "X-API-Key: $API_KEY" \ -H "Idempotency-Key: $IDEMPOTENCY_KEY" \ -H "Content-Type: application/json" \ -d '{ "query": "Analyze Q4 revenue trends" }') echo $RESPONSE | jq TASK_ID=$(echo $RESPONSE | jq -r '.task_id') echo "Track progress: http://localhost:8088/workflows/$TASK_ID" ``` ### Go ```go theme={null} package main import ( "bytes" "encoding/json" "fmt" "net/http" ) type TaskRequest struct { Query string `json:"query"` SessionID string `json:"session_id,omitempty"` Context map[string]interface{} `json:"context,omitempty"` } type TaskResponse struct { TaskID string `json:"task_id"` Status string `json:"status"` Message string `json:"message,omitempty"` } func submitTask(query string) (*TaskResponse, error) { req := TaskRequest{ Query: query, } body, _ := json.Marshal(req) httpReq, _ := http.NewRequest( "POST", "http://localhost:8080/api/v1/tasks", bytes.NewBuffer(body), ) httpReq.Header.Set("X-API-Key", "sk_test_123456") httpReq.Header.Set("Content-Type", "application/json") client := &http.Client{} resp, err := client.Do(httpReq) if err != nil { return nil, err } defer resp.Body.Close() var taskResp TaskResponse json.NewDecoder(resp.Body).Decode(&taskResp) return &taskResp, nil } func main() { task, err := submitTask("What is machine learning?") if err != nil { fmt.Println("Error:", err) return } fmt.Printf("Task ID: %s\n", task.TaskID) fmt.Printf("Status: %s\n", task.Status) } ``` ## Implementation Details ### Workflow Creation When you submit a task: 1. **Gateway receives request** → Validates authentication, rate limits 2. **Generates session ID** → If not provided, auto-generates UUID 3. **Calls Orchestrator gRPC** → `SubmitTask(metadata, query, context)` 4. **Orchestrator starts Temporal workflow** → Durable execution 5. **Response returned** → Task ID, initial status 6. **Task executes asynchronously** → Independent of HTTP connection ### Idempotency Behavior Idempotency keys allow safe retries of task submissions without creating duplicate tasks. **How it works**: 1. **First request** with an `Idempotency-Key`: * Shannon creates the task * Caches the response in Redis with 24-hour TTL * Returns task ID and status 2. **Duplicate requests** (same `Idempotency-Key`): * Shannon detects the cached response * Returns the **same task ID** without creating a new task * Response is identical to the first request 3. **After 24 hours**: * Cache expires * New request with same key creates a new task **Cache Details**: * **Storage**: Redis * **TTL**: 24 hours (86400 seconds) * **Key format**: `idempotency:<16-char-hash>` (SHA-256 of the idempotency key plus user ID, path, and request body) * **Scope**: Per authenticated user (user ID is part of the hash; when auth is disabled the hash is based on the header, path, and body) * **Cached responses**: Only 2xx responses are stored; cached hits include `X-Idempotency-Cached: true` and `X-Idempotency-Key: ` **Body Behavior**: If the request body changes, the cache key changes too, so the gateway treats it as a brand-new request. Duplicate detection only triggers when the header, user, path, and body all match. **Best Practice**: Generate a unique key per unique request body. **Example**: ```python theme={null} import uuid import httpx # Generate unique key idempotency_key = str(uuid.uuid4()) # First request - creates task response1 = httpx.post( "http://localhost:8080/api/v1/tasks", headers={"X-API-Key": "sk_test_123456", "Idempotency-Key": idempotency_key}, json={"query": "Analyze Q4 sales"} ) task_id_1 = response1.json()["task_id"] # Retry with same key - returns same task ID response2 = httpx.post( "http://localhost:8080/api/v1/tasks", headers={"X-API-Key": "sk_test_123456", "Idempotency-Key": idempotency_key}, json={"query": "Analyze Q4 sales"} ) task_id_2 = response2.json()["task_id"] assert task_id_1 == task_id_2 # Same task, no duplicate ``` **When to use**: * Network retry logic (avoid duplicate tasks on timeout) * Webhook deliveries (handle duplicate webhook calls) * Critical operations (payments, data writes) * Background job queues (prevent duplicate scheduling) ### Session Management * **No session\_id**: Auto-generates UUID, fresh context * **With session\_id**: Loads previous conversation history from Redis * **Session persistence**: 30 days default TTL * **Multi-turn conversations**: All tasks with same `session_id` share context ### Context Object The `context` object is stored as metadata and passed to: * Agent execution environment * Tool invocations (can access via `ctx.get("key")`) * Session memory (for reference in future turns) **Example use cases**: * User preferences: `{"language": "spanish", "format": "markdown"}` * Business context: `{"company_id": "acme", "department": "sales"}` * Constraints: `{"max_length": 500, "tone": "formal"}` ## Best Practices ### 1. Always Use Idempotency Keys for Critical Tasks ```python theme={null} import uuid idempotency_key = str(uuid.uuid4()) response = httpx.post( "http://localhost:8080/api/v1/tasks", headers={ "X-API-Key": "sk_test_123456", "Idempotency-Key": idempotency_key }, json={"query": "Process payment for order #12345"} ) ``` ### 2. Use Sessions for Conversations ```python theme={null} session_id = "user-456-chat" # Turn 1 httpx.post(..., json={ "query": "Load sales data for Q4", "session_id": session_id }) # Turn 2 (references Q4 data from Turn 1) httpx.post(..., json={ "query": "Compare it to Q3", "session_id": session_id }) ``` ### 3. Provide Rich Context ```python theme={null} httpx.post(..., json={ "query": "Analyze this customer feedback", "context": { "customer_id": "cust_789", "subscription_tier": "enterprise", "account_age_days": 365, "previous_tickets": 3, "sentiment_history": ["positive", "neutral", "negative"] } }) ``` ### 4. Handle Errors Gracefully ```python theme={null} try: response = httpx.post(..., timeout=30.0) response.raise_for_status() task = response.json() except httpx.TimeoutException: print("Request timed out") except httpx.HTTPStatusError as e: if e.response.status_code == 429: retry_after = int(e.response.headers.get("Retry-After", 60)) time.sleep(retry_after) # Retry... else: print(f"Error: {e.response.json()}") ``` ### 5. Store Task IDs for Tracking ```python theme={null} response = httpx.post(...) task_id = response.json()["task_id"] workflow_id = response.headers["X-Workflow-ID"] # Save to database db.tasks.insert({ "task_id": task_id, "workflow_id": workflow_id, "user_id": "user_123", "query": "...", "created_at": datetime.now() }) # Later: check status status = httpx.get(f"http://localhost:8080/api/v1/tasks/{task_id}") ``` ## Submit + Stream in One Call **Need real-time updates?** Use `POST /api/v1/tasks/stream` instead to submit a task and get a stream URL in one call. Perfect for frontend applications that need immediate progress updates. See [Unified Submit + Stream](/en/api/rest/streaming#unified-submit-stream-recommended) for examples. ## Related Endpoints POST /api/v1/tasks/stream (recommended for UIs) GET /api/v1/tasks/ Real-time task events GET /api/v1/tasks Use the SDK instead # Swarm Workflow API Source: https://docs.shannon.run/en/api/rest/swarm API reference for submitting and monitoring swarm multi-agent tasks ## Overview The swarm workflow is triggered by setting `force_swarm: true` in the task context. It uses the same `POST /api/v1/tasks` endpoint as all other workflows -- no separate endpoint is needed. Swarm mode decomposes your query into subtasks, spawns persistent agents that work in parallel with inter-agent messaging, and synthesizes results into a unified response. ## Submitting a Swarm Task ### Endpoint ``` POST http://localhost:8080/api/v1/tasks ``` ### Request Body ```json theme={null} { "query": "Your complex multi-faceted task", "session_id": "optional-session-id", "context": { "force_swarm": true } } ``` ### Swarm-Specific Context Parameters | Parameter | Type | Default | Description | | ------------- | ------- | ------- | ---------------------------------------------------------- | | `force_swarm` | boolean | `false` | **Required** to trigger swarm workflow | | `model_tier` | string | (auto) | Model tier for agent execution: `small`, `medium`, `large` | All standard task parameters (`session_id`, `mode`, `model_tier`, `model_override`, `provider_override`) work with swarm tasks. The `force_swarm` flag must be set inside the `context` object, not as a top-level parameter. The swarm must also be enabled in the server configuration (`workflows.swarm.enabled: true` in `features.yaml`). ### Example: Basic Swarm Task ```bash cURL theme={null} curl -X POST http://localhost:8080/api/v1/tasks \ -H "Content-Type: application/json" \ -d '{ "query": "Compare AI chip markets across US, Japan, and South Korea", "session_id": "swarm-demo", "context": { "force_swarm": true } }' ``` ```python Python SDK theme={null} from shannon import ShannonClient client = ShannonClient(base_url="http://localhost:8080") handle = client.submit_task( "Compare AI chip markets across US, Japan, and South Korea", force_swarm=True, session_id="swarm-demo", ) ``` ```python Python (httpx) theme={null} import httpx response = httpx.post( "http://localhost:8080/api/v1/tasks", json={ "query": "Compare AI chip markets across US, Japan, and South Korea", "session_id": "swarm-demo", "context": {"force_swarm": True}, }, ) task = response.json() print(f"Task ID: {task['task_id']}") ``` ### Response ```json theme={null} { "task_id": "task-abc123...", "status": "STATUS_CODE_OK", "message": "Task submitted successfully. Session: swarm-demo", "created_at": "2025-11-10T10:00:00Z" } ``` **Headers:** * `X-Workflow-ID`: Temporal workflow identifier (same as `task_id`) * `X-Session-ID`: Session identifier ## Submit + Stream Use the combined endpoint to submit and get a stream URL in one call: ``` POST http://localhost:8080/api/v1/tasks/stream ``` ```bash cURL theme={null} curl -s -X POST http://localhost:8080/api/v1/tasks/stream \ -H "Content-Type: application/json" \ -d '{ "query": "Analyze competitive landscape of major cloud AI platforms", "context": { "force_swarm": true } }' | jq ``` ```python Python SDK theme={null} from shannon import ShannonClient client = ShannonClient(base_url="http://localhost:8080") handle, stream_url = client.submit_and_stream( "Analyze competitive landscape of major cloud AI platforms", force_swarm=True, ) print(f"Stream URL: {stream_url}") ``` **Response (201 Created):** ```json theme={null} { "workflow_id": "task-def456...", "task_id": "task-def456...", "stream_url": "/api/v1/stream/sse?workflow_id=task-def456..." } ``` ## Monitoring Swarm Progress ### SSE Event Stream ``` GET http://localhost:8080/api/v1/stream/sse?workflow_id={workflow_id} ``` ### Swarm-Specific Events | Event Type | `agent_id` | Description | | ------------------------- | -------------------------- | --------------------------------------------------------------- | | `WORKFLOW_STARTED` | `swarm-supervisor` | Swarm workflow initialized | | `PROGRESS` (planning) | `swarm-supervisor` | Task decomposition in progress | | `LEAD_DECISION` | `swarm-lead` | Lead made a coordination decision (spawn, assign, revise, etc.) | | `TASKLIST_UPDATED` | `swarm-lead` | Task dependency graph changed | | `TEAM_STATUS` | `swarm-lead` | Team composition changed (agent spawned or shut down) | | `AGENT_STARTED` | Agent name (e.g., `takao`) | Individual agent began execution | | `PROGRESS` (iteration) | Agent name | Agent iteration progress | | `AGENT_COMPLETED` | Agent name | Individual agent finished | | `PROGRESS` (synthesizing) | `swarm-supervisor` | Combining results from all agents | | `WORKFLOW_COMPLETED` | `swarm-supervisor` | Final synthesis complete | ### Example SSE Output ``` data: {"type":"WORKFLOW_STARTED","agent_id":"swarm-supervisor","message":"Assigning a team of agents","timestamp":"..."} data: {"type":"PROGRESS","agent_id":"swarm-supervisor","message":"Planning approach","timestamp":"..."} data: {"type":"PROGRESS","agent_id":"swarm-supervisor","message":"Assigning 3 agents","timestamp":"..."} data: {"type":"AGENT_STARTED","agent_id":"takao","message":"Agent takao started","timestamp":"..."} data: {"type":"PROGRESS","agent_id":"takao","message":"Agent takao progress: iteration 1/25, action: tool_call","timestamp":"..."} data: {"type":"AGENT_COMPLETED","agent_id":"takao","message":"Agent takao completed","timestamp":"..."} data: {"type":"PROGRESS","agent_id":"swarm-supervisor","message":"Combining findings from 3 agents","timestamp":"..."} data: {"type":"WORKFLOW_COMPLETED","agent_id":"swarm-supervisor","message":"All done","timestamp":"..."} ``` ## Task Status Response ``` GET http://localhost:8080/api/v1/tasks/{task_id} ``` ### Swarm Metadata When a swarm workflow completes, the status response includes swarm-specific metadata: ```json theme={null} { "task_id": "task-abc123...", "workflow_id": "task-abc123...", "status": "TASK_STATUS_COMPLETED", "result": "## AI Chip Market Comparison\n\n...", "query": "Compare AI chip markets across US, Japan, and South Korea", "session_id": "swarm-demo", "mode": "standard", "model_used": "claude-haiku-4-5-20251001", "provider": "anthropic", "metadata": { "workflow_type": "swarm", "total_agents": 3, "total_tokens": 598235, "model_breakdown": [ { "model": "claude-haiku-4-5-20251001", "provider": "anthropic", "executions": 38, "tokens": 297524, "cost_usd": 0.372 }, { "model": "shannon_web_search", "provider": "shannon-scraper", "executions": 12, "tokens": 90000, "cost_usd": 0.048 } ] }, "usage": { "input_tokens": 289164, "output_tokens": 309071, "total_tokens": 598235, "estimated_cost": 0.780 } } ``` ### Metadata Fields | Field | Type | Description | | --------------------------------------- | ------- | -------------------------------------------------- | | `metadata.workflow_type` | string | Always `"swarm"` for swarm workflows | | `metadata.total_agents` | integer | Total agents that participated (initial + dynamic) | | `metadata.total_tokens` | integer | Total tokens consumed across all agents | | `metadata.model_breakdown` | array | Per-model execution summary | | `metadata.model_breakdown[].model` | string | Model identifier | | `metadata.model_breakdown[].provider` | string | Provider name | | `metadata.model_breakdown[].executions` | integer | Number of LLM calls with this model | | `metadata.model_breakdown[].tokens` | integer | Tokens consumed by this model | | `metadata.model_breakdown[].cost_usd` | number | Estimated cost in USD | ## Server Configuration Swarm parameters are configured in `config/features.yaml` under `workflows.swarm`: ```yaml theme={null} workflows: swarm: enabled: true # Enable/disable swarm routing max_agents: 10 # Total agent cap (initial + dynamic) max_iterations_per_agent: 25 # Max reason-act loops per agent agent_timeout_seconds: 1800 # Per-agent wall-clock timeout (30 min) max_messages_per_agent: 20 # P2P message cap per agent workspace_snippet_chars: 800 # Max chars per workspace entry in prompt workspace_max_entries: 5 # Recent entries shown per topic max_total_llm_calls: 200 # Global LLM call budget max_total_tokens: 1000000 # Global token budget (1M) max_wall_clock_minutes: 30 # Max wall-clock time ``` | Parameter | Default | Description | | -------------------------- | --------- | -------------------------------------------------- | | `enabled` | `true` | Must be `true` for `force_swarm` to work | | `max_agents` | `10` | Total cap including dynamically spawned helpers | | `max_iterations_per_agent` | `25` | Per-agent iteration limit | | `agent_timeout_seconds` | `1800` | 30-minute per-agent timeout | | `max_messages_per_agent` | `20` | Prevents P2P message flooding | | `workspace_snippet_chars` | `800` | Controls token usage from workspace context | | `workspace_max_entries` | `5` | Limits workspace entries per topic in agent prompt | | `max_total_llm_calls` | `200` | Maximum LLM calls across all agents | | `max_total_tokens` | `1000000` | Maximum tokens consumed across all agents | | `max_wall_clock_minutes` | `30` | Maximum wall-clock time for the entire swarm | ## Error Handling and Fallback ### Partial Failure If some agents fail but at least one succeeds, the swarm workflow still produces a result using the successful agents' outputs. If all agents fail, the response includes an error: ```json theme={null} { "task_id": "task-xyz...", "status": "TASK_STATUS_COMPLETED", "result": "", "error": "All 3 agents failed — no results to synthesize", "metadata": { "workflow_type": "swarm", "total_agents": 3, "total_tokens": 45200, "cost_usd": 0.058 } } ``` ### Automatic Fallback If the entire swarm workflow fails (decomposition error, all agents fail, etc.), Shannon automatically falls back to standard workflow routing (DAG or Supervisor). The `force_swarm` flag is removed from context to prevent recursive failures. ## Related Endpoints POST /api/v1/tasks (full reference) GET /api/v1/tasks/ SSE event streaming POST /api/v1/tasks//cancel # Tools API Source: https://docs.shannon.run/en/api/rest/tools List, inspect, and execute tools registered in the Shannon platform ## Overview The Tools API provides direct access to the tool registry in Shannon. You can list available tools, inspect their parameter schemas, and execute tools directly without going through a full task workflow. ## Features * **Tool discovery** — browse all available tools with category filtering * **JSON Schema inspection** — retrieve full parameter schemas for any tool * **Direct execution** — invoke tools outside of agent workflows * **Dangerous tool blocking** — hazardous tools are automatically hidden and blocked at the gateway level * **Usage tracking** — token consumption and cost are recorded per execution * **Session context** — optionally bind execution to a session (sanitized to `session_id` and `user_id` only) ## Security Model Shannon enforces a strict safety boundary around tools marked as `dangerous`: * **List endpoint** automatically applies `exclude_dangerous=true` — dangerous tools are never exposed * **Get and Execute endpoints** return `403 Forbidden` if the requested tool is marked dangerous * Tool metadata is cached in-memory for 5 minutes per gateway instance * The HTTP client timeout for tool execution is 120 seconds Dangerous tools cannot be accessed through any public API endpoint. This restriction is enforced at the gateway level and cannot be overridden by clients. ## List Tools List all available tools. Dangerous tools are automatically excluded. ```bash curl theme={null} curl http://localhost:8080/api/v1/tools \ -H "Authorization: Bearer " ``` ```python Python SDK theme={null} from shannon import ShannonClient client = ShannonClient() tools = client.list_tools() for tool in tools: print(f"{tool.name} ({tool.category}) — {tool.description}") ``` ### Query Parameters | Parameter | Type | Required | Description | | ---------- | ------ | -------- | ----------------------------------------------------- | | `category` | string | No | Filter tools by category (e.g., `"search"`, `"code"`) | ### Example with Category Filter ```bash curl theme={null} curl "http://localhost:8080/api/v1/tools?category=search" \ -H "Authorization: Bearer " ``` ```python Python SDK theme={null} from shannon import ShannonClient client = ShannonClient() search_tools = client.list_tools(category="search") ``` ### Response ```json theme={null} [ { "name": "web_search", "description": "Search the web for current information", "category": "search", "version": "1.0", "parameters": { "type": "object", "properties": { "query": { "type": "string", "description": "Search query" }, "max_results": { "type": "integer", "description": "Maximum number of results", "default": 10 } }, "required": ["query"] }, "timeout_seconds": 30, "cost_per_use": 0.004 } ] ``` ## Get Tool Get metadata and parameter schema for a specific tool. ```bash curl theme={null} curl http://localhost:8080/api/v1/tools/web_search \ -H "Authorization: Bearer " ``` ```python Python SDK theme={null} from shannon import ShannonClient client = ShannonClient() tool = client.get_tool("web_search") print(f"{tool.name} v{tool.version}") print(f"Parameters: {tool.parameters}") ``` ### Path Parameters | Parameter | Type | Required | Description | | --------- | ------ | -------- | ----------- | | `name` | string | Yes | Tool name | ### Response ```json theme={null} { "name": "web_search", "description": "Search the web for current information", "category": "search", "version": "1.0", "parameters": { "type": "object", "properties": { "query": { "type": "string", "description": "Search query" }, "max_results": { "type": "integer", "description": "Maximum number of results", "default": 10 } }, "required": ["query"] }, "timeout_seconds": 30, "cost_per_use": 0.004 } ``` ## Execute Tool Execute a tool directly with the provided arguments. ```bash curl theme={null} curl -X POST http://localhost:8080/api/v1/tools/web_search/execute \ -H "Content-Type: application/json" \ -H "Authorization: Bearer " \ -d '{ "arguments": { "query": "latest AI news", "max_results": 5 }, "session_id": "optional-session-id" }' ``` ```python Python SDK theme={null} from shannon import ShannonClient client = ShannonClient() result = client.execute_tool( "web_search", arguments={"query": "latest AI news", "max_results": 5}, session_id="optional-session-id", ) print(f"Success: {result.success}") print(f"Output: {result.text}") print(f"Execution time: {result.execution_time_ms}ms") print(f"Cost: ${result.usage.cost_usd}") ``` ### Path Parameters | Parameter | Type | Required | Description | | --------- | ------ | -------- | ----------- | | `name` | string | Yes | Tool name | ### Request Body | Field | Type | Required | Description | | ------------ | ------ | -------- | ------------------------------------------------------------ | | `arguments` | object | Yes | Tool-specific arguments matching the tool's parameter schema | | `session_id` | string | No | Session ID for context binding | When a `session_id` is provided, only `session_id` and `user_id` are passed to the tool. Other session fields such as `tenant_id` are stripped for security. ### Response ```json theme={null} { "success": true, "output": { "results": [ { "title": "Major AI Breakthrough Announced", "url": "https://example.com/ai-news", "snippet": "Researchers announce a significant advancement in..." } ] }, "text": "Found 5 results for 'latest AI news'", "error": null, "metadata": { "source": "searchapi" }, "execution_time_ms": 1234, "usage": { "tokens": 7500, "cost_usd": 0.004 } } ``` ### Response Fields | Field | Type | Description | | ------------------- | -------------- | -------------------------------------------- | | `success` | boolean | Whether the execution completed successfully | | `output` | object | Structured output from the tool | | `text` | string | Human-readable summary of the result | | `error` | string \| null | Error message if execution failed | | `metadata` | object | Tool-specific metadata (e.g., data source) | | `execution_time_ms` | integer | Execution duration in milliseconds | | `usage.tokens` | integer | Token count for this execution | | `usage.cost_usd` | number | Estimated cost in USD | Usage is recorded asynchronously (fire-and-forget) to the `token_usage` table. Token counts are synthetic: `max(100, int(cost_usd / 0.000002))`. ## Tool Response Object | Field | Type | Description | | ----------------- | -------------------- | ---------------------------------------------------- | | `name` | string | Tool identifier | | `description` | string | Human-readable description | | `category` | string | Tool category (e.g., `"search"`, `"code"`, `"data"`) | | `version` | string | Tool version | | `parameters` | object (JSON Schema) | Parameter schema for the tool | | `timeout_seconds` | integer | Maximum execution time allowed | | `cost_per_use` | number | Estimated cost per invocation in USD | ## Error Responses | Status | Error | Description | | ------ | -------------- | -------------------------------------------------- | | 400 | Bad request | Missing tool name or invalid request body | | 401 | Unauthorized | Missing or invalid authentication | | 403 | Forbidden | Tool is marked as dangerous and cannot be accessed | | 404 | Tool not found | No tool with the specified name exists | | 502 | Bad gateway | LLM service (tool backend) is unavailable | All errors follow the standard format: ```json theme={null} { "error": "error message here" } ``` ### Dangerous Tool Error Attempting to access a dangerous tool via Get or Execute returns: ```json theme={null} { "error": "Tool not available via direct execution" } ``` ## Next Steps Learn how to create and register custom tools Configure agents that use tools automatically # Workspace Files Source: https://docs.shannon.run/en/api/rest/workspace-files Access and download files from session workspaces ## Overview The Workspace Files API provides access to files generated during task execution within a session workspace. Agents can produce artifacts such as scripts, analysis results, or data exports during execution -- this API lets you browse and retrieve those files. ## Endpoints | Endpoint | Method | Description | | ---------------------------------------------- | ------ | --------------------------- | | `/api/v1/sessions/{sessionId}/files` | GET | List files in the workspace | | `/api/v1/sessions/{sessionId}/files/{path...}` | GET | Download a specific file | *** ## Security Shannon enforces strict path security on all workspace file operations: * **Path traversal protection** -- requests containing `..` or absolute paths are rejected with `400 Bad Request` * **Symlink validation** -- symbolic links pointing outside the workspace are blocked * **File size limit** -- downloads are capped at **100 MB** per file Path traversal attempts (e.g., `../../etc/passwd`) are blocked at the gateway level. All paths are resolved relative to the session workspace root. *** ## List Workspace Files List files and directories within a session workspace. ```bash curl theme={null} curl "http://localhost:8080/api/v1/sessions/{sessionId}/files" \ -H "X-API-Key: sk_test_123456" ``` ```python Python theme={null} import requests resp = requests.get( "http://localhost:8080/api/v1/sessions/{session_id}/files", headers={"X-API-Key": "sk_test_123456"}, ) data = resp.json() for f in data["files"]: print(f"{f['name']} {'DIR' if f['is_dir'] else f['size']} bytes") ``` ### Path Parameters | Parameter | Type | Required | Description | | ----------- | ------ | -------- | --------------------------------- | | `sessionId` | string | Yes | Session ID (UUID or external\_id) | ### Query Parameters | Parameter | Type | Required | Description | | ----------- | ------- | -------- | ---------------------------------------------------------- | | `path` | string | No | Subdirectory path to list (defaults to workspace root `/`) | | `recursive` | boolean | No | If `true`, list files recursively including subdirectories | ### Example with Subdirectory ```bash curl theme={null} curl "http://localhost:8080/api/v1/sessions/{sessionId}/files?path=output&recursive=true" \ -H "X-API-Key: sk_test_123456" ``` ```python Python theme={null} import requests resp = requests.get( "http://localhost:8080/api/v1/sessions/{session_id}/files", params={"path": "output", "recursive": True}, headers={"X-API-Key": "sk_test_123456"}, ) for f in resp.json()["files"]: print(f"{f['path']} {f['size']} bytes") ``` ### Response ```json theme={null} { "session_id": "abc-123", "path": "/", "files": [ { "name": "analysis.py", "path": "/analysis.py", "size": 1234, "is_dir": false, "modified_at": "2026-03-10T10:00:00Z" }, { "name": "output", "path": "/output", "size": 0, "is_dir": true, "modified_at": "2026-03-10T10:05:00Z" }, { "name": "results.csv", "path": "/output/results.csv", "size": 56789, "is_dir": false, "modified_at": "2026-03-10T10:10:00Z" } ] } ``` ### Response Fields | Field | Type | Description | | --------------------- | ------- | -------------------------------------- | | `session_id` | string | Session identifier | | `path` | string | The listed directory path | | `files` | array | List of file entries | | `files[].name` | string | File or directory name | | `files[].path` | string | Full path relative to workspace root | | `files[].size` | integer | File size in bytes (0 for directories) | | `files[].is_dir` | boolean | Whether the entry is a directory | | `files[].modified_at` | string | Last modification timestamp (ISO 8601) | *** ## Download File Download a specific file from the session workspace. The response Content-Type is automatically detected based on file content. ```bash curl theme={null} curl "http://localhost:8080/api/v1/sessions/{sessionId}/files/analysis.py" \ -H "X-API-Key: sk_test_123456" \ -o analysis.py ``` ```python Python theme={null} import requests resp = requests.get( "http://localhost:8080/api/v1/sessions/{session_id}/files/output/results.csv", headers={"X-API-Key": "sk_test_123456"}, ) with open("results.csv", "wb") as f: f.write(resp.content) ``` ### Path Parameters | Parameter | Type | Required | Description | | ----------- | ------ | -------- | ---------------------------------------------------------------- | | `sessionId` | string | Yes | Session ID (UUID or external\_id) | | `path...` | string | Yes | Full file path within the workspace (e.g., `output/results.csv`) | ### Response * **Content-Type**: Automatically detected (e.g., `text/plain`, `application/octet-stream`, `text/csv`) * **Body**: Raw file content If the Firecracker executor is unavailable, the gateway falls back to reading files from the local filesystem. This fallback is transparent to the client but may return a `502` if neither source is accessible. *** ## Error Responses | Status | Description | | ------ | ----------------------------------------------------------------------------- | | 400 | Invalid session ID or path traversal attempt (`..` or absolute path detected) | | 401 | Unauthorized -- missing or invalid API key | | 404 | Session workspace not found, or file does not exist | | 413 | File exceeds 100 MB size limit | | 502 | Firecracker executor unavailable and local fallback also failed | All errors follow the standard format: ```json theme={null} { "error": "error message here" } ``` ### Example Error: Path Traversal ```json theme={null} { "error": "invalid path: traversal not allowed" } ``` ### Example Error: File Too Large ```json theme={null} { "error": "file exceeds maximum size limit (100MB)" } ``` *** ## Related Manage sessions and view session history How agents generate workspace artifacts # Components Source: https://docs.shannon.run/en/architecture/components Shannon system components and their interactions ## System Components This page is under construction as part of Phase 2 documentation expansion. Shannon is built as a distributed system with multiple specialized components: ## Core Services ### Gateway (Go) * **Port**: 8080 * **Role**: REST API gateway, authentication, rate limiting * **Technology**: Go, Chi router * **Scaling**: Stateless, horizontal ### Orchestrator (Go) * **Port**: 50052 (gRPC), 8081 (admin) * **Role**: Task coordination, workflow management * **Technology**: Go, Temporal, gRPC * **Scaling**: Stateful via Temporal workflows ### Agent Core (Rust) * **Port**: 50051 (gRPC) * **Role**: Agent execution, tool invocation, WASI sandboxing * **Technology**: Rust, Wasmtime, gRPC * **Scaling**: Horizontal ### LLM Service (Python) * **Port**: 8000 * **Role**: LLM provider gateway, prompt management * **Technology**: Python, FastAPI, litellm * **Scaling**: Horizontal LLM Service notes: * GPT‑5 models are routed via the OpenAI Responses API, and the server prefers `output_text` when present. This avoids empty results when chat responses return structured content. * The `/agent/query` HTTP endpoint expects `allowed_tools`: * Omit/`null`: role presets may enable tools * `[]`: tools disabled * `["name", …]`: only listed tools available (built‑in, OpenAPI, MCP by registry name) ### Desktop App (Tauri + Next.js) * **Port**: — (native app, connects to Gateway :8080) * **Role**: Real-time monitoring UI (tasks, runs, events) * **Technology**: Next.js, Tauri, SSE/WebSocket * **Scaling**: Client-side; backend services scale independently ## Data Stores ### PostgreSQL * **Port**: 5432 * **Purpose**: Task metadata, events, sessions, users * **Schema**: \~15 tables (task\_executions, event\_logs, sessions, etc.) ### Redis * **Port**: 6379 * **Purpose**: Session cache, pub/sub, rate limiting * **Persistence**: Optional (RDB/AOF) ### Qdrant * **Port**: 6333 * **Purpose**: Vector embeddings, semantic memory * **Collections**: Per-tenant isolation ### Temporal * **Port**: 7233 (gRPC), 8088 (UI) * **Purpose**: Durable workflow execution * **Persistence**: PostgreSQL backend ## Component Interactions Component Interactions ## Coming Soon Detailed documentation for each component including: * Architecture diagrams * API interfaces * Configuration options * Scaling guidelines * Troubleshooting tips See [Architecture Overview](/en/architecture/overview) for high-level system design. # Database Schema Source: https://docs.shannon.run/en/architecture/database-schema Complete PostgreSQL database schema reference for Shannon ## Overview Shannon uses PostgreSQL for persistent storage. The database is organized into two schemas: * `auth` - Authentication, multi-tenancy, and security * `public` - Core application tables (tasks, agents, sessions) **Total Tables**: 25 tables **Extensions**: uuid-ossp, pg\_trgm, btree\_gin, pgcrypto ## Entity Relationship Diagram ``` ┌─────────────────┐ │ auth.tenants │ └────────┬────────┘ │ │ 1:N ▼ ┌─────────────────┐ ┌──────────────────┐ │ auth.users │◄───────│ auth.api_keys │ └────────┬────────┘ N:1 └──────────────────┘ │ │ 1:N ▼ ┌─────────────────┐ ┌──────────────────┐ │ public.users │◄───────│ sessions │ └────────┬────────┘ 1:N └──────────────────┘ │ │ 1:N ▼ ┌──────────────────────────┐ │ task_executions │ │ (workflow_id = PK) │ └──────────┬───────────────┘ │ │ 1:N ▼ ┌──────────────────────────┐ ┌──────────────────┐ │ agent_executions │◄───────│ tool_executions │ └──────────┬───────────────┘ 1:N └──────────────────┘ │ │ 1:N ▼ ┌──────────────────────────┐ │ tool_calls │ └──────────────────────────┘ ┌──────────────────────────┐ ┌──────────────────┐ │ event_logs │ │ token_usage │ │ (workflow_id) │ └──────────────────┘ └──────────────────────────┘ ┌──────────────────────────┐ ┌───────────────────┐ │ usage_daily_aggregates │ │ learning_cases │ └──────────────────────────┘ └───────────────────┘ ``` ## Schema: `auth` (Authentication & Multi-Tenancy) ### auth.tenants **Purpose**: Multi-tenant organization management | Column | Type | Constraints | Description | | ---------------------- | ------------ | ------------------------------- | ----------------------------------------- | | id | UUID | PK, DEFAULT gen\_random\_uuid() | Tenant identifier | | name | VARCHAR(255) | NOT NULL | Organization name | | slug | VARCHAR(100) | UNIQUE, NOT NULL | URL-safe identifier | | plan | VARCHAR(50) | DEFAULT 'free' | Subscription plan (free, pro, enterprise) | | token\_limit | INTEGER | DEFAULT 10000 | Monthly token allowance | | monthly\_token\_usage | INTEGER | DEFAULT 0 | Current month usage | | rate\_limit\_per\_hour | INTEGER | DEFAULT 1000 | API rate limit | | is\_active | BOOLEAN | DEFAULT true | Tenant status | | created\_at | TIMESTAMP | DEFAULT NOW() | Creation time | | updated\_at | TIMESTAMP | DEFAULT NOW() | Last update time | | metadata | JSONB | DEFAULT '' | Additional tenant data | **Indexes**: * Primary key on `slug` (UNIQUE) **Example**: ```sql theme={null} -- Get all active tenants SELECT id, name, plan, token_limit FROM auth.tenants WHERE is_active = true; -- Check token usage SELECT name, monthly_token_usage, token_limit, (monthly_token_usage::FLOAT / token_limit * 100) as usage_percentage FROM auth.tenants WHERE monthly_token_usage > token_limit * 0.8; ``` ### auth.users **Purpose**: User authentication and profile management | Column | Type | Constraints | Description | | ------------------- | ------------ | ------------------------------- | ------------------------- | | id | UUID | PK, DEFAULT gen\_random\_uuid() | User identifier | | email | VARCHAR(255) | UNIQUE, NOT NULL | User email | | username | VARCHAR(100) | UNIQUE, NOT NULL | Username | | password\_hash | VARCHAR(255) | NOT NULL | Bcrypt password hash | | full\_name | VARCHAR(255) | | Full name | | tenant\_id | UUID | FK → auth.tenants, NOT NULL | Organization | | role | VARCHAR(50) | DEFAULT 'user' | Role (user, admin, owner) | | is\_active | BOOLEAN | DEFAULT true | Account status | | is\_verified | BOOLEAN | DEFAULT false | Email verification | | email\_verified\_at | TIMESTAMP | | Verification timestamp | | created\_at | TIMESTAMP | DEFAULT NOW() | Account creation | | updated\_at | TIMESTAMP | DEFAULT NOW() | Last update | | last\_login | TIMESTAMP | | Last login time | | metadata | JSONB | DEFAULT '' | Additional user data | **Indexes**: * `idx_users_email` ON (email) * `idx_users_tenant_id` ON (tenant\_id) **Example**: ```sql theme={null} -- Get all users in a tenant SELECT u.email, u.username, u.role, u.last_login FROM auth.users u WHERE u.tenant_id = '...' AND u.is_active = true ORDER BY u.last_login DESC NULLS LAST; -- Find inactive users SELECT email, created_at, last_login FROM auth.users WHERE last_login < NOW() - INTERVAL '90 days' OR last_login IS NULL; ``` ### auth.api\_keys **Purpose**: API key management for programmatic access | Column | Type | Constraints | Description | | ---------------------- | ------------ | --------------------------------------------------------------------- | --------------------------- | | id | UUID | PK, DEFAULT gen\_random\_uuid() | Key identifier | | key\_hash | VARCHAR(255) | UNIQUE, NOT NULL | SHA256 hash of key | | key\_prefix | VARCHAR(20) | NOT NULL | First 8 chars (for display) | | user\_id | UUID | FK → auth.users | Key owner | | tenant\_id | UUID | FK → auth.tenants, NOT NULL | Organization | | name | VARCHAR(100) | NOT NULL | Key name/description | | description | TEXT | | Detailed description | | scopes | TEXT\[] | DEFAULT ARRAY\['workflows:read', 'workflows:write', 'agents:execute'] | Permissions array | | rate\_limit\_per\_hour | INTEGER | DEFAULT 1000 | Key-specific rate limit | | last\_used | TIMESTAMP | | Last usage timestamp | | expires\_at | TIMESTAMP | | Expiration time | | is\_active | BOOLEAN | DEFAULT true | Key status | | created\_at | TIMESTAMP | DEFAULT NOW() | Creation time | | metadata | JSONB | DEFAULT '' | Additional key data | **Indexes**: * `idx_api_keys_tenant_id` ON (tenant\_id) * `idx_api_keys_key_prefix` ON (key\_prefix) **Default Scopes**: * `workflows:read`: Allow reading workflow/task status and results * `workflows:write`: Allow submitting and managing workflows/tasks * `agents:execute`: Allow agent execution **Example**: ```sql theme={null} -- List active API keys SELECT key_prefix, name, last_used, expires_at FROM auth.api_keys WHERE is_active = true AND (expires_at IS NULL OR expires_at > NOW()) ORDER BY last_used DESC NULLS LAST; -- Find unused keys SELECT key_prefix, name, created_at, last_used FROM auth.api_keys WHERE last_used IS NULL AND created_at < NOW() - INTERVAL '30 days'; -- Check expiring keys SELECT key_prefix, name, expires_at FROM auth.api_keys WHERE expires_at BETWEEN NOW() AND NOW() + INTERVAL '7 days' AND is_active = true; ``` ### auth.refresh\_tokens **Purpose**: JWT refresh token storage and revocation | Column | Type | Constraints | Description | | ----------- | ------------ | ------------------------------- | ----------------- | | id | UUID | PK, DEFAULT gen\_random\_uuid() | Token identifier | | token\_hash | VARCHAR(255) | UNIQUE, NOT NULL | SHA256 hash | | user\_id | UUID | FK → auth.users | Token owner | | tenant\_id | UUID | FK → auth.tenants, NOT NULL | Organization | | expires\_at | TIMESTAMP | NOT NULL | Expiration time | | revoked | BOOLEAN | DEFAULT false | Revocation status | | revoked\_at | TIMESTAMP | | Revocation time | | ip\_address | INET | | Client IP | | user\_agent | TEXT | | Client user agent | | created\_at | TIMESTAMP | DEFAULT NOW() | Creation time | **Indexes**: * `idx_refresh_tokens_user_id` ON (user\_id) * `idx_refresh_tokens_token_hash` ON (token\_hash) **Example**: ```sql theme={null} -- Revoke all tokens for a user UPDATE auth.refresh_tokens SET revoked = true, revoked_at = NOW() WHERE user_id = '...' AND revoked = false; -- Clean up expired tokens DELETE FROM auth.refresh_tokens WHERE expires_at < NOW() - INTERVAL '7 days'; ``` ### auth.audit\_logs **Purpose**: Security event audit trail | Column | Type | Constraints | Description | | ----------- | ------------ | ------------------------------- | ----------------- | | id | UUID | PK, DEFAULT gen\_random\_uuid() | Log entry ID | | event\_type | VARCHAR(100) | NOT NULL | Event type | | user\_id | UUID | FK → auth.users | User (nullable) | | tenant\_id | UUID | FK → auth.tenants | Tenant (nullable) | | ip\_address | INET | | Client IP | | user\_agent | TEXT | | Client user agent | | details | JSONB | DEFAULT '' | Event details | | created\_at | TIMESTAMP | DEFAULT NOW() | Event time | **Indexes**: * `idx_audit_logs_user_id` ON (user\_id) * `idx_audit_logs_tenant_id` ON (tenant\_id) * `idx_audit_logs_event_type` ON (event\_type) * `idx_audit_logs_created_at` ON (created\_at) **Event Types**: * `login` - User login * `logout` - User logout * `api_key_created` - API key generation * `permission_changed` - Role/permission modification **Example**: ```sql theme={null} -- Recent security events SELECT event_type, user_id, ip_address, created_at FROM auth.audit_logs WHERE created_at > NOW() - INTERVAL '24 hours' ORDER BY created_at DESC LIMIT 100; -- Failed login attempts SELECT user_id, ip_address, COUNT(*) as attempts FROM auth.audit_logs WHERE event_type = 'login_failed' AND created_at > NOW() - INTERVAL '1 hour' GROUP BY user_id, ip_address HAVING COUNT(*) > 5; ``` ## Schema: `public` (Core Application) ### users **Purpose**: Application user profiles (legacy, linked to auth.users) | Column | Type | Constraints | Description | | ------------ | ------------ | -------------------------------- | ------------------ | | id | UUID | PK, DEFAULT uuid\_generate\_v4() | User identifier | | external\_id | VARCHAR(255) | UNIQUE, NOT NULL | External system ID | | email | VARCHAR(255) | | Email address | | tenant\_id | UUID | | Tenant reference | | metadata | JSONB | DEFAULT '' | User metadata | | created\_at | TIMESTAMPTZ | DEFAULT NOW() | Creation time | | updated\_at | TIMESTAMPTZ | DEFAULT NOW() | Last update | **Indexes**: * `idx_users_tenant_id` ON (tenant\_id) * `idx_users_external_id` ON (external\_id) **Note**: This table exists for backward compatibility. New code should use `auth.users`. ### sessions **Purpose**: User session management and context | Column | Type | Constraints | Description | | ------------- | ----------- | -------------------------------- | ------------------------ | | id | UUID | PK, DEFAULT uuid\_generate\_v4() | Session identifier | | user\_id | UUID | FK → users | Session owner | | tenant\_id | UUID | | Tenant reference | | context | JSONB | DEFAULT '' | Session context data | | token\_budget | INTEGER | DEFAULT 10000 | Token allocation | | tokens\_used | INTEGER | DEFAULT 0 | Tokens consumed | | created\_at | TIMESTAMPTZ | DEFAULT NOW() | Session start | | updated\_at | TIMESTAMPTZ | DEFAULT NOW() | Last activity | | expires\_at | TIMESTAMPTZ | | Expiration time | | deleted\_at | TIMESTAMPTZ | DEFAULT NULL | Soft delete timestamp | | deleted\_by | UUID | DEFAULT NULL | User who deleted session | **Indexes**: * `idx_sessions_user_id` ON (user\_id) * `idx_sessions_tenant_id` ON (tenant\_id) * `idx_sessions_expires_at` ON (expires\_at) * `idx_sessions_external_id` ON ((context->>'external\_id')) WHERE context->>'external\_id' IS NOT NULL * `idx_sessions_user_external_id` UNIQUE ON (user\_id, (context->>'external\_id')) WHERE context->>'external\_id' IS NOT NULL AND deleted\_at IS NULL * `idx_sessions_not_deleted` ON (id) WHERE deleted\_at IS NULL * `idx_sessions_deleted_at` ON (deleted\_at) WHERE deleted\_at IS NOT NULL **Notes**: * External IDs (non-UUID session IDs) are stored in `context->>'external_id'` to enable dual-ID lookups * Soft delete support via `deleted_at` column - sessions with non-NULL `deleted_at` are considered deleted **Example**: ```sql theme={null} -- Active sessions SELECT id, user_id, tokens_used, token_budget, created_at FROM sessions WHERE expires_at > NOW() OR expires_at IS NULL ORDER BY created_at DESC; -- Session token usage SELECT COUNT(*) as session_count, AVG(tokens_used) as avg_tokens, SUM(tokens_used) as total_tokens FROM sessions WHERE created_at > NOW() - INTERVAL '24 hours'; ``` ### task\_executions **Purpose**: Task/workflow execution history and metrics | Column | Type | Constraints | Description | | ------------------ | ------------- | -------------------------------- | ------------------------------------------ | | id | UUID | PK, DEFAULT uuid\_generate\_v4() | Task identifier | | workflow\_id | VARCHAR(255) | UNIQUE, NOT NULL | Temporal workflow ID | | user\_id | UUID | FK → users | Task creator | | tenant\_id | UUID | | Tenant reference | | session\_id | VARCHAR(255) | | Session identifier | | query | TEXT | NOT NULL | Task query/prompt | | mode | VARCHAR(50) | | Execution mode (SIMPLE, STANDARD, COMPLEX) | | status | VARCHAR(50) | NOT NULL | Task status | | started\_at | TIMESTAMPTZ | NOT NULL, DEFAULT NOW() | Start time | | completed\_at | TIMESTAMPTZ | | Completion time | | result | TEXT | | Final result | | response | JSONB | DEFAULT '' | Structured response | | error\_message | TEXT | | Error details | | total\_tokens | INTEGER | DEFAULT 0 | Total tokens used | | prompt\_tokens | INTEGER | DEFAULT 0 | Input tokens | | completion\_tokens | INTEGER | DEFAULT 0 | Output tokens | | total\_cost\_usd | DECIMAL(10,6) | DEFAULT 0 | Total cost | | duration\_ms | INTEGER | | Execution time (ms) | | agents\_used | INTEGER | DEFAULT 0 | Number of agents | | tools\_invoked | INTEGER | DEFAULT 0 | Number of tool calls | | cache\_hits | INTEGER | DEFAULT 0 | Cache hit count | | complexity\_score | DECIMAL(3,2) | | Complexity (0.0-1.0) | | metadata | JSONB | | Additional metadata | | created\_at | TIMESTAMPTZ | DEFAULT NOW() | Record creation | **Indexes**: * `idx_task_user_session` ON (user\_id, session\_id) * `idx_task_created_at` ON (created\_at DESC) * `idx_task_status` ON (status) * `idx_task_workflow_id` ON (workflow\_id) * `idx_task_executions_tenant_id` ON (tenant\_id) * `idx_task_executions_session_id` ON (session\_id) **Status Values**: * `RUNNING` - Task in progress * `COMPLETED` - Task finished successfully * `FAILED` - Task failed with error * `CANCELLED` - Task cancelled by user **Example**: ```sql theme={null} -- Recent tasks for user SELECT workflow_id, query, status, duration_ms, total_cost_usd FROM task_executions WHERE user_id = '...' ORDER BY started_at DESC LIMIT 20; -- Failed tasks analysis SELECT DATE(started_at) as date, COUNT(*) as failed_count, AVG(duration_ms) as avg_duration FROM task_executions WHERE status = 'FAILED' AND started_at > NOW() - INTERVAL '7 days' GROUP BY DATE(started_at) ORDER BY date DESC; -- Cost analysis SELECT user_id, COUNT(*) as task_count, SUM(total_cost_usd) as total_cost, AVG(total_cost_usd) as avg_cost, SUM(total_tokens) as total_tokens FROM task_executions WHERE started_at > NOW() - INTERVAL '30 days' GROUP BY user_id ORDER BY total_cost DESC; ``` ### agent\_executions **Purpose**: Individual agent execution details within tasks | Column | Type | Constraints | Description | | -------------- | ------------ | -------------------------------- | -------------------- | | id | UUID | PK, DEFAULT uuid\_generate\_v4() | Execution ID | | workflow\_id | VARCHAR(255) | | Temporal workflow ID | | task\_id | VARCHAR(255) | | Task identifier | | agent\_id | VARCHAR(255) | NOT NULL | Agent identifier | | input | TEXT | NOT NULL | Agent input | | output | TEXT | | Agent output | | state | VARCHAR(50) | | FSM state | | tokens\_used | INTEGER | DEFAULT 0 | Tokens consumed | | model\_used | VARCHAR(100) | | LLM model | | duration\_ms | INTEGER | | Execution time | | error\_message | TEXT | | Error details | | metadata | JSONB | DEFAULT '' | Additional metadata | | strategy | VARCHAR(100) | | Execution strategy | | created\_at | TIMESTAMPTZ | DEFAULT NOW() | Start time | | updated\_at | TIMESTAMPTZ | DEFAULT NOW() | Last update | **Indexes**: * `idx_agent_executions_workflow_id` ON (workflow\_id) * `idx_agent_executions_created_at` ON (created\_at DESC) * `idx_agent_executions_state` ON (state) **State Values**: * `IDLE` - Agent idle * `ANALYZING` - Analyzing input * `PLANNING` - Planning execution * `RETRIEVING` - Retrieving information * `EXECUTING` - Executing task * `VALIDATING` - Validating results * `SYNTHESIZING` - Synthesizing response * `COMPLETED` - Execution complete * `FAILED` - Execution failed **Example**: ```sql theme={null} -- Agent execution chain for a task SELECT agent_id, state, duration_ms, tokens_used FROM agent_executions WHERE workflow_id = '...' ORDER BY created_at; -- Agent performance metrics SELECT agent_id, COUNT(*) as execution_count, AVG(duration_ms) as avg_duration, AVG(tokens_used) as avg_tokens FROM agent_executions WHERE created_at > NOW() - INTERVAL '7 days' GROUP BY agent_id ORDER BY execution_count DESC; ``` ### tool\_executions **Purpose**: Tool invocation history and performance | Column | Type | Constraints | Description | | -------------------- | ------------ | -------------------------------- | ---------------------- | | id | UUID | PK, DEFAULT uuid\_generate\_v4() | Execution ID | | agent\_execution\_id | UUID | | Parent agent execution | | workflow\_id | VARCHAR(255) | | Temporal workflow ID | | agent\_id | VARCHAR(255) | | Agent identifier | | tool\_name | VARCHAR(255) | NOT NULL | Tool identifier | | input\_params | JSONB | | Input parameters | | output | TEXT | | Tool output | | success | BOOLEAN | DEFAULT true | Success status | | error | TEXT | | Error details | | duration\_ms | INTEGER | | Execution time | | tokens\_consumed | INTEGER | DEFAULT 0 | Tokens used | | metadata | JSONB | DEFAULT '' | Additional metadata | | created\_at | TIMESTAMPTZ | DEFAULT NOW() | Creation time | **Indexes**: * `idx_tool_executions_workflow_id` ON (workflow\_id) * `idx_tool_executions_tool_name` ON (tool\_name) * `idx_tool_executions_created_at` ON (created\_at DESC) * `idx_tool_executions_success` ON (success) **Example**: ```sql theme={null} -- Tool usage statistics SELECT tool_name, COUNT(*) as invocation_count, COUNT(CASE WHEN success THEN 1 END) as successful, AVG(duration_ms) as avg_duration FROM tool_executions WHERE created_at > NOW() - INTERVAL '7 days' GROUP BY tool_name ORDER BY invocation_count DESC; -- Failed tool executions SELECT tool_name, error, created_at FROM tool_executions WHERE success = false AND created_at > NOW() - INTERVAL '24 hours' ORDER BY created_at DESC; -- Tool performance SELECT tool_name, AVG(duration_ms) as avg_ms, MAX(duration_ms) as max_ms, STDDEV(duration_ms) as stddev_ms FROM tool_executions WHERE success = true GROUP BY tool_name ORDER BY avg_ms DESC; ``` ### event\_logs **Purpose**: Streaming event storage for audit and replay | Column | Type | Constraints | Description | | ------------ | ------------ | -------------------------------- | ------------------------- | | id | UUID | PK, DEFAULT uuid\_generate\_v4() | Event ID | | workflow\_id | VARCHAR(255) | NOT NULL | Workflow identifier | | task\_id | UUID | | Task reference (nullable) | | type | VARCHAR(100) | NOT NULL | Event type | | agent\_id | VARCHAR(255) | | Agent identifier | | message | TEXT | | Event message | | payload | JSONB | DEFAULT '' | Event payload | | timestamp | TIMESTAMPTZ | NOT NULL, DEFAULT NOW() | Event time | | seq | BIGINT | | Sequence number | | stream\_id | VARCHAR(64) | | Redis stream ID | | created\_at | TIMESTAMPTZ | NOT NULL, DEFAULT NOW() | Record creation | **Indexes**: * `idx_event_logs_workflow_id` ON (workflow\_id) * `idx_event_logs_task_id` ON (task\_id) * `idx_event_logs_type` ON (type) * `idx_event_logs_ts` ON (timestamp DESC) * `idx_event_logs_seq` ON (workflow\_id, seq) * `idx_event_logs_workflow_ts` ON (workflow\_id, timestamp DESC) * `uq_event_logs_wf_type_seq` UNIQUE ON (workflow\_id, type, seq) WHERE seq IS NOT NULL **Event Types**: * `task.started` - Task execution started * `task.completed` - Task execution completed * `agent.thinking` - Agent processing * `tool.called` - Tool invocation **Example**: ```sql theme={null} -- Get all events for a task SELECT type, agent_id, message, timestamp FROM event_logs WHERE workflow_id = '...' ORDER BY timestamp; -- Event frequency analysis SELECT type, COUNT(*) as event_count, DATE_TRUNC('hour', timestamp) as hour FROM event_logs WHERE timestamp > NOW() - INTERVAL '24 hours' GROUP BY type, hour ORDER BY hour DESC, event_count DESC; ``` ### token\_usage **Purpose**: Detailed token usage tracking per task | Column | Type | Constraints | Description | | ------------------ | ------------- | -------------------------------- | ------------- | | id | UUID | PK, DEFAULT uuid\_generate\_v4() | Record ID | | user\_id | UUID | FK → users | User | | task\_id | UUID | FK → task\_executions | Task | | provider | VARCHAR(50) | NOT NULL | LLM provider | | model | VARCHAR(255) | NOT NULL | Model name | | prompt\_tokens | INTEGER | NOT NULL | Input tokens | | completion\_tokens | INTEGER | NOT NULL | Output tokens | | total\_tokens | INTEGER | NOT NULL | Total tokens | | cost\_usd | DECIMAL(10,6) | NOT NULL | Cost in USD | | created\_at | TIMESTAMPTZ | DEFAULT NOW() | Record time | **Indexes**: * `idx_token_usage_user_id` ON (user\_id) * `idx_token_usage_created_at` ON (created\_at DESC) * `idx_token_usage_provider_model` ON (provider, model) * `idx_token_usage_task_id` ON (task\_id) **Example**: ```sql theme={null} -- Token usage by provider SELECT provider, model, COUNT(*) as call_count, SUM(total_tokens) as total_tokens, SUM(cost_usd) as total_cost FROM token_usage WHERE created_at > NOW() - INTERVAL '30 days' GROUP BY provider, model ORDER BY total_cost DESC; -- Daily token trend SELECT DATE(created_at) as date, SUM(total_tokens) as tokens, SUM(cost_usd) as cost FROM token_usage WHERE created_at > NOW() - INTERVAL '30 days' GROUP BY DATE(created_at) ORDER BY date; ``` ### usage\_daily\_aggregates **Purpose**: Pre-computed daily usage statistics | Column | Type | Constraints | Description | | ------------------ | ------------- | -------------------------------- | ----------------------- | | id | UUID | PK, DEFAULT uuid\_generate\_v4() | Record ID | | user\_id | UUID | FK → users | User | | date | DATE | NOT NULL | Aggregation date | | total\_tasks | INTEGER | DEFAULT 0 | Total task count | | successful\_tasks | INTEGER | DEFAULT 0 | Successful count | | failed\_tasks | INTEGER | DEFAULT 0 | Failed count | | total\_tokens | INTEGER | DEFAULT 0 | Total tokens | | total\_cost\_usd | DECIMAL(10,6) | DEFAULT 0 | Total cost | | model\_usage | JSONB | | Model distribution | | tools\_invoked | INTEGER | DEFAULT 0 | Tool invocation count | | tool\_distribution | JSONB | | Tool usage distribution | | avg\_duration\_ms | INTEGER | | Average task duration | | cache\_hit\_rate | DECIMAL(3,2) | | Cache hit percentage | | created\_at | TIMESTAMPTZ | DEFAULT NOW() | Record time | **Indexes**: * `idx_usage_daily_user_date` ON (user\_id, date) * `idx_usage_daily_date` ON (date DESC) **Unique Constraint**: (user\_id, date) **Example**: ```sql theme={null} -- User usage summary SELECT date, total_tasks, successful_tasks, total_cost_usd FROM usage_daily_aggregates WHERE user_id = '...' AND date > CURRENT_DATE - INTERVAL '30 days' ORDER BY date DESC; -- Aggregate metrics SELECT SUM(total_tasks) as total_tasks, SUM(total_cost_usd) as total_cost, AVG(cache_hit_rate) as avg_cache_rate FROM usage_daily_aggregates WHERE date > CURRENT_DATE - INTERVAL '7 days'; ``` ## Additional Tables ### tool\_calls **Purpose**: Legacy tool call tracking (use tool\_executions instead) ### prompts **Purpose**: Prompt versioning and A/B testing ### learning\_cases **Purpose**: Reinforcement learning case storage ### session\_archives **Purpose**: Long-term session snapshots from Redis ### audit\_logs (public) **Purpose**: Application audit trail (separate from auth.audit\_logs) ## Database Functions ### update\_updated\_at\_column() **Purpose**: Automatically update `updated_at` on row changes **Usage**: Attached as trigger to `users` and `sessions` tables ```sql theme={null} CREATE TRIGGER update_users_updated_at BEFORE UPDATE ON users FOR EACH ROW EXECUTE FUNCTION update_updated_at_column(); ``` ### update\_daily\_aggregate(user\_id, date) **Purpose**: Update or create daily usage aggregate for a user **Usage**: Called automatically via trigger on task completion ```sql theme={null} -- Manual call SELECT update_daily_aggregate( '00000000-0000-0000-0000-000000000002', '2025-10-22' ); ``` ### trigger\_update\_daily\_aggregate() **Purpose**: Trigger function to update aggregates on task status changes **Usage**: Automatically fires on task\_executions INSERT/UPDATE ## Sample Queries ### Task Analytics ```sql theme={null} -- Task success rate by mode SELECT mode, COUNT(*) as total, COUNT(CASE WHEN status = 'COMPLETED' THEN 1 END) as completed, ROUND(100.0 * COUNT(CASE WHEN status = 'COMPLETED' THEN 1 END) / COUNT(*), 2) as success_rate FROM task_executions WHERE started_at > NOW() - INTERVAL '7 days' GROUP BY mode; -- Average execution time by complexity SELECT CASE WHEN complexity_score < 0.3 THEN 'Low' WHEN complexity_score < 0.7 THEN 'Medium' ELSE 'High' END as complexity, COUNT(*) as task_count, AVG(duration_ms) as avg_ms, AVG(total_cost_usd) as avg_cost FROM task_executions WHERE complexity_score IS NOT NULL GROUP BY complexity; ``` ### Cost Analysis ```sql theme={null} -- Top cost users SELECT u.email, COUNT(t.id) as task_count, SUM(t.total_cost_usd) as total_cost, AVG(t.total_cost_usd) as avg_cost FROM task_executions t JOIN users u ON t.user_id = u.id WHERE t.started_at > NOW() - INTERVAL '30 days' GROUP BY u.email ORDER BY total_cost DESC LIMIT 10; -- Cost breakdown by model SELECT tu.provider, tu.model, COUNT(*) as usage_count, SUM(tu.total_tokens) as total_tokens, SUM(tu.cost_usd) as total_cost, AVG(tu.cost_usd) as avg_cost_per_call FROM token_usage tu WHERE tu.created_at > NOW() - INTERVAL '7 days' GROUP BY tu.provider, tu.model ORDER BY total_cost DESC; ``` ### Performance Monitoring ```sql theme={null} -- Slow queries (tasks > 60 seconds) SELECT workflow_id, query, duration_ms, agents_used, tools_invoked FROM task_executions WHERE duration_ms > 60000 AND started_at > NOW() - INTERVAL '24 hours' ORDER BY duration_ms DESC; -- Tool execution success rate SELECT tool_name, COUNT(*) as total, COUNT(CASE WHEN success THEN 1 END) as successful, ROUND(100.0 * COUNT(CASE WHEN success THEN 1 END) / COUNT(*), 2) as success_rate, AVG(duration_ms) as avg_duration FROM tool_executions GROUP BY tool_name ORDER BY total DESC; ``` ## Best Practices ### 1. Indexing **Always use indexes for**: * Foreign key columns (user\_id, tenant\_id, task\_execution\_id, etc.) * Frequently filtered columns (status, created\_at, expires\_at) * JSONB queries (context->>'external\_id') * Time-based queries (created\_at DESC) **Current index coverage**: Excellent (all foreign keys and common queries indexed) ### 2. Partitioning For high-volume tables, consider partitioning: ```sql theme={null} -- Monthly partitioning for task_executions CREATE TABLE task_executions_2025_10 PARTITION OF task_executions FOR VALUES FROM ('2025-10-01') TO ('2025-11-01'); ``` ### 3. Data Retention **Recommended policies**: * `event_logs`: 90 days (high volume, archivable) * `task_executions`: 1 year (archive older) * `audit_logs`: 2 years (compliance) * `token_usage`: Aggregate to daily, archive raw data after 90 days **Cleanup script**: ```sql theme={null} -- Delete old events (run monthly) DELETE FROM event_logs WHERE created_at < NOW() - INTERVAL '90 days'; -- Archive old tasks (run annually) INSERT INTO task_executions_archive SELECT * FROM task_executions WHERE started_at < NOW() - INTERVAL '1 year'; DELETE FROM task_executions WHERE started_at < NOW() - INTERVAL '1 year'; ``` ### 4. Query Optimization **Use EXPLAIN ANALYZE**: ```sql theme={null} EXPLAIN ANALYZE SELECT * FROM task_executions WHERE user_id = '...' AND started_at > NOW() - INTERVAL '7 days'; ``` **Optimize with covering indexes**: ```sql theme={null} CREATE INDEX idx_task_user_date_status ON task_executions(user_id, started_at, status) INCLUDE (total_cost_usd, duration_ms); ``` ### 5. Connection Pooling **Configuration** (already configured in Shannon): ```bash theme={null} DB_MAX_OPEN_CONNS=50 DB_MAX_IDLE_CONNS=10 ``` ## Maintenance ### Vacuum and Analyze ```sql theme={null} -- Manual vacuum (run weekly) VACUUM ANALYZE task_executions; VACUUM ANALYZE event_logs; -- Configure autovacuum ALTER TABLE task_executions SET (autovacuum_vacuum_threshold = 1000); ``` ### Statistics ```sql theme={null} -- Update statistics ANALYZE task_executions; -- Check table bloat SELECT schemaname, tablename, pg_size_pretty(pg_total_relation_size(schemaname||'.'||tablename)) as size FROM pg_tables WHERE schemaname = 'public' ORDER BY pg_total_relation_size(schemaname||'.'||tablename) DESC; ``` ## Related Documentation Database configuration Query optimization Database monitoring # Feature Flags Source: https://docs.shannon.run/en/architecture/feature-flags Configure optional features and enterprise extensions with feature flags ## Overview Shannon uses **feature flags** to control optional features, experimental functionality, and enterprise extensions without modifying code. This allows clean separation between: * **Open-source core features** (available to all) * **Enterprise/private features** (gated behind flags) * **Experimental features** (toggleable for testing) Enable/disable features without rebuilding services Different flags for dev/staging/production Enterprise features isolated from OSS core Test new features before full deployment ## Configuration ### Configuration File Feature flags are defined in `config/shannon.yaml` under the `features` section: ```yaml theme={null} # config/shannon.yaml features: # Enterprise workflow features ads_research: false # Ads research workflow (enterprise) advanced_synthesis: false # Advanced synthesis templates # Experimental features parallel_streaming: true # Multi-agent parallel streaming enhanced_memory: false # Enhanced vector memory # Custom integrations vendor_tools_enabled: false # Enable vendor-specific tools ``` ### Environment Variables Feature flags can be overridden via environment variables: ```bash theme={null} # .env SHANNON_FEATURE_ADS_RESEARCH=true SHANNON_FEATURE_PARALLEL_STREAMING=false ``` **Naming convention**: `SHANNON_FEATURE_` ### Priority Order 1. **Environment variables** (highest priority) 2. **Config overlays** (`config/overlays/shannon.{env}.yaml`) 3. **Base config** (`config/shannon.yaml`) 4. **Code defaults** (lowest priority) ## Usage Patterns ### In Go Code (Orchestrator) ```go theme={null} package main import ( "github.com/Kocoro-lab/Shannon/go/orchestrator/internal/config" ) func routeWorkflow(ctx context.Context, req *pb.Request) error { cfg, _ := config.LoadShannon() // Check feature flag if cfg.Features.AdsResearch { return routeToAdsResearchWorkflow(ctx, req) } return routeToStandardWorkflow(ctx, req) } ``` ### In Python Code (LLM Service) ```python theme={null} from llm_service.config import load_shannon_config config = load_shannon_config() # Check feature flag if config.get("features", {}).get("vendor_tools_enabled"): from llm_service.tools.vendor import register_vendor_tools register_vendor_tools() ``` ### Conditional Imports Pattern Use feature flags with conditional imports for clean separation: ```python theme={null} # roles/presets.py _PRESETS = { "general": GENERAL_PRESET, "research": RESEARCH_PRESET, } # Conditionally load enterprise roles if config.get("features", {}).get("ads_research"): try: from .enterprise.ads_analyst import ADS_ANALYST_PRESET _PRESETS["ads_analyst"] = ADS_ANALYST_PRESET except ImportError: pass # Shannon works without enterprise module ``` ## Common Feature Flags | Flag Name | Type | Description | Default | | ---------------------- | ------------ | ------------------------------------------ | ------- | | `ads_research` | Enterprise | Ads research workflow with market analysis | `false` | | `parallel_streaming` | Experimental | Multi-agent parallel SSE streaming | `true` | | `advanced_synthesis` | Enterprise | Custom synthesis templates | `false` | | `vendor_tools_enabled` | Integration | Enable vendor-specific tool adapters | `false` | | `enhanced_memory` | Experimental | Advanced vector memory with hybrid search | `false` | ## Best Practices ### 1. Default to Disabled New features should default to `false` in base config: ```yaml theme={null} # config/shannon.yaml features: new_feature: false # ✅ Safe default ``` ### 2. Use Environment Overlays Create environment-specific overlays for different deployments: ```yaml theme={null} # config/overlays/shannon.production.yaml features: ads_research: true parallel_streaming: true enhanced_memory: false # Still testing ``` Load with: ```bash theme={null} SHANNON_CONFIG_PATH=config/overlays/shannon.production.yaml ``` ### 3. Guard with Try/Except Always use graceful fallback for enterprise/optional features: ```python theme={null} if config.features.vendor_tools_enabled: try: from .vendor import CustomVendorTool except ImportError: logger.warning("Vendor tools not available, using standard tools") ``` ### 4. Document Feature Requirements ```yaml theme={null} features: # Requires: GA4_SERVICE_ACCOUNT_KEY environment variable # Vendor module: llm_service/tools/vendor_adapters/ga4/ ga4_integration: false ``` ## Enterprise Feature Pattern Shannon separates **OSS core** from **enterprise extensions** using feature flags: ### File Structure ``` Shannon/ ├── config/ │ ├── shannon.yaml # Base config (OSS) │ └── overlays/ │ └── shannon.enterprise.yaml # Enterprise flags ├── go/orchestrator/ │ └── internal/workflows/ │ ├── standard_workflow.go # OSS workflow │ └── enterprise/ # Enterprise workflows │ └── ads_research.go # Gated by flag └── python/llm-service/ └── llm_service/ ├── roles/ │ ├── presets.py # OSS roles + conditional load │ └── enterprise/ # Enterprise roles │ └── ads_analyst.py # Gated by flag └── tools/ └── vendor_adapters/ # Vendor tools (gated) ``` ### Conditional Workflow Routing ```go theme={null} // orchestrator/internal/workflows/router.go func SelectWorkflow(ctx context.Context, cfg *config.Shannon, req *pb.Request) Workflow { // Check for enterprise feature flag if cfg.Features.AdsResearch && req.Context["workflow_type"] == "ads_research" { return NewAdsResearchWorkflow() } // Fall back to OSS workflow return NewStandardWorkflow() } ``` ## Testing with Feature Flags ### Unit Tests ```go theme={null} func TestAdsResearchWorkflow(t *testing.T) { cfg := &config.Shannon{ Features: config.FeatureFlagsConfig{ AdsResearch: true, // Enable for test }, } workflow := SelectWorkflow(context.Background(), cfg, testReq) assert.IsType(t, &AdsResearchWorkflow{}, workflow) } ``` ### Integration Tests ```bash theme={null} # Run tests with feature enabled SHANNON_FEATURE_ADS_RESEARCH=true go test ./... # Run tests with feature disabled (OSS mode) SHANNON_FEATURE_ADS_RESEARCH=false go test ./... ``` ## Deployment Strategies ### Canary Deployment Enable features for subset of users: ```go theme={null} func isFeatureEnabled(userID string, featureName string) bool { // Check user-specific feature flags (e.g., from database) if userHasAccess(userID, featureName) { return true } // Fall back to global config return cfg.Features.Get(featureName) } ``` ### A/B Testing Route traffic based on feature flags: ```go theme={null} if experimentGroup(userID) == "variant_a" && cfg.Features.EnhancedMemory { return useEnhancedMemory(ctx, req) } return useStandardMemory(ctx, req) ``` ## Monitoring & Observability Log feature flag usage for debugging: ```go theme={null} logger.Info("Feature flag check", zap.String("feature", "ads_research"), zap.Bool("enabled", cfg.Features.AdsResearch), zap.String("user_id", userID), ) ``` Add metrics: ```go theme={null} featureFlagGauge.WithLabelValues("ads_research").Set( boolToFloat64(cfg.Features.AdsResearch), ) ``` ## Migration Guide ### Adding a New Feature Flag Add to `config/shannon.yaml`: ```yaml theme={null} features: my_new_feature: false # Default disabled ``` Add field to `go/orchestrator/internal/config/shannon.go`: ```go theme={null} type FeatureFlagsConfig struct { // ... existing flags MyNewFeature bool `json:"my_new_feature" yaml:"my_new_feature"` } ``` Gate functionality: ```go theme={null} if cfg.Features.MyNewFeature { return newFeatureBehavior() } return existingBehavior() ``` Test both enabled/disabled states: ```go theme={null} func TestMyNewFeature_Enabled(t *testing.T) { /* ... */ } func TestMyNewFeature_Disabled(t *testing.T) { /* ... */ } ``` Update this page and environment variables documentation. ## Related Documentation Full list of configuration options Build vendor-specific integrations Shannon configuration overview Add custom features and workflows # gRPC API Reference Source: https://docs.shannon.run/en/architecture/grpc-api Complete Protocol Buffer definitions for Shannon's internal gRPC services ## Overview Shannon's internal architecture uses **gRPC** (HTTP/2 + Protocol Buffers) for high-performance inter-service communication. This document provides complete Protocol Buffer definitions for all 5 services and 39 RPC methods. Public vs Internal APIs: gRPC services are internal and not exposed by the public SDK. For application integration, use the Gateway REST API (`http://localhost:8080/api/v1/*`) or the Python SDK (`ShannonClient(base_url=...)`). **Services**: * **OrchestratorService** (20 RPCs) - Task orchestration, workflow management, and scheduling * **StreamingService** (1 RPC) - Real-time event streaming * **AgentService** (6 RPCs) - Agent execution and tool management * **LLMService** (5 RPCs) - LLM provider gateway * **SessionService** (7 RPCs) - Multi-turn conversation management **Total**: 39 RPC methods ## Common Types Shared types used across all services. ### ExecutionMode ```protobuf theme={null} enum ExecutionMode { EXECUTION_MODE_UNSPECIFIED = 0; EXECUTION_MODE_SIMPLE = 1; // Direct tool/cache response EXECUTION_MODE_STANDARD = 2; // Single agent with LLM EXECUTION_MODE_COMPLEX = 3; // Multi-agent DAG execution } ``` **Usage**: Determines task execution strategy * `SIMPLE`: Direct tool invocation or cache lookup (fastest) * `STANDARD`: Single-agent LLM-powered execution (balanced) * `COMPLEX`: Multi-agent DAG with parallel execution (most capable) *** ### ModelTier ```protobuf theme={null} enum ModelTier { MODEL_TIER_UNSPECIFIED = 0; MODEL_TIER_SMALL = 1; // GPT-5-nano/mini, Haiku, etc. (50% target) MODEL_TIER_MEDIUM = 2; // GPT-5, Sonnet, etc. (40% target) MODEL_TIER_LARGE = 3; // GPT-5-turbo, Opus, etc. (10% target) } ``` **Cost Optimization**: Shannon automatically selects models within tier to optimize cost * Small: \$0.001-0.002 per 1K tokens * Medium: \$0.01-0.03 per 1K tokens * Large: \$0.03-0.075 per 1K tokens *** ### StatusCode ```protobuf theme={null} enum StatusCode { STATUS_CODE_UNSPECIFIED = 0; STATUS_CODE_OK = 1; STATUS_CODE_ERROR = 2; STATUS_CODE_TIMEOUT = 3; STATUS_CODE_RATE_LIMITED = 4; STATUS_CODE_BUDGET_EXCEEDED = 5; } ``` *** ### TaskMetadata ```protobuf theme={null} message TaskMetadata { string task_id = 1; string user_id = 2; string session_id = 3; string tenant_id = 4; google.protobuf.Timestamp created_at = 5; map labels = 6; int32 max_agents = 7; // Max concurrent agents (default: 5) double token_budget = 8; // Token budget for this task } ``` **Example**: ```json theme={null} { "task_id": "task-123", "user_id": "user-456", "session_id": "sess-789", "tenant_id": "tenant-001", "labels": { "environment": "production", "priority": "high" }, "max_agents": 10, "token_budget": 50000 } ``` *** ### TokenUsage ```protobuf theme={null} message TokenUsage { int32 prompt_tokens = 1; int32 completion_tokens = 2; int32 total_tokens = 3; double cost_usd = 4; string model = 5; ModelTier tier = 6; } ``` *** ### ExecutionMetrics ```protobuf theme={null} message ExecutionMetrics { int64 latency_ms = 1; TokenUsage token_usage = 2; bool cache_hit = 3; double cache_score = 4; int32 agents_used = 5; ExecutionMode mode = 6; } ``` *** ## OrchestratorService Task orchestration and workflow management service. ### Service Definition ```protobuf theme={null} service OrchestratorService { rpc SubmitTask(SubmitTaskRequest) returns (SubmitTaskResponse); rpc GetTaskStatus(GetTaskStatusRequest) returns (GetTaskStatusResponse); rpc CancelTask(CancelTaskRequest) returns (CancelTaskResponse); rpc ListTasks(ListTasksRequest) returns (ListTasksResponse); rpc GetSessionContext(GetSessionContextRequest) returns (GetSessionContextResponse); rpc ListTemplates(ListTemplatesRequest) returns (ListTemplatesResponse); rpc ApproveTask(ApproveTaskRequest) returns (ApproveTaskResponse); rpc GetPendingApprovals(GetPendingApprovalsRequest) returns (GetPendingApprovalsResponse); } ``` *** ### SubmitTask Submit a new task for execution. **Request**: ```protobuf theme={null} message SubmitTaskRequest { TaskMetadata metadata = 1; string query = 2; google.protobuf.Struct context = 3; bool auto_decompose = 4; TaskDecomposition manual_decomposition = 5; SessionContext session_context = 6; bool require_approval = 10; } ``` **Response**: ```protobuf theme={null} message SubmitTaskResponse { string workflow_id = 1; string task_id = 2; StatusCode status = 3; TaskDecomposition decomposition = 4; string message = 5; } ``` **Example** (gRPC CLI): ```bash theme={null} grpcurl -plaintext -d '{ "metadata": { "user_id": "user-123", "session_id": "sess-456", "token_budget": 10000 }, "query": "Analyze Q4 sales data and create visualizations", "auto_decompose": true }' localhost:50052 shannon.orchestrator.OrchestratorService/SubmitTask ``` **Response**: ```json theme={null} { "workflow_id": "wf-550e8400-e29b-41d4-a716-446655440000", "task_id": "task-789", "status": "STATUS_CODE_OK", "decomposition": { "mode": "EXECUTION_MODE_COMPLEX", "complexity_score": 0.78, "agent_tasks": [ { "agent_id": "data-loader", "description": "Load Q4 sales data", "required_tools": ["csv_loader"], "model_tier": "MODEL_TIER_SMALL" }, { "agent_id": "analyst", "description": "Analyze sales trends", "dependencies": ["data-loader"], "model_tier": "MODEL_TIER_MEDIUM" }, { "agent_id": "visualizer", "description": "Create charts", "dependencies": ["analyst"], "required_tools": ["matplotlib"], "model_tier": "MODEL_TIER_SMALL" } ] }, "message": "Task submitted successfully" } ``` *** ### GetTaskStatus Retrieve current status and result of a task. **Request**: ```protobuf theme={null} message GetTaskStatusRequest { string task_id = 1; bool include_details = 2; } ``` **Response**: ```protobuf theme={null} message GetTaskStatusResponse { string task_id = 1; TaskStatus status = 2; double progress = 3; // 0.0 - 1.0 string result = 4; ExecutionMetrics metrics = 5; repeated AgentTaskStatus agent_statuses = 6; string error_message = 7; } enum TaskStatus { TASK_STATUS_UNSPECIFIED = 0; TASK_STATUS_QUEUED = 1; TASK_STATUS_RUNNING = 2; TASK_STATUS_COMPLETED = 3; TASK_STATUS_FAILED = 4; TASK_STATUS_CANCELLED = 5; TASK_STATUS_TIMEOUT = 6; } ``` **Example**: ```bash theme={null} grpcurl -plaintext -d '{ "task_id": "task-789", "include_details": true }' localhost:50052 shannon.orchestrator.OrchestratorService/GetTaskStatus ``` **Response**: ```json theme={null} { "task_id": "task-789", "status": "TASK_STATUS_COMPLETED", "progress": 1.0, "result": "Analysis complete. Q4 revenue increased 15% YoY...", "metrics": { "latency_ms": 45000, "token_usage": { "total_tokens": 5420, "cost_usd": 0.0814, "model": "gpt-5" }, "agents_used": 3, "mode": "EXECUTION_MODE_COMPLEX" }, "agent_statuses": [ { "agent_id": "data-loader", "status": "TASK_STATUS_COMPLETED", "result": "Loaded 15,000 records", "token_usage": { "total_tokens": 150, "cost_usd": 0.0003 } } ] } ``` *** ### CancelTask Cancel a running task. **Request**: ```protobuf theme={null} message CancelTaskRequest { string task_id = 1; string reason = 2; } ``` **Response**: ```protobuf theme={null} message CancelTaskResponse { bool success = 1; string message = 2; } ``` *** ### ListTasks List tasks with optional filtering. **Request**: ```protobuf theme={null} message ListTasksRequest { string user_id = 1; string session_id = 2; int32 limit = 3; int32 offset = 4; TaskStatus filter_status = 5; } ``` **Response**: ```protobuf theme={null} message ListTasksResponse { repeated TaskSummary tasks = 1; int32 total_count = 2; } message TaskSummary { string task_id = 1; string query = 2; TaskStatus status = 3; ExecutionMode mode = 4; google.protobuf.Timestamp created_at = 5; google.protobuf.Timestamp completed_at = 6; TokenUsage total_token_usage = 7; } ``` *** ### GetSessionContext Retrieve session context and history. **Request**: ```protobuf theme={null} message GetSessionContextRequest { string session_id = 1; } ``` **Response**: ```protobuf theme={null} message GetSessionContextResponse { string session_id = 1; google.protobuf.Struct context = 2; repeated TaskSummary recent_tasks = 3; TokenUsage session_token_usage = 4; } ``` *** ### ListTemplates List available task templates. **Request**: ```protobuf theme={null} message ListTemplatesRequest {} ``` **Response**: ```protobuf theme={null} message ListTemplatesResponse { repeated TemplateSummary templates = 1; } message TemplateSummary { string name = 1; string version = 2; string key = 3; string content_hash = 4; } ``` *** ### ApproveTask Approve or deny a task requiring human approval. **Request**: ```protobuf theme={null} message ApproveTaskRequest { string approval_id = 1; string workflow_id = 2; string run_id = 3; bool approved = 4; string feedback = 5; string modified_action = 6; string approved_by = 7; } ``` **Response**: ```protobuf theme={null} message ApproveTaskResponse { bool success = 1; string message = 2; } ``` *** ### GetPendingApprovals List pending approval requests. **Request**: ```protobuf theme={null} message GetPendingApprovalsRequest { string user_id = 1; string session_id = 2; } ``` **Response**: ```protobuf theme={null} message GetPendingApprovalsResponse { repeated PendingApproval approvals = 1; } message PendingApproval { string approval_id = 1; string workflow_id = 2; string query = 3; string proposed_action = 4; string reason = 5; google.protobuf.Timestamp requested_at = 6; google.protobuf.Struct metadata = 7; } ``` *** ## StreamingService Real-time event streaming service. ### Service Definition ```protobuf theme={null} service StreamingService { rpc StreamTaskExecution(StreamRequest) returns (stream TaskUpdate); } ``` *** ### StreamTaskExecution Stream real-time events for a task (server-streaming RPC). **Request**: ```protobuf theme={null} message StreamRequest { string workflow_id = 1; repeated string types = 2; // Optional event type filter uint64 last_event_id = 3; // Resume from this event (backward compat) string last_stream_id = 4; // Resume from Redis stream ID (preferred) } ``` **Response** (stream): ```protobuf theme={null} message TaskUpdate { string workflow_id = 1; string type = 2; // Event type (see Event Types Catalog) string agent_id = 3; string message = 4; google.protobuf.Timestamp timestamp = 5; uint64 seq = 6; // Sequence number string stream_id = 7; // Redis stream ID for resumption } ``` **Example** (gRPC CLI): ```bash theme={null} grpcurl -plaintext -d '{ "workflow_id": "wf-550e8400-e29b-41d4-a716-446655440000" }' localhost:50052 shannon.orchestrator.StreamingService/StreamTaskExecution ``` **Response Stream**: ```json theme={null} {"workflow_id": "wf-...", "type": "WORKFLOW_STARTED", "message": "Task started", "seq": 1} {"workflow_id": "wf-...", "type": "AGENT_STARTED", "agent_id": "data-loader", "seq": 2} {"workflow_id": "wf-...", "type": "STATUS_UPDATE", "message": "Loading data...", "seq": 3} {"workflow_id": "wf-...", "type": "TOOL_INVOKED", "message": "Invoking csv_loader", "seq": 4} {"workflow_id": "wf-...", "type": "TOOL_OBSERVATION", "message": "Loaded 15,000 rows", "seq": 5} ... {"workflow_id": "wf-...", "type": "WORKFLOW_COMPLETED", "message": "Task complete", "seq": 42} ``` **Event Type Filtering**: ```bash theme={null} # Only workflow lifecycle events grpcurl -plaintext -d '{ "workflow_id": "wf-...", "types": ["WORKFLOW_STARTED", "WORKFLOW_COMPLETED", "ERROR_OCCURRED"] }' localhost:50052 shannon.orchestrator.StreamingService/StreamTaskExecution ``` **Stream Resumption**: ```bash theme={null} # Resume from last seen event grpcurl -plaintext -d '{ "workflow_id": "wf-...", "last_stream_id": "1698765432000-0" }' localhost:50052 shannon.orchestrator.StreamingService/StreamTaskExecution ``` See [Event Types Catalog](/en/api/event-types) for all event types. *** ## AgentService Agent execution and tool management service. ### Service Definition ```protobuf theme={null} service AgentService { rpc ExecuteTask(ExecuteTaskRequest) returns (ExecuteTaskResponse); rpc StreamExecuteTask(ExecuteTaskRequest) returns (stream TaskUpdate); rpc GetCapabilities(GetCapabilitiesRequest) returns (GetCapabilitiesResponse); rpc HealthCheck(HealthCheckRequest) returns (HealthCheckResponse); rpc DiscoverTools(DiscoverToolsRequest) returns (DiscoverToolsResponse); rpc GetToolCapability(GetToolCapabilityRequest) returns (GetToolCapabilityResponse); } ``` *** ### ExecuteTask Execute a task with a single agent (unary RPC). **Request**: ```protobuf theme={null} message ExecuteTaskRequest { TaskMetadata metadata = 1; string query = 2; google.protobuf.Struct context = 3; ExecutionMode mode = 4; repeated string available_tools = 5; AgentConfig config = 6; SessionContext session_context = 7; } message AgentConfig { int32 max_iterations = 1; int32 timeout_seconds = 2; bool enable_sandbox = 3; int64 memory_limit_mb = 4; bool enable_learning = 5; } ``` **Response**: ```protobuf theme={null} message ExecuteTaskResponse { string task_id = 1; StatusCode status = 2; string result = 3; repeated ToolCall tool_calls = 4; repeated ToolResult tool_results = 5; ExecutionMetrics metrics = 6; string error_message = 7; AgentState final_state = 8; } enum AgentState { AGENT_STATE_UNSPECIFIED = 0; AGENT_STATE_IDLE = 1; AGENT_STATE_PLANNING = 2; AGENT_STATE_EXECUTING = 3; AGENT_STATE_WAITING = 4; AGENT_STATE_COMPLETED = 5; AGENT_STATE_FAILED = 6; } ``` *** ### StreamExecuteTask Execute task with streaming updates (server-streaming RPC). **Request**: Same as `ExecuteTask` **Response** (stream): ```protobuf theme={null} message TaskUpdate { string task_id = 1; AgentState state = 2; string message = 3; ToolCall tool_call = 4; ToolResult tool_result = 5; double progress = 6; } ``` *** ### GetCapabilities Get agent capabilities. **Request**: ```protobuf theme={null} message GetCapabilitiesRequest {} ``` **Response**: ```protobuf theme={null} message GetCapabilitiesResponse { repeated string supported_tools = 1; repeated ExecutionMode supported_modes = 2; int64 max_memory_mb = 3; int32 max_concurrent_tasks = 4; string version = 5; } ``` *** ### HealthCheck Check agent service health. **Request**: ```protobuf theme={null} message HealthCheckRequest {} ``` **Response**: ```protobuf theme={null} message HealthCheckResponse { bool healthy = 1; string message = 2; int64 uptime_seconds = 3; int32 active_tasks = 4; double memory_usage_percent = 5; } ``` *** ### DiscoverTools Discover available tools by query or category. **Request**: ```protobuf theme={null} message DiscoverToolsRequest { string query = 1; repeated string categories = 2; repeated string tags = 3; bool exclude_dangerous = 4; int32 max_results = 5; } ``` **Response**: ```protobuf theme={null} message DiscoverToolsResponse { repeated ToolCapability tools = 1; } message ToolCapability { string id = 1; string name = 2; string description = 3; string category = 4; google.protobuf.Struct input_schema = 5; google.protobuf.Struct output_schema = 6; repeated string required_permissions = 7; int64 estimated_duration_ms = 8; bool is_dangerous = 9; string version = 10; string author = 11; repeated string tags = 12; repeated ToolExample examples = 13; RateLimit rate_limit = 14; int64 cache_ttl_ms = 15; } ``` **Example**: ```bash theme={null} grpcurl -plaintext -d '{ "query": "data analysis", "categories": ["data"], "exclude_dangerous": true, "max_results": 10 }' localhost:50051 shannon.agent.AgentService/DiscoverTools ``` *** ### GetToolCapability Get detailed capability of a specific tool. **Request**: ```protobuf theme={null} message GetToolCapabilityRequest { string tool_id = 1; } ``` **Response**: ```protobuf theme={null} message GetToolCapabilityResponse { ToolCapability tool = 1; } ``` *** ## LLMService LLM provider gateway service. ### Service Definition ```protobuf theme={null} service LLMService { rpc GenerateCompletion(GenerateCompletionRequest) returns (GenerateCompletionResponse); rpc StreamCompletion(GenerateCompletionRequest) returns (stream CompletionChunk); rpc EmbedText(EmbedTextRequest) returns (EmbedTextResponse); rpc AnalyzeComplexity(AnalyzeComplexityRequest) returns (AnalyzeComplexityResponse); rpc ListModels(ListModelsRequest) returns (ListModelsResponse); } ``` *** ### GenerateCompletion Generate LLM completion (unary RPC). **Request**: ```protobuf theme={null} message GenerateCompletionRequest { repeated Message messages = 1; ModelTier tier = 2; string specific_model = 3; GenerationConfig config = 4; TaskMetadata metadata = 5; repeated ToolDefinition available_tools = 6; } message Message { string role = 1; string content = 2; repeated ToolCall tool_calls = 3; string name = 4; } message GenerationConfig { double temperature = 1; double top_p = 2; int32 max_tokens = 3; repeated string stop_sequences = 4; double presence_penalty = 5; double frequency_penalty = 6; bool enable_caching = 7; string cache_key = 8; } ``` **Response**: ```protobuf theme={null} message GenerateCompletionResponse { string completion = 1; repeated ToolCall tool_calls = 2; TokenUsage usage = 3; string model_used = 4; Provider provider = 5; bool cache_hit = 6; string finish_reason = 7; } enum Provider { PROVIDER_UNSPECIFIED = 0; PROVIDER_OPENAI = 1; PROVIDER_ANTHROPIC = 2; PROVIDER_GOOGLE = 3; PROVIDER_DEEPSEEK = 4; PROVIDER_QWEN = 5; PROVIDER_BEDROCK = 6; PROVIDER_OLLAMA = 7; PROVIDER_VLLM = 8; } ``` *** ### StreamCompletion Generate streaming LLM completion (server-streaming RPC). **Request**: Same as `GenerateCompletion` **Response** (stream): ```protobuf theme={null} message CompletionChunk { string delta = 1; ToolCall tool_call_delta = 2; string finish_reason = 3; TokenUsage usage = 4; } ``` *** ### EmbedText Generate text embeddings. **Request**: ```protobuf theme={null} message EmbedTextRequest { repeated string texts = 1; string model = 2; } ``` **Response**: ```protobuf theme={null} message EmbedTextResponse { repeated Embedding embeddings = 1; int32 dimensions = 2; string model_used = 3; } message Embedding { repeated float vector = 1; string text = 2; } ``` *** ### AnalyzeComplexity Analyze query complexity and recommend execution mode. **Request**: ```protobuf theme={null} message AnalyzeComplexityRequest { string query = 1; google.protobuf.Struct context = 2; repeated string available_tools = 3; } ``` **Response**: ```protobuf theme={null} message AnalyzeComplexityResponse { ExecutionMode recommended_mode = 1; double complexity_score = 2; repeated string required_capabilities = 3; int32 estimated_agents = 4; int32 estimated_tokens = 5; double estimated_cost_usd = 6; string reasoning = 7; } ``` **Example**: ```bash theme={null} grpcurl -plaintext -d '{ "query": "Analyze Q4 sales data and create visualizations", "available_tools": ["csv_loader", "pandas", "matplotlib"] }' localhost:8000 shannon.llm.LLMService/AnalyzeComplexity ``` **Response**: ```json theme={null} { "recommended_mode": "EXECUTION_MODE_COMPLEX", "complexity_score": 0.78, "required_capabilities": ["data_loading", "analysis", "visualization"], "estimated_agents": 3, "estimated_tokens": 5000, "estimated_cost_usd": 0.08, "reasoning": "Task requires data loading, statistical analysis, and visualization generation" } ``` *** ### ListModels List available LLM models. **Request**: ```protobuf theme={null} message ListModelsRequest { ModelTier tier = 1; Provider provider = 2; } ``` **Response**: ```protobuf theme={null} message ListModelsResponse { repeated ModelInfo models = 1; } message ModelInfo { string id = 1; string name = 2; Provider provider = 3; ModelTier tier = 4; int32 context_window = 5; double cost_per_1k_prompt_tokens = 6; double cost_per_1k_completion_tokens = 7; bool supports_tools = 8; bool supports_streaming = 9; bool available = 10; } ``` *** ## SessionService Multi-turn conversation management service. ### Service Definition ```protobuf theme={null} service SessionService { rpc CreateSession(CreateSessionRequest) returns (CreateSessionResponse); rpc GetSession(GetSessionRequest) returns (GetSessionResponse); rpc UpdateSession(UpdateSessionRequest) returns (UpdateSessionResponse); rpc DeleteSession(DeleteSessionRequest) returns (DeleteSessionResponse); rpc ListSessions(ListSessionsRequest) returns (ListSessionsResponse); rpc AddMessage(AddMessageRequest) returns (AddMessageResponse); rpc ClearHistory(ClearHistoryRequest) returns (ClearHistoryResponse); } ``` *** ### CreateSession Create a new conversation session. **Request**: ```protobuf theme={null} message CreateSessionRequest { string user_id = 1; google.protobuf.Struct initial_context = 2; int32 max_history = 3; // Max messages to keep int32 ttl_seconds = 4; // Session TTL } ``` **Response**: ```protobuf theme={null} message CreateSessionResponse { string session_id = 1; google.protobuf.Timestamp created_at = 2; google.protobuf.Timestamp expires_at = 3; StatusCode status = 4; string message = 5; } ``` *** ### GetSession Retrieve session details. **Request**: ```protobuf theme={null} message GetSessionRequest { string session_id = 1; bool include_history = 2; } ``` **Response**: ```protobuf theme={null} message GetSessionResponse { Session session = 1; StatusCode status = 2; string message = 3; } message Session { string id = 1; string user_id = 2; google.protobuf.Struct context = 3; repeated Message history = 4; google.protobuf.Timestamp created_at = 5; google.protobuf.Timestamp last_active = 6; google.protobuf.Timestamp expires_at = 7; SessionMetrics metrics = 8; } ``` *** ### UpdateSession Update session context or extend TTL. **Request**: ```protobuf theme={null} message UpdateSessionRequest { string session_id = 1; google.protobuf.Struct context_updates = 2; int32 extend_ttl_seconds = 3; } ``` **Response**: ```protobuf theme={null} message UpdateSessionResponse { bool success = 1; google.protobuf.Timestamp new_expires_at = 2; StatusCode status = 3; string message = 4; } ``` *** ### DeleteSession Delete a session. **Request**: ```protobuf theme={null} message DeleteSessionRequest { string session_id = 1; } ``` **Response**: ```protobuf theme={null} message DeleteSessionResponse { bool success = 1; StatusCode status = 2; string message = 3; } ``` *** ### ListSessions List user sessions. **Request**: ```protobuf theme={null} message ListSessionsRequest { string user_id = 1; bool active_only = 2; int32 limit = 3; int32 offset = 4; } ``` **Response**: ```protobuf theme={null} message ListSessionsResponse { repeated SessionSummary sessions = 1; int32 total_count = 2; StatusCode status = 3; string message = 4; } message SessionSummary { string id = 1; string user_id = 2; google.protobuf.Timestamp created_at = 3; google.protobuf.Timestamp last_active = 4; int32 message_count = 5; int32 total_tokens = 6; bool is_active = 7; } ``` *** ### AddMessage Add message to session history. **Request**: ```protobuf theme={null} message AddMessageRequest { string session_id = 1; Message message = 2; } message Message { string id = 1; string role = 2; // "user", "assistant", "system" string content = 3; google.protobuf.Timestamp timestamp = 4; int32 tokens_used = 5; google.protobuf.Struct metadata = 6; } ``` **Response**: ```protobuf theme={null} message AddMessageResponse { bool success = 1; int32 history_size = 2; StatusCode status = 3; string message = 4; } ``` *** ### ClearHistory Clear session message history. **Request**: ```protobuf theme={null} message ClearHistoryRequest { string session_id = 1; bool keep_context = 2; // Keep persistent context } ``` **Response**: ```protobuf theme={null} message ClearHistoryResponse { bool success = 1; StatusCode status = 2; string message = 3; } ``` *** ## Error Handling ### Status Codes All responses include a `StatusCode`: * `STATUS_CODE_OK (1)` - Success * `STATUS_CODE_ERROR (2)` - Generic error * `STATUS_CODE_TIMEOUT (3)` - Operation timeout * `STATUS_CODE_RATE_LIMITED (4)` - Rate limit exceeded * `STATUS_CODE_BUDGET_EXCEEDED (5)` - Token/cost budget exceeded ### gRPC Status Codes Standard gRPC status codes used: * `OK (0)` - Success * `CANCELLED (1)` - Request cancelled * `INVALID_ARGUMENT (3)` - Invalid request parameters * `DEADLINE_EXCEEDED (4)` - Timeout * `NOT_FOUND (5)` - Resource not found * `ALREADY_EXISTS (6)` - Resource already exists * `PERMISSION_DENIED (7)` - Insufficient permissions * `RESOURCE_EXHAUSTED (8)` - Rate limit/quota exceeded * `UNAUTHENTICATED (16)` - Missing/invalid credentials * `UNAVAILABLE (14)` - Service unavailable * `INTERNAL (13)` - Internal server error ### Example Error Handling (Python) ```python theme={null} import grpc from shannon.orchestrator import orchestrator_pb2, orchestrator_pb2_grpc channel = grpc.insecure_channel('localhost:50052') stub = orchestrator_pb2_grpc.OrchestratorServiceStub(channel) try: response = stub.SubmitTask( orchestrator_pb2.SubmitTaskRequest( query="Analyze data", metadata=orchestrator_pb2.TaskMetadata(user_id="user-123") ) ) if response.status == orchestrator_pb2.STATUS_CODE_OK: print(f"Task submitted: {response.workflow_id}") else: print(f"Error: {response.message}") except grpc.RpcError as e: if e.code() == grpc.StatusCode.UNAUTHENTICATED: print("Authentication required") elif e.code() == grpc.StatusCode.RESOURCE_EXHAUSTED: print("Rate limit exceeded") elif e.code() == grpc.StatusCode.DEADLINE_EXCEEDED: print("Request timeout") else: print(f"gRPC error: {e.code()} - {e.details()}") ``` *** ## Service Endpoints | Service | Default Port | Protocol | TLS | | ------------------- | ------------ | ------------- | -------- | | OrchestratorService | 50052 | gRPC (HTTP/2) | Optional | | StreamingService | 50052 | gRPC (HTTP/2) | Optional | | AgentService | 50051 | gRPC (HTTP/2) | Optional | | LLMService | 8000 | gRPC (HTTP/2) | Optional | | SessionService | 50052 | gRPC (HTTP/2) | Optional | **Gateway REST API** also available on port 8080 (HTTP/REST wrapper around gRPC). See [REST API Reference](/en/api/rest/overview) for HTTP endpoints. *** ## Code Generation Generate client code from .proto files: ### Python ```bash theme={null} python -m grpc_tools.protoc \ -I protos \ --python_out=. \ --grpc_python_out=. \ protos/**/*.proto ``` ### Go ```bash theme={null} protoc -I protos \ --go_out=. \ --go-grpc_out=. \ protos/**/*.proto ``` ### TypeScript ```bash theme={null} protoc -I protos \ --ts_out=. \ --grpc_out=. \ protos/**/*.proto ``` *** ## Related Topics HTTP REST endpoints Streaming event catalog Python client library Data persistence # LLM Caching Source: https://docs.shannon.run/en/architecture/llm-caching Intelligent prompt caching for faster responses and reduced costs ## Overview Shannon implements a multi-layered caching system for LLM responses, providing significant cost savings and latency reduction for repeated or similar queries. The caching system supports both in-memory and Redis backends. ## Architecture LLM Response Caching ## Cache Backends ### In-Memory Cache (Default) The default caching backend uses an LRU (Least Recently Used) dictionary with automatic eviction. **Features:** * Zero external dependencies * Fast lookup (O(1) average) * Automatic eviction when capacity reached * Hit rate tracking **Limitations:** * Not shared across instances * Lost on service restart ### Redis Cache For production deployments, Redis provides distributed caching across multiple instances. **Features:** * Distributed across all LLM service instances * Persistent storage * Automatic TTL expiration * High availability with Redis Sentinel/Cluster **Configuration:** ```bash theme={null} # Option 1: Full URL export REDIS_URL="redis://localhost:6379" # Option 2: Individual components export REDIS_HOST="redis" export REDIS_PORT="6379" export REDIS_PASSWORD="your-password" # Optional ``` ## Configuration ### Global Settings Configure caching in `config/models.yaml`: ```yaml theme={null} prompt_cache: enabled: true ttl_seconds: 3600 # Default 1 hour max_cache_size_mb: 2048 # Memory limit similarity_threshold: 0.95 # Semantic similarity threshold ``` | Parameter | Default | Description | | ---------------------- | ------- | ------------------------------- | | `enabled` | `true` | Master switch for caching | | `ttl_seconds` | `3600` | Default cache entry lifetime | | `max_cache_size_mb` | `2048` | Maximum cache size in MB | | `similarity_threshold` | `0.95` | Threshold for semantic matching | ### Per-Request Override Override cache behavior on individual requests: ```python theme={null} from shannon import Client client = Client() response = client.complete( messages=[{"role": "user", "content": "Explain quantum computing"}], cache_key="quantum-intro", # Custom cache key cache_ttl=7200 # 2 hours for this request ) # Check if response was cached if response.cached: print("Response served from cache") ``` ## Cache Key Generation Cache keys are generated deterministically from request parameters: ```python theme={null} key_data = { "messages": messages, "model_tier": model_tier, "model": model, "temperature": temperature, "max_tokens": max_tokens, "functions": functions, "seed": seed, } # SHA-256 hash of sorted JSON ``` ### Included Parameters * Message content and roles * Model tier and specific model * Temperature and max\_tokens * Function definitions * Random seed ### Excluded Parameters * Streaming flag (streaming not cached) * Session/task IDs (cache is request-based) ## Caching Rules ### When Responses Are Cached Responses are cached when: * Caching is enabled globally * Request is non-streaming * Response has non-empty content OR has function\_call * Finish reason is not "length" (truncated) or "content\_filter" * For JSON mode: content is valid JSON object ### When Responses Are NOT Cached The following responses are never cached to ensure quality: * Streaming responses * Truncated responses (finish\_reason: "length") * Content-filtered responses * Empty responses without function calls * Invalid JSON in strict JSON mode ## Cache Validation Before serving cached responses, Shannon validates: 1. **Finish Reason Check**: Skips truncated or filtered responses 2. **JSON Mode Validation**: Ensures valid JSON object for JSON mode 3. **Content Presence**: Requires non-empty content or function\_call ```python theme={null} def _should_cache_response(self, request, response) -> bool: # Check finish_reason fr = (response.finish_reason or "").lower() if fr in {"length", "content_filter"}: return False # For strict JSON mode if is_strict_json_mode(request): try: obj = json.loads(response.content or "") if not isinstance(obj, dict): return False except: return False # Require content or function_call if not response.content.strip() and not response.function_call: return False return True ``` ## Environment Variables | Variable | Default | Description | | ---------------- | ------- | ------------------------- | | `REDIS_URL` | - | Full Redis connection URL | | `LLM_REDIS_URL` | - | Alternative Redis URL | | `REDIS_HOST` | `redis` | Redis hostname | | `REDIS_PORT` | `6379` | Redis port | | `REDIS_PASSWORD` | - | Optional Redis password | ## Performance Impact ### Latency Reduction | Scenario | Latency | | ------------------------ | ------------ | | Cache hit | 1-5ms | | Cache miss (small model) | 500-2000ms | | Cache miss (large model) | 2000-10000ms | ### Cost Savings Cache hits eliminate LLM provider costs entirely: * Typical hit rates: 20-40% for diverse workloads * High hit rates: 60-80% for repetitive queries * Potential cost reduction: 20-80% depending on workload ## Monitoring ### Cache Metrics The LLM service exposes cache metrics: ```python theme={null} # Access hit rate cache_hit_rate = manager.cache.hit_rate # Response includes cache status response = await manager.complete(...) print(f"Cached: {response.cached}") ``` ### API Response Fields ```json theme={null} { "content": "...", "cached": true, "finish_reason": "stop", "usage": { "prompt_tokens": 50, "completion_tokens": 100 } } ``` ## Best Practices ### Maximize Cache Hits 1. **Normalize prompts**: Consistent formatting improves hit rates 2. **Use deterministic seeds**: Set `seed` for reproducible outputs 3. **Standardize temperatures**: Use consistent temperature values 4. **Reuse system prompts**: Keep system messages consistent ### Cache Key Strategy ```python theme={null} # Good: Specific, reusable cache key cache_key="product-summary-v1-{product_id}" # Avoid: Generic keys that may collide cache_key="summary" ``` ### Redis Configuration For production: ```yaml theme={null} # Redis with persistence redis: appendonly: yes maxmemory: 2gb maxmemory-policy: allkeys-lru ``` ## Troubleshooting ### Low Hit Rate * Check prompt normalization * Verify temperature consistency * Review cache TTL settings * Monitor cache eviction rate ### Cache Not Working 1. Verify `prompt_cache.enabled: true` in config 2. Check Redis connection (if using Redis) 3. Ensure requests are non-streaming 4. Verify responses are not being filtered ### Memory Issues * Reduce `max_cache_size_mb` * Use Redis for large-scale deployments * Implement cache partitioning by tenant ## Next Steps Configure model tiers and routing Understand budget management # Architecture Overview Source: https://docs.shannon.run/en/architecture/overview High-level overview of Shannon's architecture and components ## System Architecture Shannon is built as a distributed microservices system designed for production AI agent orchestration: Shannon System Architecture ## Core Components ### Gateway (Port 8080) **Technology**: Go **Purpose**: REST API layer for external clients The Gateway provides: * HTTP/JSON API interface * Authentication and authorization (API keys) * Rate limiting per user * Idempotency support * SSE and WebSocket streaming * OpenAPI specification Authentication is **disabled by default** for easy adoption. Enable it in production with `GATEWAY_SKIP_AUTH=0`. ### Orchestrator (Port 50052) **Technology**: Go + Temporal **Purpose**: Central workflow coordination The Orchestrator handles: * Task routing and decomposition * Cognitive pattern selection (CoT, ToT, ReAct) * Budget and token usage enforcement * Session management * OPA policy evaluation * Multi-agent coordination **Key Technology**: [Temporal](https://temporal.io) - Provides durable, deterministic workflows that can be replayed for debugging. ### Agent Core (Port 50051) **Technology**: Rust **Purpose**: Secure execution layer The Agent Core provides: * WASI (WebAssembly System Interface) sandboxing * Secure Python code execution (CPython 3.11 in WASI) * Tool registry and execution * Result caching (LRU with TTL) * Circuit breakers and rate limiting All code execution happens in a WASI sandbox with **no network access** and **read-only filesystem**. ### LLM Service (Port 8000) **Technology**: Python + FastAPI **Purpose**: Multi-provider LLM gateway The LLM Service handles: * Multi-provider abstraction (OpenAI, Anthropic, Google, etc.) * Intelligent caching with SHA256-based deduplication * MCP (Model Context Protocol) tool integration * Web search integration (Exa, Perplexity, etc.) * Embeddings and document chunking ## Data Flow Here's how a task flows through Shannon: Client submits task via REST API to Gateway Gateway forwards to Orchestrator, which creates a Temporal workflow Orchestrator analyzes task complexity and selects cognitive pattern For complex tasks, breaks into subtasks and creates DAG (Directed Acyclic Graph) Orchestrator invokes Agent Core for each subtask Agent Core calls LLM Service, which routes to appropriate provider If needed, executes tools in WASI sandbox or calls external APIs Orchestrator combines results from all agents Final result returned to client via Gateway ## Persistence Layer ### PostgreSQL Stores: * Task metadata and execution history * Session state and context * User and API key data * Workflow history **Schema**: Relational storage for tasks, sessions, and agent metadata ### Redis Provides: * Session caching (TTL: 3600s) * LLM response caching * Rate limiter state * Pub/sub for events ### Qdrant Vector database for: * Semantic memory retrieval * Session-scoped vector collections * MMR (Maximal Marginal Relevance) for diversity ## Observability Shannon includes comprehensive observability: ### Metrics (Prometheus) Each service exposes metrics: * **Orchestrator**: `:2112/metrics` * **Agent Core**: `:2113/metrics` * **LLM Service**: `:8000/metrics` Metrics include: * Request rates and latency * Token usage and costs * Cache hit/miss rates * Error rates by type * Circuit breaker status ### Tracing (OpenTelemetry) Distributed tracing across all services with context propagation via `traceparent` headers. ### Desktop Application Native Tauri/Next.js desktop client providing: * Active tasks and workflows (Runs view) * Event streams (Run Details) * Basic system and task-level insights ### Temporal UI (Port 8088) Native Temporal interface for: * Workflow visualization * Execution history * Replay debugging * Worker status ## Design Principles ### 1. Reliability * **Temporal workflows** ensure durability - workflows survive service restarts * **Circuit breakers** prevent cascading failures * **Graceful degradation** when services are unavailable ### 2. Security * **WASI sandboxing** isolates untrusted code execution * **OPA policies** enforce fine-grained access control * **Multi-tenancy** with tenant isolation ### 3. Cost Control * **Token budgets** prevent runaway costs * **Intelligent routing** to cheaper models when appropriate * **Learning router** improves cost efficiency over time (85-95% savings) ### 4. Observability * **Prometheus metrics** for monitoring * **OpenTelemetry tracing** for debugging * **Deterministic replay** via Temporal ## Scalability Shannon scales horizontally: * **Stateless services**: Gateway, Orchestrator, Agent Core can scale independently * **Temporal workers**: Add more workers to increase throughput * **Database**: PostgreSQL with read replicas, Redis cluster, Qdrant distributed mode ## Next Steps Deep dive into agents and workflows Explore the complete API Manage and optimize costs Get started with the SDK # Temporal Workflows Source: https://docs.shannon.run/en/architecture/temporal-workflows Durable execution with Temporal for reliable AI agent orchestration ## Overview Shannon uses [Temporal](https://temporal.io) as its workflow engine, providing durable, deterministic execution that survives service restarts and can be replayed for debugging. ## Architecture Temporal Architecture Layers ## Workflow Types Shannon implements several workflow types for different use cases: ### OrchestratorWorkflow The main entry point that routes queries to appropriate strategy workflows based on complexity analysis. ### Strategy Workflows | Workflow | Best For | Patterns Used | | ----------------------- | ------------------------------------ | -------------------------------- | | **DAGWorkflow** | Multi-step tasks with clear subtasks | Parallel/Sequential + Reflection | | **ReactWorkflow** | Tool-using tasks, iterative solving | Reason-Act-Observe loops | | **ResearchWorkflow** | Information gathering, synthesis | React or Parallel + Reflection | | **ExploratoryWorkflow** | Open-ended discovery | Tree-of-Thoughts + Debate | | **ScientificWorkflow** | Hypothesis testing | CoT + Debate + ToT + Reflection | ### SupervisorWorkflow Advanced workflow for dynamic team management with agent recruitment and retirement. ### ScheduledTaskWorkflow Wrapper workflow for recurring task execution via cron schedules. ## Workflow Selection Logic Workflow Selection Logic ## Reasoning Patterns Shannon composes these patterns within workflows: ### Chain-of-Thought (CoT) Step-by-step reasoning with confidence tracking for complex problems. ### Tree-of-Thoughts (ToT) Systematic exploration with branching and pruning for problems with multiple solution paths. ### ReAct Reason-Act-Observe loops with tool integration for iterative problem solving. ### Debate Multi-agent debate for exploring perspectives and strengthening answers. ### Reflection Iterative quality improvement through self-evaluation. ## Execution Patterns ### Parallel Execution * Concurrent agent execution with semaphore control * Configurable concurrency limits * Result aggregation ### Sequential Execution * Step-by-step execution with result passing * Context accumulation between steps ### Hybrid (DAG) Execution * Dependency graph execution with topological sorting * Combines parallel and sequential based on dependencies ## Configuration ### Workflow Options ```go theme={null} type WorkflowConfig struct { ExploratoryMaxIterations int // Max exploration rounds ExploratoryConfidenceThreshold float64 // Confidence target ScientificMaxHypotheses int // Number of hypotheses ScientificMaxIterations int // Max testing rounds } ``` ### Environment Variables | Variable | Default | Description | | --------------------------- | --------------- | -------------------------- | | `TEMPORAL_HOST` | `temporal:7233` | Temporal server address | | `AGENT_TIMEOUT_SECONDS` | `600` | Agent execution timeout | | `DECOMPOSE_TIMEOUT_SECONDS` | `30` | Task decomposition timeout | ## Temporal UI Access the Temporal UI at `http://localhost:8088` for: * **Workflow Visualization**: See workflow execution graphs * **Execution History**: Full event history for each workflow * **Replay Debugging**: Step through workflow execution * **Worker Status**: Monitor worker health and task queues ## Deterministic Replay Temporal ensures workflows are deterministic: All workflow code must be deterministic - no random numbers, current time, or external calls directly in workflow code. Use activities for non-deterministic operations. ### Version Gates Shannon uses version gates for backward-compatible changes: ```go theme={null} v := workflow.GetVersion(ctx, "memory_retrieval_v1", workflow.DefaultVersion, 1) if v >= 1 { // New memory retrieval logic } ``` ## Token Budget Management All workflows respect token budgets through: 1. **Middleware**: Budget enforcement at workflow entry 2. **Pattern Options**: `BudgetAgentMax` field limits per-agent tokens 3. **Activity Budgets**: Controlled execution with budget tracking ## Best Practices ### When to Use Each Mode | Mode | Use Case | Example | | ------------ | ---------------- | ------------------------- | | `simple` | Direct questions | "What is 2+2?" | | `standard` | Multi-step tasks | "Summarize this document" | | `complex` | Deep analysis | "Compare frameworks" | | `supervisor` | Dynamic teams | "Research and implement" | ### Pattern Selection 1. **Use Reflection** when quality matters more than speed 2. **Use Chain-of-Thought** for explicit reasoning steps 3. **Use Debate** when multiple perspectives strengthen answers 4. **Use Tree-of-Thoughts** for complex problems with multiple paths 5. **Use React** when environmental feedback is needed ## Monitoring ### Prometheus Metrics * `shannon_workflow_duration_seconds` - Workflow execution time * `shannon_workflow_status` - Success/failure counts * `shannon_activity_duration_seconds` - Activity execution time ### OpenTelemetry Tracing Distributed tracing spans across: * Workflow execution * Activity calls * Agent invocations * LLM requests ## Next Steps Learn about cognitive patterns System architecture # Tool Execution Architecture Source: https://docs.shannon.run/en/architecture/tool-execution Deep dive into Shannon's tool registry, WASI sandboxing, and execution pipeline ## Overview Shannon's tool execution system provides a secure, extensible framework for integrating external capabilities into AI agents. The architecture supports three tool types with unified execution semantics. ## Tool Types External HTTP APIs following Model Context Protocol Auto-generated from OpenAPI 3.x specifications Python tools with direct service access All tool types share: * Unified JSON schema for parameters * Consistent error handling * Rate limiting and circuit breakers * Cost tracking and budget enforcement * Security validation ## Architecture Layers Tool Execution Architecture ## Tool Registry ### Registration Tools are registered at service startup with metadata: ```python theme={null} # python/llm-service/llm_service/tools/base.py class ToolMetadata: name: str # Unique identifier version: str # Semantic version description: str # LLM-visible description category: str # search, data, analytics, code, etc. parameters: List[ToolParameter] # JSON schema # Execution constraints timeout_seconds: int = 30 memory_limit_mb: int = 128 rate_limit: int = 60 # requests per minute cost_per_use: float = 0.0 # USD # Security flags dangerous: bool = False # File writes, code exec requires_auth: bool = False sandboxed: bool = False session_aware: bool = False ``` ### Discovery Tools are exposed via HTTP API for runtime discovery: ```bash theme={null} # List all tools GET /tools/list # Get tool schema GET /tools/{name}/schema # Get tool metadata GET /tools/{name}/metadata # List by category GET /tools/list?category=data ``` ## WASI Sandboxing Python code execution happens in WebAssembly System Interface (WASI) sandboxes for security isolation. ### Architecture WASI Sandbox Security ### Security Boundaries WASI sandboxes have no network stack: * ✅ Can compute, parse, transform data * ❌ Cannot make HTTP requests * ❌ Cannot open sockets * ❌ Cannot access external resources **Use case**: Safe execution of user-provided Python code Filesystem is mounted read-only: * ✅ Can read Python standard library * ✅ Can read allowed data files * ❌ Cannot write files * ❌ Cannot modify system files **Use case**: Prevent data exfiltration and tampering Memory usage is capped per execution: ```rust theme={null} // rust/agent-core/src/wasi_sandbox.rs const DEFAULT_MEMORY_LIMIT_MB: usize = 512; const DEFAULT_TABLE_LIMIT: u32 = 10000; // Required for Python ``` **Use case**: Prevent resource exhaustion attacks Each execution has strict time limits: ```yaml theme={null} # Default timeout timeout_seconds: 60 # Configurable per tool WASI_TIMEOUT_SECONDS=30 ``` **Use case**: Prevent infinite loops and hanging processes ### Setup ```bash theme={null} # Download Python WASI interpreter (20MB) ./scripts/setup_python_wasi.sh # Verify installation ls -lh wasm-interpreters/python-3.11.4.wasm # Configuration PYTHON_WASI_WASM_PATH=./wasm-interpreters/python-3.11.4.wasm WASI_MEMORY_LIMIT_MB=512 WASI_TIMEOUT_SECONDS=60 ``` ## OpenAPI Tool Loader Shannon dynamically generates tools from OpenAPI specifications. ### Spec Processing Retrieve OpenAPI spec from URL or inline YAML: ```python theme={null} spec_url = "https://api.example.com/openapi.json" spec = fetch_openapi_spec(spec_url) ``` Validate OpenAPI version and structure: ```python theme={null} # Supports OpenAPI 3.0 and 3.1 assert spec["openapi"].startswith("3.") # Extract operations operations = extract_operations(spec) ``` Create tool for each operation: ```python theme={null} for operation in operations: tool = Tool( name=operation["operationId"], description=operation["summary"], parameters=convert_schema(operation["parameters"]), execute=lambda params: call_api(operation, params) ) registry.register(tool) ``` If configured, load vendor-specific transformations: ```python theme={null} if auth_config.get("vendor"): adapter = get_vendor_adapter(auth_config["vendor"]) if adapter: body = adapter.transform_body(body, operation_id, prompt_params) ``` ### Schema Resolution ```python theme={null} # Resolve local $ref references # #/components/schemas/Pet → Pet schema definition def resolve_ref(ref: str, spec: dict) -> dict: path = ref.lstrip("#/").split("/") schema = spec for key in path: schema = schema[key] return schema ``` Remote `$ref` resolution (e.g., `https://example.com/schemas/Pet.json`) is not supported. Merge external schemas into a single spec file. ## Vendor Adapter Integration Vendor adapters transform requests/responses for domain-specific APIs without modifying Shannon's core. ### Execution Flow Vendor Adapter Sequence ### Adapter Loading ```python theme={null} # python/llm-service/llm_service/tools/vendor_adapters/__init__.py def get_vendor_adapter(name: str): """Return adapter instance by name, or None if not available.""" if not name: return None try: if name.lower() == "datainsight": from .datainsight import DataInsightAdapter return DataInsightAdapter() # Graceful fallback for missing vendor modules except ImportError: logger.warning(f"Vendor adapter '{name}' not available") return None except Exception as e: logger.error(f"Failed to load vendor adapter '{name}': {e}") return None return None ``` ### Transformation Examples **Field Aliasing:** ```python theme={null} # Transform: "users" → "di:unique_users" metric_aliases = { "users": "di:unique_users", "sessions": "di:total_sessions", } body["metrics"] = [metric_aliases.get(m, m) for m in body["metrics"]] ``` **Session Context Injection:** ```python theme={null} # Inject account_id from prompt_params if prompt_params and "account_id" in prompt_params: body["account_id"] = prompt_params["account_id"] ``` **Time Range Normalization:** ```python theme={null} # Transform: {start, end} → {startTime, endTime} if "start" in body["timeRange"]: body["timeRange"]["startTime"] = body["timeRange"].pop("start") if "end" in body["timeRange"]: body["timeRange"]["endTime"] = body["timeRange"].pop("end") ``` ## Rate Limiting ### Per-Tool Rate Limits ```python theme={null} class ToolMetadata: rate_limit: int = 60 # Requests per minute (default) ``` Implemented with token bucket algorithm: ```python theme={null} class RateLimiter: def __init__(self, rate_limit: int): self.rate = rate_limit self.tokens = rate_limit self.last_update = time.time() def allow(self) -> bool: now = time.time() elapsed = now - self.last_update # Refill tokens self.tokens = min( self.rate, self.tokens + elapsed * (self.rate / 60.0) ) self.last_update = now if self.tokens >= 1: self.tokens -= 1 return True return False ``` ### Global Rate Limits ```bash theme={null} # Environment variables MCP_RATE_LIMIT_DEFAULT=60 # Default for MCP tools WEB_SEARCH_RATE_LIMIT=120 # Web search specific CALCULATOR_RATE_LIMIT=2000 # Calculator specific PYTHON_EXECUTOR_RATE_LIMIT=60 # Python execution ``` ## Circuit Breakers Prevent cascading failures when external services are unavailable. ### Configuration ```bash theme={null} # MCP circuit breaker MCP_CB_FAILURES=5 # Open after 5 failures MCP_CB_RECOVERY_SECONDS=60 # Stay open for 60s # Agent Core enforcement ENFORCE_CB_ERROR_THRESHOLD=0.5 # Error rate to open (50%) ENFORCE_CB_WINDOW_SECONDS=30 # Sliding window ENFORCE_CB_MIN_REQUESTS=20 # Min requests before activation ``` ### States Circuit Breaker States **Closed**: Normal operation, all requests allowed **Open**: Service unavailable, all requests fail fast **Half-Open**: Testing recovery, limited requests allowed ## Cost Tracking Each tool execution tracks cost for budget enforcement. ### Per-Tool Costs ```python theme={null} class ToolMetadata: cost_per_use: float = 0.001 # USD per invocation ``` ### Budget Enforcement ```go theme={null} // go/orchestrator/internal/budget/manager.go type BudgetManager struct { maxCostPerRequest float64 maxTokensPerRequest int } func (bm *BudgetManager) CheckBudget(currentCost, currentTokens float64) error { if currentCost >= bm.maxCostPerRequest { return ErrBudgetExceeded } if currentTokens >= bm.maxTokensPerRequest { return ErrTokenLimitExceeded } return nil } ``` ### Configuration ```bash theme={null} MAX_COST_PER_REQUEST=0.50 # USD MAX_TOKENS_PER_REQUEST=10000 TOKEN_BUDGET_PER_AGENT=5000 TOKEN_BUDGET_PER_TASK=20000 ``` ## Error Handling ### Error Categories ```python theme={null} class ValidationError(Exception): """Parameter validation failed.""" pass # Missing required parameter # Invalid parameter type # Value out of range # Enum constraint violation ``` ```python theme={null} class ExecutionError(Exception): """Tool execution failed.""" pass # Timeout exceeded # Memory limit exceeded # Sandbox violation # Tool-specific errors ``` ```python theme={null} class NetworkError(Exception): """External API call failed.""" pass # Connection timeout # DNS resolution failed # TLS handshake failed # HTTP error codes (4xx, 5xx) ``` ```python theme={null} class CircuitBreakerError(Exception): """Circuit breaker is open.""" pass # Too many failures # Service unavailable # Automatic retry prevented ``` ### Retry Logic ```python theme={null} # Exponential backoff with jitter @retry( retries=3, backoff=exponential_backoff(base=1.0, max_delay=10.0), on_exception=(NetworkError, TimeoutError) ) def execute_tool(tool_name, parameters): return registry.execute(tool_name, parameters) ``` ## Performance Optimizations ### Connection Pooling ```python theme={null} # HTTP connection pool for external APIs session = aiohttp.ClientSession( connector=aiohttp.TCPConnector( limit=100, # Max total connections limit_per_host=10, # Max per host ttl_dns_cache=300, # DNS cache TTL ) ) ``` ### Response Caching ```python theme={null} # Cache OpenAPI specs @lru_cache(maxsize=100) def load_openapi_spec(spec_url: str) -> dict: return fetch_and_parse(spec_url) ``` ### Parallel Execution ```go theme={null} // Execute multiple tools in parallel func ExecuteToolsParallel(tools []Tool, parallelism int) []Result { sem := make(chan struct{}, parallelism) // Semaphore results := make([]Result, len(tools)) var wg sync.WaitGroup for i, tool := range tools { wg.Add(1) go func(idx int, t Tool) { defer wg.Done() sem <- struct{}{} // Acquire defer func() { <-sem }() // Release results[idx] = t.Execute() }(i, tool) } wg.Wait() return results } ``` Configuration: ```bash theme={null} TOOL_PARALLELISM=5 # Execute up to 5 tools concurrently ``` ## Security Considerations Always validate tool inputs and restrict execution based on user permissions. ### Domain Allowlisting ```bash theme={null} # Restrict external API access MCP_ALLOWED_DOMAINS=api.example.com,api.partner.com OPENAPI_ALLOWED_DOMAINS=api.github.com,api.stripe.com # Development only MCP_ALLOWED_DOMAINS=* # Allow all (⚠️ NOT FOR PRODUCTION) ``` ### Tool Permissions ```python theme={null} class ToolMetadata: dangerous: bool = False # File writes, code execution requires_auth: bool = False # User authentication required ``` OPA policies can gate access: ```rego theme={null} # config/opa/policies/tools.rego package tools deny[msg] { input.tool == "file_write" not is_admin(input.user) msg := "file_write requires admin role" } ``` ### API Key Management ```bash theme={null} # .env (not committed to git) WEATHER_API_KEY=sk-xxx GITHUB_TOKEN=ghp_xxx STRIPE_API_KEY=sk_live_xxx ``` ```yaml theme={null} # docker-compose.yml services: llm-service: secrets: - weather_api_key - github_token secrets: weather_api_key: external: true github_token: external: true ``` ```yaml theme={null} apiVersion: v1 kind: Secret metadata: name: api-keys type: Opaque data: weather-api-key: github-token: ``` ## Monitoring & Observability ### Metrics ```prometheus theme={null} # Tool execution metrics shannon_tool_executions_total{tool_name, status} shannon_tool_execution_duration_seconds{tool_name} shannon_tool_errors_total{tool_name, error_type} shannon_tool_cost_usd{tool_name} # Circuit breaker metrics shannon_circuit_breaker_state{base_url} shannon_circuit_breaker_failures_total{base_url} # Rate limiter metrics shannon_rate_limit_exceeded_total{tool_name} shannon_rate_limit_tokens_available{tool_name} ``` ### Logging ```python theme={null} # Structured logging for tool execution logger.info( "Tool execution completed", extra={ "tool_name": tool_name, "duration_ms": duration, "cost_usd": cost, "success": success, "error": error_message, } ) ``` ### Tracing ```python theme={null} # OpenTelemetry tracing with tracer.start_as_current_span("tool_execution") as span: span.set_attribute("tool.name", tool_name) span.set_attribute("tool.category", category) result = execute_tool(tool_name, parameters) span.set_attribute("tool.success", result.success) span.set_attribute("tool.cost_usd", result.cost) ``` ## Next Steps Learn how to add MCP, OpenAPI, and built-in tools Build domain-specific integrations Complete configuration reference Harden your Shannon deployment # Vector Memory Source: https://docs.shannon.run/en/architecture/vector-memory Semantic memory with Qdrant for intelligent context retrieval ## Overview Shannon's memory system provides intelligent context retention and retrieval across user sessions, enabling agents to maintain conversational continuity and leverage historical interactions for improved responses. ## Architecture Memory Architecture ## Storage Layers ### PostgreSQL * **Session Context**: Session-level state and metadata * **Execution Persistence**: Agent and tool execution history * **Task Tracking**: High-level task and workflow metadata ### Redis * **Session Cache**: Fast access to active session data (TTL: 3600s) * **Token Budgets**: Real-time token usage tracking * **Compression State**: Tracks context compression status ### Qdrant (Vector Store) * **Semantic Memory**: High-performance vector similarity search * **Collection Organization**: task\_embeddings, summaries, tool\_results, document\_chunks * **Hybrid Search**: Combines recency and semantic relevance ## Memory Types ### Hierarchical Memory (Default) Combines multiple retrieval strategies: * **Recent Memory**: Last N interactions from current session * **Semantic Memory**: Contextually relevant based on query similarity * **Compressed Summaries**: Condensed representations of older conversations ### Session Memory Chronological retrieval of recent interactions within a session. ### Agent Memory Individual agent execution records including: * Input queries and generated responses * Token usage and model information * Tool executions and results ### Supervisor Memory Strategic memory for intelligent task decomposition: * **Decomposition Patterns**: Successful task breakdowns for reuse * **Strategy Performance**: Aggregated metrics per strategy type * **Failure Patterns**: Known failures with mitigation strategies ## Configuration ### Environment Variables | Variable | Default | Description | | ------------------- | -------- | ---------------------- | | `QDRANT_HOST` | `qdrant` | Qdrant server hostname | | `QDRANT_PORT` | `6333` | Qdrant server port | | `REDIS_TTL_SECONDS` | `3600` | Session cache TTL | ### Embedding Requirements Memory features require OpenAI API access for text embeddings. * **Default Model**: `text-embedding-3-small` (1536 dimensions) * **Fallback Behavior**: If OpenAI key is not configured, memory operations silently degrade - workflows continue without historical context ## Key Features ### Intelligent Chunking * Splits long answers (>2000 tokens) into manageable chunks * 200-token overlap for context preservation * Batch embeddings for efficiency ### MMR (Maximal Marginal Relevance) * Diversity-aware reranking balances relevance with information diversity * Default lambda=0.7 optimizes for relevant yet diverse context * Fetches 3x requested items, then reranks for diversity ### Context Compression * Automatic triggers based on message count and token estimates * Rate limiting prevents excessive compression * Model-aware thresholds for different tiers ## Memory Retrieval Flow Incoming query is analyzed for semantic content Retrieves last N messages from current session via Redis Performs vector similarity search in Qdrant Combines results and removes duplicates Injects relevant memory into agent context ## Privacy & Data Governance ### PII Protection * Data minimization: Store only essential fields * Anonymization: UUIDs instead of real identities * Automatic PII detection and redaction ### Data Retention * **Conversation History**: 30-day default retention * **Decomposition Patterns**: 90-day retention * **User Preferences**: Session-based, 24-hour expiry ## Performance Optimizations * **Batch Processing**: Single API call for multiple chunks (5x faster) * **Smart Caching**: LRU (2048 entries) + Redis * **Payload Indexes**: 50-90% faster filtering on session\_id, tenant\_id, user\_id * **Optimized HNSW**: m=16, ef\_construct=100 for fast similarity search ## Limitations * Memory retrieval adds latency (mitigated by caching) * Vector similarity may miss exact keyword matches * Compression is lossy (preserves key points only) * Cross-session memory requires explicit session linking ## Enabling Semantic Memory Follow the steps below to enable Shannon's semantic memory system backed by Qdrant. ### Prerequisites Before proceeding, ensure the following are in place: * **Qdrant** is running (included by default in Shannon's `docker-compose.yaml`) * **`OPENAI_API_KEY`** is set in your environment (required for the `text-embedding-3-small` embedding model) ### Step-by-Step Setup Add or update the `vector` block in your `shannon.yaml` configuration: ```yaml shannon.yaml theme={null} vector: enabled: true # Must set to true (default: false) host: "qdrant" port: 6333 top_k: 10 threshold: 0.5 default_model: "text-embedding-3-small" cache_ttl: "1h" use_redis_cache: true expected_embedding_dim: 1536 ``` `vector.enabled` defaults to `false`. You must explicitly set it to `true` for semantic memory to function. Shannon automatically creates 5 collections (all using 1536-dimensional vectors from `text-embedding-3-small`): | Collection | Purpose | | ----------------- | ------------------------------------------ | | `task_embeddings` | Task result embeddings for semantic search | | `tool_results` | Tool execution result embeddings | | `cases` | Case library for pattern matching | | `document_chunks` | Document chunk embeddings for RAG | | `summaries` | Summary embeddings | You can verify the collections are created by querying the Qdrant REST API: ```bash theme={null} curl http://localhost:6333/collections ``` MMR (Maximal Marginal Relevance) balances relevance and diversity in retrieval results. When enabled, Shannon fetches a larger candidate pool and reranks to reduce redundancy while preserving relevance. ```yaml shannon.yaml theme={null} mmr_enabled: true mmr_lambda: 0.7 # 0 = pure diversity, 1 = pure relevance mmr_pool_multiplier: 3 # Fetch 3x candidates for reranking ``` A `mmr_lambda` of `0.7` is a good default — it strongly favours relevance while still filtering out near-duplicate results. If your LLM Service runs on a non-default address, or you want to tune caching behaviour, update the `embeddings` block: ```yaml shannon.yaml theme={null} embeddings: base_url: "http://llm-service:8000" default_model: "text-embedding-3-small" timeout: "10s" cache_ttl: "1h" max_lru: 4096 ``` * `cache_ttl` controls how long embedding vectors are cached in Redis. * `max_lru` sets the maximum number of entries in the in-memory LRU cache. ## Next Steps System architecture Session management # AWS Deployment Source: https://docs.shannon.run/en/deployment/aws Deploy to Amazon Web Services ## Coming Soon - Phase 3 **Estimated availability**: Phase 3 (Month 2) See [Deployment Overview](/deployment) for more information. # Azure Deployment Source: https://docs.shannon.run/en/deployment/azure Deploy to Microsoft Azure ## Coming Soon - Phase 3 **Estimated availability**: Phase 3 (Month 2) See [Deployment Overview](/deployment) for more information. # Configuration Troubleshooting Source: https://docs.shannon.run/en/deployment/configuration-troubleshooting Common configuration issues and solutions ## Overview This guide covers common configuration issues, how to diagnose them, and proven solutions. ## Quick Diagnostics ### Check Environment Variables ```bash theme={null} # View all environment variables for a service docker compose exec orchestrator env | sort # Check specific variable docker compose exec orchestrator env | grep MAX_COST_PER_REQUEST # Check if variable is set docker compose exec orchestrator printenv MAX_COST_PER_REQUEST ``` ### Verify Configuration Files ```bash theme={null} # Check if config file exists docker compose exec orchestrator ls -la ./config/ # View config file contents docker compose exec orchestrator cat ./config/features.yaml # Check for syntax errors docker compose exec orchestrator cat ./config/models.yaml | yq . ``` ### Check Service Health ```bash theme={null} # Gateway health curl http://localhost:8080/health # Orchestrator metrics curl http://localhost:2112/metrics # Agent Core health grpcurl -plaintext localhost:50051 list ``` ## Common Issues ### 1. Services Won't Start #### Missing Environment Variables **Symptoms**: * Service crashes immediately * Logs show "variable not set" errors * Container exits with code 1 **Diagnosis**: ```bash theme={null} docker compose logs orchestrator | grep -i "not set\|missing\|required" ``` **Solution**: ```bash theme={null} # Check .env file exists ls -la .env # Verify required variables are set grep -E "OPENAI_API_KEY|POSTGRES" .env # Copy from example if missing cp .env.example .env nano .env # Fill in required values # Restart services docker compose restart ``` **Required Variables**: * At least one LLM provider key (OPENAI\_API\_KEY, ANTHROPIC\_API\_KEY, etc.) * Database credentials (POSTGRES\_\*) * Redis connection (REDIS\_\*) #### Invalid Configuration Syntax **Symptoms**: * "Failed to parse config" errors * YAML syntax errors * Service fails to start **Diagnosis**: ```bash theme={null} # Check YAML syntax docker compose exec orchestrator cat ./config/features.yaml | yq . ``` **Solution**: ```bash theme={null} # Validate YAML locally yq eval ./config/features.yaml # Check for common issues cat ./config/features.yaml | grep -E "^\s+- |^\w+:" # Reset to defaults cp ./config/features.yaml.example ./config/features.yaml ``` ### 2. Authentication Failures #### Gateway Returns 401 Unauthorized **Symptoms**: * All requests return 401 * "Unauthorized" error * API key rejected **Diagnosis**: ```bash theme={null} # Check if auth is enabled docker compose exec gateway env | grep GATEWAY_SKIP_AUTH # Test with curl curl -v http://localhost:8080/api/v1/tasks \ -H "X-API-Key: sk_test_123456" 2>&1 | grep "401" ``` **Solution 1**: Disable auth for development ```bash theme={null} # Add to .env GATEWAY_SKIP_AUTH=1 # Restart gateway docker compose restart gateway # Test curl http://localhost:8080/api/v1/tasks ``` **Solution 2**: Use valid API key ```bash theme={null} # Insert API key in database docker compose exec postgres psql -U shannon -d shannon -c " INSERT INTO auth.api_keys (key, user_id, tenant_id, name, enabled) VALUES ('sk_test_123456', gen_random_uuid(), gen_random_uuid(), 'Test Key', true); " # Test with key curl -H "X-API-Key: sk_test_123456" \ http://localhost:8080/api/v1/tasks ``` #### JWT Secret Not Set **Symptoms**: * "JWT secret not configured" error * Authentication middleware fails **Solution**: ```bash theme={null} # Generate secure secret JWT_SECRET=$(openssl rand -base64 32) # Add to .env echo "JWT_SECRET=$JWT_SECRET" >> .env # Restart gateway docker compose restart gateway ``` ### 3. Database Connection Issues #### Cannot Connect to PostgreSQL **Symptoms**: * "connection refused" errors * "dial tcp: connect: connection refused" * Services crash on startup **Diagnosis**: ```bash theme={null} # Check if PostgreSQL is running docker compose ps postgres # Check PostgreSQL logs docker compose logs postgres --tail=50 # Test connection docker compose exec postgres pg_isready -U shannon ``` **Solution 1**: PostgreSQL not started ```bash theme={null} # Start PostgreSQL docker compose up -d postgres # Wait for ready docker compose exec postgres pg_isready -U shannon # Restart dependent services docker compose restart gateway orchestrator ``` **Solution 2**: Wrong credentials ```bash theme={null} # Verify .env settings grep POSTGRES .env # Should match docker-compose.yml docker compose exec postgres psql -U shannon -d shannon -c "SELECT 1;" # If password wrong, update .env and restart docker compose down docker compose up -d ``` **Solution 3**: Port conflict ```bash theme={null} # Check if port 5432 is in use lsof -i :5432 # If conflict, change port in .env POSTGRES_PORT=5433 # Update docker-compose.yml # Restart docker compose down docker compose up -d ``` #### Database Schema Not Initialized **Symptoms**: * "table does not exist" errors * "column not found" errors * SQL errors in logs **Solution**: ```bash theme={null} # Run migrations docker compose exec orchestrator make migrate # Or reset database (⚠️ DESTRUCTIVE) docker compose down -v docker compose up -d ``` ### 4. Redis Connection Issues #### Cannot Connect to Redis **Symptoms**: * "connection refused" to Redis * Session state not persisting * Cache misses **Diagnosis**: ```bash theme={null} # Check Redis status docker compose ps redis # Test connection docker compose exec redis redis-cli ping # Check logs docker compose logs redis --tail=20 ``` **Solution**: ```bash theme={null} # Start Redis docker compose up -d redis # Test connection docker compose exec redis redis-cli ping # Should return: PONG # Restart dependent services docker compose restart gateway orchestrator llm-service ``` #### Redis Authentication Failed **Symptoms**: * "NOAUTH Authentication required" * Connection works but commands fail **Solution**: ```bash theme={null} # Check if password is set docker compose exec redis redis-cli CONFIG GET requirepass # If password required, add to .env REDIS_PASSWORD=your-password # Or disable auth (development only) docker compose exec redis redis-cli CONFIG SET requirepass "" # Restart services docker compose restart ``` ### 5. LLM Provider Issues #### API Key Invalid or Expired **Symptoms**: * "Invalid API key" errors * 401 from LLM provider * Tasks fail immediately **Diagnosis**: ```bash theme={null} # Check which provider is configured docker compose exec llm-service env | grep API_KEY # Test OpenAI key curl https://api.openai.com/v1/models \ -H "Authorization: Bearer $OPENAI_API_KEY" # Test Anthropic key curl https://api.anthropic.com/v1/messages \ -H "X-API-Key: $ANTHROPIC_API_KEY" \ -H "Content-Type: application/json" \ -d '{"model":"claude-3-haiku-20240307","max_tokens":10,"messages":[{"role":"user","content":"Hi"}]}' ``` **Solution**: ```bash theme={null} # Update key in .env OPENAI_API_KEY=sk-...new-key... # Restart LLM service docker compose restart llm-service # Verify docker compose logs llm-service | grep "API key" ``` #### Rate Limit Exceeded **Symptoms**: * 429 errors from LLM provider * "Rate limit exceeded" in logs * Tasks timeout or fail **Solution 1**: Wait for rate limit reset ```bash theme={null} # Check rate limit headers docker compose logs llm-service | grep "rate" # Typical reset: 60 seconds for most providers ``` **Solution 2**: Configure rate limiting ```bash theme={null} # Add to .env RATE_LIMIT_REQUESTS=50 # Lower than provider limit RATE_LIMIT_WINDOW=60 # Restart docker compose restart llm-service ``` **Solution 3**: Use multiple providers ```bash theme={null} # Configure fallback providers in models.yaml providers: - id: openai primary: true - id: anthropic fallback: true ``` #### Quota Exceeded **Symptoms**: * "insufficient\_quota" errors * "You exceeded your current quota" * All LLM calls fail **Solution**: ```bash theme={null} # Check quota # OpenAI: https://platform.openai.com/account/usage # Anthropic: https://console.anthropic.com/settings/limits # Add credits or upgrade plan # Or use different provider OPENAI_API_KEY= ANTHROPIC_API_KEY=sk-ant-... # Restart docker compose restart llm-service ``` ### 6. Model Configuration Issues #### Model Not Found **Symptoms**: * "model not found" errors * "invalid model" errors * Tasks fail with model errors **Diagnosis**: ```bash theme={null} # Check configured models docker compose exec llm-service cat ./config/models.yaml | grep "id:" # Check environment variables docker compose exec orchestrator env | grep MODEL ``` **Solution**: ```bash theme={null} # Use valid model IDs in .env DEFAULT_MODEL_TIER=small COMPLEXITY_MODEL_ID=gpt-5 # Verify this exists DECOMPOSITION_MODEL_ID=claude-sonnet-4-5-20250929 # Or configure in models.yaml docker compose exec orchestrator cat ./config/models.yaml # Restart docker compose restart orchestrator llm-service ``` ### 7. Budget and Cost Issues #### Tasks Exceed Budget **Symptoms**: * "Budget exceeded" errors * Tasks fail with cost errors * `MAX_COST_PER_REQUEST` exceeded **Solution**: ```bash theme={null} # Increase budget limits # In .env: MAX_COST_PER_REQUEST=1.00 # Increase from 0.50 MAX_TOKENS_PER_REQUEST=20000 # Increase from 10000 # Restart docker compose restart orchestrator # Or use cheaper models DEFAULT_MODEL_TIER=small # Use GPT-5-nano instead of GPT-5 ``` #### Budget Enforcement Not Working **Symptoms**: * Costs exceed limits * No budget errors **Diagnosis**: ```bash theme={null} # Check budget enforcement docker compose exec orchestrator env | grep LLM_DISABLE_BUDGETS ``` **Solution**: ```bash theme={null} # Enable budget enforcement LLM_DISABLE_BUDGETS=1 # Orchestrator enforces budgets # Set limits MAX_COST_PER_REQUEST=0.50 MAX_TOKENS_PER_REQUEST=10000 # Restart docker compose restart orchestrator ``` ### 8. Performance Issues #### Slow Task Execution **Symptoms**: * Tasks take 2-3x expected time * High latency * Timeouts **Diagnosis**: ```bash theme={null} # Check resource usage docker stats # Check worker concurrency docker compose exec orchestrator env | grep WORKER # Check tool parallelism docker compose exec orchestrator env | grep TOOL_PARALLELISM ``` **Solution 1**: Increase parallelism ```bash theme={null} # In .env: TOOL_PARALLELISM=10 # Increase from 5 WORKER_ACT_CRITICAL=20 # Increase from 10 # Restart docker compose restart orchestrator ``` **Solution 2**: Enable caching ```bash theme={null} # In .env: ENABLE_CACHE=true CACHE_SIMILARITY_THRESHOLD=0.95 # Restart docker compose restart llm-service ``` **Solution 3**: Optimize model selection ```bash theme={null} # Use faster models DEFAULT_MODEL_TIER=small # GPT-5-nano is 10x faster than GPT-5 ``` #### High Memory Usage **Symptoms**: * OOM errors * Container restarts * High swap usage **Diagnosis**: ```bash theme={null} docker stats ``` **Solution**: ```bash theme={null} # Reduce cache sizes HISTORY_WINDOW_MESSAGES=25 # Reduce from 50 STREAMING_RING_CAPACITY=500 # Reduce from 1000 # Limit tool parallelism TOOL_PARALLELISM=3 # Reduce from 5 # Restart docker compose restart ``` ### 9. Streaming Issues #### SSE Connection Drops **Symptoms**: * SSE stream disconnects * Events stop mid-task * "Connection closed" errors **Solution 1**: Increase timeouts ```bash theme={null} # In nginx/proxy config: proxy_read_timeout 600s; proxy_connect_timeout 600s; # In docker-compose.yml for gateway: GATEWAY_READ_TIMEOUT=600 ``` **Solution 2**: Handle reconnection ```python theme={null} # Client-side reconnection while True: try: for event in stream_events(task_id): process(event) break # Task completed except ConnectionError: time.sleep(2) # Wait and retry ``` #### Events Not Received **Symptoms**: * No events in stream * Empty SSE response * Stream connects but no data **Diagnosis**: ```bash theme={null} # Check if events are being created docker compose exec postgres psql -U shannon -d shannon -c " SELECT COUNT(*) FROM event_logs WHERE workflow_id = 'task_abc123'; " # Check Redis streams docker compose exec redis redis-cli XLEN "stream:task_abc123" ``` **Solution**: ```bash theme={null} # Verify admin server is running docker compose ps orchestrator # Check admin server endpoint curl http://localhost:8081/health # Restart orchestrator docker compose restart orchestrator ``` ### 10. Tool Execution Issues #### Python Code Execution Fails **Symptoms**: * "WASI interpreter not found" * Python code tools fail * Sandbox errors **Solution**: ```bash theme={null} # Download Python WASI interpreter ./scripts/setup_python_wasi.sh # Or manual download wget https://github.com/vmware-labs/webassembly-language-runtimes/releases/download/python%2F3.11.4%2B20230908-ba7c2cf/python-3.11.4.wasm mkdir -p ./wasm-interpreters mv python-3.11.4.wasm ./wasm-interpreters/ # Verify path in .env PYTHON_WASI_WASM_PATH=./wasm-interpreters/python-3.11.4.wasm # Restart docker compose restart agent-core llm-service ``` #### Tool Timeout **Symptoms**: * "Tool execution timeout" errors * Tools hang indefinitely * WASI timeout errors **Solution**: ```bash theme={null} # Increase timeouts WASI_TIMEOUT_SECONDS=120 # Increase from 60 ENFORCE_TIMEOUT_SECONDS=180 # Increase from 90 # Restart docker compose restart agent-core ``` ## Configuration Validation ### Validate All Settings ```bash theme={null} #!/bin/bash echo "=== Shannon Configuration Validation ===" # Check .env file if [ ! -f .env ]; then echo "❌ .env file not found" exit 1 fi echo "✓ .env file exists" # Check required variables required_vars=( "POSTGRES_HOST" "REDIS_HOST" "TEMPORAL_HOST" ) for var in "${required_vars[@]}"; do if grep -q "^${var}=" .env; then echo "✓ $var is set" else echo "❌ $var is missing" fi done # Check at least one LLM provider if grep -qE "^(OPENAI|ANTHROPIC|GOOGLE)_API_KEY=.+" .env; then echo "✓ LLM provider configured" else echo "❌ No LLM provider API key set" fi # Check services are running echo "" echo "=== Service Health ===" services=("postgres" "redis" "temporal" "qdrant" "orchestrator" "agent-core" "llm-service" "gateway") for service in "${services[@]}"; do if docker compose ps | grep -q "$service.*running"; then echo "✓ $service is running" else echo "❌ $service is not running" fi done echo "" echo "=== Endpoint Tests ===" # Test Gateway if curl -f -s http://localhost:8080/health > /dev/null; then echo "✓ Gateway health check passed" else echo "❌ Gateway health check failed" fi # Test Orchestrator metrics if curl -f -s http://localhost:2112/metrics > /dev/null; then echo "✓ Orchestrator metrics available" else echo "❌ Orchestrator metrics failed" fi echo "" echo "=== Configuration Validation Complete ===" ``` ## Best Practices ### 1. Use Environment-Specific Configs ```bash theme={null} # Development .env.development ENVIRONMENT=dev DEBUG=true GATEWAY_SKIP_AUTH=1 # Production .env.production ENVIRONMENT=prod DEBUG=false GATEWAY_SKIP_AUTH=0 JWT_SECRET= ``` ### 2. Document Custom Settings ```bash theme={null} # In .env, add comments # Custom rate limit for high-volume API RATE_LIMIT_REQUESTS=500 # Increased for enterprise tier ``` ### 3. Version Control ```bash theme={null} # .gitignore .env .env.local # Commit templates .env.example .env.template ``` ### 4. Regular Validation ```bash theme={null} # Add to CI/CD ./scripts/validate-config.sh ``` ### 5. Monitor Configuration ```bash theme={null} # Track configuration changes git diff .env.example # Alert on critical changes # Monitor environment variables in production ``` ## Quick Fixes Checklist When things go wrong, try these in order: * [ ] Restart all services: `docker compose restart` * [ ] Check logs: `docker compose logs --tail=50` * [ ] Verify .env file exists and has required variables * [ ] Test database connection: `docker compose exec postgres pg_isready` * [ ] Test Redis: `docker compose exec redis redis-cli ping` * [ ] Verify at least one LLM API key is set * [ ] Check disk space: `df -h` * [ ] Check memory: `docker stats` * [ ] Full reset (last resort): `docker compose down -v && docker compose up -d` ## Getting Help If issues persist: 1. **Collect logs**: ```bash theme={null} docker compose logs > shannon-logs.txt ``` 2. **Export configuration**: ```bash theme={null} docker compose exec orchestrator env | grep -v API_KEY > config.txt ``` 3. **Check GitHub issues**: [https://github.com/Kocoro-lab/Shannon/issues](https://github.com/Kocoro-lab/Shannon/issues) ## Related Documentation Complete variable reference Docker deployment guide General troubleshooting Performance optimization # Docker Compose Deployment Source: https://docs.shannon.run/en/deployment/docker-compose Production Docker Compose configuration ## Coming Soon - Phase 3 **Estimated availability**: Phase 3 (Month 2) For development setup, see [Installation Guide](/en/quickstart/installation). # Environment Variables Source: https://docs.shannon.run/en/deployment/environment-variables Complete environment variable reference for Shannon configuration ## Overview Shannon uses environment variables for configuration across all services. This page documents all 100+ configuration options with types, defaults, and impact. ## Quick Reference | Category | Variables | Service | | ------------------------------------------------- | --------- | ------------------------- | | [Core Runtime](#core-runtime) | 3 | All | | [LLM Provider Keys](#llm-provider-api-keys) | 11 | LLM Service | | [Web Search](#web-search-providers) | 6 | LLM Service | | [Data Stores](#data-stores) | 17 | All | | [Service Endpoints](#service-endpoints) | 8 | All | | [Model Routing](#model-routing-and-budgets) | 13 | Orchestrator, LLM Service | | [Cache & Rate Limiting](#cache-and-rate-limiting) | 8 | All | | [Tool Execution](#tool-execution-and-workflow) | 11 | Orchestrator, Agent Core | | [Security](#approvals-and-security) | 6 | Gateway, Orchestrator | | [Observability](#observability-and-telemetry) | 5 | All | | [WASI Sandbox](#python-wasi-sandbox) | 4 | Agent Core, LLM Service | | [Integrations](#openapi-and-mcp) | 13 | LLM Service | | [Advanced](#advanced-orchestrator-controls) | 20+ | Orchestrator | **Total**: 100+ configuration variables ## Configuration File Copy `.env.example` to `.env` and customize: ```bash theme={null} cp .env.example .env nano .env ``` Restart services after changes: ```bash theme={null} docker compose restart ``` ## Core Runtime ### ENVIRONMENT **Type**: String **Default**: `dev` **Valid Values**: `dev`, `staging`, `prod` **Services**: All **Hot-Reload**: No (requires restart) Controls logging verbosity and policy enforcement. ```bash theme={null} ENVIRONMENT=prod ``` **Impact**: * `dev`: Verbose logging, relaxed policies * `staging`: Moderate logging, standard policies * `prod`: Minimal logging, strict policies ### DEBUG **Type**: Boolean **Default**: `false` **Valid Values**: `true`, `false` **Services**: All **Hot-Reload**: No Enable debug logging across all services. ```bash theme={null} DEBUG=true ``` **Impact**: * `true`: Debug-level logs, stack traces * `false`: Info-level logs only ### SERVICE\_NAME **Type**: String **Default**: `shannon-llm-service` **Services**: LLM Service **Hot-Reload**: No Service identifier for logging and telemetry. ```bash theme={null} SERVICE_NAME=shannon-llm-service ``` ## LLM Provider API Keys ### OPENAI\_API\_KEY **Type**: String **Required**: At least one provider key **Services**: LLM Service **Hot-Reload**: Yes OpenAI API key for GPT models. ```bash theme={null} OPENAI_API_KEY=sk-... ``` **Models Enabled**: GPT-5, GPT-5-mini, GPT-5-nano Required for semantic memory features (text embeddings). Without this key, vector search and agent memory will be disabled. ### ANTHROPIC\_API\_KEY **Type**: String **Services**: LLM Service **Hot-Reload**: Yes Anthropic API key for Claude models. ```bash theme={null} ANTHROPIC_API_KEY=sk-ant-... ``` **Models Enabled**: Claude 4.5 Sonnet, Claude 4.1 Opus, Claude 4.5 Haiku ### GOOGLE\_API\_KEY **Type**: String **Services**: LLM Service **Hot-Reload**: Yes Google AI API key for Gemini models. ```bash theme={null} GOOGLE_API_KEY=... ``` **Models Enabled**: Gemini Pro, Gemini Flash ### GROQ\_API\_KEY **Type**: String **Services**: LLM Service **Hot-Reload**: Yes Groq API key for fast inference. ```bash theme={null} GROQ_API_KEY=... ``` **Models Enabled**: Llama 3, Mixtral ### XAI\_API\_KEY **Type**: String **Services**: LLM Service **Hot-Reload**: Yes xAI API key for Grok models. ```bash theme={null} XAI_API_KEY=... ``` ### DEEPSEEK\_API\_KEY **Type**: String **Services**: LLM Service **Hot-Reload**: Yes DeepSeek API key. ```bash theme={null} DEEPSEEK_API_KEY=... ``` ### QWEN\_API\_KEY **Type**: String **Services**: LLM Service **Hot-Reload**: Yes Qwen API key for Alibaba Cloud models. ```bash theme={null} QWEN_API_KEY=... ``` ### MISTRAL\_API\_KEY **Type**: String **Services**: LLM Service **Hot-Reload**: Yes Mistral API key. ```bash theme={null} MISTRAL_API_KEY=... ``` ### AWS\_ACCESS\_KEY\_ID / AWS\_SECRET\_ACCESS\_KEY **Type**: String **Services**: LLM Service **Hot-Reload**: Yes AWS credentials for Bedrock models. ```bash theme={null} AWS_ACCESS_KEY_ID=AKIA... AWS_SECRET_ACCESS_KEY=... AWS_REGION=us-east-1 ``` **Models Enabled**: Claude (Bedrock), Llama (Bedrock), Titan ### ZAI\_API\_KEY **Type**: String **Services**: LLM Service **Hot-Reload**: Yes ZAI API key. ```bash theme={null} ZAI_API_KEY=... ``` ## Web Search Providers ### WEB\_SEARCH\_PROVIDER **Type**: String **Default**: `google` **Valid Values**: `google`, `serper`, `serpapi`, `searchapi`, `bing`, `exa`, `firecrawl` **Services**: LLM Service **Hot-Reload**: Yes Active web search provider. ```bash theme={null} WEB_SEARCH_PROVIDER=google ``` **Providers**: * `searchapi` — SearchAPI.io — multi-engine search (\$0.004/call) ### GOOGLE\_SEARCH\_API\_KEY **Type**: String **Services**: LLM Service **Hot-Reload**: Yes Google Custom Search API key. ```bash theme={null} GOOGLE_SEARCH_API_KEY=... GOOGLE_SEARCH_ENGINE_ID=... ``` Obtain from: [https://console.cloud.google.com/apis/credentials](https://console.cloud.google.com/apis/credentials) ### SERPER\_API\_KEY **Type**: String **Services**: LLM Service **Hot-Reload**: Yes Serper.dev API key (Google Search API alternative). ```bash theme={null} SERPER_API_KEY=... ``` Obtain from: [https://serper.dev](https://serper.dev) ### BING\_API\_KEY **Type**: String **Services**: LLM Service **Hot-Reload**: Yes Microsoft Bing Search API key. ```bash theme={null} BING_API_KEY=... ``` ### EXA\_API\_KEY **Type**: String **Services**: LLM Service **Hot-Reload**: Yes Exa AI search API key. ```bash theme={null} EXA_API_KEY=... ``` ### FIRECRAWL\_API\_KEY **Type**: String **Services**: LLM Service **Hot-Reload**: Yes Firecrawl API key for web scraping. ```bash theme={null} FIRECRAWL_API_KEY=... ``` ### SEARCHAPI\_API\_KEY **Type**: String **Required**: No **Default**: (none) **Services**: LLM Service **Hot-Reload**: Yes API key for SearchAPI.io. Get one at [searchapi.io](https://www.searchapi.io). ```bash theme={null} SEARCHAPI_API_KEY=... ``` ## Data Stores ### PostgreSQL #### POSTGRES\_HOST **Type**: String **Default**: `postgres` **Services**: Gateway, Orchestrator **Hot-Reload**: No PostgreSQL hostname. ```bash theme={null} POSTGRES_HOST=postgres ``` #### POSTGRES\_PORT **Type**: Integer **Default**: `5432` **Range**: 1-65535 **Services**: Gateway, Orchestrator **Hot-Reload**: No PostgreSQL port. ```bash theme={null} POSTGRES_PORT=5432 ``` #### POSTGRES\_DB **Type**: String **Default**: `shannon` **Services**: Gateway, Orchestrator **Hot-Reload**: No PostgreSQL database name. ```bash theme={null} POSTGRES_DB=shannon ``` #### POSTGRES\_USER **Type**: String **Default**: `shannon` **Services**: Gateway, Orchestrator **Hot-Reload**: No PostgreSQL username. ```bash theme={null} POSTGRES_USER=shannon ``` #### POSTGRES\_PASSWORD **Type**: String **Default**: `shannon` **Services**: Gateway, Orchestrator **Hot-Reload**: No PostgreSQL password. ```bash theme={null} POSTGRES_PASSWORD=secure-password ``` **Security**: Change in production! #### POSTGRES\_SSLMODE **Type**: String **Default**: `disable` **Valid Values**: `disable`, `require`, `verify-ca`, `verify-full` **Services**: Gateway, Orchestrator **Hot-Reload**: No SSL mode for PostgreSQL connections. ```bash theme={null} POSTGRES_SSLMODE=require ``` ### Redis #### REDIS\_HOST **Type**: String **Default**: `redis` **Services**: All **Hot-Reload**: No Redis hostname. ```bash theme={null} REDIS_HOST=redis ``` #### REDIS\_PORT **Type**: Integer **Default**: `6379` **Services**: All **Hot-Reload**: No Redis port. ```bash theme={null} REDIS_PORT=6379 ``` #### REDIS\_PASSWORD **Type**: String **Default**: Empty **Services**: All **Hot-Reload**: No Redis password (if authentication enabled). ```bash theme={null} REDIS_PASSWORD=secure-password ``` #### REDIS\_TTL\_SECONDS **Type**: Integer **Default**: `3600` **Range**: 0-∞ (0 = no expiration) **Services**: All **Hot-Reload**: Yes Default TTL for Redis keys. ```bash theme={null} REDIS_TTL_SECONDS=3600 ``` **Impact**: Cache duration for sessions, events, responses #### REDIS\_URL **Type**: String **Default**: `redis://redis:6379` **Services**: All **Hot-Reload**: No Full Redis connection URL. ```bash theme={null} REDIS_URL=redis://redis:6379 # With auth: REDIS_URL=redis://:password@redis:6379 ``` #### LLM\_REDIS\_URL **Type**: String **Default**: Empty (uses REDIS\_URL) **Services**: LLM Service **Hot-Reload**: No Dedicated Redis instance for LLM response caching. ```bash theme={null} LLM_REDIS_URL=redis://llm-redis:6379 ``` **Use Case**: Separate LLM cache from session data for scaling ### Qdrant (Vector Store) #### QDRANT\_URL **Type**: String **Default**: `http://qdrant:6333` **Services**: Orchestrator, LLM Service **Hot-Reload**: No Qdrant connection URL. ```bash theme={null} QDRANT_URL=http://qdrant:6333 ``` #### QDRANT\_HOST / QDRANT\_PORT **Type**: String / Integer **Default**: `qdrant` / `6333` **Services**: Orchestrator, LLM Service **Hot-Reload**: No Alternative to QDRANT\_URL (host/port separately). ```bash theme={null} QDRANT_HOST=qdrant QDRANT_PORT=6333 ``` ## Service Endpoints ### TEMPORAL\_HOST **Type**: String **Default**: `temporal:7233` **Services**: Orchestrator **Hot-Reload**: No Temporal server address. ```bash theme={null} TEMPORAL_HOST=temporal:7233 ``` ### LLM\_SERVICE\_URL **Type**: String **Default**: `http://llm-service:8000` **Services**: Orchestrator, Agent Core **Hot-Reload**: No LLM Service endpoint. ```bash theme={null} LLM_SERVICE_URL=http://llm-service:8000 ``` ### AGENT\_CORE\_ADDR **Type**: String **Default**: `agent-core:50051` **Services**: Orchestrator **Hot-Reload**: No Agent Core gRPC address. ```bash theme={null} AGENT_CORE_ADDR=agent-core:50051 ``` ### ORCHESTRATOR\_GRPC **Type**: String **Default**: `orchestrator:50052` **Services**: Gateway **Hot-Reload**: No Orchestrator gRPC endpoint. ```bash theme={null} ORCHESTRATOR_GRPC=orchestrator:50052 ``` ### ADMIN\_SERVER **Type**: String **Default**: `http://orchestrator:8081` **Services**: Gateway **Hot-Reload**: No Admin/SSE server for streaming events. ```bash theme={null} ADMIN_SERVER=http://orchestrator:8081 ``` ### CONFIG\_PATH **Type**: String **Default**: `./config/features.yaml` **Services**: Orchestrator, Agent Core **Hot-Reload**: Yes (with file watcher) Path to feature flags configuration. ```bash theme={null} CONFIG_PATH=./config/features.yaml ``` ### MODELS\_CONFIG\_PATH **Type**: String **Default**: `./config/models.yaml` **Services**: LLM Service, Orchestrator **Hot-Reload**: Yes (with file watcher) Path to model and pricing configuration. ```bash theme={null} MODELS_CONFIG_PATH=./config/models.yaml ``` ### EVENTS\_INGEST\_URL **Type**: String **Default**: `http://orchestrator:8081/events` **Services**: Agent Core **Hot-Reload**: No Event ingestion endpoint. ```bash theme={null} EVENTS_INGEST_URL=http://orchestrator:8081/events ``` ## Model Routing and Budgets ### DEFAULT\_MODEL\_TIER **Type**: String **Default**: `small` **Valid Values**: `small`, `medium`, `large` **Services**: Orchestrator **Hot-Reload**: Yes Default model tier for tasks. ```bash theme={null} DEFAULT_MODEL_TIER=small ``` **Tiers**: * `small`: GPT-5-nano, Claude Haiku (\~\$0.15/1M tokens) * `medium`: GPT-5-mini, Claude Sonnet (\~\$3-15/1M tokens) * `large`: GPT-5.1, Claude Opus (\~\$15-75/1M tokens) ### COMPLEXITY\_MODEL\_ID **Type**: String **Default**: `gpt-5` **Services**: Orchestrator **Hot-Reload**: Yes Model used for complexity analysis. ```bash theme={null} COMPLEXITY_MODEL_ID=gpt-5 ``` ### DECOMPOSITION\_MODEL\_ID **Type**: String **Default**: `claude-sonnet-4-5-20250929` **Services**: Orchestrator **Hot-Reload**: Yes Model used for task decomposition. ```bash theme={null} DECOMPOSITION_MODEL_ID=claude-sonnet-4-5-20250929 ``` ### MAX\_TOKENS **Type**: Integer **Default**: `2000` **Range**: 1-∞ **Services**: LLM Service **Hot-Reload**: Yes Default max output tokens per LLM call. ```bash theme={null} MAX_TOKENS=2000 ``` ### TEMPERATURE **Type**: Float **Default**: `0.7` **Range**: 0.0-2.0 **Services**: LLM Service **Hot-Reload**: Yes Default temperature for LLM sampling. ```bash theme={null} TEMPERATURE=0.7 ``` **Impact**: * `0.0`: Deterministic, focused * `0.7`: Balanced creativity * `1.0+`: More random, creative ### MAX\_TOKENS\_PER\_REQUEST **Type**: Integer **Default**: `10000` **Range**: 1-∞ **Services**: Orchestrator **Hot-Reload**: Yes Maximum total tokens per task request. ```bash theme={null} MAX_TOKENS_PER_REQUEST=10000 ``` **Impact**: Hard budget limit, task fails when exceeded ### MAX\_COST\_PER\_REQUEST **Type**: Float **Default**: `0.50` **Range**: 0.0-∞ **Services**: Orchestrator **Hot-Reload**: Yes Maximum cost (USD) per task request. ```bash theme={null} MAX_COST_PER_REQUEST=0.50 ``` **Impact**: Task fails when cost exceeds this limit ### LLM\_DISABLE\_BUDGETS **Type**: Integer **Default**: `1` **Valid Values**: `0`, `1` **Services**: Orchestrator, LLM Service **Hot-Reload**: Yes Budget enforcement location. ```bash theme={null} LLM_DISABLE_BUDGETS=1 ``` **Values**: * `1`: Orchestrator manages budgets (recommended) * `0`: LLM service enforces budgets ### HISTORY\_WINDOW\_MESSAGES **Type**: Integer **Default**: `50` **Range**: 1-∞ **Services**: Orchestrator **Hot-Reload**: Yes Number of messages to include in conversation history. ```bash theme={null} HISTORY_WINDOW_MESSAGES=50 ``` ### HISTORY\_WINDOW\_DEBUG\_MESSAGES **Type**: Integer **Default**: `75` **Services**: Orchestrator **Hot-Reload**: Yes History window size for debug mode. ```bash theme={null} HISTORY_WINDOW_DEBUG_MESSAGES=75 ``` ### WORKFLOW\_SYNTH\_BYPASS\_SINGLE **Type**: Boolean **Default**: `true` **Services**: Orchestrator **Hot-Reload**: Yes Skip synthesis for single-result workflows. ```bash theme={null} WORKFLOW_SYNTH_BYPASS_SINGLE=true ``` **Impact**: * `true`: Faster for simple tasks (skips synthesis step) * `false`: Always synthesize results ### TOKEN\_BUDGET\_PER\_AGENT **Type**: Integer **Default**: Empty (unlimited) **Services**: Orchestrator **Hot-Reload**: Yes Token budget per agent execution. ```bash theme={null} TOKEN_BUDGET_PER_AGENT=5000 ``` ### TOKEN\_BUDGET\_PER\_TASK **Type**: Integer **Default**: Empty (unlimited) **Services**: Orchestrator **Hot-Reload**: Yes Token budget per entire task. ```bash theme={null} TOKEN_BUDGET_PER_TASK=20000 ``` ## Cache and Rate Limiting ### ENABLE\_CACHE **Type**: Boolean **Default**: `true` **Services**: LLM Service **Hot-Reload**: Yes Enable LLM response caching. ```bash theme={null} ENABLE_CACHE=true ``` **Impact**: * `true`: Cache responses in Redis, faster & cheaper * `false`: Always hit LLM API ### CACHE\_SIMILARITY\_THRESHOLD **Type**: Float **Default**: `0.95` **Range**: 0.0-1.0 **Services**: LLM Service **Hot-Reload**: Yes Semantic similarity threshold for cache hits. ```bash theme={null} CACHE_SIMILARITY_THRESHOLD=0.95 ``` **Impact**: * `1.0`: Exact matches only * `0.95`: Very similar queries * `0.8`: Looser matching (more cache hits, less accuracy) ### RATE\_LIMIT\_REQUESTS **Type**: Integer **Default**: `100` **Services**: Gateway **Hot-Reload**: Yes Requests per window per API key. ```bash theme={null} RATE_LIMIT_REQUESTS=100 ``` ### RATE\_LIMIT\_WINDOW **Type**: Integer **Default**: `60` **Range**: 1-∞ (seconds) **Services**: Gateway **Hot-Reload**: Yes Rate limit window duration. ```bash theme={null} RATE_LIMIT_WINDOW=60 ``` **Combined**: ```bash theme={null} RATE_LIMIT_REQUESTS=100 RATE_LIMIT_WINDOW=60 # = 100 requests per 60 seconds ``` ### WEB\_SEARCH\_RATE\_LIMIT **Type**: Integer **Default**: `120` **Services**: LLM Service **Hot-Reload**: Yes Web search requests per minute. ```bash theme={null} WEB_SEARCH_RATE_LIMIT=120 ``` ### CALCULATOR\_RATE\_LIMIT **Type**: Integer **Default**: `2000` **Services**: LLM Service **Hot-Reload**: Yes Calculator tool requests per minute. ```bash theme={null} CALCULATOR_RATE_LIMIT=2000 ``` ### PYTHON\_EXECUTOR\_RATE\_LIMIT **Type**: Integer **Default**: `60` **Services**: Agent Core **Hot-Reload**: Yes Python code execution requests per minute. ```bash theme={null} PYTHON_EXECUTOR_RATE_LIMIT=60 ``` ### PARTIAL\_CHUNK\_CHARS **Type**: Integer **Default**: `512` **Services**: LLM Service **Hot-Reload**: Yes Characters per streaming chunk. ```bash theme={null} PARTIAL_CHUNK_CHARS=512 ``` ## Tool Execution and Workflow ### TOOL\_PARALLELISM **Type**: Integer **Default**: `1` **Range**: 1-∞ (1 = sequential) **Services**: Orchestrator, Agent Core **Hot-Reload**: Yes Concurrent tool executions. ```bash theme={null} TOOL_PARALLELISM=1 ``` **Impact**: * `1`: Sequential execution (slower, safer) * `5`: 5 tools in parallel (faster, more resources) * `10+`: High parallelism (fastest, highest resource usage) ### ENABLE\_TOOL\_SELECTION **Type**: Integer **Default**: `1` **Valid Values**: `0`, `1` **Services**: Orchestrator **Hot-Reload**: Yes Automatic tool selection by planner. ```bash theme={null} ENABLE_TOOL_SELECTION=1 ``` **Values**: * `1`: Planner auto-selects tools (recommended) * `0`: Manual tool specification only ### PRIORITY\_QUEUES **Type**: String **Default**: `off` **Valid Values**: `on`, `off` **Services**: Orchestrator **Hot-Reload**: Yes Enable priority-based task queuing. ```bash theme={null} PRIORITY_QUEUES=on ``` ### STREAMING\_RING\_CAPACITY **Type**: Integer **Default**: `1000` **Services**: Orchestrator **Hot-Reload**: No Ring buffer capacity for streaming events. ```bash theme={null} STREAMING_RING_CAPACITY=1000 ``` ### COMPRESSION\_TRIGGER\_RATIO **Type**: Float **Default**: `0.75` **Range**: 0.0-1.0 **Services**: Orchestrator **Hot-Reload**: Yes Context compression trigger threshold. ```bash theme={null} COMPRESSION_TRIGGER_RATIO=0.75 ``` **Impact**: Compress context when 75% of token budget used ### COMPRESSION\_TARGET\_RATIO **Type**: Float **Default**: `0.375` **Range**: 0.0-1.0 **Services**: Orchestrator **Hot-Reload**: Yes Target size after compression. ```bash theme={null} COMPRESSION_TARGET_RATIO=0.375 ``` ### ENFORCE\_TIMEOUT\_SECONDS **Type**: Integer **Default**: `90` **Services**: Agent Core **Hot-Reload**: Yes Tool execution timeout. ```bash theme={null} ENFORCE_TIMEOUT_SECONDS=90 ``` ### ENFORCE\_MAX\_TOKENS **Type**: Integer **Default**: `32768` **Services**: Agent Core **Hot-Reload**: Yes Maximum tokens per agent execution. ```bash theme={null} ENFORCE_MAX_TOKENS=32768 ``` ### ENFORCE\_RATE\_RPS **Type**: Integer **Default**: `20` **Services**: Agent Core **Hot-Reload**: Yes Rate limit (requests per second) for agent operations. ```bash theme={null} ENFORCE_RATE_RPS=20 ``` ### ENFORCE\_CB\_ERROR\_THRESHOLD **Type**: Float **Default**: `0.5` **Range**: 0.0-1.0 **Services**: Agent Core **Hot-Reload**: Yes Circuit breaker error rate threshold. ```bash theme={null} ENFORCE_CB_ERROR_THRESHOLD=0.5 ``` **Impact**: Circuit opens when 50% of requests fail ### ENFORCE\_CB\_WINDOW\_SECONDS **Type**: Integer **Default**: `30` **Services**: Agent Core **Hot-Reload**: Yes Circuit breaker measurement window. ```bash theme={null} ENFORCE_CB_WINDOW_SECONDS=30 ``` ### ENFORCE\_CB\_MIN\_REQUESTS **Type**: Integer **Default**: `20` **Services**: Agent Core **Hot-Reload**: Yes Minimum requests before circuit breaker activates. ```bash theme={null} ENFORCE_CB_MIN_REQUESTS=20 ``` ## Approvals and Security ### APPROVAL\_ENABLED **Type**: Boolean **Default**: `false` **Services**: Orchestrator **Hot-Reload**: Yes Enable human-in-the-loop approvals. ```bash theme={null} APPROVAL_ENABLED=true ``` ### APPROVAL\_COMPLEXITY\_THRESHOLD **Type**: Float **Default**: `0.5` **Range**: 0.0-1.0 **Services**: Orchestrator **Hot-Reload**: Yes Complexity threshold requiring approval. ```bash theme={null} APPROVAL_COMPLEXITY_THRESHOLD=0.5 ``` ### APPROVAL\_DANGEROUS\_TOOLS **Type**: String (comma-separated) **Default**: `file_system,code_execution` **Services**: Orchestrator **Hot-Reload**: Yes Tools requiring approval. ```bash theme={null} APPROVAL_DANGEROUS_TOOLS=file_system,code_execution,database_query ``` ### APPROVAL\_TIMEOUT\_SECONDS **Type**: Integer **Default**: `1800` **Services**: Orchestrator **Hot-Reload**: Yes Approval request timeout (30 minutes). ```bash theme={null} APPROVAL_TIMEOUT_SECONDS=1800 ``` ### JWT\_SECRET **Type**: String **Default**: `development-only-secret-change-in-production` **Services**: Gateway **Hot-Reload**: No JWT signing secret. ```bash theme={null} JWT_SECRET=secure-random-256-bit-key ``` **Security**: **Must change in production!** ### GATEWAY\_SKIP\_AUTH **Type**: Integer **Default**: `1` **Valid Values**: `0`, `1` **Services**: Gateway **Hot-Reload**: Yes (restart recommended) Skip authentication (development only). ```bash theme={null} GATEWAY_SKIP_AUTH=0 # Enable auth ``` **Security**: **Must set to `0` in production!** ## Templates ### TEMPLATE\_FALLBACK\_ENABLED **Type**: Boolean **Default**: `false` **Services**: Orchestrator **Hot-Reload**: Yes Fallback to AI when template fails. ```bash theme={null} TEMPLATE_FALLBACK_ENABLED=true ``` ## Observability and Telemetry ### OTEL\_SERVICE\_NAME **Type**: String **Default**: `shannon-llm-service` **Services**: All **Hot-Reload**: No Service name for OpenTelemetry. ```bash theme={null} OTEL_SERVICE_NAME=shannon-llm-service ``` ### OTEL\_EXPORTER\_OTLP\_ENDPOINT **Type**: String **Default**: `localhost:4317` **Services**: All **Hot-Reload**: No OTLP exporter endpoint. ```bash theme={null} OTEL_EXPORTER_OTLP_ENDPOINT=localhost:4317 ``` ### OTEL\_ENABLED **Type**: Boolean **Default**: `false` **Services**: All **Hot-Reload**: No Enable OpenTelemetry tracing. ```bash theme={null} OTEL_ENABLED=true ``` ### LOG\_FORMAT **Type**: String **Default**: `plain` **Valid Values**: `plain`, `json` **Services**: Agent Core (Rust) **Hot-Reload**: No Log output format. ```bash theme={null} LOG_FORMAT=json ``` ### METRICS\_PORT **Type**: Integer **Default**: `2112` **Services**: All **Hot-Reload**: No Prometheus metrics port. ```bash theme={null} METRICS_PORT=2112 ``` ## Python WASI Sandbox ### PYTHON\_WASI\_WASM\_PATH **Type**: String **Default**: `./wasm-interpreters/python-3.11.4.wasm` **Services**: Agent Core, LLM Service **Hot-Reload**: No Path to Python WASI interpreter. ```bash theme={null} PYTHON_WASI_WASM_PATH=./wasm-interpreters/python-3.11.4.wasm ``` ### PYTHON\_WASI\_SESSION\_TIMEOUT **Type**: Integer **Default**: `3600` **Services**: LLM Service **Hot-Reload**: Yes Python session timeout (seconds). ```bash theme={null} PYTHON_WASI_SESSION_TIMEOUT=3600 ``` ### WASI\_MEMORY\_LIMIT\_MB **Type**: Integer **Default**: `512` **Services**: Agent Core **Hot-Reload**: Yes WASI sandbox memory limit (MB). ```bash theme={null} WASI_MEMORY_LIMIT_MB=512 ``` ### WASI\_TIMEOUT\_SECONDS **Type**: Integer **Default**: `60` **Services**: Agent Core **Hot-Reload**: Yes WASI execution timeout. ```bash theme={null} WASI_TIMEOUT_SECONDS=60 ``` ## OpenAPI and MCP ### OpenAPI Integration #### OPENAPI\_ALLOWED\_DOMAINS **Type**: String **Default**: `*` **Services**: LLM Service **Hot-Reload**: Yes Allowed domains for OpenAPI spec fetching. ```bash theme={null} OPENAPI_ALLOWED_DOMAINS=* # Or specific domains: OPENAPI_ALLOWED_DOMAINS=api.example.com,api2.example.com ``` #### OPENAPI\_MAX\_SPEC\_SIZE **Type**: Integer **Default**: `5242880` (5 MB) **Services**: LLM Service **Hot-Reload**: Yes Maximum OpenAPI spec file size. ```bash theme={null} OPENAPI_MAX_SPEC_SIZE=5242880 ``` #### OPENAPI\_FETCH\_TIMEOUT **Type**: Integer **Default**: `30` **Services**: LLM Service **Hot-Reload**: Yes Timeout for fetching OpenAPI specs. ```bash theme={null} OPENAPI_FETCH_TIMEOUT=30 ``` #### OPENAPI\_RETRIES **Type**: Integer **Default**: `3` **Services**: LLM Service **Hot-Reload**: Yes Retry attempts for OpenAPI operations (spec fetching and API calls). ```bash theme={null} OPENAPI_RETRIES=3 ``` ### MCP Integration #### MCP\_ALLOWED\_DOMAINS **Type**: String **Default**: `*` **Services**: LLM Service **Hot-Reload**: Yes Allowed domains for MCP servers. ```bash theme={null} MCP_ALLOWED_DOMAINS=* ``` #### MCP\_MAX\_RESPONSE\_BYTES **Type**: Integer **Default**: `10485760` (10 MB) **Services**: LLM Service **Hot-Reload**: Yes Maximum MCP response size. ```bash theme={null} MCP_MAX_RESPONSE_BYTES=10485760 ``` #### MCP\_RETRIES **Type**: Integer **Default**: `3` **Services**: LLM Service **Hot-Reload**: Yes MCP request retry attempts. ```bash theme={null} MCP_RETRIES=3 ``` #### MCP\_TIMEOUT\_SECONDS **Type**: Integer **Default**: `10` **Services**: LLM Service **Hot-Reload**: Yes MCP request timeout. ```bash theme={null} MCP_TIMEOUT_SECONDS=10 ``` #### MCP\_REGISTER\_TOKEN **Type**: String **Default**: Empty **Services**: LLM Service **Hot-Reload**: Yes Authentication token for MCP server registration. ```bash theme={null} MCP_REGISTER_TOKEN=secret-token ``` #### MCP\_RATE\_LIMIT\_DEFAULT **Type**: Integer **Default**: `60` **Services**: LLM Service **Hot-Reload**: Yes Default rate limit for MCP tools (requests/minute). ```bash theme={null} MCP_RATE_LIMIT_DEFAULT=60 ``` #### MCP\_CB\_FAILURES **Type**: Integer **Default**: `5` **Services**: LLM Service **Hot-Reload**: Yes Circuit breaker failure threshold for MCP. ```bash theme={null} MCP_CB_FAILURES=5 ``` #### MCP\_CB\_RECOVERY\_SECONDS **Type**: Integer **Default**: `60` **Services**: LLM Service **Hot-Reload**: Yes Circuit breaker recovery time. ```bash theme={null} MCP_CB_RECOVERY_SECONDS=60 ``` #### MCP\_COST\_TO\_TOKENS **Type**: Integer **Default**: `0` **Services**: LLM Service **Hot-Reload**: Yes Token cost multiplier for MCP tools. ```bash theme={null} MCP_COST_TO_TOKENS=100 ``` ## Advanced Orchestrator Controls ### EVENTLOG\_BATCH\_SIZE **Type**: Integer **Default**: `100` **Services**: Orchestrator **Hot-Reload**: Yes Events per batch for database writes. ```bash theme={null} EVENTLOG_BATCH_SIZE=100 ``` ### EVENTLOG\_BATCH\_INTERVAL\_MS **Type**: Integer **Default**: `100` **Services**: Orchestrator **Hot-Reload**: Yes Batch interval (milliseconds). ```bash theme={null} EVENTLOG_BATCH_INTERVAL_MS=100 ``` ### RATE\_LIMIT\_INTERVAL\_MS **Type**: Integer **Default**: `60000` **Services**: Orchestrator **Hot-Reload**: Yes Rate limit window (milliseconds). ```bash theme={null} RATE_LIMIT_INTERVAL_MS=60000 ``` ### Worker Concurrency Configure Temporal worker concurrency per priority queue: ```bash theme={null} # Default workers (all priorities) WORKER_ACT=10 WORKER_WF=10 # Critical priority WORKER_ACT_CRITICAL=12 WORKER_WF_CRITICAL=12 # High priority WORKER_ACT_HIGH=10 WORKER_WF_HIGH=10 # Normal priority WORKER_ACT_NORMAL=8 WORKER_WF_NORMAL=8 # Low priority WORKER_ACT_LOW=4 WORKER_WF_LOW=4 ``` **Impact**: * Higher values = more concurrency, more resource usage * Lower values = less concurrency, lower resource usage ### LLM\_TIMEOUT\_SECONDS **Type**: Integer **Default**: `120` **Services**: Orchestrator **Hot-Reload**: Yes Timeout for LLM API calls. ```bash theme={null} LLM_TIMEOUT_SECONDS=120 ``` ## Miscellaneous ### SHANNON\_WORKSPACE **Type**: String **Default**: `./workspace` **Services**: Agent Core **Hot-Reload**: No Workspace directory for file operations. ```bash theme={null} SHANNON_WORKSPACE=./workspace ``` ### SHANNON\_SESSION\_WORKSPACES\_DIR **Type**: String **Default**: `/tmp/shannon-sessions` **Services**: Gateway, Agent Core **Hot-Reload**: No Base directory for session-isolated workspace files. Each session gets a subdirectory. ```bash theme={null} SHANNON_SESSION_WORKSPACES_DIR=/tmp/shannon-sessions ``` ### SHANNON\_USE\_WASI\_SANDBOX **Type**: Boolean (0/1) **Default**: `0` **Services**: Agent Core **Hot-Reload**: No Enable WASI sandbox for file operations. When enabled, file access is mediated through the Firecracker executor for security isolation. ```bash theme={null} SHANNON_USE_WASI_SANDBOX=1 ``` ### SHANNON\_MAX\_WORKSPACE\_SIZE\_MB **Type**: Integer **Default**: `100` **Services**: Agent Core **Hot-Reload**: No Maximum disk quota per session workspace in megabytes. ```bash theme={null} SHANNON_MAX_WORKSPACE_SIZE_MB=100 ``` ### SEED\_DATA **Type**: Boolean **Default**: `false` **Services**: All **Hot-Reload**: No Seed Qdrant with sample data on startup. ```bash theme={null} SEED_DATA=true ``` ### AGENT\_TIMEOUT\_SECONDS **Type**: Integer **Default**: `600` **Services**: Agent Core **Hot-Reload**: Yes Maximum runtime per agent execution. ```bash theme={null} AGENT_TIMEOUT_SECONDS=600 ``` ## Configuration Examples ### Development ```bash theme={null} ENVIRONMENT=dev DEBUG=true GATEWAY_SKIP_AUTH=1 ENABLE_CACHE=true DEFAULT_MODEL_TIER=small MAX_COST_PER_REQUEST=0.10 TOOL_PARALLELISM=3 ``` ### Production ```bash theme={null} ENVIRONMENT=prod DEBUG=false GATEWAY_SKIP_AUTH=0 JWT_SECRET= POSTGRES_PASSWORD= REDIS_PASSWORD= ENABLE_CACHE=true DEFAULT_MODEL_TIER=medium MAX_COST_PER_REQUEST=1.00 TOOL_PARALLELISM=5 OTEL_ENABLED=true LOG_FORMAT=json ``` ### High Performance ```bash theme={null} TOOL_PARALLELISM=10 WORKER_ACT_CRITICAL=30 WORKER_WF_CRITICAL=15 ENABLE_CACHE=true CACHE_SIMILARITY_THRESHOLD=0.90 STREAMING_RING_CAPACITY=2000 ``` ### Cost Optimized ```bash theme={null} DEFAULT_MODEL_TIER=small MAX_COST_PER_REQUEST=0.25 MAX_TOKENS_PER_REQUEST=5000 ENABLE_CACHE=true CACHE_SIMILARITY_THRESHOLD=0.95 WORKFLOW_SYNTH_BYPASS_SINGLE=true ``` ## Troubleshooting See [Configuration Troubleshooting](/en/deployment/configuration-troubleshooting) for common issues. ## Related Documentation Docker deployment guide Kubernetes deployment Optimize performance Security hardening # Deployment Source: https://docs.shannon.run/en/deployment/index Deploy Shannon to production environments ## Production Deployment Guide Deploy Shannon to your infrastructure with confidence. This section covers deployment patterns, cloud platform integrations, and operational best practices. Shannon is currently in active development. Production deployment guides are being finalized. For production use, we recommend: * Thorough testing in staging environments * Monitoring all services closely ## Deployment Options Production-ready Docker Compose configuration
**Status**: 🚧 Phase 3
Kubernetes manifests and Helm charts
**Status**: 🚧 Phase 3
Deploy to Amazon Web Services (ECS, RDS, ElastiCache)
**Status**: 🚧 Phase 3
Deploy to Microsoft Azure (AKS, PostgreSQL, Redis)
**Status**: 🚧 Phase 3
## Operations Prometheus metrics, Grafana dashboards, and alerting
**Status**: 🚧 Phase 3
Optimize throughput, latency, and resource usage
**Status**: 🚧 Phase 3
Production security hardening and best practices
**Status**: 🚧 Phase 3
## Quick Start: Local Development For development and testing, use Docker Compose: ```bash theme={null} # Clone repository git clone https://github.com/Kocoro-lab/Shannon.git cd Shannon # Setup configuration make setup echo "OPENAI_API_KEY=sk-your-key-here" >> .env # Download Python WASI interpreter ./scripts/setup_python_wasi.sh # Start all services make dev ``` See [Installation Guide](/en/quickstart/installation) for detailed setup instructions. ## Architecture Overview Shannon consists of multiple services that need to be deployed: Deployment Architecture ### Core Services | Service | Purpose | Scaling | | ---------------- | --------------------------------------- | ----------------------------------------- | | **Gateway** | REST API, authentication | Horizontal (stateless) | | **Orchestrator** | Task coordination, gRPC | Horizontal (stateful via Temporal) | | **Agent Core** | Agent execution, Rust runtime | Horizontal | | **LLM Service** | LLM provider gateway | Horizontal | | **Desktop App** | Real-time monitoring UI (native client) | Client-side; backend scales independently | ### Data Stores | Store | Purpose | Scaling | | -------------- | ---------------------------------- | ------------------------ | | **PostgreSQL** | Task metadata, events, sessions | Vertical + read replicas | | **Redis** | Caching, pub/sub, sessions | Cluster mode | | **Qdrant** | Vector embeddings, semantic memory | Horizontal | | **Temporal** | Workflow state, durable execution | Cluster mode | ## Production Checklist Before deploying to production: ### Security * [ ] Enable authentication (`GATEWAY_SKIP_AUTH=0`) * [ ] Configure TLS/SSL for all services * [ ] Rotate API keys regularly * [ ] Set up OPA policies for access control * [ ] Enable audit logging * [ ] Configure network policies/firewalls ### Reliability * [ ] Set up health checks and readiness probes * [ ] Configure auto-scaling policies * [ ] Implement circuit breakers * [ ] Set resource limits (CPU, memory) * [ ] Configure backup and disaster recovery * [ ] Test failover scenarios ### Observability * [ ] Deploy Prometheus and Grafana * [ ] Configure alerting rules * [ ] Set up log aggregation (ELK/Loki) * [ ] Enable distributed tracing (OpenTelemetry) * [ ] Create runbooks for common issues ### Performance * [ ] Tune Temporal worker concurrency * [ ] Optimize database connections * [ ] Configure Redis caching * [ ] Set appropriate resource limits * [ ] Load test before production launch ## Resource Requirements ### Minimum (Development) * **CPU**: 4 cores * **RAM**: 8GB * **Storage**: 20GB SSD ### Recommended (Production - Small) * **CPU**: 16 cores total (distributed across services) * **RAM**: 32GB total * **Storage**: 100GB SSD * **Network**: 1Gbps ### Recommended (Production - Large) * **CPU**: 64+ cores * **RAM**: 128GB+ * **Storage**: 500GB+ SSD * **Network**: 10Gbps * **Load Balancer**: Required * **Multi-AZ**: Recommended ## What's Next? Install Shannon locally first Understand environment variables Learn system architecture Set up monitoring ## Get Help * **GitHub**: File deployment issues or questions * **Docs**: Check [Troubleshooting](/en/quickstart/troubleshooting) for common problems # Kubernetes Deployment Source: https://docs.shannon.run/en/deployment/kubernetes K8s manifests and Helm charts ## Coming Soon - Phase 3 **Estimated availability**: Phase 3 (Month 2) See [Deployment Overview](/deployment) for more information. # Monitoring Source: https://docs.shannon.run/en/deployment/monitoring Prometheus, Grafana, and observability ## Coming Soon - Phase 3 **Estimated availability**: Phase 3 (Month 2) See [Monitoring Concepts](/en/quickstart/concepts/monitoring) for basics. # Performance Tuning Source: https://docs.shannon.run/en/deployment/performance-tuning Optimize Shannon for production ## Coming Soon - Phase 3 **Estimated availability**: Phase 3 (Month 2) See [Deployment Overview](/deployment) for more information. # Security Hardening Source: https://docs.shannon.run/en/deployment/security Production security best practices ## Coming Soon - Phase 3 **Estimated availability**: Phase 3 (Month 2) See [Authentication](/en/api/authentication) for API security. # What are AI Agents? Source: https://docs.shannon.run/en/quickstart/concepts/agents Understanding AI agents in Shannon ## Introduction An **AI agent** is an autonomous system that can perceive its environment, make decisions, and take actions to achieve goals. In Shannon, agents are the fundamental execution units that process tasks using Large Language Models (LLMs) and tools. ## Agent Capabilities Shannon agents can: Use LLMs to analyze tasks, plan solutions, and make decisions Call functions, run code, search the web, and interact with APIs Work with other agents to solve complex multi-step problems Improve over time through pattern learning and cached results ## Agent Lifecycle Here's how an agent processes a task: Agent Lifecycle Agent receives task and analyzes what needs to be done LLM creates a plan, potentially breaking down into steps Agent identifies which tools are needed Iteratively executes tools and processes results Combines results into final answer ## Agent Types in Shannon ### Single Agent (Simple Mode) A single agent handles the entire task without decomposition. **Best for:** * Simple queries * Fact retrieval * Basic calculations * Quick responses **Example:** ```python theme={null} client.submit_task( query="What is the capital of France?" ) ``` Shannon automatically selects single-agent mode for simple queries. ### Multi-Agent (Standard/Complex Mode) Multiple specialized agents work together, coordinated by Shannon's orchestrator. **Best for:** * Complex research * Multi-step workflows * Tasks requiring different expertise * Tasks needing parallel processing **Example:** ```python theme={null} client.submit_task( query="Research top 5 AI trends, analyze market impact, and create summary" ) ``` Shannon automatically decomposes complex queries into multi-agent workflows based on query complexity. ## Agent Components ### 1. LLM Brain The decision-making core powered by language models: * **Model Selection**: Shannon auto-selects models based on task complexity * Small tasks → `gpt-5-mini`, `claude-haiku` * Complex tasks → `gpt-5`, `claude-opus` * **Context Management**: Automatically manages context windows * **Caching**: Reuses previous LLM responses when applicable ### 2. Tool System Agents can execute various tools: **Built-in Tools:** * Python code execution (WASI sandboxed) * Web search (Google/Serper/Bing/Exa/Firecrawl) * Document retrieval * Mathematical calculations **MCP Tools:** Shannon supports [Model Context Protocol](https://modelcontextprotocol.io) for external tool integration. **Custom Tools:** Add your own tools via OpenAPI specs or Python implementations. ### 3. Memory System Agents maintain two types of memory: **Session Memory:** * Short-term context within a conversation * Stored in Redis with configurable TTL (default 30 days) * Enables multi-turn dialogues **Vector Memory:** * Long-term semantic memory in Qdrant * Cross-session retrieval * MMR diversity for relevant context ## Platform Configuration Shannon behavior is configured via environment variables. Common examples: ```bash theme={null} # In .env (examples) DEFAULT_MODEL_TIER=small # small | medium | large MAX_TOKENS_PER_REQUEST=10000 # Per-request token budget (LLM service) MAX_COST_PER_REQUEST=0.50 # Per-request cost limit (USD, LLM service) AGENT_TIMEOUT_SECONDS=600 # Agent execution timeout (orchestrator) # Apply changes docker compose restart ``` ### Model Tiers Shannon automatically selects models based on the configured tier: | Tier | Models | Use Case | Cost | | ---------- | ------------------------- | --------------------------------- | ------ | | **SMALL** | gpt-5-nano, claude-haiku | Simple queries, high volume | \$ | | **MEDIUM** | gpt-5-mini, claude-sonnet | General purpose | \$\$ | | **LARGE** | gpt-5.1, claude-opus | Complex reasoning, critical tasks | \$\$\$ | Shannon's intelligent router selects the most cost-effective model for each task, often yielding 60–90% savings compared to always using premium models (workload‑dependent). See [Configuration Guide](/en/quickstart/configuration) for all available environment variables. ## Agent Coordination Patterns Shannon uses proven cognitive patterns for multi-agent coordination: ### Chain-of-Thought (CoT) Sequential reasoning where each step builds on the previous: ``` Task: "Calculate compound interest" Step 1: Identify formula Step 2: Gather inputs Step 3: Calculate result Step 4: Format output ``` ### Tree-of-Thoughts (ToT) Exploration with backtracking for complex problem-solving: ``` Task: "Design system architecture" Branch 1: Microservices approach ├─ Evaluate pros/cons └─ Estimate complexity Branch 2: Monolithic approach ├─ Evaluate pros/cons └─ Estimate complexity Select: Best option based on criteria ``` ### ReAct (Reasoning + Acting) Interleaved reasoning and action for dynamic tasks: ``` Think: "I need to find the latest stock price" Act: Search web for "AAPL stock price" Observe: "$150.25" Think: "Now calculate 10% gain" Act: Calculate 150.25 * 1.10 Result: "$165.28" ``` ## Security and Isolation Shannon agents run in secure environments: All code execution happens in WebAssembly System Interface sandboxes with: * **No network access** * **Read-only filesystem** * **Memory limits** * **Execution timeouts** ### OPA Policy Enforcement Control what agents can do: ```rego theme={null} # Example policy: Restrict models by team package shannon.teams.datascience allow { input.team == "data-science" input.model in ["gpt-5", "claude-sonnet"] } max_tokens = 50000 { input.team == "data-science" } ``` ## Best Practices ### 1. Choose the Right Mode * **Simple**: Single-step tasks, fast responses * **Standard**: Multi-step tasks, moderate complexity * **Complex**: Research, analysis, advanced reasoning ### 2. Set Budget Limits Configure token and cost limits at the platform level to prevent unexpected charges: ```bash theme={null} # In .env file MAX_TOKENS_PER_REQUEST=5000 MAX_COST_PER_REQUEST=1.0 ``` Monitor costs via task status: ```python theme={null} status = client.get_status(task_id, include_details=True) if status.token_usage: print(f"Cost: ${status.token_usage.cost_usd:.4f}") print(f"Tokens: {status.token_usage.total_tokens}") ``` ### 3. Use Sessions for Context For multi-turn conversations, use consistent `session_id`: ```python theme={null} session_id = "user-123-conversation" client.submit_task(query="...", session_id=session_id) ``` ### 4. Monitor Performance Check metrics to optimize: ```python theme={null} # Wait for task to complete status = client.wait(handle.task_id, timeout=300) if status.token_usage: print(f"Tokens used: {status.token_usage.total_tokens}") print(f"Cost: ${status.token_usage.cost_usd:.4f}") ``` ## Next Steps Learn about workflow patterns Master budget management Explore agent APIs Build with the SDK # Cost Control Source: https://docs.shannon.run/en/quickstart/concepts/cost-control Managing token budgets and minimizing LLM costs in Shannon ## Overview Shannon provides comprehensive cost control features to prevent unexpected LLM charges and optimize spending. With built-in budget enforcement and intelligent routing, teams often see **significant cost reductions (60–90%)** versus naive implementations (workload‑dependent). ## Setting Budgets Budgets are configured at the platform level (not per request via REST). Use environment variables in `.env`: ```bash theme={null} # LLM service budget guards MAX_TOKENS_PER_REQUEST=10000 # Max tokens per request MAX_COST_PER_REQUEST=0.50 # Max cost per request (USD) # Apply changes docker compose restart ``` Shannon enforces budgets during execution. When limits are reached, the system halts further spending and returns the best available result or an error depending on context. ## Model Tiers Shannon categorizes models into tiers based on capability and cost: | Tier | Models | Cost per 1M tokens | Use Case | | ---------- | ----------------------------- | ------------------ | --------------------------------- | | **SMALL** | gpt-5-nano
claude-haiku | $0.15 - $0.25 | Simple queries, high volume | | **MEDIUM** | gpt-5-mini
claude-sonnet | $3.00 - $15.00 | General purpose tasks | | **LARGE** | gpt-5.1
claude-opus | $15.00 - $75.00 | Complex reasoning, critical tasks | ### Explicit Tier Preference Set a preferred default tier via environment variable: ```bash theme={null} DEFAULT_MODEL_TIER=small # small | medium | large ``` ## Intelligent Router Shannon's learning router automatically selects the cheapest model capable of handling each task. ### How It Works 1. **Task Analysis**: Analyzes complexity, required capabilities 2. **Model Selection**: Starts with smallest viable model 3. **Quality Check**: Validates output quality 4. **Learning**: Remembers successful model-task pairings ### Cost Savings ```python theme={null} # Without intelligent routing Traditional: Always use GPT-5 → $0.50 per task # With Shannon's routing Shannon: - 70% routed to gpt-5-nano → $0.01 - 25% routed to gpt-5-mini → $0.15 - 5% routed to gpt-5.1 → $0.50 Example average: $0.05 per task (~90% savings) ``` ### Monitoring Router Decisions Use the dashboard or SDK status to review costs: ```python theme={null} status = client.wait(handle.task_id) if status.token_usage: print(f"Cost (USD): {status.token_usage.cost_usd:.4f}") ``` ## Response Caching Shannon caches LLM responses to eliminate redundant API calls: ### Cache Strategy * **Key**: SHA256 hash of `(messages + model + parameters)` * **TTL**: Configurable (often \~1 hour via Redis TTL) * **Storage**: In-memory LRU + optional Redis for distributed caching * **Hit Rate**: Typical 30-50% for production workloads ### Cache Benefits ```bash theme={null} # First call: Cache miss Task 1: "What is Python?" → $0.002 (LLM call) # Second call: Cache hit Task 2: "What is Python?" → $0.000 (cached) ``` ### Monitoring Cache Performance ```python theme={null} status = client.wait(handle.task_id) if status.token_usage: if status.token_usage.cost_usd == 0: print("Likely served from cache (no LLM cost)") else: print(f"Cost: ${status.token_usage.cost_usd:.4f}") ``` ## Provider Rate Limits Shannon respects provider rate limits automatically: ### Configured Limits From `config/models.yaml`: ```yaml theme={null} providers: openai: rpm: 10000 # Requests per minute tpm: 2000000 # Tokens per minute anthropic: rpm: 4000 tpm: 400000 ``` ### Automatic Throttling When approaching limits: 1. Queues requests 2. Spreads load over time 3. Falls back to alternative providers if available ## Cost Monitoring ### Track Spending Per Task ```python theme={null} status = client.wait(handle.task_id) if status.token_usage: print(f"Tokens used: {status.token_usage.total_tokens}") print(f"Cost: ${status.token_usage.cost_usd:.4f}") ``` ### Aggregate Metrics Shannon tracks cumulative costs via metrics pipelines: * Total spend by day/week/month * Cost per user/team * Cost per cognitive pattern * Token usage trends Use Prometheus/Grafana (once configured) or your observability stack to visualize these metrics. The Desktop App Runs view can also help you inspect per-task costs. ## Best Practices ### 1. Always Set Budgets Never run production tasks without budget limits: ```python theme={null} # ❌ Bad: No budget limits client.submit_task(query="...") # ✅ Good: Budget protection client.submit_task( query="...", # Budget configured via .env} ) ``` ### 2. Use Simple Mode When Possible Complex patterns cost more: ```python theme={null} # Simple query: Use simple mode client.submit_task( query="What is the capital of France?", # Mode auto-selected # Single agent, minimal tokens ) # Complex query: Use standard/complex mode client.submit_task( query="Research and compare 5 database technologies", # Mode auto-selected # Task decomposition justified ) ``` ### 3. Leverage Caching For repeated queries, use consistent phrasing to maximize cache hits: ```python theme={null} # ❌ Bad: Different phrasing prevents cache hits client.submit_task(query="What's Python?") client.submit_task(query="Tell me about Python") client.submit_task(query="Explain Python") # ✅ Good: Consistent queries hit cache standard_query = "What is Python?" client.submit_task(query=standard_query) # Cache miss client.submit_task(query=standard_query) # Cache hit client.submit_task(query=standard_query) # Cache hit ``` ### 4. Monitor and Optimize Review cost metrics regularly: ```python theme={null} # Enable detailed logging import logging logging.basicConfig(level=logging.INFO) total_cost = 0.0 for t in tasks: st = client.wait(t.task_id) if st.token_usage: total_cost += st.token_usage.cost_usd print(f"Task: ${st.token_usage.cost_usd:.4f}, Running total: ${total_cost:.4f}") ``` ### 5. Use Smaller Models First Let the intelligent router prove when larger models are needed: ```python theme={null} # Let Shannon choose client.submit_task(query="...") # Auto-selects tier # Model tier is selected by the platform router. No per-request model tier or budget parameters are accepted by the SDK. ``` ## Cost Optimization Checklist * [ ] Set budget env vars (`MAX_COST_PER_REQUEST`, `MAX_TOKENS_PER_REQUEST`) * [ ] Use `# Mode auto-selected` for straightforward queries * [ ] Enable response caching (default: enabled) * [ ] Use `model_tier="small"` when appropriate * [ ] Standardize query phrasing for cache hits * [ ] Monitor cost metrics in dashboard * [ ] Set up budget alerts (via Prometheus) * [ ] Review and optimize prompt templates * [ ] Use session context to reduce token usage * [ ] Enable learning router (default: enabled) ## Example: Cost-Optimized Workflow ```python theme={null} from shannon import ShannonClient client = ShannonClient() # High-volume, simple queries simple_tasks = [ "Classify sentiment: Great product!", "Classify sentiment: Terrible experience", "Classify sentiment: It's okay" ] total_cost = 0 for query in simple_tasks: handle = client.submit_task(query=query) status = client.wait(handle.task_id) if status.token_usage: total_cost += status.token_usage.cost_usd print(f"Total cost for 3 tasks: ${total_cost:.4f}") # Example: ~$0.006 (~90% savings vs always GPT-5) ``` ## Next Steps Real-time task monitoring Advanced cost settings Endpoints and usage Cost monitoring UI # Monitoring & Observability Source: https://docs.shannon.run/en/quickstart/concepts/monitoring Monitor Shannon task execution and system health Monitoring documentation is under development. Core concepts are outlined below. ## Overview Shannon provides comprehensive monitoring and observability features to track task execution, system performance, and resource usage in production environments. ## Monitoring Capabilities ### Task Monitoring Track individual task execution: * Execution status and progress * Resource consumption * Error rates and types * Latency metrics * Cost tracking ### System Monitoring Monitor Shannon infrastructure: * Service health status * API endpoint latency * Queue depths * Agent availability * LLM provider status ## Metrics ### Task Metrics | Metric | Description | Unit | | ------------------------ | ------------------------------- | ----- | | `task.latency` | End-to-end task completion time | ms | | `task.cost` | Total cost per task | USD | | `task.tokens.input` | Input tokens consumed | count | | `task.tokens.output` | Output tokens generated | count | | `task.iterations` | Number of agent iterations | count | | `task.tools.invocations` | Tool usage count | count | ### System Metrics | Metric | Description | Unit | | --------------- | ------------------ | -------- | | `api.latency` | API response time | ms | | `api.requests` | Request rate | req/s | | `api.errors` | Error rate | errors/s | | `queue.depth` | Tasks waiting | count | | `agents.active` | Active agent count | count | ## Health Checks ### API Health ```bash theme={null} curl http://localhost:8080/health ``` Response (gateway): ```json theme={null} { "status": "healthy", "version": "0.3.0", "time": "2025-01-20T10:00:00Z", "checks": { "gateway": "ok" } } ``` Readiness (checks orchestrator connectivity): ```bash theme={null} curl http://localhost:8080/readiness ``` Response: ```json theme={null} { "status": "ready", "version": "0.3.0", "time": "2025-01-20T10:00:02Z", "checks": { "orchestrator": "ok" } } ``` ### Component Health ```python theme={null} import requests health = requests.get("http://localhost:8080/health").json() print("Gateway:", health.get("status"), health.get("checks")) ready = requests.get("http://localhost:8080/readiness").json() print("Readiness:", ready.get("status"), ready.get("checks")) ``` ## Logging ### Log Levels Shannon uses structured logging with levels: * `DEBUG` - Detailed diagnostic information * `INFO` - General operational messages * `WARN` - Warning conditions * `ERROR` - Error conditions * `FATAL` - Critical failures ### Log Format ```json theme={null} { "timestamp": "2024-10-27T10:00:00Z", "level": "INFO", "service": "orchestrator", "task_id": "task-dev-1730000000", "message": "Task submitted", "metadata": { "mode": "standard", "estimated_cost": 0.15 } } ``` ## Dashboards ### Task Dashboard Monitor task execution in real-time: * Active tasks * Completion rate * Average latency * Error rate * Cost per hour ### System Dashboard Track system health: * Service status * Resource utilization * Queue lengths * Provider availability ## Alerting ### Alert Types Configure alerts for: * Task failures * Budget exceeded * High latency * Service degradation * Rate limiting ### Alert Configuration ```yaml theme={null} alerts: - name: high_error_rate condition: error_rate > 0.05 action: notify channels: [email, slack] - name: budget_warning condition: daily_cost > 100 action: notify channels: [email] - name: service_down condition: health_check_failed action: page channels: [pagerduty] ``` ## Prometheus Integration Export metrics to Prometheus (example scrape targets for local dev): ```yaml theme={null} # prometheus.yml scrape_configs: - job_name: 'orchestrator' static_configs: - targets: ['localhost:2112'] # Go Orchestrator /metrics - job_name: 'agent_core' static_configs: - targets: ['localhost:2113'] # Rust Agent Core /metrics - job_name: 'llm_service' metrics_path: '/metrics' static_configs: - targets: ['localhost:8000'] # Python LLM Service /metrics ``` ### Available Metrics ``` # HELP shannon_task_total Total number of tasks # TYPE shannon_task_total counter shannon_task_total{status="completed"} 1234 shannon_task_total{status="failed"} 12 # HELP shannon_task_duration_seconds Task execution duration # TYPE shannon_task_duration_seconds histogram shannon_task_duration_seconds_bucket{le="1.0"} 100 shannon_task_duration_seconds_bucket{le="5.0"} 450 ``` ## Grafana Dashboards Pre-built Grafana dashboards for: * Task analytics * Cost tracking * Performance monitoring * Error analysis ## OpenTelemetry Shannon supports OpenTelemetry for distributed tracing: ```python theme={null} from opentelemetry import trace from shannon import ShannonClient tracer = trace.get_tracer(__name__) with tracer.start_as_current_span("analyze_data"): client = ShannonClient() handle = client.submit_task(query="Analyze dataset") result = client.get_status(handle.task_id) ``` ## Best Practices 1. **Set up alerts** for critical metrics 2. **Monitor costs** to prevent budget overruns 3. **Track error patterns** to identify issues 4. **Use distributed tracing** for debugging 5. **Archive logs** for compliance 6. **Create custom dashboards** for your use case 7. **Implement SLOs** for reliability ## Debugging ### Enable Debug Logging ```python theme={null} import logging logging.basicConfig(level=logging.DEBUG) # Now Shannon will output detailed logs client = ShannonClient() ``` ### Trace Requests Use distributed tracing via OpenTelemetry or increase logging verbosity in services. Refer to your observability stack configuration (Jaeger/Tempo) for exporters. ## Next Steps Common issues and solutions Manage and optimize costs # Real-Time Streaming Source: https://docs.shannon.run/en/quickstart/concepts/streaming Monitor task execution with Server-Sent Events and WebSockets ## Overview Shannon provides real-time event streaming so you can monitor task execution as it happens. This is essential for: * Providing live feedback to users * Debugging workflow execution * Building interactive UIs * Monitoring long-running tasks ## Streaming Technologies Shannon supports two streaming protocols: | Protocol | Use Case | Features | | ---------------------------- | --------------------- | ---------------------------------- | | **SSE** (Server-Sent Events) | One-way server→client | Simple, HTTP-based, auto-reconnect | | **WebSocket** | Bidirectional | Full duplex, lower latency | SSE is recommended for most use cases due to its simplicity and built-in reconnection handling. ## Server-Sent Events (SSE) ### Using cURL ```bash theme={null} curl -N http://localhost:8080/api/v1/stream/sse?workflow_id={task_id} ``` Output: ``` event: WORKFLOW_STARTED data: {"workflow_id":"task-123","agent_id":"orchestrator","message":"Task processing started"} event: DATA_PROCESSING data: {"workflow_id":"task-123","message":"Preparing context"} event: PROGRESS data: {"workflow_id":"task-123","agent_id":"planner","message":"Created a plan with 3 steps"} event: AGENT_STARTED data: {"workflow_id":"task-123","agent_id":"research-agent","message":"Processing query"} event: LLM_PROMPT data: {"workflow_id":"task-123","agent_id":"research-agent","message":"Searching for information..."} event: TOOL_INVOKED data: {"workflow_id":"task-123","agent_id":"research-agent","message":"Calling web_search with query='topic A'"} event: TOOL_OBSERVATION data: {"workflow_id":"task-123","agent_id":"research-agent","message":"Found 5 results"} event: LLM_OUTPUT data: {"workflow_id":"task-123","agent_id":"research-agent","message":"Research complete: Found relevant information"} event: AGENT_COMPLETED data: {"workflow_id":"task-123","agent_id":"research-agent","message":"Task done"} event: WORKFLOW_COMPLETED data: {"workflow_id":"task-123","message":"Final synthesized result ready"} ``` ### Using Python SDK ```python theme={null} from shannon import ShannonClient client = ShannonClient(base_url="http://localhost:8080") # Submit task handle = client.submit_task( query="Research AI trends and create summary" ) # Stream events for event in client.stream(handle.workflow_id): print(f"[{event.type}] {event.message}") if event.type == "WORKFLOW_COMPLETED": print(f"Task completed: {event.message}") break ``` ### Event Types Shannon emits 33 event types across different categories. Here are the most commonly used ones: #### Core Workflow Events | Event Type | Description | Example Message | | -------------------- | ------------------------------ | ------------------------------ | | `WORKFLOW_STARTED` | Task processing started | `"Task processing started"` | | `WORKFLOW_COMPLETED` | Workflow finished successfully | `"All done"` | | `AGENT_STARTED` | Agent began processing | `"Processing query"` | | `AGENT_COMPLETED` | Agent finished | `"Task done"` | | `STATUS_UPDATE` | Status update/progress | `"Planning next step"` | | `ERROR_OCCURRED` | Error during execution | `"Failed to connect: timeout"` | #### LLM & Tool Events | Event Type | Description | Example Message | | ------------------ | -------------------------------- | -------------------------- | | `LLM_PROMPT` | Prompt sent to LLM | `"What is 5 + 5?"` | | `LLM_OUTPUT` | Complete LLM response | `"5 + 5 equals 10"` | | `LLM_PARTIAL` | Streaming chunk (often filtered) | `"5 + 5"` | | `TOOL_INVOKED` | Tool execution started | `"Calling database_query"` | | `TOOL_OBSERVATION` | Tool result/output | `"Query returned 42 rows"` | #### Progress & Status Events | Event Type | Description | Example Message | | ----------------- | ------------------------------- | ------------------------------- | | `PROGRESS` | Step completion update | `"Created a plan with 3 steps"` | | `DATA_PROCESSING` | Processing/analyzing data | `"Preparing context"` | | `DELEGATION` | Task delegated to another agent | `"Handing off to simple task"` | #### Multi-Agent Events (requires feature gates) | Event Type | Description | Example Message | | ------------------ | -------------------------------------------- | ---------------------------- | | `MESSAGE_SENT` | Agent sent message (requires `p2p_v1`) | `"Please analyze section 3"` | | `MESSAGE_RECEIVED` | Agent received message (requires `p2p_v1`) | `"Received task"` | | `TEAM_RECRUITED` | Agent recruited (requires `dynamic_team_v1`) | `"Summarize section 3"` | | `TEAM_RETIRED` | Agent retired (requires `dynamic_team_v1`) | `"Task completed"` | | `BUDGET_THRESHOLD` | Token budget threshold reached | `"Budget threshold: 80%"` | For a complete list of all 27 event types, see the [Event Types Reference](/en/api/event-types). ## WebSocket Streaming ### Connect via WebSocket ```python theme={null} import asyncio import websockets import json async def stream_task(): uri = f"ws://localhost:8080/api/v1/stream/ws?workflow_id={workflow_id}" # Pass API key via headers (gateway requires header-based auth) async with websockets.connect( uri, extra_headers={'X-API-Key': 'sk_test_123456'} ) as websocket: while True: message = await websocket.recv() event = json.loads(message) print(f"Event: {event['type']}") if event['type'] == 'WORKFLOW_COMPLETED': break asyncio.run(stream_task()) ``` WebSocket streaming is currently server-to-client only. Use the REST API `/api/v1/tasks/{id}/cancel` to cancel tasks. ## Filtering Events Filter events by type to reduce noise: ```python theme={null} # Only show important events for event in client.stream(workflow_id): if event.type in ['PROGRESS', 'AGENT_COMPLETED', 'WORKFLOW_COMPLETED']: print(f"{event.type}: {event.message}") ``` ## Progress Tracking Calculate task progress from events: ```python theme={null} def track_progress(workflow_id): agents_started = 0 agents_completed = 0 for event in client.stream(workflow_id): if event.type == 'PROGRESS': # Track progress updates from planner print(f"Progress: {event.message}") elif event.type == 'AGENT_STARTED': agents_started += 1 print(f"Agent started: {event.agent_id}") elif event.type == 'AGENT_COMPLETED': agents_completed += 1 if agents_started > 0: progress = (agents_completed / agents_started) * 100 print(f"Progress: {progress:.1f}% ({agents_completed}/{agents_started})") elif event.type == 'WORKFLOW_COMPLETED': print("✅ Task complete!") break track_progress(handle.workflow_id) ``` Output: ``` Progress: Created a plan with 3 steps Agent started: research-agent Agent started: analysis-agent Agent started: writer-agent Progress: 33.3% (1/3) Progress: 66.7% (2/3) Progress: 100.0% (3/3) ✅ Task complete! ``` ## Real-Time UI Example ### React Component ```jsx theme={null} import { useEffect, useState } from 'react'; function TaskMonitor({ taskId, apiKey }) { const [events, setEvents] = useState([]); const [progress, setProgress] = useState(0); useEffect(() => { const params = new URLSearchParams({ workflow_id: taskId }); // Note: Browser EventSource doesn't support custom headers. // Production: initiate SSE from a backend that injects auth headers, // or use GATEWAY_SKIP_AUTH=1 in development. const eventSource = new EventSource( `http://localhost:8080/api/v1/stream/sse?${params}` ); const handleEvent = (e) => { const event = JSON.parse(e.data); setEvents(prev => [...prev, event]); // Update progress based on workflow lifecycle if (event.type === 'WORKFLOW_STARTED') { setProgress(10); } else if (event.type === 'PROGRESS') { setProgress(prev => Math.min(prev + 20, 80)); } else if (event.type === 'WORKFLOW_COMPLETED') { setProgress(100); } }; // Listen to specific event types (Shannon uses named SSE events) const eventTypes = [ 'WORKFLOW_STARTED', 'WORKFLOW_COMPLETED', 'AGENT_STARTED', 'AGENT_COMPLETED', 'STATUS_UPDATE', 'PROGRESS', 'ERROR_OCCURRED', 'LLM_OUTPUT', 'TOOL_INVOKED', 'TOOL_OBSERVATION' ]; eventTypes.forEach(type => { eventSource.addEventListener(type, handleEvent); }); // Fallback for unnamed events eventSource.onmessage = handleEvent; eventSource.onerror = () => { console.error('SSE connection error'); }; return () => { eventTypes.forEach(type => { eventSource.removeEventListener(type, handleEvent); }); eventSource.close(); }; }, [taskId, apiKey]); return (
    {events.map((e, i) => (
  • [{e.type}]{' '} {e.agent_id && ({e.agent_id})}{' '} {e.message}
  • ))}
); } ``` **Key Implementation Details:** * Shannon emits **named SSE events** (e.g., `event: AGENT_STARTED`), so you must use `addEventListener()` for each event type * Browser `EventSource` API doesn't support custom headers. Do not pass API keys via query params; use a backend proxy to inject headers or disable auth in development * Always clean up event listeners in the cleanup function to prevent memory leaks ## Error Handling Handle connection failures gracefully: ```python theme={null} from shannon import ShannonClient from shannon.errors import ShannonError, ConnectionError import time def robust_streaming(workflow_id, max_retries=3): client = ShannonClient(base_url="http://localhost:8080") for attempt in range(max_retries): try: for event in client.stream(workflow_id): print(f"Event: {event.type}") if event.type == 'WORKFLOW_COMPLETED': # Get final status to retrieve result status = client.get_status(workflow_id.replace('wf-', 'task-')) return status.result except (ShannonError, ConnectionError) as e: print(f"Stream error (attempt {attempt + 1}): {e}") if attempt < max_retries - 1: time.sleep(2 ** attempt) # Exponential backoff else: raise ``` ## Authentication with Streaming When authentication is enabled, provide API key: ### SSE with API Key ```bash theme={null} curl -N \ -H "X-API-Key: sk_test_123456" \ http://localhost:8080/api/v1/stream/sse?workflow_id={task_id} ``` ### Python SDK with API Key ```python theme={null} client = ShannonClient( base_url="http://localhost:8080", api_key="sk_test_123456" ) # API key automatically included in all requests for event in client.stream(workflow_id): print(event) ``` ## Performance Considerations ### Event Buffering Shannon buffers events to prevent overwhelming clients: ```yaml theme={null} # config/shannon.yaml streaming: buffer_size: 100 # Maximum queued events flush_interval_ms: 100 # Send batches every 100ms ``` ### Keepalive SSE sends keepalive comments every 15 seconds to prevent timeout: ``` : keepalive : keepalive event: LLM_PROMPT data: {"message":"..."} ``` ### Resource Cleanup Always close streams when done: ```python theme={null} # Iterate through events (automatically handles connection) for event in client.stream(workflow_id): if event.type == 'WORKFLOW_COMPLETED': break # No explicit close needed ``` ## Multi-Task Monitoring Monitor multiple tasks simultaneously: ```python theme={null} import asyncio async def monitor_task(client, workflow_id): # Use sync stream() in async context for event in client.stream(workflow_id): print(f"[{workflow_id}] {event.type}") if event.type == 'WORKFLOW_COMPLETED': # Get final result from status task_id = workflow_id.replace('wf-', 'task-') status = client.get_status(task_id) return status.result async def monitor_all(task_ids): client = AsyncShannonClient(base_url="http://localhost:8080") # Monitor all tasks in parallel results = await asyncio.gather(*[ monitor_task(client, tid) for tid in task_ids ]) return results # Run task_ids = ["task-1", "task-2", "task-3"] results = asyncio.run(monitor_all(task_ids)) ``` ## Desktop Application Shannon provides a native desktop application built with Tauri for real-time task monitoring and management. The desktop app offers: * Live event streaming for active tasks * Task submission and management interface * Real-time metrics and visualizations * Session history browsing The desktop application has replaced the previous web dashboard. See the [Desktop App Guide](/en/quickstart/desktop-app) for installation and usage instructions. ## Best Practices ### 1. Use SSE for Simple Monitoring ```python theme={null} # ✅ Good: Simple SSE streaming for event in client.stream(workflow_id): print(event.type) ``` ### 2. Handle Disconnections ```python theme={null} # ✅ Good: Retry logic for attempt in range(3): try: for event in client.stream(workflow_id): process_event(event) break except (ShannonError, ConnectionError): if attempt == 2: raise time.sleep(2) ``` ### 3. Filter Unnecessary Events ```python theme={null} # ✅ Good: Only critical events critical_events = ['PROGRESS', 'WORKFLOW_COMPLETED', 'ERROR_OCCURRED'] for event in client.stream(workflow_id): if event.type in critical_events: handle_event(event) ``` ### 4. Set Timeouts ```python theme={null} # ✅ Good: Timeout protection import signal def timeout_handler(signum, frame): raise TimeoutError("Stream timeout") signal.signal(signal.SIGALRM, timeout_handler) signal.alarm(300) # 5 minute timeout try: for event in client.stream(workflow_id): print(event) finally: signal.alarm(0) # Cancel alarm ``` ## Next Steps Async streaming with SDK Streaming API details Task monitoring guide WebSocket documentation # Swarm Multi-Agent Orchestration Source: https://docs.shannon.run/en/quickstart/concepts/swarm Understanding Shannon's swarm workflow with Lead Agent coordination for autonomous multi-agent collaboration ## What is Swarm Mode? Swarm mode deploys multiple persistent, autonomous agents that work in parallel to solve complex tasks. An LLM-powered **Lead Agent** dynamically coordinates the team -- planning tasks, spawning agents, reassigning work, and making decisions based on real-time events. Unlike static orchestration where tasks are pre-decomposed and never change, the Lead Agent continuously monitors progress and adapts: creating new tasks, canceling redundant work, and reassigning idle agents as the situation evolves. ## How It Works Shannon's swarm workflow is driven by an **event-driven Lead Agent loop**: ### Lead Agent Event Loop The Lead Agent wakes on specific events and decides what to do next: ```text theme={null} Events that wake the Lead: ├── agent_idle — An agent finished its current task or became available ├── agent_completed — An agent produced final output ├── help_request — An agent requested the Lead to spawn a helper ├── checkpoint — Periodic timer (every 120s) to review overall progress └── human_input — User sent new instructions mid-execution ``` On each wake, the Lead receives the full team status (agents, tasks, budget) and chooses one or more actions. ### Lifecycle Overview 1. **Initial Planning** -- The Lead receives the user query and creates an initial set of tasks with optional dependency chains 2. **Agent Spawning** -- The Lead spawns agents and assigns tasks, respecting dependency order 3. **Event-Driven Coordination** -- As agents complete work, report idle, or hit checkpoints, the Lead dynamically reassigns tasks, revises the plan, or spawns new agents 4. **Synthesis** -- When all tasks are complete, the Lead can spawn a dedicated synthesis agent or declare `done` to produce the final response ### Task Dependencies (DAG) Tasks can declare dependencies on other tasks, forming a directed acyclic graph (DAG): ```text theme={null} Tasks: ├── task-1: "Research US AI chip market" (depends_on: []) ├── task-2: "Research Japan AI chip market" (depends_on: []) ├── task-3: "Research South Korea AI chip market" (depends_on: []) └── task-4: "Write comparative analysis" (depends_on: [task-1, task-2, task-3]) ``` The system enforces dependency order -- `task-4` cannot be assigned until all three research tasks are complete. The Lead can dynamically create new dependency chains via `revise_plan`. ## Lead Agent Actions Each time the Lead wakes, it selects one or more actions: | Action | Description | | ---------------- | ------------------------------------------------------------------- | | `spawn_agent` | Create a new agent for a specific task | | `assign_task` | Assign a pending task to an idle agent | | `revise_plan` | Dynamically create new tasks or cancel existing ones | | `send_message` | Send a message to a specific agent | | `broadcast` | Send a message to all agents | | `file_read` | Read a workspace file (zero LLM cost, max 3 rounds) | | `shutdown_agent` | Terminate a specific agent | | `interim_reply` | Push a progress update to the user | | `noop` | Do nothing (no action needed right now) | | `done` | Declare all work complete, proceed to closing phase | | `reply` | Return the final response directly to the user (closing phase only) | | `synthesize` | Trigger the synthesis pipeline instead of replying directly | ## Agent Actions Each iteration, an agent chooses exactly one action: | Action | Description | | -------------- | --------------------------------------------------------------- | | `tool_call` | Execute a tool (web search, file read, etc.) | | `publish_data` | Share findings with the team via the workspace | | `send_message` | Send a direct message to a specific teammate | | `request_help` | Ask the Lead to spawn a new helper agent | | `idle` | Signal that the current task is complete and await reassignment | | `done` | Return final response (auto-converts to `idle`) | Agents cannot self-exit. When an agent returns `done`, it automatically converts to `idle` status. Only the Lead Agent can terminate agents via `shutdown_agent`. This ensures the Lead maintains full control over team composition. ## Inter-Agent Communication Swarm agents collaborate through two mechanisms: ### P2P Messaging Agents send direct messages to specific teammates through Redis-backed mailboxes. Message types include `request`, `offer`, `accept`, `delegation`, and `info`. Before each LLM call, the agent's mailbox is checked for new messages. Incoming messages appear in the agent's prompt context. ### Shared Workspace Agents publish findings to topic-based workspace lists. Before each iteration, every agent fetches recent workspace entries from all topics, so the entire team stays aware of collective progress. ```text theme={null} Shared Workspace: ├── Topic: "findings" │ ├── Agent-Takao: "NVIDIA dominates US with 80% market share..." │ └── Agent-Mitaka: "Japan focuses on edge AI chips..." └── Topic: "sources" └── Agent-Kichijoji: "Samsung foundry plans announced..." ``` ## Knowledge Deduplication Shannon prevents redundant work across agents with three layers of deduplication: Each agent caches URLs it has already fetched. If the same URL is requested again within the same agent loop, the cached content is returned without a network call. URL metadata (title, summary, key facts) is shared across all agents in the team. When Agent B tries to fetch a URL that Agent A already processed, it receives the cached metadata instead of re-fetching -- saving both time and tokens. URLs discovered by search results are tracked across all agents. When a new search returns URLs where 70% or more have already been discovered by other agents, the system injects a warning to find new angles. Additionally, a search saturation detector compares recent queries using Jaccard word-level similarity (threshold 0.7, window of 3 queries) to flag repetitive searches. ## Convergence Detection Three mechanisms prevent agents from running indefinitely: If an agent takes 3 consecutive iterations with no meaningful action (empty or unrecognized actions), it is considered converged and transitions to idle status. Note that `tool_call`, `send_message`, and `publish_data` all reset this counter. If 3 consecutive permanent tool errors occur (not transient errors like rate limits), the agent aborts and reports the failure. On the last iteration, if the agent has not called `done` or `idle`, the workflow forces completion and builds a summary from the most recent iterations. Transient errors (rate limits, timeouts, 503s) trigger automatic retry with escalating backoff (5s increments, max 30s) and do not count toward the abort threshold. ## Global Budget Control Swarm execution is bounded by three budget layers that prevent runaway costs: | Budget Layer | Default | Description | | ------------------------ | ----------- | -------------------------------------------- | | `max_total_llm_calls` | `200` | Maximum LLM calls across all agents | | `max_total_tokens` | `1,000,000` | Maximum tokens consumed across all agents | | `max_wall_clock_minutes` | `30` | Maximum wall-clock time for the entire swarm | The Lead Agent receives budget information (remaining calls, tokens, time) in its context, enabling it to make cost-aware decisions -- such as shutting down low-priority agents or skipping optional tasks when budget is tight. ## When to Use Swarm vs Other Workflows | Scenario | Recommended Workflow | | ------------------------------------------------------- | -------------------- | | Simple Q\&A, single-step tasks | Simple / DAG | | Multi-step research with citations | Research Workflow | | Multi-agent code review, testing, and fixes | **Swarm** | | Financial analysis with multiple analyst perspectives | **Swarm** | | Data processing pipelines with Python/Bash execution | **Swarm** | | Tasks where agents need to share intermediate findings | **Swarm** | | Long-running exploration with dynamic subtask discovery | **Swarm** | | Tasks with complex dependency chains between subtasks | **Swarm** | Swarm mode uses more tokens than standard workflows because each agent runs multiple LLM iterations and the Lead Agent consumes tokens for coordination decisions. Use it for tasks that genuinely benefit from persistent, collaborative multi-agent execution. ## Configuration Swarm behavior is controlled via `config/features.yaml`: | Parameter | Default | Description | | -------------------------------- | --------- | ------------------------------------------- | | `swarm.enabled` | `true` | Enable/disable swarm workflow | | `swarm.max_agents` | `10` | Maximum total agents (initial + dynamic) | | `swarm.max_iterations_per_agent` | `25` | Max reason-act loops per agent | | `swarm.agent_timeout_seconds` | `1800` | Per-agent timeout (30 minutes) | | `swarm.max_messages_per_agent` | `20` | P2P message cap per agent | | `swarm.workspace_snippet_chars` | `800` | Max chars per workspace entry in prompt | | `swarm.workspace_max_entries` | `5` | Max recent entries shown to each agent | | `swarm.max_total_llm_calls` | `200` | Global LLM call budget for the entire swarm | | `swarm.max_total_tokens` | `1000000` | Global token budget for the entire swarm | | `swarm.max_wall_clock_minutes` | `30` | Maximum wall-clock time for the swarm | ## Streaming Events Swarm workflows emit SSE events for real-time monitoring: | Event Type | Agent ID | When | | -------------------- | --------------------------------- | ----------------------------------------------------------- | | `WORKFLOW_STARTED` | `swarm-supervisor` | Workflow begins | | `PROGRESS` | `swarm-lead` / `swarm-supervisor` | Planning, spawning, reassigning | | `LEAD_DECISION` | `swarm-lead` | Lead made a planning decision (spawn, assign, revise, etc.) | | `TASKLIST_UPDATED` | `swarm-lead` | Task dependency graph changed (tasks created or canceled) | | `TEAM_STATUS` | `swarm-lead` | Team composition changed (agent spawned or shut down) | | `AGENT_STARTED` | Agent name | Agent begins first iteration | | `AGENT_COMPLETED` | Agent name | Agent finishes | | `WORKFLOW_COMPLETED` | `swarm-supervisor` | Final synthesis complete | ## Next Steps Step-by-step guide to running swarm workflows Other workflow types and cognitive patterns Real-time event streaming Budget management for multi-agent tasks # Workflows and Patterns Source: https://docs.shannon.run/en/quickstart/concepts/workflows Understanding Shannon's workflow orchestration and cognitive patterns ## What are Workflows? Workflows in Shannon are durable, stateful processes that orchestrate AI agents to complete complex tasks. Built on [Temporal](https://temporal.io), they provide: * **Durability**: Workflows survive service restarts and failures * **Determinism**: Can be replayed for debugging * **Visibility**: Full execution history and state inspection * **Reliability**: Automatic retries and error handling ## Workflow Architecture Workflow Architecture ## Cognitive Patterns Shannon implements several proven cognitive patterns for different task types: ### Chain-of-Thought (CoT) Sequential reasoning where each step builds logically on the previous one. **Best for:** * Mathematical problems * Step-by-step analysis * Linear workflows **Example:** ```python theme={null} client.submit_task( query="Calculate the ROI of a $100k investment with 8% annual return over 5 years" ) ``` Shannon automatically applies Chain-of-Thought reasoning for sequential mathematical and analytical tasks. **Execution:** ``` Step 1: Identify formula (ROI = (Final Value - Initial Value) / Initial Value) Step 2: Calculate year-by-year growth Step 3: Compute final value ($146,933) Step 4: Calculate ROI (46.93%) ``` ### Tree-of-Thoughts (ToT) Explores multiple solution paths simultaneously, evaluates them, and selects the best. **Best for:** * Design decisions * Strategic planning * Problems with multiple approaches **Example:** ```python theme={null} client.submit_task( query="Design a scalable message queue system. Evaluate multiple architectures and recommend the best approach." ) ``` Shannon automatically applies Tree-of-Thoughts exploration for design and planning tasks with multiple solution paths. **Execution:** ``` Branch 1: Redis-based solution ├─ Pros: Fast, simple, familiar ├─ Cons: Limited persistence, memory-bound └─ Score: 7/10 Branch 2: Kafka-based solution ├─ Pros: High throughput, durable, battle-tested ├─ Cons: Operational complexity, resource-heavy └─ Score: 9/10 Branch 3: RabbitMQ-based solution ├─ Pros: Feature-rich, good balance ├─ Cons: Lower throughput than Kafka └─ Score: 8/10 Selected: Kafka (highest score for requirements) ``` ### ReAct (Reasoning + Acting) Interleaves thinking and acting in loops, ideal for dynamic problem-solving. **Best for:** * Research tasks * Debugging problems * Adaptive workflows **Example:** ```python theme={null} client.submit_task( query="Find the latest Rust async runtime benchmarks" ) ``` Shannon automatically applies ReAct (Reasoning + Acting) for research and iterative discovery tasks. **Execution:** ``` Think: "I need to search for recent benchmarks" Act: web_search("Rust async runtime benchmarks 2024") Observe: Found 3 articles from 2024 Think: "Let me get detailed comparison data" Act: web_search("tokio vs async-std vs smol benchmark comparison") Observe: Found performance data Think: "Now I can summarize findings" Act: Synthesize results into report ``` ### Debate Pattern Multiple agents argue different perspectives, then synthesize consensus. **Best for:** * Decision making * Evaluating trade-offs * Balanced analysis **Example:** ```python theme={null} client.submit_task( query="Should we migrate our monolith to microservices? Provide arguments for and against, then recommend." ) ``` Shannon automatically applies the Debate pattern for decision-making tasks requiring multiple perspectives. **Execution:** ``` Agent 1 (Pro-Microservices): - Better scalability - Independent deployment - Technology flexibility Agent 2 (Pro-Monolith): - Simpler operations - Easier debugging - Lower latency Agent 3 (Synthesizer): - Evaluates both arguments - Considers context - Provides recommendation ``` ## Task Decomposition For complex tasks, Shannon automatically decomposes them into subtasks: ### DAG (Directed Acyclic Graph) Execution DAG Execution **Parallel Execution:** Subtasks without dependencies run in parallel, reducing latency. **Example:** ```python theme={null} # This query will be automatically decomposed into 3 parallel subtasks client.submit_task( query="Research: 1) GPT-5 capabilities, 2) Claude 3 features, 3) Gemini updates" ) ``` ### Decomposition Strategy Shannon analyzes tasks and creates execution plans internally. While the decomposition structure isn't directly exposed in the SDK response, you can observe the workflow execution through events: ```python theme={null} # Stream events to see workflow execution in action for event in client.stream(workflow_id): if event.type == "PROGRESS": print(f"Progress: {event.message}") elif event.type == "AGENT_STARTED": print(f"Agent started: {event.agent_id} - {event.message}") elif event.type == "AGENT_COMPLETED": print(f"Agent completed: {event.agent_id}") ``` **Internal Structure** (for understanding, not directly accessible): * Subtasks run in parallel when no dependencies exist * Synthesis task waits for all subtasks to complete * Each subtask is assigned to specialized agents ## Workflow Activities Temporal workflows are composed of activities - discrete units of work: | Activity | Purpose | | ----------------------- | -------------------------------------------------------------------------------------------- | | **DecomposeTask** | Analyzes task and creates subtasks | | **ExecuteAgent** | Runs a single agent task | | **SynthesizeResults** | Combines outputs from multiple agents | | **UpdateSessionResult** | Persists session state and appends the assistant message to session history (fix 2025‑11‑05) | | **RecordQuery** | Stores in vector memory | | **FetchSessionMemory** | Retrieves relevant context | ## Monitoring Workflows ### Via Python SDK ```python theme={null} # Get task status status = client.get_status(task_id) print(f"Status: {status.status}") print(f"Progress: {status.progress}") if status.result: print(f"Result: {status.result}") ``` Task decomposition happens internally in Shannon. Use event streaming to observe workflow execution in real-time. ### Via Temporal UI Visit [http://localhost:8088](http://localhost:8088) to see: * Workflow execution timeline * Activity statuses * Input/output payloads * Error traces * Replay history ## Deterministic Replay Shannon workflows are deterministic - they produce the same result when replayed with the same inputs. **Use cases:** * **Debugging**: Replay failed workflows to find bugs * **Testing**: Validate code changes don't break existing workflows * **Auditing**: Understand exactly what happened **Example:** ```bash theme={null} # Export workflow history make replay-export WORKFLOW_ID=task-123 OUT=history.json # Replay against current code make replay HISTORY=history.json # If code changed in non-deterministic way, replay fails ``` ## Workflow Configuration Workflow behavior is configured via environment variables (examples): ```bash theme={null} # In .env MAX_AGENTS_PER_TASK=5 # Max parallel agents (if enabled) MAX_TOKENS_PER_REQUEST=50000 # LLM token budget per request (LLM service) MAX_COST_PER_REQUEST=5.0 # LLM cost limit per request (USD) AGENT_TIMEOUT_SECONDS=600 # Agent execution timeout (orchestrator) ``` Monitor workflow execution via streaming events: ```python theme={null} handle = client.submit_task(query="Complex analysis task") # Stream workflow events for event in client.stream(handle.workflow_id): print(f"[{event.type}] {event.message}") ``` See [Configuration Guide](/en/quickstart/configuration) for all environment variables. ## Error Handling Workflows automatically handle failures: Activities retry automatically with exponential backoff: ``` Attempt 1: Immediate Attempt 2: After 1s Attempt 3: After 2s Attempt 4: After 4s ... Max: 5 attempts ``` If an LLM provider is failing, circuit breaker opens and routes to fallback: ``` Primary: OpenAI (failing) Fallback: Anthropic (healthy) ``` If complex mode fails, automatically falls back to simpler execution. Tasks halt immediately when budget limits are reached, preventing cost overruns. ## Best Practices ### 1. Choose the Right Strategy Match the cognitive strategy to your task: | Task Type | Recommended Strategy | | ----------- | ------------------------------ | | Simple Q\&A | DIRECT (Single agent) | | Research | REACT (Web search + synthesis) | | Analysis | DECOMPOSE (Break into parts) | | Design | EXPLORATORY (ToT evaluation) | ### 2. Use Appropriate Mode * **simple**: Direct execution, no overhead * **standard**: Task decomposition, multi-agent * **complex**: Full cognitive patterns (CoT, ToT, etc.) ### 3. Monitor Progress Stream events to track workflow execution: ```python theme={null} for event in client.stream(workflow_id): if event.type == "LLM_PROMPT": print(f"Prompt: {event.message}") elif event.type == "AGENT_COMPLETED": print(f"Agent done: {event.agent_id}") ``` ### 4. Set Timeouts Prevent workflows from running indefinitely via environment variables: ```bash theme={null} # In .env file AGENT_TIMEOUT_SECONDS=300 # 5 minute per-agent execution limit ``` ## Next Steps Learn budget management Real-time event streaming System architecture overview Get started with the SDK # Configuration Reference Source: https://docs.shannon.run/en/quickstart/configuration Complete environment variables and configuration options for Shannon ## Overview Shannon is configured through **environment variables** and **YAML configuration files**. This guide documents all available configuration options. ## Configuration Files Shannon uses multiple configuration approaches: 1. **`.env` file**: Environment variables (this document) 2. **`config/features.yaml`**: Feature flags and toggles 3. **`config/models.yaml`**: LLM model definitions and pricing 4. **Docker Compose**: Service orchestration and networking ### Setup ```bash theme={null} # Create .env from template cp .env.example .env # Edit with your values nano .env # Apply changes docker compose down docker compose up -d ``` *** ## Core Runtime Essential variables for all deployments. | Variable | Type | Default | Description | | -------------- | ------- | --------------------- | --------------------------------------------- | | `ENVIRONMENT` | string | `dev` | Runtime environment: `dev`, `staging`, `prod` | | `DEBUG` | boolean | `false` | Enable debug logging | | `SERVICE_NAME` | string | `shannon-llm-service` | Service identifier for logs and metrics | **Example**: ```bash theme={null} ENVIRONMENT=prod DEBUG=false SERVICE_NAME=shannon-production ``` *** ## LLM Provider API Keys **At least one provider must be configured.** | Variable | Provider | Required | Format | | ------------------- | ------------------ | ----------- | ------------ | | `OPENAI_API_KEY` | OpenAI | Conditional | `sk-...` | | `ANTHROPIC_API_KEY` | Anthropic (Claude) | Conditional | `sk-ant-...` | | `GOOGLE_API_KEY` | Google (Gemini) | Conditional | `AIza...` | | `GROQ_API_KEY` | Groq | No | `gsk_...` | | `XAI_API_KEY` | xAI (Grok) | No | Custom | | `DEEPSEEK_API_KEY` | DeepSeek | No | Custom | | `QWEN_API_KEY` | Qwen | No | Custom | | `MISTRAL_API_KEY` | Mistral | No | Custom | | `ZAI_API_KEY` | ZAI | No | Custom | **AWS Bedrock Configuration**: | Variable | Default | Description | | ----------------------- | ----------- | -------------------------- | | `AWS_ACCESS_KEY_ID` | - | AWS access key for Bedrock | | `AWS_SECRET_ACCESS_KEY` | - | AWS secret key | | `AWS_REGION` | `us-east-1` | AWS region | **Example**: ```bash theme={null} OPENAI_API_KEY=sk-proj-abc123... ANTHROPIC_API_KEY=sk-ant-xyz789... AWS_REGION=us-west-2 ``` **Testing API Keys**: ```bash theme={null} # Test OpenAI curl https://api.openai.com/v1/models \ -H "Authorization: Bearer $OPENAI_API_KEY" # Test Anthropic curl https://api.anthropic.com/v1/messages \ -H "x-api-key: $ANTHROPIC_API_KEY" \ -H "anthropic-version: 2023-06-01" \ -H "Content-Type: application/json" \ -d '{"model":"claude-3-sonnet-20240229","max_tokens":10,"messages":[{"role":"user","content":"Hi"}]}' ``` *** ## Web Search Providers Optional but **highly recommended** for research and data gathering tasks. | Variable | Type | Default | Options | | --------------------- | ------ | --------- | --------------------------------------------------------- | | `WEB_SEARCH_PROVIDER` | string | `serpapi` | `google`, `serper`, `serpapi`, `bing`, `exa`, `firecrawl` | **Provider-Specific Keys**: | Variable | Provider | Get Key From | | ------------------------- | --------- | -------------------------------------------------------------------------- | | `GOOGLE_SEARCH_API_KEY` | Google | [Google Cloud Console](https://console.cloud.google.com/) | | `GOOGLE_SEARCH_ENGINE_ID` | Google | [Programmable Search Engine](https://programmablesearchengine.google.com/) | | `SERPER_API_KEY` | Serper | [serper.dev](https://serper.dev) | | `BING_API_KEY` | Bing | [Azure Portal](https://portal.azure.com/) | | `EXA_API_KEY` | Exa | [exa.ai](https://exa.ai) | | `FIRECRAWL_API_KEY` | Firecrawl | [firecrawl.dev](https://firecrawl.dev) | **Example**: ```bash theme={null} WEB_SEARCH_PROVIDER=serpapi SERPAPI_API_KEY=your-serpapi-key ``` *** ## Data Stores Configuration for PostgreSQL, Redis, and Qdrant. ### PostgreSQL | Variable | Type | Default | Description | | ------------------- | ------- | ---------- | --------------------------------------------- | | `POSTGRES_HOST` | string | `postgres` | Database hostname | | `POSTGRES_PORT` | integer | `5432` | Database port | | `POSTGRES_DB` | string | `shannon` | Database name | | `POSTGRES_USER` | string | `shannon` | Database username | | `POSTGRES_PASSWORD` | string | `shannon` | Database password | | `POSTGRES_SSLMODE` | string | `disable` | SSL mode: `disable`, `require`, `verify-full` | | `DB_MAX_OPEN_CONNS` | integer | `25` | Maximum open connections | | `DB_MAX_IDLE_CONNS` | integer | `5` | Maximum idle connections | ### Redis | Variable | Type | Default | Description | | ------------------- | ------- | -------------------- | ------------------------------------------ | | `REDIS_HOST` | string | `redis` | Redis hostname | | `REDIS_PORT` | integer | `6379` | Redis port | | `REDIS_PASSWORD` | string | ` ` | Redis password (empty = no auth) | | `REDIS_TTL_SECONDS` | integer | `3600` | Default TTL for cached items (1 hour) | | `REDIS_ADDR` | string | `redis:6379` | Redis address (host:port) | | `REDIS_URL` | string | `redis://redis:6379` | Redis connection URL | | `LLM_REDIS_URL` | string | - | Dedicated Redis for LLM caching (optional) | ### Qdrant (Vector Database) | Variable | Type | Default | Description | | ------------- | ------- | -------------------- | -------------------- | | `QDRANT_URL` | string | `http://qdrant:6333` | Qdrant HTTP endpoint | | `QDRANT_HOST` | string | `qdrant` | Qdrant hostname | | `QDRANT_PORT` | integer | `6333` | Qdrant port | **Example**: ```bash theme={null} # PostgreSQL POSTGRES_HOST=db.example.com POSTGRES_PORT=5432 POSTGRES_PASSWORD=secure_password_here # Redis (with auth) REDIS_HOST=redis.example.com REDIS_PASSWORD=redis_password_here REDIS_TTL_SECONDS=7200 # 2 hours # Qdrant QDRANT_URL=http://vector-db.example.com:6333 ``` *** ## Service Endpoints Internal service URLs for communication. | Variable | Default | Description | | ---------------------- | --------------------------------- | -------------------------------- | | `TEMPORAL_HOST` | `temporal:7233` | Temporal workflow engine | | `LLM_SERVICE_URL` | `http://llm-service:8000` | Python LLM service HTTP endpoint | | `AGENT_CORE_ADDR` | `agent-core:50051` | Rust agent core gRPC endpoint | | `ADMIN_SERVER` | `http://orchestrator:8081` | Orchestrator admin API | | `ORCHESTRATOR_GRPC` | `orchestrator:50052` | Orchestrator gRPC endpoint | | `EVENTS_INGEST_URL` | `http://orchestrator:8081/events` | Event ingestion endpoint | | `EVENTS_AUTH_TOKEN` | - | Auth token for event ingestion | | `APPROVALS_AUTH_TOKEN` | - | Auth token for approval webhooks | **Config File Paths**: | Variable | Default | Description | | -------------------- | ------------------------ | ----------------------------- | | `CONFIG_PATH` | `./config/features.yaml` | Feature flags configuration | | `MODELS_CONFIG_PATH` | `./config/models.yaml` | Model definitions and pricing | *** ## Model Routing & Budgets Control LLM selection, token limits, and cost management. | Variable | Type | Default | Description | | ------------------------------- | ------- | ---------------------------- | ---------------------------------------------------------------- | | `DEFAULT_MODEL_TIER` | string | `small` | Default model size: `small`, `medium`, `large` | | `COMPLEXITY_MODEL_ID` | string | `gpt-5` | Model for complexity analysis | | `DECOMPOSITION_MODEL_ID` | string | `claude-sonnet-4-5-20250929` | Model for task decomposition | | `MAX_TOKENS` | integer | `2000` | Default max output tokens | | `TEMPERATURE` | float | `0.7` | Default sampling temperature (0.0-1.0) | | `MAX_TOKENS_PER_REQUEST` | integer | `10000` | Maximum tokens per API request | | `MAX_COST_PER_REQUEST` | float | `0.50` | Maximum cost per request (USD) | | `LLM_DISABLE_BUDGETS` | integer | `1` | `1` = orchestrator manages budgets, `0` = enforce in LLM service | | `HISTORY_WINDOW_MESSAGES` | integer | `50` | Number of history messages to include | | `HISTORY_WINDOW_DEBUG_MESSAGES` | integer | `75` | History messages in debug mode | | `WORKFLOW_SYNTH_BYPASS_SINGLE` | boolean | `true` | Skip synthesis for single-result tasks | | `TOKEN_BUDGET_PER_AGENT` | integer | - | Per-agent token limit | | `TOKEN_BUDGET_PER_TASK` | integer | - | Per-task token limit | **Example - Cost-Optimized**: ```bash theme={null} DEFAULT_MODEL_TIER=small MAX_COST_PER_REQUEST=0.10 MAX_TOKENS_PER_REQUEST=5000 TEMPERATURE=0.5 ``` **Example - High-Quality**: ```bash theme={null} DEFAULT_MODEL_TIER=large MAX_COST_PER_REQUEST=2.00 MAX_TOKENS_PER_REQUEST=50000 TEMPERATURE=0.7 ``` *** ## Cache & Rate Limiting Performance and cost optimization through caching and rate limits. | Variable | Type | Default | Description | | ---------------------------- | ------- | ------- | --------------------------------------- | | `ENABLE_CACHE` | boolean | `true` | Enable LLM response caching | | `CACHE_SIMILARITY_THRESHOLD` | float | `0.95` | Semantic similarity threshold (0.0-1.0) | | `RATE_LIMIT_REQUESTS` | integer | `100` | Requests per window | | `RATE_LIMIT_WINDOW` | integer | `60` | Rate limit window (seconds) | | `WEB_SEARCH_RATE_LIMIT` | integer | `120` | Web search requests per minute | | `CALCULATOR_RATE_LIMIT` | integer | `2000` | Calculator tool requests per minute | | `PYTHON_EXECUTOR_RATE_LIMIT` | integer | `60` | Python execution requests per minute | | `PARTIAL_CHUNK_CHARS` | integer | `512` | Streaming chunk size (characters) | **Cache Behavior**: * Responses are cached by **semantic similarity** * Cache key: SHA256 hash of (prompt + model + temperature) * TTL: Controlled by `REDIS_TTL_SECONDS` **Example**: ```bash theme={null} ENABLE_CACHE=true CACHE_SIMILARITY_THRESHOLD=0.98 # Higher = more exact matches RATE_LIMIT_REQUESTS=200 RATE_LIMIT_WINDOW=60 ``` *** ## Tool Execution & Workflow Controls Fine-tune parallelism, timeouts, and execution behavior. | Variable | Type | Default | Range | Description | | --------------------------- | ------- | ------- | ------- | ----------------------------------------- | | `TOOL_PARALLELISM` | integer | `1` | 1-10 | Concurrent tool executions (1=sequential) | | `ENABLE_TOOL_SELECTION` | integer | `1` | 0,1 | `1`=auto tool selection, `0`=manual only | | `PRIORITY_QUEUES` | string | `off` | on/off | Enable priority-based task queuing | | `STREAMING_RING_CAPACITY` | integer | `1000` | - | Event stream buffer size | | `COMPRESSION_TRIGGER_RATIO` | float | `0.75` | 0.0-1.0 | Context compression trigger threshold | | `COMPRESSION_TARGET_RATIO` | float | `0.375` | 0.0-1.0 | Target compression ratio | | `ENFORCE_TIMEOUT_SECONDS` | integer | `90` | - | Hard timeout for operations | | `ENFORCE_MAX_TOKENS` | integer | `32768` | - | Absolute maximum tokens | | `ENFORCE_RATE_RPS` | integer | `20` | - | Requests per second limit | **Circuit Breaker Settings**: | Variable | Type | Default | Description | | ---------------------------- | ------- | ------- | --------------------------------------- | | `ENFORCE_CB_ERROR_THRESHOLD` | float | `0.5` | Error rate to open circuit (50%) | | `ENFORCE_CB_WINDOW_SECONDS` | integer | `30` | Sliding window for error rate | | `ENFORCE_CB_MIN_REQUESTS` | integer | `20` | Minimum requests before opening circuit | **Performance Tuning**: ```bash theme={null} # High throughput TOOL_PARALLELISM=10 ENFORCE_RATE_RPS=50 # Conservative / Low resources TOOL_PARALLELISM=2 ENFORCE_RATE_RPS=10 ``` *** ## Approvals & Security Human-in-the-loop and authentication settings. | Variable | Type | Default | Description | | ------------------------------- | ------- | ---------------------------------------------- | ------------------------------------------------ | | `APPROVAL_ENABLED` | boolean | `false` | Enable manual approval workflow | | `APPROVAL_COMPLEXITY_THRESHOLD` | float | `0.5` | Complexity score requiring approval (0.0-1.0) | | `APPROVAL_DANGEROUS_TOOLS` | string | `file_system,code_execution` | Comma-separated tools requiring approval | | `APPROVAL_TIMEOUT_SECONDS` | integer | `1800` | Approval wait timeout (30 minutes) | | `JWT_SECRET` | string | `development-only-secret-change-in-production` | JWT signing secret (⚠️ **CHANGE IN PRODUCTION**) | | `GATEWAY_SKIP_AUTH` | integer | `1` | `1`=auth disabled, `0`=auth enabled | **Security Best Practices**: ```bash theme={null} # Production setup APPROVAL_ENABLED=true APPROVAL_DANGEROUS_TOOLS=file_system,code_execution,shell,network_access JWT_SECRET=$(openssl rand -base64 64) GATEWAY_SKIP_AUTH=0 ``` **Development setup**: ```bash theme={null} # Fast iteration (⚠️ NOT FOR PRODUCTION) APPROVAL_ENABLED=false GATEWAY_SKIP_AUTH=1 ``` *** ## Python WASI Sandbox Secure Python code execution environment. | Variable | Type | Default | Description | | ----------------------------- | ------- | ---------------------------------------- | ------------------------------- | | `PYTHON_WASI_WASM_PATH` | string | `./wasm-interpreters/python-3.11.4.wasm` | Path to Python WASI interpreter | | `PYTHON_WASI_SESSION_TIMEOUT` | integer | `3600` | Session timeout (seconds) | | `WASI_MEMORY_LIMIT_MB` | integer | `512` | Memory limit per execution (MB) | | `WASI_TIMEOUT_SECONDS` | integer | `60` | Execution timeout per run | **Setup**: ```bash theme={null} # Download Python WASI interpreter (20MB) ./scripts/setup_python_wasi.sh # Verify ls -lh wasm-interpreters/python-3.11.4.wasm ``` **Tuning**: ```bash theme={null} # Tight limits (basic scripts) WASI_MEMORY_LIMIT_MB=256 WASI_TIMEOUT_SECONDS=30 # Generous limits (data processing) WASI_MEMORY_LIMIT_MB=1024 WASI_TIMEOUT_SECONDS=300 ``` *** ## OpenAPI & MCP Integrations External tool and API integration settings. ### OpenAPI Tools | Variable | Type | Default | Description | | ------------------------- | ------- | --------- | ---------------------------------------- | | `OPENAPI_ALLOWED_DOMAINS` | string | `*` | Allowed domains (`*` or comma-separated) | | `OPENAPI_MAX_SPEC_SIZE` | integer | `5242880` | Max OpenAPI spec size (5MB) | | `OPENAPI_FETCH_TIMEOUT` | integer | `30` | Spec fetch timeout (seconds) | | `OPENAPI_RETRIES` | integer | `2` | Retry attempts | ### MCP (Model Context Protocol) | Variable | Type | Default | Description | | ------------------------- | ------- | ---------- | --------------------------------- | | `MCP_ALLOWED_DOMAINS` | string | `*` | Allowed MCP domains | | `MCP_MAX_RESPONSE_BYTES` | integer | `10485760` | Max response size (10MB) | | `MCP_RETRIES` | integer | `3` | Retry attempts | | `MCP_TIMEOUT_SECONDS` | integer | `10` | Request timeout | | `MCP_REGISTER_TOKEN` | string | - | Registration auth token | | `MCP_RATE_LIMIT_DEFAULT` | integer | `60` | Default rate limit (req/min) | | `MCP_CB_FAILURES` | integer | `5` | Circuit breaker failure threshold | | `MCP_CB_RECOVERY_SECONDS` | integer | `60` | Circuit breaker recovery time | | `MCP_COST_TO_TOKENS` | integer | `0` | Cost-to-token conversion | **Example - Restricted**: ```bash theme={null} OPENAPI_ALLOWED_DOMAINS=api.example.com,api.partner.com MCP_ALLOWED_DOMAINS=mcp.trusted.com MCP_REGISTER_TOKEN=secret_token_here ``` *** ## Observability & Telemetry Metrics, tracing, and logging configuration. | Variable | Type | Default | Description | | ----------------------------- | ------- | --------------------- | ----------------------------- | | `OTEL_SERVICE_NAME` | string | `shannon-llm-service` | OpenTelemetry service name | | `OTEL_EXPORTER_OTLP_ENDPOINT` | string | `localhost:4317` | OTLP endpoint | | `OTEL_ENABLED` | boolean | `false` | Enable OpenTelemetry tracing | | `LOG_FORMAT` | string | `plain` | Log format: `plain` or `json` | | `METRICS_PORT` | integer | `2112` | Prometheus metrics port | **Prometheus Endpoints**: * Orchestrator: `http://localhost:2112/metrics` * Agent Core: `http://localhost:2113/metrics` * LLM Service: `http://localhost:8000/metrics` **Example - Production Observability**: ```bash theme={null} OTEL_ENABLED=true OTEL_EXPORTER_OTLP_ENDPOINT=otel-collector:4317 LOG_FORMAT=json METRICS_PORT=2112 ``` *** ## Advanced Orchestrator Controls Low-level tuning for Temporal workers and orchestrator behavior. ### Worker Concurrency | Variable | Type | Default | Description | | --------------------- | ------- | ------- | -------------------------------------------- | | `WORKER_ACT` | integer | - | Activity worker concurrency (all priorities) | | `WORKER_WF` | integer | - | Workflow worker concurrency (all priorities) | | `WORKER_ACT_CRITICAL` | integer | `10` | Critical priority activity workers | | `WORKER_WF_CRITICAL` | integer | `5` | Critical priority workflow workers | | `WORKER_ACT_HIGH` | integer | - | High priority activity workers | | `WORKER_WF_HIGH` | integer | - | High priority workflow workers | | `WORKER_ACT_NORMAL` | integer | - | Normal priority activity workers | | `WORKER_WF_NORMAL` | integer | - | Normal priority workflow workers | | `WORKER_ACT_LOW` | integer | - | Low priority activity workers | | `WORKER_WF_LOW` | integer | - | Low priority workflow workers | ### Event & Circuit Settings | Variable | Type | Default | Description | | ---------------------------- | ------- | ------- | ------------------------------ | | `EVENTLOG_BATCH_SIZE` | integer | `100` | Event batch size | | `EVENTLOG_BATCH_INTERVAL_MS` | integer | `100` | Event batch interval (ms) | | `RATE_LIMIT_INTERVAL_MS` | integer | `60000` | Rate limit window (ms) | | `BACKPRESSURE_THRESHOLD` | integer | - | Backpressure trigger threshold | | `MAX_BACKPRESSURE_DELAY_MS` | integer | - | Max backpressure delay | | `CIRCUIT_FAILURE_THRESHOLD` | integer | - | Circuit breaker failure count | | `CIRCUIT_HALF_OPEN_REQUESTS` | integer | - | Half-open state test requests | | `CIRCUIT_RESET_TIMEOUT_MS` | integer | - | Circuit reset timeout | | `LLM_TIMEOUT_SECONDS` | integer | `120` | LLM request timeout | **Performance Tuning**: ```bash theme={null} # High load WORKER_ACT_CRITICAL=20 WORKER_WF_CRITICAL=10 EVENTLOG_BATCH_SIZE=500 # Low resources WORKER_ACT_CRITICAL=5 WORKER_WF_CRITICAL=3 EVENTLOG_BATCH_SIZE=50 ``` *** ## Miscellaneous Additional configuration options. | Variable | Type | Default | Description | | --------------------------- | ------- | ------------- | -------------------------------------------- | | `SHANNON_WORKSPACE` | string | `./workspace` | Workspace directory for file operations | | `SEED_DATA` | boolean | `false` | Seed Qdrant with sample data on startup | | `AGENT_TIMEOUT_SECONDS` | integer | `600` | Max runtime per agent execution (10 minutes) | | `TEMPLATE_FALLBACK_ENABLED` | boolean | `false` | Fallback to AI if template execution fails | *** ## Configuration Profiles ### Development Profile ```bash theme={null} # .env.dev ENVIRONMENT=dev DEBUG=true GATEWAY_SKIP_AUTH=1 APPROVAL_ENABLED=false LOG_FORMAT=plain MAX_COST_PER_REQUEST=0.10 LLM_DISABLE_BUDGETS=1 OTEL_ENABLED=false ``` ### Staging Profile ```bash theme={null} # .env.staging ENVIRONMENT=staging DEBUG=false GATEWAY_SKIP_AUTH=0 APPROVAL_ENABLED=true LOG_FORMAT=json MAX_COST_PER_REQUEST=1.00 LLM_DISABLE_BUDGETS=0 OTEL_ENABLED=true JWT_SECRET=$(openssl rand -base64 64) ``` ### Production Profile ```bash theme={null} # .env.prod ENVIRONMENT=prod DEBUG=false GATEWAY_SKIP_AUTH=0 APPROVAL_ENABLED=true APPROVAL_DANGEROUS_TOOLS=file_system,code_execution,shell,network_access LOG_FORMAT=json MAX_COST_PER_REQUEST=2.00 LLM_DISABLE_BUDGETS=0 OTEL_ENABLED=true JWT_SECRET=$(openssl rand -base64 64) POSTGRES_SSLMODE=require REDIS_PASSWORD=secure_password # Add strong passwords and restrict domains OPENAPI_ALLOWED_DOMAINS=api.trusted.com MCP_ALLOWED_DOMAINS=mcp.trusted.com ``` *** ## Hot-Reload Support Most configuration changes require a service restart: ```bash theme={null} # After editing .env docker compose restart orchestrator docker compose restart agent-core docker compose restart llm-service docker compose restart gateway ``` **Services that auto-reload**: * ✅ Feature flags (`config/features.yaml`) * ✅ Model configuration (`config/models.yaml`) **Services requiring restart**: * ❌ Environment variables (`.env`) * ❌ Database credentials * ❌ Service endpoints *** ## Validation & Testing ### Verify Configuration ```bash theme={null} # Check loaded environment variables docker compose exec orchestrator env | sort # Test database connection docker compose exec postgres psql -U shannon -d shannon -c "SELECT 1;" # Test Redis connection docker compose exec redis redis-cli ping # Test API endpoints curl http://localhost:8080/health curl http://localhost:8000/health ``` ### Configuration Debugging ```bash theme={null} # View service logs docker compose logs orchestrator | grep -i "config\|environment" # Check for errors docker compose logs orchestrator | grep -i "error\|warning" # Validate YAML syntax docker compose config ``` *** ## Security Checklist * [ ] Change `JWT_SECRET` to strong random value * [ ] Enable authentication (`GATEWAY_SKIP_AUTH=0`) * [ ] Set strong database passwords * [ ] Enable Redis authentication * [ ] Use SSL for PostgreSQL (`POSTGRES_SSLMODE=require`) * [ ] Enable approvals (`APPROVAL_ENABLED=true`) * [ ] Restrict `OPENAPI_ALLOWED_DOMAINS` * [ ] Restrict `MCP_ALLOWED_DOMAINS` * [ ] Enable structured logging (`LOG_FORMAT=json`) * [ ] Set up monitoring (`OTEL_ENABLED=true`) * [ ] Configure budget limits appropriately * [ ] Review worker concurrency for your load * [ ] Backup `.env` file securely *** ## Related Topics Initial setup guide Common configuration issues Budget management Observability setup # Desktop Application Source: https://docs.shannon.run/en/quickstart/desktop-app Native desktop app for Shannon task monitoring and management ## Overview Shannon provides a native desktop application built with [Tauri](https://tauri.app/) for real-time task monitoring and management. The desktop app offers a streamlined interface for interacting with Shannon services. The desktop application has replaced the previous web dashboard and provides improved performance and native OS integration. ## Features * **Live Task Streaming** - Real-time SSE event feed for active tasks * **Task Management** - Submit, monitor, and cancel tasks * **Session History** - Browse and continue previous conversations * **Multi-Platform** - macOS (Universal), Windows, Linux, iOS ### Swarm Radar Canvas New in v0.3.0 The desktop app includes a **Radar Canvas** for visualizing multi-agent swarm execution in real time: * **Lead Agent Pulse** — The central node pulses to indicate Lead decision-making activity * **Agent Color Coding** — Each swarm agent is displayed with a distinct color based on its role and status (idle, running, completed) * **Live Topology** — Agent spawn, shutdown, and message flow are animated on the canvas as they happen ## Supported Platforms | Platform | Architecture | Format | | -------- | --------------------------------- | ------------ | | macOS | Universal (Intel + Apple Silicon) | DMG | | Windows | x86\_64 | MSI | | Linux | x86\_64 | AppImage/DEB | | iOS | ARM64 | App Bundle | ## Installation ### macOS 1. Download the latest DMG from the [Releases page](https://github.com/Kocoro-lab/Shannon/releases) 2. Open the DMG file 3. Drag Shannon to Applications folder 4. Launch from Applications ```bash theme={null} # Or install via command line open shannon-desktop_0.3.0_universal.dmg ``` ### Building from Source Ensure you have the required tools installed: * Node.js 18+ * Rust 1.70+ * Xcode Command Line Tools (macOS) ```bash theme={null} # Install Rust targets for universal binary (macOS) rustup target add aarch64-apple-darwin x86_64-apple-darwin ``` ```bash theme={null} git clone https://github.com/Kocoro-lab/Shannon.git cd Shannon/desktop npm install ``` ```bash theme={null} cp .env.local.example .env.local # Edit .env.local to set your Shannon API URL # Default: http://localhost:8080 ``` ```bash theme={null} npm run tauri:dev ``` ```bash theme={null} npm run tauri:build ``` Output: `src-tauri/target/release/bundle/dmg/shannon-desktop_0.3.0_universal.dmg` ## Configuration ### API Endpoint Set the Shannon API endpoint via environment variable: ```bash theme={null} # .env.local NEXT_PUBLIC_API_URL=http://localhost:8080 ``` For production deployments: ```bash theme={null} NEXT_PUBLIC_API_URL=https://api.your-domain.com ``` ## Usage ### Main Interface The desktop app provides: 1. **Chat Interface** - Submit queries and view responses 2. **Runs View** - Monitor active and completed tasks 3. **Run Details** - View full event stream for a specific task 4. **Settings** - Configure API endpoint and preferences ### Submitting Tasks 1. Open the app 2. Enter your query in the chat input 3. Press Enter or click Send 4. Watch real-time events as the task executes ### Monitoring Tasks Navigate to the Runs page to see: * Active tasks with live status * Completed tasks with results * Failed tasks with error details Click on any run to see the full event stream. ## Troubleshooting ### Build Fails: Module Not Found ```bash theme={null} npm install @tauri-apps/plugin-shell ``` ### Universal Binary Requirements (macOS) Ensure both Rust targets are installed: ```bash theme={null} rustup target list --installed # Should show: # aarch64-apple-darwin # x86_64-apple-darwin ``` ### Connection Issues 1. Verify Shannon services are running 2. Check API URL in `.env.local` 3. Ensure no firewall blocking port 8080 ### Clean Rebuild ```bash theme={null} rm -rf .next out src-tauri/target/release/bundle npm run tauri:build ``` ## Technology Stack | Component | Technology | | --------- | ------------------------ | | Framework | Next.js 16 (Turbopack) | | Runtime | Tauri 2.x | | UI | Shadcn/ui + Tailwind CSS | | State | Redux Toolkit | | Backend | Shannon Gateway API | ## Next Steps Set up Shannon services Submit your first task Understand real-time events Full API documentation # Welcome to Shannon Source: https://docs.shannon.run/en/quickstart/index Production AI agent orchestration with cost control and reliability ## What is Shannon? Shannon is a production-grade AI agent orchestration platform designed to solve the critical challenges of deploying AI agents at scale: Hard budget limits, token tracking, and intelligent model selection often delivering 60–90% cost savings (workload‑dependent) Deterministic replay debugging, circuit breakers, and automatic degradation WASI sandboxing for code execution, OPA policy enforcement, multi-tenant isolation Temporal-backed distributed workflows, horizontal scaling, comprehensive observability ## Key Features ### Multi-Agent Orchestration Shannon coordinates multiple AI agents using proven cognitive patterns like Chain-of-Thought (CoT), Tree-of-Thoughts (ToT), and ReAct, enabling complex task decomposition and parallel execution. ### Production-Ready Built with production workloads in mind: * **Temporal workflows** for durable, deterministic execution * **WASI sandboxing** for secure Python code execution * **Circuit breakers** and failure protection * **Comprehensive observability** with Prometheus metrics and OpenTelemetry tracing ### Multi-Provider Support Seamlessly switch between LLM providers: * OpenAI (GPT-5 family) * Anthropic (Claude 4 Opus/Sonnet/Haiku) * Google Gemini * Groq, Azure OpenAI, Ollama, and more ## Quick Start Get Shannon running in under 10 minutes: Set up Shannon with Docker Compose in one command ## Architecture Overview Shannon consists of four main components working together: Shannon Architecture REST API layer with authentication and rate limiting Temporal-based workflow coordination and task routing Secure execution layer with WASI sandboxing Multi-provider LLM gateway with intelligent caching ## Use Cases Shannon excels at: * **Complex Task Automation**: Break down complex tasks into manageable subtasks with automatic orchestration * **Research & Analysis**: Coordinate multiple agents for comprehensive research and synthesis * **Code Generation**: Secure Python code execution in WASI sandbox * **Multi-Step Workflows**: Durable workflows that survive failures and can be replayed for debugging ## Next Steps Submit your first task in 5 minutes Learn about agents, workflows, and patterns Explore the REST and gRPC APIs Use the Python SDK for easy integration # Installation Source: https://docs.shannon.run/en/quickstart/installation Get Shannon running in under 10 minutes with Docker Compose ## Prerequisites Before installing Shannon, ensure you have: * **Docker** (20.10+) and **Docker Compose** (v2.0+) * **Git** for cloning the repository * **LLM API Key** (OpenAI, Anthropic, or other supported provider) * At least **4GB RAM** available for Docker Shannon works on Linux, macOS, and Windows (with WSL2). All examples assume a Unix-like environment. ## One-Command Setup Shannon provides a streamlined setup process that gets you running in minutes: ```bash theme={null} # Clone the repository git clone https://github.com/Kocoro-lab/Shannon.git cd Shannon # Initialize configuration and generate protocol buffers make setup ``` Tip: `make setup` is the one‑stop setup (creates `.env` and generates protobufs). If you only need the environment file, use `make setup-env`. You can also regenerate protobufs anytime with `make proto`. ```bash theme={null} # Add your LLM API key echo "OPENAI_API_KEY=sk-your-key-here" >> .env # Download Python WASI interpreter (required for sandboxed execution) ./scripts/setup_python_wasi.sh # Start all services make dev ``` That's it! Shannon is now running with all required services. ## What Gets Installed The `make dev` command starts the following services via Docker Compose: | Service | Port | Description | | ---------------------- | ----- | --------------------------- | | **Gateway** | 8080 | REST API gateway | | **Orchestrator** | 50052 | gRPC orchestration service | | **Agent Core** | 50051 | Rust-based agent execution | | **LLM Service** | 8000 | Python LLM provider gateway | | **Playwright Service** | 8002 | Browser automation service | | **PostgreSQL** | 5432 | Persistent storage | | **Redis** | 6379 | Caching and pub/sub | | **Qdrant** | 6333 | Vector database | | **Temporal** | 7233 | Workflow engine | | **Temporal UI** | 8088 | Workflow visualization | The setup script also installs **synthesis templates** (7 templates for research reports, domain analysis, etc.) and **workflow templates** (8 example YAML workflows for common task patterns). See [Templates](/en/tutorials/templates) for details. ## Verify Installation Check that all services are running: ```bash theme={null} # View service status docker compose ps # All services should show "healthy" or "running" ``` Test the API: ```bash theme={null} # Submit a simple task curl -X POST http://localhost:8080/api/v1/tasks \ -H "Content-Type: application/json" \ -d '{ "query": "What is 2+2?" }' # You should receive a response with a task_id ``` ## Access the UI Open your browser and tools as needed: * **Shannon Desktop App**: see [Desktop Application](/en/quickstart/desktop-app) for installation and usage * **Temporal UI**: [http://localhost:8088](http://localhost:8088) ## Configuration Shannon is pre-configured for local development, but you can customize it: ### Environment Variables The `.env` file (created by `make setup`) contains key configuration: ```bash theme={null} # LLM Provider Keys OPENAI_API_KEY=sk-your-key-here ANTHROPIC_API_KEY=sk-ant-your-key-here # Service Configuration GATEWAY_SKIP_AUTH=1 # Disable auth for development LOG_LEVEL=info # Database POSTGRES_USER=shannon POSTGRES_PASSWORD=shannon_dev POSTGRES_DB=shannon # Redis REDIS_URL=redis://redis:6379 ``` In production, set `GATEWAY_SKIP_AUTH=0` to enable API key authentication. ### Configuration Files Advanced configuration is available in the `config/` directory: * `shannon.yaml` - Main system configuration * `features.yaml` - Feature flags * `models.yaml` - LLM provider pricing and routing ## Common Issues If you see port binding errors, check that ports 8080, 50051, 50052, 8000, etc. are not in use: ```bash theme={null} # macOS/Linux lsof -i :8080 # Stop conflicting services or change ports in docker-compose.yml ``` Shannon requires at least 4GB RAM. Increase Docker's memory limit: * **Docker Desktop**: Settings → Resources → Memory (set to 6GB+) * **Linux**: Docker uses all available memory by default If `setup_python_wasi.sh` fails, manually download: ```bash theme={null} mkdir -p wasm-interpreters cd wasm-interpreters wget https://github.com/vmware-labs/webassembly-language-runtimes/releases/download/python%2F3.11.4%2B20230714-11be424/python-3.11.4.wasm ``` Check Docker logs for errors: ```bash theme={null} docker compose logs orchestrator docker compose logs agent-core docker compose logs llm-service ``` Common causes: * Missing `.env` file (run `make setup`) * Invalid API keys * Insufficient Docker resources ## Next Steps Now that Shannon is running: Learn how to interact with Shannon Understand agents and workflows ## Development Setup Run core dependencies via Docker, then run services locally to iterate: ```bash theme={null} # Terminal 1: Start dependencies only (DB, cache, vector, Temporal) docker compose -f deploy/compose/docker-compose.yml up -d postgres redis qdrant temporal # Terminal 2: Run Orchestrator locally (gRPC 50052, admin 8081) cd go/orchestrator go run ./cmd/server # Terminal 3: Run Agent Core locally (gRPC 50051) cd ../../rust/agent-core cargo run # Terminal 4: Run LLM Service locally (HTTP 8000) cd ../../python/llm-service pip install -r requirements.txt python main.py # (Optional) Terminal 5: Run Gateway locally (REST 8080) cd ../../go/orchestrator/cmd/gateway go run . ``` See the [Architecture Overview](/en/architecture/overview) for system architecture details. # Quick Start Source: https://docs.shannon.run/en/quickstart/quickstart Submit your first task to Shannon in 5 minutes ## Your First Task Let's submit a simple task to Shannon and see it in action. This guide assumes you've completed the [installation](/en/quickstart/installation). ## Using the REST API The simplest way to interact with Shannon is through its REST API: ```bash theme={null} curl -X POST http://localhost:8080/api/v1/tasks \ -H "Content-Type: application/json" \ -d '{ "query": "Analyze the sentiment of: Shannon makes AI agents simple and reliable!" }' ``` You'll receive a response like: ```json theme={null} { "task_id": "task-dev-1730000000", "status": "TASK_STATUS_RUNNING", "message": "Task submitted", "created_at": "2024-10-22T10:30:00Z" } ``` ## Check Task Status Use the `task_id` to check the status: ```bash theme={null} curl http://localhost:8080/api/v1/tasks/task-dev-1730000000 ``` Response: ```json theme={null} { "task_id": "task-dev-1730000000", "status": "TASK_STATUS_COMPLETED", "response": { "result": "The sentiment is overwhelmingly positive..." }, "error": "", "query": "Analyze the sentiment of: Shannon makes AI agents simple and reliable!", "session_id": "...", "mode": "EXECUTION_MODE_STANDARD", "created_at": "2024-10-22T10:30:00Z", "updated_at": "2024-10-22T10:30:25Z" } ``` ## Stream Events in Real-Time Shannon supports Server-Sent Events (SSE) for real-time updates: ```bash theme={null} curl -N http://localhost:8080/api/v1/stream/sse?workflow_id=task-dev-1730000000 ``` You'll see events as they happen: ``` event: LLM_PROMPT data: {"message":"Analyzing sentiment..."} event: WORKFLOW_COMPLETED data: {"result":"The sentiment is positive..."} ``` ## Using the Python SDK For a better developer experience, use the Python SDK: ### Installation ```bash theme={null} pip install shannon-sdk ``` ### Submit and Monitor Tasks ```python theme={null} from shannon import ShannonClient # HTTP client (default: http://localhost:8080) client = ShannonClient() handle = client.submit_task( query="Analyze the sentiment of: Shannon makes AI agents simple!" ) print("Task submitted:", handle.task_id) # Wait for completion final = client.wait(handle.task_id, timeout=60) print("Result:", final.result) ``` ### Stream Events ```python theme={null} # Stream events in real-time for event in client.stream(handle.workflow_id): print(f"{event.type}: {event.message}") ``` ## Execution Modes Shannon auto-selects execution modes based on task complexity. You can override this by passing the `mode` parameter (`simple`, `standard`, `complex`, `supervisor`) in your task submission. ## Cost Control Budget controls are managed by Shannon. Explicit per‑request budget settings are not yet available via REST. ## Multi-Turn Conversations Shannon supports session-based conversations: ```python theme={null} session_id = "my-conversation-123" # First message handle1 = client.submit_task( query="What is the capital of France?", session_id=session_id ) # Follow-up message (Shannon remembers context) handle2 = client.submit_task( query="What's the population?", session_id=session_id ) # Shannon knows you're asking about Paris's population ``` ## Using the Desktop App For a graphical view of tasks and sessions, use the Shannon Desktop Application (Tauri-based client). See [Desktop Application](/en/quickstart/desktop-app) for installation and usage details. ## Example Use Cases Shannon automatically selects the appropriate execution mode and cognitive strategy based on query complexity. You don't need to specify these parameters. ### Sentiment Analysis ```python theme={null} client.submit_task( query="Analyze sentiment: The product exceeded my expectations!" ) ``` ### Research & Summarization ```python theme={null} client.submit_task( query="Research quantum computing breakthroughs in 2024 and create a summary" ) ``` ### Code Generation ```python theme={null} client.submit_task( query="Write a Python function to calculate Fibonacci numbers with memoization" ) ``` ### Complex Analysis with Context ```python theme={null} client.submit_task( query="Compare the pros and cons of microservices vs monolithic architecture", context={ "domain": "software_architecture", "depth": "comprehensive", "include_examples": True } ) ``` ## What's Next? Learn about agents, workflows, and cognitive patterns Explore all API endpoints and parameters Deep dive into SDK features and async usage Customize Shannon for your needs ## Troubleshooting Check the logs for errors: ```bash theme={null} docker compose logs orchestrator docker compose logs agent-core ``` Common causes: * Invalid LLM API key * LLM provider rate limiting * Network connectivity issues Shannon runs with authentication disabled by default (`GATEWAY_SKIP_AUTH=1`). If you've enabled auth, include an API key: ```bash theme={null} curl -H "X-API-Key: sk_test_123456" \ http://localhost:8080/api/v1/tasks ``` Budget limits are configured at the platform level via environment variables: ```bash theme={null} # In .env file MAX_TOKENS_PER_REQUEST=10000 MAX_COST_PER_REQUEST=0.50 # Restart services for changes to take effect docker compose restart ``` For per-task monitoring, check costs via the status endpoint or streaming events. # Troubleshooting Source: https://docs.shannon.run/en/quickstart/troubleshooting Common issues and solutions when using Shannon ## Quick Diagnostics Before diving into specific issues, run these quick checks: ```bash theme={null} # Check all services are running docker compose ps # View recent logs from all services docker compose logs --tail=50 # Check specific service health curl http://localhost:8080/health curl http://localhost:8000/health # LLM Service ``` ## Installation & Setup Issues ### Docker Compose Fails to Start **Symptoms**: * Services won't start * Exit code errors * Container crashes immediately **Common Causes**: **Check**: ```bash theme={null} docker info ``` **Solution**: ```bash theme={null} # macOS open -a Docker # Linux sudo systemctl start docker # Verify docker info ``` **Check which ports are in use**: ```bash theme={null} # Check all Shannon ports lsof -i :8080 # Gateway lsof -i :50051 # Agent Core lsof -i :50052 # Orchestrator lsof -i :8000 # LLM Service lsof -i :5432 # PostgreSQL lsof -i :6379 # Redis lsof -i :6333 # Qdrant lsof -i :7233 # Temporal ``` **Solution - Kill conflicting processes**: ```bash theme={null} # Find process using port lsof -ti :8080 # Kill the process (macOS/Linux) kill -9 $(lsof -ti :8080) ``` **Solution - Change Shannon ports**: Edit `docker-compose.yml` to use different ports: ```yaml theme={null} gateway: ports: - "8081:8080" # Use 8081 instead of 8080 ``` **Check Docker resources**: ```bash theme={null} docker system df docker stats ``` **Solution - Increase Docker resources**: * **macOS**: Docker Desktop → Preferences → Resources * RAM: Minimum 8GB (16GB recommended) * CPUs: Minimum 4 cores * Disk: Minimum 20GB free * **Linux**: Edit Docker daemon config ```bash theme={null} sudo nano /etc/docker/daemon.json ``` ```json theme={null} { "default-ulimits": { "nofile": { "Name": "nofile", "Hard": 64000, "Soft": 64000 } } } ``` **Error**: `WARNING: The OPENAI_API_KEY variable is not set` **Solution**: ```bash theme={null} # Create .env from template make setup # Or manually cp .env.example .env # Add your API keys echo "OPENAI_API_KEY=sk-..." >> .env echo "ANTHROPIC_API_KEY=sk-ant-..." >> .env ``` **Error**: `python_wasi/bin/python3.11: No such file or directory` **Solution**: ```bash theme={null} # Download and setup Python WASI (20MB) ./scripts/setup_python_wasi.sh # Verify installation ls -lh python_wasi/bin/python3.11 ``` ## API & Connection Issues ### 401 Unauthorized **Symptoms**: * HTTP 401 responses * "Unauthorized" error messages **Diagnosis**: ```bash theme={null} # Check if auth is enabled docker compose exec orchestrator env | grep GATEWAY_SKIP_AUTH ``` **Edit `.env`**: ```bash theme={null} GATEWAY_SKIP_AUTH=1 # 1 = disabled, 0 = enabled ``` **Restart**: ```bash theme={null} docker compose restart gateway ``` **Test**: ```bash theme={null} curl http://localhost:8080/api/v1/tasks # Should work without X-API-Key header ``` **Request with API key**: ```bash theme={null} curl -H "X-API-Key: sk_test_123456" \ http://localhost:8080/api/v1/tasks ``` **Python SDK**: ```python theme={null} from shannon import ShannonClient client = ShannonClient( base_url="http://localhost:8080", api_key="sk_test_123456" ) ``` ### Connection Refused / Service Unavailable **Symptoms**: * `connection refused` * `dial tcp: connect: connection refused` * Services not responding **Diagnosis**: ```bash theme={null} # Check service status docker compose ps # Check specific service logs docker compose logs orchestrator --tail=50 docker compose logs agent-core --tail=50 docker compose logs llm-service --tail=50 # Test endpoints curl http://localhost:8080/health curl http://localhost:50052 # Should fail - gRPC doesn't support HTTP GET ``` **Wait for all services to initialize**: ```bash theme={null} # Watch logs until services are ready docker compose logs -f # Look for these messages: # orchestrator: "gRPC server listening on :50052" # agent-core: "Server started on :50051" # llm-service: "Uvicorn running on http://0.0.0.0:8000" # gateway: "Gateway listening on :8080" ``` **Typical startup time**: 30-60 seconds **Check for crash errors**: ```bash theme={null} docker compose logs orchestrator | grep -i error docker compose logs orchestrator | grep -i fatal ``` **Restart crashed service**: ```bash theme={null} docker compose restart orchestrator docker compose restart agent-core docker compose restart llm-service ``` **Full reset if needed**: ```bash theme={null} docker compose down docker compose up -d ``` **Check PostgreSQL**: ```bash theme={null} docker compose logs postgres --tail=20 # Test connection docker compose exec postgres psql -U shannon -d shannon -c "SELECT 1;" ``` **Solution**: ```bash theme={null} # Restart database docker compose restart postgres # Wait for it to be ready docker compose exec postgres pg_isready -U shannon ``` ### Task Stuck in RUNNING or QUEUED State **Symptoms**: * Task never completes * Status remains RUNNING for hours * No progress updates **Diagnosis**: ```bash theme={null} # Check Temporal workflows docker compose logs temporal --tail=100 # Check orchestrator worker docker compose logs orchestrator | grep -i workflow # View task in Temporal UI open http://localhost:8088 ``` **Check LLM service logs**: ```bash theme={null} docker compose logs llm-service | grep -i "api key\|unauthorized\|quota" ``` **Solution**: ```bash theme={null} # Verify API keys in .env grep -E "OPENAI_API_KEY|ANTHROPIC_API_KEY" .env # Test API key curl https://api.openai.com/v1/models \ -H "Authorization: Bearer $OPENAI_API_KEY" # Update .env with valid key nano .env # Restart LLM service docker compose restart llm-service ``` **Restart Temporal workers**: ```bash theme={null} docker compose restart orchestrator # Check workflow in Temporal UI open http://localhost:8088 # Navigate to Workflows → Find your workflow → View execution history ``` **Force workflow termination** (last resort): ```bash theme={null} # In Temporal UI: Workflows → Select workflow → Terminate ``` **Check circuit breaker status**: ```bash theme={null} docker compose logs orchestrator | grep -i "circuit" ``` **Circuit breakers protect against cascading failures**: * LLM Service circuit breaker * Database circuit breaker * Redis circuit breaker **Solution - Wait for automatic recovery** (30-60 seconds) Or **restart services**: ```bash theme={null} docker compose restart orchestrator agent-core llm-service ``` ## Budget & Cost Issues ### Budget Exceeded Errors **Symptoms**: * `budget exceeded` error * Tasks fail with cost limit errors * HTTP 429 (Rate Limited) Payment Required **Diagnosis**: ```bash theme={null} # Check budget configuration docker compose exec orchestrator env | grep BUDGET docker compose exec orchestrator env | grep MAX_COST ``` **Edit `.env`**: ```bash theme={null} MAX_COST_PER_REQUEST=1.00 # Increase from 0.50 MAX_TOKENS_PER_REQUEST=20000 # Increase from 10000 ``` **Restart**: ```bash theme={null} docker compose restart orchestrator llm-service ``` Budgets are configured server-side via environment variables. The SDK does not accept per-request budget parameters. ```python theme={null} # Instead of advanced mode client.submit_task(query="...", # Mode auto-selected) # Advanced → Standard → Simple (cheapest) ``` **Cost comparison**: * **Simple**: 1 LLM call, \$0.01-0.05 * **Standard**: 3-5 LLM calls, \$0.05-0.20 * **Advanced**: 10+ LLM calls, \$0.20-1.00+ **⚠️ Warning**: Only for development/testing **Edit `.env`**: ```bash theme={null} LLM_DISABLE_BUDGETS=1 # Disable budget checks ``` **Restart**: ```bash theme={null} docker compose restart orchestrator llm-service ``` ## Performance Issues ### Slow Response Times **Symptoms**: * Tasks take 2-3x longer than expected * High latency * Timeouts **Diagnosis**: ```bash theme={null} # Check resource usage docker stats # Check for slow queries docker compose logs postgres | grep "duration:" # Check Redis latency docker compose exec redis redis-cli --latency # Check Qdrant performance curl http://localhost:6333/metrics ``` **Check resources**: ```bash theme={null} docker stats # Look for CPU > 80% or Memory near limit ``` **Increase Docker resources**: * macOS: Docker Desktop → Resources → increase RAM to 16GB, CPUs to 6 * Linux: More powerful machine or reduce concurrent workflows **Tune worker concurrency** in `.env`: ```bash theme={null} WORKER_ACT_CRITICAL=5 # Reduce from 10 WORKER_WF_CRITICAL=3 # Reduce from 5 TOOL_PARALLELISM=2 # Reduce from 5 ``` **First request is always slower** (10-30s) **Subsequent requests use caching**: * LLM response cache (Redis) * Session context cache * Tool result cache **Solution**: Warm up with a test request ```bash theme={null} curl -X POST http://localhost:8080/api/v1/tasks \ -H "Content-Type: application/json" \ -d '{"query": "Hello"}' ``` **Increase pool size** in `.env`: ```bash theme={null} DB_MAX_OPEN_CONNS=50 # Increase from 25 DB_MAX_IDLE_CONNS=10 # Increase from 5 ``` **Restart**: ```bash theme={null} docker compose restart orchestrator ``` ## Tokens > 0 but empty result **Symptoms**: * Database or logs show non‑zero completion tokens, but the final `result` text is empty. * Complex prompts return nothing while simple prompts work. **Cause**: * Some GPT‑5 chat responses return content as structured parts instead of a plain string. Older parsing could miss the text. This is fixed by routing GPT‑5 models via the Responses API and defensively normalizing content for chat responses. **Fix (Shannon ≥ 2025‑11‑05)**: * LLM Service routes GPT‑5 models to the Responses API and prefers `output_text` when available. * Chat providers normalize content by joining text parts when a list is returned. * If you upgraded from an older build, restart the LLM Service to clear cached empty responses. **Verify**: * Re‑run a long, multi‑paragraph prompt. `result` length should be > 0 and session history should include the assistant message. ### High Memory Usage **Symptoms**: * OOM (Out of Memory) errors * Container restarts * Swap usage high **Diagnosis**: ```bash theme={null} docker stats # Check session cache size docker compose logs orchestrator | grep "session.*cache" ``` **Edit `config/shannon.yaml`** or **set env vars**: ```bash theme={null} # Reduce session cache SESSION_CACHE_SIZE=5000 # From 10000 # Reduce history SESSION_MAX_HISTORY=250 # From 500 # Reduce LRU caches TOOL_CACHE_SIZE=1000 # From 5000 ``` **Restart**: ```bash theme={null} docker compose restart orchestrator agent-core ``` ## Data & State Issues ### Sessions Not Persisting **Symptoms**: * Session context lost between requests * Agent doesn't remember previous tasks **Diagnosis**: ```bash theme={null} # Check Redis connectivity docker compose exec orchestrator nc -zv redis 6379 # Check session data docker compose exec redis redis-cli KEYS "session:*" ``` **Check Redis status**: ```bash theme={null} docker compose ps redis docker compose logs redis --tail=20 ``` **Restart Redis**: ```bash theme={null} docker compose restart redis ``` **Test connection**: ```bash theme={null} docker compose exec redis redis-cli ping # Should return "PONG" ``` **Sessions expire after 30 days by default** **Increase TTL** in `.env`: ```bash theme={null} REDIS_TTL_SECONDS=7776000 # 90 days ``` **Check session expiry**: ```bash theme={null} docker compose exec redis redis-cli TTL "session:YOUR_SESSION_ID" # Returns seconds until expiry, or -1 for no expiry ``` **Provide a stable session\_id explicitly**: ```python theme={null} session_id = "user-123-conversation" handle1 = client.submit_task("Load data", session_id=session_id) handle2 = client.submit_task("Analyze data", session_id=session_id) ``` ### Database Migration Errors **Symptoms**: * Table doesn't exist errors * Column not found errors * Schema version mismatch **Solution**: ```bash theme={null} # Run migrations docker compose exec orchestrator make migrate # Or reset database (⚠️ DESTRUCTIVE) docker compose down -v # Remove volumes docker compose up -d ``` ## Debugging Tools ### Viewing Logs ```bash theme={null} # All services docker compose logs -f # Specific service docker compose logs -f orchestrator docker compose logs -f agent-core docker compose logs -f llm-service # Last N lines docker compose logs --tail=100 orchestrator # Search logs docker compose logs orchestrator | grep -i error docker compose logs orchestrator | grep "task_id=YOUR_TASK_ID" ``` ### Temporal UI **Access**: [http://localhost:8088](http://localhost:8088) **Features**: * View all workflows * See execution history * Replay failed workflows * Terminate stuck workflows * Time-travel debugging **Usage**: 1. Navigate to **Workflows** 2. Search by workflow ID (task ID) 3. View execution history to see where it failed 4. Check Activity logs for detailed errors ### Prometheus Metrics ```bash theme={null} # Orchestrator metrics curl http://localhost:2112/metrics # Agent Core metrics curl http://localhost:2113/metrics # LLM Service metrics curl http://localhost:8000/metrics ``` **Key metrics**: * `tasks_submitted_total` * `tasks_completed_total` * `tasks_failed_total` * `llm_requests_total` * `circuit_breaker_state` ### Real-time Monitoring For real-time views of task execution: * Use the Shannon Desktop App (Runs view and Run Details) for live event streams * Use Prometheus/Grafana for metrics once configured (see Monitoring concepts) ## Getting Help Detailed setup instructions Complete API reference Report bugs or request features ## Quick Reference Commands ```bash theme={null} # Health checks curl http://localhost:8080/health curl http://localhost:8000/health # Service status docker compose ps docker stats # Restart services docker compose restart orchestrator docker compose restart agent-core docker compose restart llm-service # View logs docker compose logs -f orchestrator # Full reset docker compose down -v docker compose up -d # Database access docker compose exec postgres psql -U shannon -d shannon # Redis CLI docker compose exec redis redis-cli # Check environment docker compose exec orchestrator env | grep -E "OPENAI|ANTHROPIC" ``` # Async/Await Usage Source: https://docs.shannon.run/en/sdk/python/async-usage Using Shannon Python SDK with asyncio Full async documentation coming soon. Basic patterns are shown below. ## Overview The Shannon Python SDK provides both synchronous and asynchronous clients. Use async operations for: * Concurrent task submission * Non-blocking event streaming * Parallel API calls * High-throughput applications ## AsyncShannonClient ### Basic Usage ```python theme={null} import asyncio from shannon import AsyncShannonClient async def main(): async with AsyncShannonClient(base_url="http://localhost:8080") as client: handle = await client.submit_task( query=( "Summarize the paragraph into 3 bullet points focusing on revenue trends. " "Output: Markdown list." ), model_tier="small", mode="standard", ) final = await client.wait(handle.task_id) print(final.result) asyncio.run(main()) ``` ## Concurrent Tasks Submit multiple tasks simultaneously: ```python theme={null} import asyncio from shannon import AsyncShannonClient async def main(): async with AsyncShannonClient() as client: # Submit multiple tasks concurrently tasks = [ client.submit_task(query="What is 5+5?"), client.submit_task(query="What is 10*2?"), client.submit_task(query="What is 100/4?"), ] # Wait for all submissions handles = await asyncio.gather(*tasks) # Get all results results = await asyncio.gather(*[client.wait(h.task_id) for h in handles]) for i, r in enumerate(results, 1): print(f"Task {i}: {r.result}") asyncio.run(main()) ``` ## Async Streaming Stream events without blocking. Tip: don’t await other client calls inside the `async for` loop — break out first, then await: ```python theme={null} import asyncio from shannon import AsyncShannonClient async def main(): async with AsyncShannonClient() as client: # Submit task h = await client.submit_task(query="What is the capital of France?") # Stream events async for e in client.stream(h.workflow_id): print(f"{e.type}: {e.message[:50]}") if e.type == "WORKFLOW_COMPLETED": break # Exit loop cleanly first # Now safe to await final = await client.wait(h.task_id) print(f"\nFinal result: {final.result}") asyncio.run(main()) ``` ## Timeout Handling ```python theme={null} async def with_timeout(): async with AsyncShannonClient() as client: try: # Submit with timeout handle = await asyncio.wait_for( client.submit_task( query=( "Extract the top 3 insights from the paragraph and return a Markdown bullet list." ) ), timeout=30.0 ) # Wait for completion with timeout result = await asyncio.wait_for( client.wait(handle.task_id), timeout=300.0 ) except asyncio.TimeoutError: print("Operation timed out") ``` ## Background Tasks Run tasks in background while doing other work: ```python theme={null} import asyncio from shannon import AsyncShannonClient async def main(): async with AsyncShannonClient() as client: # Submit task handle = await client.submit_task(query="Generate a ~500-word report on quarterly revenue drivers and risks. Output: Markdown.") # Start background wait (doesn't block) task = asyncio.create_task(client.wait(handle.task_id)) # Do other work while task runs print("Processing in background...") await asyncio.sleep(2) # Your other async work here # Check status if not task.done(): print("Still processing...") # Get result when needed result = await task print(f"Result: {result.result}") asyncio.run(main()) ``` ## Error Handling ```python theme={null} import asyncio from shannon import AsyncShannonClient, ConnectionError, TaskTimeoutError async def main(): async with AsyncShannonClient() as client: try: handle = await client.submit_task(query="What is AI?") result = await client.wait(handle.task_id) print(f"Result: {result.result}") except ConnectionError: print("Failed to connect to Shannon") except TaskTimeoutError: print("Task timed out") except Exception as e: print(f"Unexpected error: {e}") asyncio.run(main()) ``` ## Integration with Web Frameworks ### FastAPI Example ```python theme={null} from contextlib import asynccontextmanager from fastapi import FastAPI from shannon import AsyncShannonClient @asynccontextmanager async def lifespan(app: FastAPI): # Startup: create client app.state.client = AsyncShannonClient() yield # Shutdown: close client await app.state.client.close() app = FastAPI(lifespan=lifespan) @app.post("/analyze") async def analyze(query: str): client = app.state.client handle = await client.submit_task(query=query) result = await client.wait(handle.task_id) return {"result": result.result} ``` To test: ```bash theme={null} # Install FastAPI and uvicorn pip install fastapi uvicorn # Run server uvicorn your_file:app --reload # Test endpoint curl -X POST "http://127.0.0.1:8000/analyze?query=What+is+AI?" ``` ## Best Practices 1. **Use context managers** (`async with`) for proper cleanup 2. **Handle timeouts** for long-running operations 3. **Implement retry logic** for network failures 4. **Use gather()** for concurrent operations 5. **Stream events** for real-time updates ## Next Steps More code examples Robust error handling # CLI Reference Source: https://docs.shannon.run/en/sdk/python/cli Complete reference for the Shannon CLI - submit tasks, stream events, manage sessions, schedules, and more from the terminal ## Installation ```bash theme={"dark"} pip install shannon-sdk ``` This installs the `shannon` CLI command. Verify with: ```bash theme={"dark"} shannon --help ``` From source (development): ```bash theme={"dark"} git clone https://github.com/Kocoro-lab/Shannon.git cd Shannon/clients/python pip install -e . ``` ## Connection ### Local (self-hosted) ```bash theme={"dark"} shannon --base-url http://localhost:8080 submit "Hello world" --wait ``` ### Shannon Cloud Register at [https://shannon.run](https://shannon.run) to get an API key, then: ```bash theme={"dark"} shannon --base-url http://localhost:8080 --api-key YOUR_API_KEY \ submit "Hello world" --wait ``` ### Environment Variables Set these to avoid passing flags on every call: ```bash theme={"dark"} export SHANNON_BASE_URL=http://localhost:8080 export SHANNON_API_KEY=sk_your_key_here # export SHANNON_BEARER_TOKEN=eyJ... # alternative to API key ``` Then simply: ```bash theme={"dark"} shannon submit "Hello world" --wait ``` | Variable | Default | Description | | ---------------------- | ----------------------- | ------------------------------------------ | | `SHANNON_BASE_URL` | `http://localhost:8080` | Gateway URL | | `SHANNON_API_KEY` | (none) | API key auth (sent as `X-API-Key` header) | | `SHANNON_BEARER_TOKEN` | (none) | Bearer token auth (alternative to API key) | ## Global Options These go before the command name: ```bash theme={"dark"} shannon [--base-url URL] [--api-key KEY] [--bearer-token TOKEN] COMMAND [OPTIONS] ``` *** ## Tasks ### submit - Submit a task ```bash theme={"dark"} shannon submit QUERY [OPTIONS] ``` **Arguments:** | Argument | Required | Description | | -------- | -------- | --------------------- | | `QUERY` | Yes | The task query string | **Options:** | Flag | Type | Description | | ------------------------- | --------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------ | | `--wait` | flag | Block until task completes, print result | | `--session-id` | string | Attach task to a session | | `--mode` | `simple` \| `standard` \| `complex` \| `supervisor` | Execution mode hint | | `--model-tier` | `small` \| `medium` \| `large` | Model tier selection | | `--model-override` | string | Specific model (e.g. `gpt-5-nano-2025-08-07`) | | `--provider-override` | string | Force provider: `openai`, `anthropic`, `google`, `groq`, `xai`, `deepseek`, `qwen`, `zai`, `ollama`, `mistral`, `cohere` | | `--force-research` | flag | Force ResearchWorkflow with citations | | `--research-strategy` | `quick` \| `standard` \| `deep` \| `academic` | Research strategy preset | | `--max-iterations` | int (1-50) | Override max research iterations | | `--max-concurrent-agents` | int (1-20) | Override max concurrent agents | | `--enable-verification` | flag | Enable claim verification | | `--disable-verification` | flag | Disable claim verification | | `--enable-citations` | flag | Enable citation collection | | `--disable-citations` | flag | Disable citation collection | | `--swarm` | flag | Force swarm multi-agent workflow | | `--idempotency-key` | string | Deduplicate submissions | | `--traceparent` | string | W3C traceparent for distributed tracing | **Examples:** ```bash theme={"dark"} # Simple task shannon submit "What is 2+2?" --wait # Research with citations shannon submit "Latest advances in quantum computing" \ --force-research --research-strategy deep --wait # Cost-optimized with small model shannon submit "Summarize this text" --model-tier small --mode simple --wait # Specific model and provider shannon submit "Analyze trends" --model-override gpt-5-nano-2025-08-07 --provider-override openai --wait # Multi-agent swarm shannon submit "Compare top 3 cloud providers for AI workloads" --swarm --wait # Attach to a session shannon submit "Follow-up question" --session-id my-session --wait ``` **Output:** ``` Task submitted: Task ID: task-abc123 Workflow ID: workflow-xyz789 Waiting for completion... Result: 4 ``` ### status - Get task status ```bash theme={"dark"} shannon status TASK_ID ``` **Example:** ```bash theme={"dark"} shannon status task-abc123 ``` **Output:** ``` Task: task-abc123 Status: COMPLETED Progress: 100.0% Result: The answer is 4. ``` ### cancel - Cancel a task ```bash theme={"dark"} shannon cancel TASK_ID [--reason REASON] ``` **Example:** ```bash theme={"dark"} shannon cancel task-abc123 --reason "No longer needed" ``` ### pause - Pause a task Pauses at the next workflow checkpoint (not immediate). ```bash theme={"dark"} shannon pause TASK_ID [--reason REASON] ``` **Example:** ```bash theme={"dark"} shannon pause task-abc123 --reason "Review intermediate results" ``` ### resume - Resume a paused task ```bash theme={"dark"} shannon resume TASK_ID [--reason REASON] ``` **Example:** ```bash theme={"dark"} shannon resume task-abc123 --reason "Continue after review" ``` ### control-state - Get pause/cancel state ```bash theme={"dark"} shannon control-state TASK_ID ``` **Output:** ``` Task: task-abc123 Paused: True Cancelled: False Paused at: 2025-01-15T10:30:00 Pause reason: Review intermediate results ``` *** ## Streaming ### stream - Stream task events via SSE ```bash theme={"dark"} shannon stream WORKFLOW_ID [OPTIONS] ``` | Flag | Type | Description | | --------------- | ------ | --------------------------------------- | | `--types` | string | Comma-separated event types to filter | | `--traceparent` | string | W3C traceparent for distributed tracing | **Event types:** `WORKFLOW_STARTED`, `WORKFLOW_COMPLETED`, `LLM_PROMPT`, `LLM_PARTIAL`, `LLM_OUTPUT`, `TOOL_INVOKED`, `TOOL_OBSERVATION`, `APPROVAL_REQUESTED`, `APPROVAL_DECISION`, `ERROR_OCCURRED` **Examples:** ```bash theme={"dark"} # Stream all events shannon stream workflow-xyz789 # Stream only LLM output and completion shannon stream workflow-xyz789 --types LLM_OUTPUT,WORKFLOW_COMPLETED ``` **Output:** ``` Streaming events for workflow: workflow-xyz789 ------------------------------------------------------------ 10:30:01 WORKFLOW_STARTED: Task started 10:30:02 [agent-1] LLM_OUTPUT: The answer is 4. 10:30:03 WORKFLOW_COMPLETED: Task completed successfully ``` *** ## HITL Review (Human-in-the-Loop) ### review-get - Get review state ```bash theme={"dark"} shannon review-get WORKFLOW_ID ``` **Output:** ``` Status: reviewing Round: 2 Version: 3 Current plan: 1. Research topic 2. Analyze data 3. Write report Query: Research AI trends Conversation (2 messages): [system] (2025-01-15T10:00:00): Initial plan generated [user] (2025-01-15T10:05:00): Add error handling section ``` ### review-feedback - Submit feedback ```bash theme={"dark"} shannon review-feedback WORKFLOW_ID MESSAGE [--version N] ``` The `--version` flag enables optimistic concurrency control. **Example:** ```bash theme={"dark"} shannon review-feedback workflow-xyz789 "Add more detail on pricing" --version 3 ``` ### review-approve - Approve review plan ```bash theme={"dark"} shannon review-approve WORKFLOW_ID [--version N] ``` **Example:** ```bash theme={"dark"} shannon review-approve workflow-xyz789 --version 4 ``` ### approve - Approve/reject pending approval request ```bash theme={"dark"} shannon approve APPROVAL_ID WORKFLOW_ID [--approve | --reject] [--feedback TEXT] ``` `--approve` is the default. Use `--reject` to reject. **Examples:** ```bash theme={"dark"} # Approve shannon approve approval-001 workflow-xyz789 --feedback "Looks good" # Reject shannon approve approval-001 workflow-xyz789 --reject --feedback "Needs revision" ``` *** ## Sessions ### session-list - List sessions ```bash theme={"dark"} shannon session-list [--limit N] [--offset N] ``` | Flag | Default | Description | | ---------- | ------- | ---------------------- | | `--limit` | 50 | Max sessions to return | | `--offset` | 0 | Pagination offset | **Example:** ```bash theme={"dark"} shannon session-list --limit 10 ``` ### session-get - Get session details ```bash theme={"dark"} shannon session-get SESSION_ID [--no-history] ``` **Example:** ```bash theme={"dark"} shannon session-get my-session shannon session-get my-session --no-history ``` ### session-title - Update session title ```bash theme={"dark"} shannon session-title SESSION_ID TITLE ``` **Example:** ```bash theme={"dark"} shannon session-title my-session "Q1 Research Project" ``` ### session-delete - Delete a session ```bash theme={"dark"} shannon session-delete SESSION_ID ``` *** ## Schedules ### schedule-create - Create a recurring task ```bash theme={"dark"} shannon schedule-create NAME CRON QUERY [OPTIONS] ``` **Arguments:** | Argument | Required | Description | | -------- | -------- | ------------------------------ | | `NAME` | Yes | Schedule name | | `CRON` | Yes | Cron expression | | `QUERY` | Yes | Task query to execute each run | **Options:** | Flag | Default | Description | | --------------------- | ------- | --------------------------------------------- | | `--description` | (none) | Schedule description | | `--timezone` | `UTC` | Timezone for cron evaluation | | `--force-research` | off | Enable research mode | | `--research-strategy` | (none) | `quick` \| `standard` \| `deep` \| `academic` | | `--budget` | (none) | Max budget per run in USD | | `--timeout` | (none) | Timeout per run in seconds | **Cron expression reference:** ``` MIN HOUR DAY MONTH WEEKDAY 0 9 * * 1-5 = Weekdays at 9:00 AM 0 */4 * * * = Every 4 hours 0 0 * * 1 = Every Monday at midnight 30 8 1 * * = First of month at 8:30 AM ``` **Examples:** ```bash theme={"dark"} # Daily research briefing shannon schedule-create "Daily AI News" "0 9 * * *" "Latest AI news summary" \ --timezone Asia/Tokyo --force-research --research-strategy quick # Weekly deep analysis shannon schedule-create "Weekly Market Report" "0 0 * * 1" \ "Comprehensive market analysis for the past week" \ --force-research --research-strategy deep --budget 5.0 ``` ### schedule-list - List schedules ```bash theme={"dark"} shannon schedule-list [--page N] [--page-size N] [--status ACTIVE|PAUSED] ``` **Example:** ```bash theme={"dark"} shannon schedule-list --status ACTIVE ``` **Output:** ``` Total: 3 sched-001 Daily AI News 0 9 * * * ACTIVE sched-002 Weekly Report 0 0 * * 1 ACTIVE sched-003 Hourly Check 0 * * * * PAUSED ``` ### schedule-get - Get schedule details ```bash theme={"dark"} shannon schedule-get SCHEDULE_ID ``` ### schedule-update - Update a schedule ```bash theme={"dark"} shannon schedule-update SCHEDULE_ID [OPTIONS] ``` | Flag | Description | | ----------------- | ----------------------------- | | `--name` | New name | | `--description` | New description | | `--cron` | New cron expression | | `--timezone` | New timezone | | `--query` | New task query | | `--budget` | New max budget per run (USD) | | `--timeout` | New timeout per run (seconds) | | `--clear-context` | Clear task context | **Example:** ```bash theme={"dark"} shannon schedule-update sched-001 --cron "0 10 * * *" --query "Updated news query" ``` ### schedule-pause - Pause a schedule ```bash theme={"dark"} shannon schedule-pause SCHEDULE_ID [--reason REASON] ``` ### schedule-resume - Resume a schedule ```bash theme={"dark"} shannon schedule-resume SCHEDULE_ID [--reason REASON] ``` ### schedule-delete - Delete a schedule ```bash theme={"dark"} shannon schedule-delete SCHEDULE_ID ``` ### schedule-runs - View execution history ```bash theme={"dark"} shannon schedule-runs SCHEDULE_ID [--page N] [--page-size N] ``` | Flag | Default | Description | | ------------- | ------- | -------------- | | `--page` | 1 | Page number | | `--page-size` | 10 | Items per page | **Output:** ``` Total runs: 42 2025-01-15T09:00:00 COMPLETED 1523 tokens $0.0032 2025-01-14T09:00:00 COMPLETED 1891 tokens $0.0041 2025-01-13T09:00:00 FAILED 0 tokens - ``` *** ## Tools ### tools-list - List available tools ```bash theme={"dark"} shannon tools-list [--category CATEGORY] ``` **Example:** ```bash theme={"dark"} shannon tools-list ``` **Output:** ``` Name Description -------------------------------------------------------------------------------- web_search Search the web for information calculator Evaluate mathematical expressions ``` ### tool-get - Get tool details ```bash theme={"dark"} shannon tool-get NAME ``` **Example:** ```bash theme={"dark"} shannon tool-get web_search ``` **Output:** ``` Tool: web_search Description: Search the web for information Category: research Parameters: { "query": { "type": "string", "description": "Search query" } } ``` ### tool-exec - Execute a tool directly ```bash theme={"dark"} shannon tool-exec NAME [--arguments JSON] [--session-id ID] ``` | Flag | Type | Description | | -------------- | ----------- | ----------------------------------------------- | | `--arguments` | JSON string | Tool arguments as a JSON object (default: `{}`) | | `--session-id` | string | Optional session ID for tool context | **Example:** ```bash theme={"dark"} shannon tool-exec web_search --arguments '{"query": "Shannon AI"}' ``` **Output:** ``` Success: True Text: Found 5 results for "Shannon AI" Output: { "results": [...] } Execution time (ms): 342 ``` *** ## Agents ### agents-list - List deterministic agents ```bash theme={"dark"} shannon agents-list ``` **Output:** ``` ID Category Tool Name ------------------------------------------------------------------------------------------ agent-001 analysis data_analyzer Data Analysis Agent agent-002 coding code_review Code Review Agent ``` ### agent-get - Get agent details ```bash theme={"dark"} shannon agent-get AGENT_ID ``` **Example:** ```bash theme={"dark"} shannon agent-get agent-001 ``` **Output:** ``` Agent: agent-001 Name: Data Analysis Agent Category: analysis Tool: data_analyzer Description: Analyzes structured data and produces reports Input schema: { "query": { "type": "string" } } ``` ### agent-exec - Execute a deterministic agent ```bash theme={"dark"} shannon agent-exec AGENT_ID --input JSON [--session-id ID] [--stream] ``` | Flag | Type | Description | | -------------- | ----------- | -------------------------------------------- | | `--input` | JSON string | Agent input as a JSON object (default: `{}`) | | `--session-id` | string | Optional session ID | | `--stream` | flag | Request streamed agent execution | **Example:** ```bash theme={"dark"} shannon agent-exec agent-001 --input '{"query": "Analyze Q4 trends"}' ``` **Output:** ``` Task ID: task-abc123 Workflow ID: workflow-xyz789 Agent ID: agent-001 Status: RUNNING ``` ### swarm-message - Send message to running swarm ```bash theme={"dark"} shannon swarm-message WORKFLOW_ID MESSAGE ``` **Example:** ```bash theme={"dark"} shannon swarm-message workflow-xyz789 "Focus on cost analysis" ``` **Output:** ``` Success: True Status: ACCEPTED ``` *** ## Files ### session-files - List session workspace files ```bash theme={"dark"} shannon session-files SESSION_ID [--path SUBDIR] ``` | Flag | Type | Description | | -------- | ------ | ------------------------------- | | `--path` | string | Optional workspace subdirectory | **Example:** ```bash theme={"dark"} shannon session-files my-session ``` **Output:** ``` Type Size Path -------------------------------------------------------------------------------- file 1234 report.md file 567 data.json dir 0 images/ ``` ### session-file-get - Download a workspace file ```bash theme={"dark"} shannon session-file-get SESSION_ID PATH ``` **Example:** ```bash theme={"dark"} shannon session-file-get my-session report.md ``` ### memory-files - List user memory files ```bash theme={"dark"} shannon memory-files ``` **Output:** ``` Type Size Path -------------------------------------------------------------------------------- file 2048 profile.md file 512 preferences.json ``` ### memory-file-get - Download a memory file ```bash theme={"dark"} shannon memory-file-get PATH ``` **Example:** ```bash theme={"dark"} shannon memory-file-get profile.md ``` *** ## Skills ### skills-list - List available skills ```bash theme={"dark"} shannon skills-list [--category CATEGORY] ``` **Example:** ```bash theme={"dark"} shannon skills-list shannon skills-list --category research ``` **Output:** ``` Name Version Category Description -------------------------------------------------------------------------------- web_search 1.0 research Search the web for information code_analysis 1.0 coding Analyze code structure ``` ### skill-get - Get skill details ```bash theme={"dark"} shannon skill-get NAME ``` **Example:** ```bash theme={"dark"} shannon skill-get web_search ``` ### skill-versions - List skill versions ```bash theme={"dark"} shannon skill-versions NAME ``` **Example:** ```bash theme={"dark"} shannon skill-versions web_search ``` *** ## Common Workflows ### Submit and stream results ```bash theme={"dark"} # Submit task, capture workflow ID, then stream shannon submit "Research quantum computing breakthroughs" --force-research --research-strategy deep # Output: Workflow ID: workflow-xyz789 shannon stream workflow-xyz789 --types LLM_OUTPUT,WORKFLOW_COMPLETED ``` ### HITL review loop ```bash theme={"dark"} # 1. Submit task that triggers review shannon submit "Create deployment plan for production" --wait # 2. Check review state shannon review-get workflow-xyz789 # 3. Provide feedback shannon review-feedback workflow-xyz789 "Add rollback procedure" # 4. Approve when satisfied shannon review-approve workflow-xyz789 ``` ### Schedule with monitoring ```bash theme={"dark"} # Create schedule shannon schedule-create "Daily Report" "0 9 * * 1-5" \ "Generate daily business report" --timezone America/New_York # Check runs shannon schedule-runs sched-001 # Pause during holidays shannon schedule-pause sched-001 --reason "Holiday break" # Resume shannon schedule-resume sched-001 ``` *** ## Exit Codes | Code | Meaning | | ---- | -------------------------------------------------- | | 0 | Success | | 1 | Error (task failure, API error, invalid arguments) | # Error Handling Source: https://docs.shannon.run/en/sdk/python/error-handling Handle errors gracefully in the Shannon Python SDK Error handling documentation is being expanded. Core patterns are shown below. ## Overview The Shannon Python SDK provides comprehensive error handling to help you build robust applications. All SDK exceptions inherit from `ShannonError`. ## Exception Hierarchy ```python theme={null} ShannonError # Base exception ├── ConnectionError # Network/connection issues (SDK-specific) ├── AuthenticationError # API key/auth problems ├── PermissionDeniedError # Forbidden / authorization failure ├── ValidationError # Invalid parameters ├── RateLimitError # Too many requests / throttled ├── ServerError # Upstream 5xx server error ├── TaskNotFoundError # Task doesn't exist ├── TaskTimeoutError # Task exceeded timeout ├── TaskCancelledError # Task was cancelled ├── SessionNotFoundError # Session doesn't exist ├── SessionExpiredError # Session expired ├── TemplateError # Template/route errors └── TemplateNotFoundError # Template not found ``` Budget and Task Failure Handling: Budget exceeded and task failures are not exceptions. For failures, check `status.status`. For token usage and cost totals, use `list_tasks()` and read `total_token_usage` from the returned task summaries. ## Basic Error Handling ### Try-Catch Pattern ```python theme={null} from shannon import ( ShannonClient, ShannonError, AuthenticationError, PermissionDeniedError, ConnectionError, RateLimitError, ServerError, TaskTimeoutError, TaskStatusEnum, ) client = ShannonClient(base_url="http://localhost:8080") try: handle = client.submit_task(query="Analyze this data") status = client.wait(handle.task_id, timeout=120) if status.status == TaskStatusEnum.FAILED: print(f"Task failed: {status.error_message}") else: print("Result:", status.result) except ConnectionError: print("Could not connect to Shannon server") except AuthenticationError: print("Invalid API credentials") except PermissionDeniedError: print("Forbidden: insufficient permissions") except RateLimitError: print("Rate limited: slow down or add backoff") except ServerError: print("Server error (5xx): try again later") except TaskTimeoutError: print("Task exceeded timeout limit") except ShannonError as e: print(f"Shannon error: {e}") except Exception as e: print(f"Unexpected error: {e}") ``` ## Specific Error Types ### Connection Errors Handle network and connection issues: ```python theme={null} import time from shannon import ShannonClient, ConnectionError def connect_with_retry(max_retries=3): client = ShannonClient() for attempt in range(max_retries): try: # Test connection with a simple task handle = client.submit_task(query="ping") return client except ConnectionError as e: if attempt < max_retries - 1: wait_time = 2 ** attempt # Exponential backoff print(f"Connection failed, retrying in {wait_time}s...") time.sleep(wait_time) else: print(f"Failed to connect after {max_retries} attempts") raise ``` ### Check Cost and Failures Monitor failures from status and get usage totals via `list_tasks()`: ```python theme={null} from shannon import ShannonClient, TaskStatusEnum client = ShannonClient() handle = client.submit_task(query="Analyze data") status = client.wait(handle.task_id) # Failure check (TaskStatus returns enum) if status.status == TaskStatusEnum.FAILED: print(f"Failed with error: {status.error_message}") # Usage and cost totals (from task listings) tasks, _ = client.list_tasks(limit=50) summary = next((t for t in tasks if t.task_id == handle.task_id), None) usage = summary.total_token_usage if summary else None if usage: print(f"tokens={usage.total_tokens} prompt={usage.prompt_tokens} completion={usage.completion_tokens} cost=${usage.cost_usd:.6f}") # Note: status on TaskSummary is a string if summary and summary.status == "FAILED": print("Summary indicates failure") ``` ### Timeout Errors Handle long-running operations: ```python theme={null} import asyncio from shannon import AsyncShannonClient, TaskTimeoutError async def with_timeout_handling(): async with AsyncShannonClient() as client: try: # asyncio.TimeoutError comes from wait_for wrappers (client-side) handle = await asyncio.wait_for( client.submit_task(query="Complex analysis"), timeout=10.0, ) # TaskTimeoutError comes from Shannon if the task exceeded its own timeout result = await asyncio.wait_for( client.wait(handle.task_id), timeout=60.0, ) return result except asyncio.TimeoutError: print("Operation timed out (client-side asyncio timeout)") return None except TaskTimeoutError: print("Task timed out (Shannon reported timeout)") return None ``` ### Rate Limiting Handle API rate limits gracefully: ```python theme={null} from shannon import ShannonClient, RateLimitError import time def handle_rate_limits(queries): client = ShannonClient() results = [] for query in queries: backoff = 1 while True: try: handle = client.submit_task(query=query) result = client.wait(handle.task_id) results.append(result) break # Success, move to next except RateLimitError: # Rate limited print(f"Rate limited, retrying in {backoff}s...") time.sleep(backoff) backoff = min(backoff * 2, 30) return results ``` Import clarity: `from shannon import ConnectionError` refers to the SDK's exception (not Python's built‑in `ConnectionError`). Use `RateLimitError` for rate limiting and `ConnectionError` for network issues. Advanced patterns such as backoff and circuit breakers are reference implementations — validate in your environment. ## Validation Errors Handle invalid parameters: ```python theme={null} from shannon import ShannonClient, ValidationError def validate_and_submit(query, session_id=None): client = ShannonClient() try: return client.submit_task(query=query, session_id=session_id) except ValidationError as e: print(f"Invalid parameters: {e}") return None ``` ## Task Failure Handling Handle task execution failures: ```python theme={null} from shannon import ShannonClient, TaskTimeoutError, TaskStatusEnum def handle_task_failure(query: str): client = ShannonClient() try: handle = client.submit_task(query=query) status = client.wait(handle.task_id, timeout=120) if status.status == TaskStatusEnum.FAILED: print(f"Task failed: {status.error_message}") return None return status except TaskTimeoutError: print("Task timed out; consider increasing timeout or simplifying the request.") return None ``` ## Logging Errors Implement comprehensive error logging: ```python theme={null} import logging from shannon import ShannonClient, ShannonError # Configure logging logging.basicConfig( level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s' ) logger = logging.getLogger('shannon') def logged_task_submission(query): client = ShannonClient() try: logger.info(f"Submitting task: {query[:50]}...") handle = client.submit_task(query=query) logger.info(f"Task submitted: task_id={handle.task_id}") result = client.wait(handle.task_id) logger.info("Task completed successfully") return result.result except ShannonError as e: logger.error(f"Shannon error: {e}", exc_info=True) raise except Exception as e: logger.critical(f"Unexpected error: {e}", exc_info=True) raise ``` ## Circuit Breaker Pattern Implement circuit breaker for resilience: ```python theme={null} class CircuitBreaker: def __init__(self, failure_threshold=5, reset_timeout=60): self.failure_count = 0 self.failure_threshold = failure_threshold self.reset_timeout = reset_timeout self.last_failure_time = None self.state = "closed" # closed, open, half-open def call(self, func, *args, **kwargs): if self.state == "open": if time.time() - self.last_failure_time > self.reset_timeout: self.state = "half-open" else: raise Exception("Circuit breaker is open") try: result = func(*args, **kwargs) if self.state == "half-open": self.state = "closed" self.failure_count = 0 return result except Exception as e: self.failure_count += 1 self.last_failure_time = time.time() if self.failure_count >= self.failure_threshold: self.state = "open" raise # Usage breaker = CircuitBreaker() client = ShannonClient() try: result = breaker.call( client.submit_task, query="Analyze data" ) except Exception as e: print(f"Service unavailable: {e}") ``` ## Best Practices 1. **Always catch specific exceptions** before generic ones 2. **Implement retry logic** with exponential backoff 3. **Log errors** for debugging and monitoring 4. **Provide fallback options** for critical operations 5. **Set reasonable timeouts** to avoid hanging 6. **Validate inputs** before submission 7. **Use circuit breakers** for external dependencies ## Next Steps See error handling in action Async error patterns ## Complete Retry Example ```python theme={null} #!/usr/bin/env python3 """Error handling with retry logic example""" import time from shannon import ShannonClient, ConnectionError, TaskTimeoutError, ShannonError def robust_task_submission(query: str, max_retries: int = 3): """ Submit a task with retry logic and comprehensive error handling. Args: query: The task query max_retries: Maximum retry attempts for recoverable errors Returns: TaskStatus object or None if all attempts fail """ client = ShannonClient() for attempt in range(max_retries): try: print(f"\n[Attempt {attempt + 1}/{max_retries}] Submitting task...") handle = client.submit_task(query=query) print(f"✅ Task submitted: {handle.task_id}") print("⏳ Waiting for result (300s timeout)...") result = client.wait(handle.task_id, timeout=300) print("✅ Task completed successfully") return result except ConnectionError as e: wait_time = 2 ** attempt # 1s, 2s, 4s print(f"❌ Connection error: {e}") if attempt < max_retries - 1: print(f"⏳ Retrying in {wait_time} seconds...") time.sleep(wait_time) else: print(f"❌ Max retries ({max_retries}) reached. Giving up.") raise except TaskTimeoutError as e: print(f"❌ Task timeout error: {e}") print("⚠️ Task exceeded 300 second limit. Not retrying.") raise except ShannonError as e: print(f"❌ Shannon API error: {e}") print("⚠️ API error encountered. Not retrying.") raise except Exception as e: print(f"❌ Unexpected error: {type(e).__name__}: {e}") raise return None def main(): print("=" * 60) print("Error Handling Examples") print("=" * 60) # Example 1: Normal execution print("\n📝 Example 1: Normal execution") print("-" * 60) try: result = robust_task_submission("What is 2+2?") if result: print(f"\nFinal result: {result.result}") except Exception as e: print(f"\n⚠️ Failed: {e}") # Example 2: Timeout scenario (fail fast) print("\n\n📝 Example 2: Timeout scenario") print("-" * 60) print("Note: This demonstrates timeout handling") try: result = robust_task_submission( "Analyze this complex dataset... (simulated long task)" ) if result: print(f"\nFinal result: {result.result}") except TaskTimeoutError: print("\n⚠️ Task timeout - no retry attempted (expected behavior)") except Exception as e: print(f"\n⚠️ Error: {e}") # Example 3: Simple try/catch pattern print("\n\n📝 Example 3: Simple try-catch pattern") print("-" * 60) client = ShannonClient() try: handle = client.submit_task(query="What is the capital of France?") result = client.wait(handle.task_id) print(f"✅ Success: {result.result}") except ConnectionError: print("❌ Network issue - check Shannon service") except TaskTimeoutError: print("❌ Task took too long") except ShannonError as e: print(f"❌ API error: {e}") except Exception as e: print(f"❌ Unexpected error: {e}") print("\n" + "=" * 60) print("✅ Error handling examples completed!") print("=" * 60) if __name__ == "__main__": main() ``` To run: ```bash theme={null} # Ensure Shannon is running make dev # Run the example python3 error_handling.py ``` When to use retry logic: * Network instability * Transient service issues * Production systems requiring resilience When NOT to retry: * Timeouts (task is too complex) * API errors (invalid parameters) * Authentication failures # Code Examples Source: https://docs.shannon.run/en/sdk/python/examples Practical examples using the Shannon Python SDK This examples collection is growing. More use cases will be added regularly. ## Basic Examples ### Model Selection ```python theme={"dark"} from shannon import ShannonClient client = ShannonClient(base_url="http://localhost:8080") handle = client.submit_task( "Summarize the paragraph into three bullet points focusing on revenue trends. Output: Markdown list.", model_tier="small", # model_override="gpt-5-nano-2025-08-07", # provider_override="openai", mode="simple", ) print(client.wait(handle.task_id).result) ``` ### Simple Question Answering ```python theme={"dark"} from shannon import ShannonClient client = ShannonClient(base_url="http://localhost:8080") # Ask a simple question handle = client.submit_task(query="What is the capital of France?") final = client.wait(handle.task_id) print(f"Answer: {final.result}") ``` ### Data Analysis ```python theme={"dark"} from shannon import ShannonClient client = ShannonClient() # Realistic data analysis handle = client.submit_task( query=( "Given the sales data below, calculate:\n" "1. Total revenue per product\n" "2. Month-over-month growth percentage\n" "3. Return top 3 by total revenue\n\n" "Data:\n" "ProductA: Jan=$10000, Feb=$12000\n" "ProductB: Jan=$8000, Feb=$9500\n" "ProductC: Jan=$15000, Feb=$14000\n" "ProductD: Jan=$5000, Feb=$6000\n\n" "Format: JSON array [{product, revenue, mom_growth_pct}]" ) ) result = client.wait(handle.task_id) print(result.result) ``` ## Advanced Examples ### Multi-Step Workflow ```python theme={"dark"} """Multi-step workflow example with Shannon SDK""" from shannon import ShannonClient def main(): client = ShannonClient() session_id = "quarterly-analysis-demo" print("=" * 60) print("Multi-Step Workflow Example") print("=" * 60) # Step 1: Initial data query print("\n[Step 1] Loading Q4 data...") h1 = client.submit_task( query="What is 1000 + 500? This represents Q4 revenue (1000) and expenses (500).", session_id=session_id ) result1 = client.wait(h1.task_id) print(f"Result: {result1.result}") # Step 2: Analysis based on previous context print("\n[Step 2] Analyzing trends...") h2 = client.submit_task( query="Based on the Q4 numbers you just calculated, what is the profit margin percentage?", session_id=session_id ) result2 = client.wait(h2.task_id) print(f"Result: {result2.result}") # Step 3: Summary using all previous context print("\n[Step 3] Creating executive summary...") h3 = client.submit_task( query="Summarize the Q4 financial analysis in 2 sentences.", session_id=session_id ) result3 = client.wait(h3.task_id) print(f"Result: {result3.result}") print("\n" + "=" * 60) print("✅ Multi-step workflow completed!") print(f"Session ID: {session_id}") print("=" * 60) if __name__ == "__main__": main() ``` ### Parallel Processing ```python theme={"dark"} """Parallel processing example with Shannon SDK""" import asyncio from shannon import AsyncShannonClient async def main(): print("=" * 60) print("Parallel Processing Example") print("=" * 60) async with AsyncShannonClient() as client: # Topics to process in parallel topics = ["AI", "Quantum Computing", "Biotechnology"] print(f"\n[Step 1] Submitting {len(topics)} tasks in parallel...") # Submit all tasks concurrently (non-blocking) tasks = [ client.submit_task( query=f"Give a one-sentence summary of {topic} and its main application." ) for topic in topics ] # Wait for all submissions to complete handles = await asyncio.gather(*tasks) print(f"✅ All {len(handles)} tasks submitted") print("\n[Step 2] Waiting for all results...") # Wait for all tasks to complete in parallel results = await asyncio.gather( *[client.wait(h.task_id) for h in handles] ) print(f"✅ All {len(results)} tasks completed") print("\n[Step 3] Results:") print("-" * 60) # Display results for topic, result in zip(topics, results): print(f"\n📌 {topic}:") print(f" {result.result}") # Safely access metadata if available metadata = [] if hasattr(result, 'metadata') and result.metadata: model = result.metadata.get('model_used') or result.metadata.get('model') if model: metadata.append(f"Model: {model}") tokens = result.metadata.get('total_tokens') if tokens: metadata.append(f"Tokens: {tokens}") if metadata: print(f" ({', '.join(metadata)})") print("\n" + "=" * 60) print("✅ Parallel processing completed!") print(f"Processed {len(topics)} topics concurrently") print("=" * 60) if __name__ == "__main__": asyncio.run(main()) ``` ### Streaming with filters ```python theme={"dark"} """Streaming with event filters example""" from shannon import ShannonClient, EventType def main(): print("=" * 60) print("Streaming with Filters Example") print("=" * 60) client = ShannonClient() print("\n[Step 1] Submitting task...") handle = client.submit_task( query=( "Compare AWS, Azure, and GCP pricing models in 3 sentences. " "Stream the output as you generate it." ) ) print(f"✅ Task submitted: {handle.task_id}") print("\n[Step 2] Streaming with filters [LLM_OUTPUT, WORKFLOW_COMPLETED]...") print("-" * 60) event_count = 0 llm_outputs = [] # Stream only LLM_OUTPUT and WORKFLOW_COMPLETED events for event in client.stream( handle.workflow_id, types=[EventType.LLM_OUTPUT, EventType.WORKFLOW_COMPLETED] ): event_count += 1 print(f"[{event_count}] {event.type}") if event.type == EventType.LLM_OUTPUT: print(f" Content: {event.message[:80]}...") llm_outputs.append(event.message) if event.type == EventType.WORKFLOW_COMPLETED: print(f" Status: {event.message}") break print("\n" + "-" * 60) print(f"✅ Received {event_count} filtered events") print(f"✅ Captured {len(llm_outputs)} LLM outputs") print("\n[Step 3] Full result:") print("-" * 60) final = client.wait(handle.task_id) print(final.result) print("\n" + "=" * 60) print("✅ Streaming with filters completed!") print("=" * 60) if __name__ == "__main__": main() ``` See the full list of event types in the [Event Types Catalog](/en/api/event-types). Available event types * `LLM_OUTPUT` — LLM responses * `LLM_PARTIAL` — streaming tokens * `WORKFLOW_COMPLETED` — task finished * `AGENT_THINKING` — agent reasoning * `AGENT_STARTED` — agent begins work * `AGENT_COMPLETED` — agent finishes * `PROGRESS` — progress updates * `ERROR_OCCURRED` — errors Common filters * Only final outputs: `types=[EventType.LLM_OUTPUT, EventType.WORKFLOW_COMPLETED]` * Real-time streaming: `types=[EventType.LLM_PARTIAL, EventType.WORKFLOW_COMPLETED]` * Monitor agents: `types=[EventType.AGENT_STARTED, EventType.AGENT_COMPLETED]` * Everything (no filter): `types=None` (or omit the parameter) ## Task Control Examples ### Pause and Resume Long-Running Tasks ```python theme={"dark"} """Task control (pause/resume) example""" from shannon import ShannonClient import time def main(): print("=" * 60) print("Task Control (Pause/Resume) Example") print("=" * 60) client = ShannonClient(base_url="http://localhost:8080") # Submit a long-running research task print("\n[Step 1] Submitting long-running task...") handle = client.submit_task( query="Research the latest developments in quantum computing and AI integration. Provide detailed analysis." ) print(f"✅ Task submitted: {handle.task_id}") # Let it run for a bit print("\n[Step 2] Letting task run for 5 seconds...") time.sleep(5) # Pause the task print("\n[Step 3] Pausing task...") success = client.pause_task(handle.task_id, reason="Review intermediate results") if success: print("✅ Pause signal sent") # Check control state print("\n[Step 4] Checking control state...") state = client.get_control_state(handle.task_id) print(f"Is paused: {state.is_paused}") print(f"Is cancelled: {state.is_cancelled}") if state.paused_at: print(f"Paused at: {state.paused_at}") print(f"Pause reason: {state.pause_reason}") print(f"Paused by: {state.paused_by}") # Wait a bit before resuming print("\n[Step 5] Waiting 3 seconds before resume...") time.sleep(3) # Resume the task print("\n[Step 6] Resuming task...") success = client.resume_task(handle.task_id, reason="Continue after review") if success: print("✅ Resume signal sent") # Wait for completion print("\n[Step 7] Waiting for task completion...") result = client.wait(handle.task_id) print(f"\n✅ Task completed!") print(f"Status: {result.status}") print(f"\nResult preview: {result.result[:200]}...") print("\n" + "=" * 60) print("✅ Task control example completed!") print("=" * 60) if __name__ == "__main__": main() ``` ### Error Handling for Control Signals ```python theme={"dark"} """Error handling for pause/resume operations""" from shannon import ShannonClient from shannon.errors import ShannonError def main(): client = ShannonClient() task_id = "task-123" # Attempt to pause try: client.pause_task(task_id, reason="Review needed") print("✅ Task paused successfully") except ShannonError as e: # Handle specific error cases if "cannot pause completed task" in str(e): print("⚠️ Task already completed, cannot pause") elif "task is already paused" in str(e): print("⚠️ Task is already paused") elif "Task not found" in str(e): print("❌ Task not found") else: print(f"❌ Error: {e}") # Attempt to resume try: client.resume_task(task_id, reason="Continue execution") print("✅ Task resumed successfully") except ShannonError as e: if "task is not paused" in str(e): print("⚠️ Task is not paused, cannot resume") elif "cannot resume completed task" in str(e): print("⚠️ Task already completed, cannot resume") else: print(f"❌ Error: {e}") if __name__ == "__main__": main() ``` ## Swarm Mode ### Force Multi-Agent Execution ```python theme={"dark"} """Swarm mode example with Shannon SDK""" from shannon import ShannonClient client = ShannonClient(base_url="http://localhost:8080") # Submit with swarm mode to coordinate multiple agents handle = client.submit_task( query="Research and compare AWS, Azure, and GCP for ML workloads. Cover pricing, GPU availability, and managed services.", force_swarm=True, ) result = client.wait(handle.task_id) print(result.result) # Check model and usage info if result.usage: print(f"Total tokens: {result.usage.get('total_tokens')}") print(f"Cost: ${result.usage.get('cost_usd', 0):.6f}") ``` ## Human-in-the-Loop Review Examples ### Interactive Review Workflow ```python theme={"dark"} """Human-in-the-loop review example""" from shannon import ShannonClient client = ShannonClient(base_url="http://localhost:8080") # Assume a workflow that requires review is already running workflow_id = "workflow-123" # Check current review state state = client.get_review_state(workflow_id) print(f"Review status: {state.status}") print(f"Round: {state.round}, Version: {state.version}") if state.current_plan: print(f"Current plan:\n{state.current_plan}") # Display conversation history for r in state.rounds: print(f" [{r.role}] {r.message}") # Provide feedback if still in review if state.status == "reviewing": updated = client.submit_review_feedback( workflow_id, "Please add input validation and unit tests.", version=state.version, ) print(f"Feedback submitted. Now at round {updated.round}") # Approve the updated plan result = client.approve_review(workflow_id, version=updated.version) print(f"Approved: {result['status']}") ``` ### Review with Optimistic Concurrency ```python theme={"dark"} """Handle version conflicts during review""" from shannon import ShannonClient, ValidationError client = ShannonClient() workflow_id = "workflow-456" try: state = client.get_review_state(workflow_id) updated = client.submit_review_feedback( workflow_id, "Looks good, minor style changes needed.", version=state.version, ) client.approve_review(workflow_id, version=updated.version) except ValidationError as e: if "409" in str(e.code): print("Version conflict: another user modified the review. Refresh and retry.") else: raise ``` ## Skills Examples ### Browse and Inspect Skills ```python theme={"dark"} """Skills browsing example""" from shannon import ShannonClient client = ShannonClient(base_url="http://localhost:8080") # List all available skills skills = client.list_skills() print(f"Found {len(skills)} skills:\n") for s in skills: flag = " [DANGEROUS]" if s.dangerous else "" status = "enabled" if s.enabled else "disabled" print(f" {s.name} v{s.version} ({s.category}) - {status}{flag}") print(f" {s.description}") # Filter by category print("\n--- Coding Skills ---") coding = client.list_skills(category="coding") for s in coding: print(f" {s.name}: {s.description}") # Get detailed info detail = client.get_skill("web_search") print(f"\nSkill detail: {detail.name} v{detail.version}") print(f" Author: {detail.author}") print(f" Requires tools: {detail.requires_tools}") print(f" Requires role: {detail.requires_role}") print(f" Budget max: {detail.budget_max}") # Get version history versions = client.get_skill_versions("web_search") print(f"\nVersions of web_search:") for v in versions: print(f" v{v.version} - {v.description}") ``` ## Schedule Management Examples ### Create and Manage Scheduled Tasks ```python theme={"dark"} """Schedule management example""" from shannon import ShannonClient client = ShannonClient(base_url="http://localhost:8080") # Create a daily research task at 9am UTC on weekdays result = client.create_schedule( name="Daily AI News", cron_expression="0 9 * * 1-5", task_query="Summarize the latest AI research news", task_context={"force_research": "true", "research_strategy": "quick"}, timezone="UTC", max_budget_per_run_usd=0.50, ) schedule_id = result["schedule_id"] print(f"Schedule created: {schedule_id}") # List all schedules schedules, total = client.list_schedules() print(f"\n{total} schedules:") for s in schedules: print(f" {s.name}: {s.cron_expression} ({s.status})") print(f" Runs: {s.total_runs} total, {s.successful_runs} success, {s.failed_runs} failed") # Get schedule details sched = client.get_schedule(schedule_id) print(f"\nSchedule: {sched.name}") print(f" Next run: {sched.next_run_at}") print(f" Last run: {sched.last_run_at}") # View execution history runs, _ = client.get_schedule_runs(schedule_id) for run in runs: print(f" {run.triggered_at}: {run.status} - ${run.total_cost_usd:.4f}") # Pause schedule (e.g. for maintenance) client.pause_schedule(schedule_id, reason="Maintenance window") # Resume schedule client.resume_schedule(schedule_id) # Update schedule configuration client.update_schedule( schedule_id, cron_expression="0 8 * * 1-5", # Change to 8am max_budget_per_run_usd=0.75, ) # Delete schedule when no longer needed # client.delete_schedule(schedule_id) ``` ## Direct Tool Execution ### List, Inspect, and Execute Tools ```python theme={"dark"} """Direct tool execution example""" import json from shannon import ShannonClient def main(): client = ShannonClient(base_url="http://localhost:8080") try: # List available tools tools = client.list_tools() print(f"Found {len(tools)} tools") for tool in tools[:10]: print(f" - {tool.name}: {tool.description}") if not tools: return # Get detailed info for a specific tool target = tools[0].name detail = client.get_tool(target) print(f"\nTool: {detail.name}") print(f"Category: {detail.category}") print(f"Description: {detail.description}") print(f"Parameters: {json.dumps(detail.parameters, indent=2)}") # Execute the tool result = client.execute_tool(target, arguments={"expression": "6 * 7"}) print(f"\nSuccess: {result.success}") if result.text: print(f"Text: {result.text}") if result.output is not None: print(f"Output: {result.output}") if result.error: print(f"Error: {result.error}") finally: client.close() if __name__ == "__main__": main() ``` ## Deterministic Agent Execution ### Execute Agents and Send Swarm Follow-ups ```python theme={"dark"} """Deterministic agent execution example""" import json from shannon import ShannonClient def main(): client = ShannonClient(base_url="http://localhost:8080") try: # List available agents agents = client.list_agents() print(f"Agents: {len(agents)}") for agent in agents[:10]: print(f" - {agent.id}: {agent.name} ({agent.tool})") if not agents: return # Get agent details agent = client.get_agent(agents[0].id) print(f"\nAgent: {agent.id}") print(f"Name: {agent.name}") print(f"Description: {agent.description}") print(f"Input schema: {json.dumps(agent.input_schema, indent=2)}") # Execute the agent execution = client.execute_agent( agent.id, {"query": "Analyze recent trends"}, ) print(f"\nTask ID: {execution.task_id}") print(f"Workflow ID: {execution.workflow_id}") print(f"Status: {execution.status}") # Wait for result final = execution.wait(timeout=120) print(f"\nResult: {final.result}") # Optional: send follow-up message to a swarm workflow # result = client.send_swarm_message(execution.workflow_id, "Focus on cost analysis") # print(f"Swarm follow-up accepted: {result.success}") finally: client.close() if __name__ == "__main__": main() ``` ## OpenAI-Compatible Chat Completions ### Non-Streaming and Streaming ```python theme={"dark"} """OpenAI-compatible chat completions example""" from shannon import ShannonClient, OpenAIChatMessage def main(): client = ShannonClient(base_url="http://localhost:8080") try: # List available models models = client.list_openai_models() print(f"OpenAI-compatible models: {len(models)}") for entry in models[:10]: print(f" - {entry.id}") messages = [OpenAIChatMessage(role="user", content="Summarize Shannon's strengths in two sentences.")] # Non-streaming completion completion = client.create_chat_completion( messages, model="shannon-chat", ) print(f"\nResponse: {completion.choices[0].message.content}") if completion.usage: print(f"Tokens: {completion.usage.total_tokens}") # Streaming completion print("\nStreaming response:") for chunk in client.stream_chat_completion( [OpenAIChatMessage(role="user", content="Tell me a story in 3 sentences.")], model="shannon-chat", include_usage=True, ): if chunk.choices and chunk.choices[0].delta and chunk.choices[0].delta.content: print(chunk.choices[0].delta.content, end="", flush=True) print() finally: client.close() if __name__ == "__main__": main() ``` ## Session Files and Memory ### List and Download Workspace and Memory Files ```python theme={"dark"} """Session files and memory example""" from shannon import ShannonClient def main(): client = ShannonClient(base_url="http://localhost:8080") session_id = "my-session" try: # List memory files memory_files = client.list_memory_files() print(f"Memory files: {len(memory_files)}") for entry in memory_files[:10]: print(f" - {entry.path} ({entry.size_bytes} bytes)") # Download a memory file if memory_files: downloaded = client.download_memory_file(memory_files[0].path) print(f"\nMemory file content preview:") print(downloaded.content[:400]) # List session workspace files session_files = client.list_session_files(session_id) print(f"\nSession files for {session_id}: {len(session_files)}") for entry in session_files[:10]: print(f" - {entry.path} ({entry.size_bytes} bytes)") # Download a session file if session_files: downloaded = client.download_session_file(session_id, session_files[0].path) print(f"\nSession file content preview:") print(downloaded.content[:400]) finally: client.close() if __name__ == "__main__": main() ``` ## Error Handling Examples See the dedicated page for complete patterns and a full retry-logic example: [Error Handling](/en/sdk/python/error-handling). ## Next Steps Async/await patterns Handle errors properly # Python SDK Installation Source: https://docs.shannon.run/en/sdk/python/installation Install and configure the Shannon Python SDK ## Install ```bash theme={null} pip install shannon-sdk ``` From source (dev): ```bash theme={null} git clone https://github.com/Kocoro-lab/Shannon.git cd Shannon/clients/python pip install -e . ``` Optional (WebSocket streaming support): ```bash theme={null} pip install websockets ``` ## Quick Verify ```python theme={null} from shannon import ShannonClient client = ShannonClient(base_url="http://localhost:8080") print("✓ SDK ready") ``` ## Auth ```python theme={null} from shannon import ShannonClient client = ShannonClient( base_url="http://localhost:8080", api_key="sk_test_123" ) ``` Env-driven: ```python theme={null} import os from shannon import ShannonClient client = ShannonClient( base_url=os.getenv("SHANNON_BASE_URL", "http://localhost:8080"), api_key=os.getenv("SHANNON_API_KEY"), ) ``` ## Client Options | Param | Type | Default | Notes | | ----------------- | ----- | ----------------------- | ----------------------------------- | | `base_url` | str | `http://localhost:8080` | Gateway HTTP URL | | `api_key` | str | None | Sent as `X-API-Key` | | `bearer_token` | str | None | Alternative `Authorization: Bearer` | | `default_timeout` | float | `30.0` | Seconds | ## Async Client ```python theme={null} import asyncio from shannon import AsyncShannonClient async def main(): async with AsyncShannonClient(base_url="http://localhost:8080") as client: h = await client.submit_task("Analyze data") s = await client.wait(h.task_id) print(s.result) asyncio.run(main()) ``` ## CLI ```bash theme={null} python -m shannon.cli --base-url http://localhost:8080 submit "What is 2+2?" --wait ``` # Python SDK Quick Start Source: https://docs.shannon.run/en/sdk/python/quickstart Get started with the Shannon Python SDK in minutes ## Install ```bash theme={"dark"} pip install shannon-sdk ``` ## First Task ```python theme={"dark"} from shannon import ShannonClient client = ShannonClient(base_url="http://localhost:8080") handle = client.submit_task("Return the capital of France as a single word.") final = client.wait(handle.task_id, timeout=60) print(final.result) ``` ## Streaming (SSE) ```python theme={"dark"} from shannon import ShannonClient, EventType client = ShannonClient() h = client.submit_task( "Draft a ~120-word executive summary of three recent quantum computing milestones. Output as a Markdown paragraph." ) for e in client.stream(h.workflow_id, types=[EventType.LLM_OUTPUT, EventType.WORKFLOW_COMPLETED]): print(e.type, e.message) if e.type == EventType.WORKFLOW_COMPLETED: break ``` ## Model/Mode Selection (optional) Control cost and provider choice when submitting: ```python theme={"dark"} handle = client.submit_task( "Summarize the paragraph into three bullet points focusing on revenue trends. Output: Markdown list.", model_tier="small", # small|medium|large # model_override="gpt-5-nano-2025-08-07", # provider_override="openai", # or anthropic, google, ... mode="simple", # simple|standard|complex|supervisor ) print(client.wait(handle.task_id).result) ``` ## Swarm Mode Force multi-agent swarm execution with `force_swarm`: ```python theme={"dark"} handle = client.submit_task( "Research and compare the top 3 cloud providers for AI workloads.", force_swarm=True, ) result = client.wait(handle.task_id) print(result.result) ``` ## Async Version ```python theme={"dark"} import asyncio from shannon import AsyncShannonClient async def main(): async with AsyncShannonClient(base_url="http://localhost:8080") as client: h = await client.submit_task( "Classify sentiment (positive|neutral|negative) for: 'I love this product!'. Return JSON {label, score}." ) f = await client.wait(h.task_id) print(f.result) asyncio.run(main()) ``` ## Task Control (Pause/Resume) Control long-running tasks with pause, resume, and state checking: ```python theme={"dark"} from shannon import ShannonClient client = ShannonClient(base_url="http://localhost:8080") # Submit a long-running research task handle = client.submit_task("Research AI trends in 2025 with detailed analysis") # Pause the task at the next checkpoint client.pause_task(handle.task_id, reason="Review intermediate results") # Check control state state = client.get_control_state(handle.task_id) print(f"Is paused: {state.is_paused}") print(f"Paused at: {state.paused_at}") print(f"Pause reason: {state.pause_reason}") # Resume execution client.resume_task(handle.task_id, reason="Continue after review") # Wait for completion result = client.wait(handle.task_id) print(result.result) ``` **CLI Usage:** ```bash theme={"dark"} # Pause a running task shannon pause task-123 --reason "Hold for review" # Check control state shannon control-state task-123 # Resume the task shannon resume task-123 --reason "Continue" ``` ## Human-in-the-Loop Review Interact with workflows that require human review before proceeding: ```python theme={"dark"} from shannon import ShannonClient client = ShannonClient(base_url="http://localhost:8080") # Get the current review state for a workflow state = client.get_review_state("workflow-123") print(f"Status: {state.status}") # "reviewing" or "approved" print(f"Round: {state.round}") print(f"Current plan: {state.current_plan}") # View conversation rounds for r in state.rounds: print(f" [{r.role}] {r.message}") # Submit feedback to refine the plan updated = client.submit_review_feedback( "workflow-123", "Please add error handling for network failures.", version=state.version, # optimistic concurrency ) print(f"Updated round: {updated.round}") # Approve the plan to let the workflow proceed result = client.approve_review("workflow-123", version=updated.version) print(f"Review status: {result['status']}") ``` **CLI Usage:** ```bash theme={"dark"} # Get review state shannon review-get workflow-123 # Submit feedback shannon review-feedback workflow-123 "Add error handling" # Approve shannon review-approve workflow-123 ``` ## Skills List and inspect available skills: ```python theme={"dark"} from shannon import ShannonClient client = ShannonClient(base_url="http://localhost:8080") # List all skills skills = client.list_skills() for s in skills: print(f"{s.name} v{s.version} [{s.category}] - {s.description}") # Filter by category coding_skills = client.list_skills(category="coding") # Get detailed information about a specific skill detail = client.get_skill("web_search") print(f"Author: {detail.author}") print(f"Requires tools: {detail.requires_tools}") print(f"Content: {detail.content}") # Get all versions of a skill versions = client.get_skill_versions("web_search") for v in versions: print(f" {v.name} v{v.version}") ``` **CLI Usage:** ```bash theme={"dark"} # List skills shannon skills-list # Get skill details shannon skill-get web_search # Get skill versions shannon skill-versions web_search ``` ## Tool API List, inspect, and execute tools directly: ```python theme={"dark"} from shannon import ShannonClient client = ShannonClient(base_url="http://localhost:8080") # List available tools tools = client.list_tools() for tool in tools: print(f"{tool.name}: {tool.description}") # Get tool details detail = client.get_tool("web_search") print(detail.parameters) # Execute a tool directly result = client.execute_tool("web_search", arguments={"query": "Shannon AI"}) print(result.output) ``` **CLI Usage:** ```bash theme={"dark"} # List tools shannon tools-list # Get tool details shannon tool-get web_search # Execute a tool shannon tool-exec web_search --arguments '{"query": "Shannon AI"}' ``` ## Agents API List, inspect, and execute deterministic agents: ```python theme={"dark"} from shannon import ShannonClient client = ShannonClient(base_url="http://localhost:8080") # List deterministic agents agents = client.list_agents() # Get agent details agent = client.get_agent(agents[0].id) print(f"{agent.name}: {agent.description}") # Execute an agent execution = client.execute_agent(agent.id, {"query": "test input"}) status = execution.wait(timeout=60) print(status.result) # Send follow-up message to a running swarm result = client.send_swarm_message(workflow_id, "Please focus on cost analysis") ``` **CLI Usage:** ```bash theme={"dark"} # List agents shannon agents-list # Get agent details shannon agent-get AGENT_ID # Execute an agent shannon agent-exec AGENT_ID --input '{"query": "test input"}' # Send follow-up to swarm shannon swarm-message WORKFLOW_ID "Please focus on cost analysis" ``` ## OpenAI-Compatible Use the OpenAI-compatible chat completions endpoint: ```python theme={"dark"} from shannon import ShannonClient client = ShannonClient(base_url="http://localhost:8080") # List available models models = client.list_openai_models() # Chat completion (non-streaming) completion = client.create_chat_completion( messages=[{"role": "user", "content": "Hello!"}], model="gpt-4o-mini", ) print(completion.choices[0].message.content) # Streaming chat completion for chunk in client.stream_chat_completion( messages=[{"role": "user", "content": "Tell me a story"}] ): if chunk.choices and chunk.choices[0].delta: print(chunk.choices[0].delta.content, end="") ``` ## File Access Access session workspace files and user memory files: ```python theme={"dark"} from shannon import ShannonClient client = ShannonClient(base_url="http://localhost:8080") # List workspace files from a session files = client.list_session_files(session_id) for f in files: print(f"{f.name} ({f.size_bytes} bytes)") # Download a file content = client.download_session_file(session_id, "report.md") print(content.content) # List and download memory files memory_files = client.list_memory_files() memory = client.download_memory_file("profile.md") ``` **CLI Usage:** ```bash theme={"dark"} # List session workspace files shannon session-files SESSION_ID # Download a workspace file shannon session-file-get SESSION_ID report.md # List memory files shannon memory-files # Download a memory file shannon memory-file-get profile.md ``` ## CLI ```bash theme={"dark"} # Submit with model selection python -m shannon.cli --base-url http://localhost:8080 \ submit "Summarize" --model-tier small --mode simple --wait # Submit with swarm mode python -m shannon.cli --base-url http://localhost:8080 \ submit "Research cloud providers" --swarm --wait ``` # Event Streaming Source: https://docs.shannon.run/en/sdk/python/streaming Real-time event streaming with the Python SDK ## Overview Use SSE for simple, reliable real-time updates. Optional WebSocket is available for environments that require WS. ## SSE (recommended) ```python theme={null} from shannon import ShannonClient, EventType client = ShannonClient() h = client.submit_task( "Draft a ~120-word executive summary of Q4 revenue drivers and risks. Stream partial output; end the last line with 'Conclusion:'." ) for e in client.stream(h.workflow_id, types=[EventType.LLM_OUTPUT, EventType.WORKFLOW_COMPLETED]): print(f"{e.type}: {e.message}") if e.type == EventType.WORKFLOW_COMPLETED: break ``` Resume from last event: ```python theme={null} last_id = None for e in client.stream(h.workflow_id, last_event_id=last_id): # ... last_id = e.id ``` Notes: * `last_event_id` accepts either a Redis stream ID (preferred) or a numeric sequence. The SDK exposes `event.id` which chooses `stream_id` when present or falls back to `seq`. ## WebSocket (optional) Requires `pip install websockets`. ```python theme={null} from shannon import ShannonClient client = ShannonClient() h = client.submit_task( "Produce a ~120-word brief on recent renewable energy policy changes and their impact on costs. Stream partial output." ) for e in client.stream_ws(h.workflow_id): print(e.type, e.message) if e.type == "WORKFLOW_COMPLETED": break ``` ## Common Event Types * WORKFLOW\_STARTED / WORKFLOW\_COMPLETED * LLM\_PROMPT / LLM\_PARTIAL / LLM\_OUTPUT * TOOL\_INVOKED / TOOL\_OBSERVATION * APPROVAL\_REQUESTED / APPROVAL\_DECISION * ERROR\_OCCURRED Tip: Filter using `EventType` enums or raw strings. # Adding Custom Tools Source: https://docs.shannon.run/en/tutorials/custom-tools Complete guide to extending Shannon with MCP, OpenAPI, and built-in Python tools ## Overview Shannon supports three ways to add custom tools: | Method | Best For | Code Changes | Restart Required | | ------------------ | ------------------------------------------- | ------------ | ---------------- | | **MCP Tools** | External HTTP APIs, rapid prototyping | None | ✅ Service only | | **OpenAPI Tools** | REST APIs with OpenAPI specs | None | ✅ Service only | | **Built-in Tools** | Complex logic, database access, performance | Python code | ✅ Service only | No Proto/Rust/Go changes required - all tools use generic containers for maximum flexibility. **Key Features:** * ✅ Dynamic registration via API or YAML config * ✅ Built-in rate limiting and circuit breakers * ✅ Domain allowlisting for security * ✅ Cost tracking and budget enforcement ## Quick Start: Adding MCP Tools MCP (Model Context Protocol) tools let you integrate any HTTP endpoint as a Shannon tool with zero code changes. Edit `config/shannon.yaml` under the `mcp_tools` section: ```yaml theme={null} mcp_tools: weather_forecast: enabled: true url: "https://api.weather.com/v1/forecast" func_name: "get_weather" description: "Get weather forecast for a location" category: "data" cost_per_use: 0.001 parameters: - name: "location" type: "string" required: true description: "City name or coordinates" - name: "units" type: "string" required: false description: "Temperature units (celsius/fahrenheit)" enum: ["celsius", "fahrenheit"] headers: X-API-Key: "${WEATHER_API_KEY}" # Resolves from .env ``` Required Fields: * `enabled`: Set to `true` to activate * `url`: HTTP endpoint (must be POST, accepts JSON) * `func_name`: Internal function name * `description`: Clear description shown to LLM * `category`: Tool category (e.g., `search`, `data`, `analytics`, `code`) * `cost_per_use`: Estimated cost in USD * `parameters`: Array of parameter definitions **For Development (permissive):** Add to `.env`: ```bash theme={null} MCP_ALLOWED_DOMAINS=* # Wildcard - allows all domains ``` **For Production (recommended):** ```bash theme={null} MCP_ALLOWED_DOMAINS=localhost,127.0.0.1,api.weather.com,api.example.com ``` Add your API key to `.env`: ```bash theme={null} # MCP Tool API Keys WEATHER_API_KEY=your_api_key_here STOCK_API_KEY=your_stock_key_here ``` You must **recreate** the service (not just restart): ```bash theme={null} docker compose -f deploy/compose/docker-compose.yml up -d --force-recreate llm-service ``` Wait for health check: ```bash theme={null} docker inspect shannon-llm-service-1 --format='{{.State.Health.Status}}' ``` Check logs: ```bash theme={null} docker compose logs llm-service | grep "Loaded MCP tool" ``` List tools via API: ```bash theme={null} curl http://localhost:8000/tools/list | jq . ``` Get tool schema: ```bash theme={null} curl http://localhost:8000/tools/weather_forecast/schema | jq . ``` **Direct execution:** ```bash theme={null} curl -X POST http://localhost:8000/tools/execute \ -H "Content-Type: application/json" \ -d '{ "tool_name": "weather_forecast", "parameters": {"location": "Tokyo", "units": "celsius"} }' ``` **Via workflow:** ```bash theme={null} SESSION_ID="test-$(date +%s)" ./scripts/submit_task.sh "What's the weather forecast for Tokyo?" ``` ### MCP Request Convention Shannon sends POST requests in this format: ```json theme={null} { "function": "get_weather", "args": { "location": "Tokyo", "units": "celsius" } } ``` Your endpoint should return JSON: ```json theme={null} { "temperature": 18, "condition": "Cloudy", "humidity": 65 } ``` ### Alternative: Runtime API Registration For development/testing only (tools lost on restart): ```bash theme={null} # Set admin token in .env MCP_REGISTER_TOKEN=your_secret_token # Register tool curl -X POST http://localhost:8000/tools/mcp/register \ -H "Authorization: Bearer your_secret_token" \ -H "Content-Type: application/json" \ -d '{ "name": "weather_forecast", "url": "https://api.weather.com/v1/forecast", "func_name": "get_weather", "description": "Get weather forecast", "category": "data", "parameters": [ {"name": "location", "type": "string", "required": true}, {"name": "units", "type": "string", "enum": ["celsius", "fahrenheit"]} ] }' ``` ## Adding OpenAPI Tools For REST APIs with OpenAPI 3.x specifications, Shannon can automatically generate tools. For domain-specific APIs requiring custom transformations, see the [Vendor Adapter Pattern](#vendor-adapter-pattern) section below or the comprehensive [Vendor Adapters Guide](/en/tutorials/vendor-adapters). ### Features **Supported:** * ✅ OpenAPI 3.0 and 3.1 specs * ✅ URL-based or inline spec loading * ✅ JSON request/response bodies * ✅ Path and query parameters * ✅ Bearer, API Key (header/query), Basic auth * ✅ Operation filtering by operationId or tags * ✅ Circuit breaker (5 failures → 60s cooldown) * ✅ Retry logic with exponential backoff (2 retries, configurable via `OPENAPI_RETRIES`) * ✅ Configurable rate limits and timeouts * ✅ Relative server URLs (resolved against spec URL) * ✅ Basic `$ref` resolution (local references to `#/components/schemas/*`) ### Limitations Shannon OpenAPI integration is production-ready for \~70% of REST APIs (JSON-based with simple auth). The following features are **not yet supported:** * Cannot upload files or binary data * **Workaround**: Use base64-encoded files in JSON body * **Affected**: Image generation, file processing, document upload APIs * No OAuth 2.0 flows (Authorization Code, Client Credentials) * Can only use Bearer tokens (manually obtained) * **Affected**: Google APIs, GitHub, Slack, Twitter, etc. * **Workaround**: Manually obtain OAuth token and use `bearer` auth\_type * No `style`, `explode`, or `deepObject` serialization * Only basic path/query parameter substitution * **Affected**: APIs with complex array/object query parameters * No remote `$ref` resolution (e.g., `https://example.com/schemas/Pet.json`) * Only local refs (`#/components/...`) supported * **Workaround**: Merge external schemas into single spec file * No `allOf`, `oneOf`, `anyOf` support * Only basic type mapping * **Affected**: APIs with polymorphic types or complex validation * No `application/x-www-form-urlencoded` content type * Only JSON request bodies supported **What Works Well:** * ✅ Simple REST APIs with JSON request/response * ✅ APIs with Bearer/API Key/Basic authentication * ✅ Read-heavy operations (GET requests) * ✅ Well-structured specs with local `$ref` references * ✅ Path and query parameters (primitives) For specs with relative server URLs (e.g., `/api/v3`), you must provide the spec via `spec_url` (not `spec_inline`) so Shannon can resolve the full base URL. ### Quick Start Edit `config/shannon.yaml` under `openapi_tools`: ```yaml theme={null} openapi_tools: petstore: enabled: true spec_url: "https://petstore3.swagger.io/api/v3/openapi.json" # OR use inline spec: # spec_inline: | # auth_type: "api_key" # none|api_key|bearer|basic auth_config: api_key_name: "X-API-Key" # Header name or query param name api_key_location: "header" # header|query api_key_value: "$PETSTORE_API_KEY" # Use $ prefix for env vars category: "data" base_cost_per_use: 0.001 rate_limit: 30 # Requests per minute timeout_seconds: 30 # Request timeout max_response_bytes: 10485760 # Max response size (10MB) # Optional: Filter to specific operations operations: - "getPetById" - "findPetsByStatus" # Optional: Filter by tags # tags: # - "pet" # Optional: Override base URL from spec # base_url: "https://custom-petstore.example.com" ``` Add to `.env`: ```bash theme={null} # OpenAPI Security OPENAPI_ALLOWED_DOMAINS=* # Use * for dev, specific domains for prod OPENAPI_MAX_SPEC_SIZE=5242880 # 5MB default OPENAPI_FETCH_TIMEOUT=30 # Seconds # API Keys PETSTORE_API_KEY=your_key_here GITHUB_TOKEN=ghp_xxxxxxxxxxxxx OPENWEATHER_API_KEY=your_key API_USERNAME=username API_PASSWORD=password # Same registration token as MCP MCP_REGISTER_TOKEN=your_admin_token ``` ```bash theme={null} docker compose -f deploy/compose/docker-compose.yml up -d --force-recreate llm-service ``` **Validate spec first:** ```bash theme={null} curl -X POST http://localhost:8000/tools/openapi/validate \ -H "Content-Type: application/json" \ -d '{"spec_url": "https://petstore3.swagger.io/api/v3/openapi.json"}' | jq . ``` **Response:** ```json theme={null} { "valid": true, "operations_count": 19, "operations": [ {"operation_id": "getPetById", "method": "GET", "path": "/pet/{petId}"}, {"operation_id": "addPet", "method": "POST", "path": "/pet"} ], "base_url": "https://petstore3.swagger.io/api/v3" } ``` **Execute tool:** ```bash theme={null} curl -X POST http://localhost:8000/tools/execute \ -H "Content-Type: application/json" \ -d '{ "tool_name": "getPetById", "parameters": {"petId": 1} }' | jq . ``` ### Authentication Examples ```yaml theme={null} github: enabled: true spec_url: "https://raw.githubusercontent.com/github/rest-api-description/main/descriptions/api.github.com/api.github.com.json" auth_type: "bearer" auth_config: token: "$GITHUB_TOKEN" operations: - "repos/get" - "repos/list-for-user" ``` ```yaml theme={null} weather: enabled: true spec_url: "https://api.openweathermap.org/data/3.0/openapi.json" auth_type: "api_key" auth_config: api_key_name: "appid" api_key_location: "query" api_key_value: "$OPENWEATHER_API_KEY" ``` ```yaml theme={null} custom_api: enabled: true spec_url: "https://api.example.com/openapi.json" auth_type: "basic" auth_config: username: "$API_USERNAME" password: "$API_PASSWORD" ``` ```yaml theme={null} petstore: enabled: true spec_url: "https://petstore3.swagger.io/api/v3/openapi.json" auth_type: "api_key" auth_config: api_key_name: "X-API-Key" api_key_location: "header" api_key_value: "$PETSTORE_API_KEY" ``` ## Adding Built-in Python Tools For complex logic, database access, or performance-critical operations. ### When to Use Built-in Tools **Use built-in tools when:** * Need direct database/Redis access * Require complex Python libraries (pandas, numpy) * Performance-critical (avoid HTTP roundtrip) * Need session state management * Implement security-sensitive operations **Use MCP/OpenAPI instead when:** * Integrating external APIs * Want no-code deployment * Prototyping quickly * Third-party service integration Create file in `python/llm-service/llm_service/tools/builtin/my_custom_tool.py`: ```python theme={null} from typing import Any, Dict, List, Optional from ..base import Tool, ToolMetadata, ToolParameter, ToolParameterType, ToolResult class MyCustomTool(Tool): """ Brief description of what this tool does. """ def _get_metadata(self) -> ToolMetadata: """Define tool metadata.""" return ToolMetadata( name="my_custom_tool", version="1.0.0", description="Clear description for LLM to understand when/how to use this tool", category="custom", # search, data, analytics, code, file, custom author="Your Name", requires_auth=False, timeout_seconds=30, memory_limit_mb=128, sandboxed=False, session_aware=False, # Set True if tool needs session state dangerous=False, # Set True for file writes, code execution cost_per_use=0.001, # USD per invocation rate_limit=60, # Requests per minute (enforced by base class) ) def _get_parameters(self) -> List[ToolParameter]: """Define tool parameters with validation.""" return [ ToolParameter( name="required_param", type=ToolParameterType.STRING, description="Description shown to LLM", required=True, ), ToolParameter( name="optional_number", type=ToolParameterType.INTEGER, description="An optional number parameter", required=False, default=10, min_value=1, max_value=100, ), ToolParameter( name="choice_param", type=ToolParameterType.STRING, description="Parameter with predefined choices", required=False, enum=["option1", "option2", "option3"], ), ] async def _execute_impl( self, session_context: Optional[Dict] = None, **kwargs ) -> ToolResult: """ Execute the tool logic. Args: session_context: Session data if session_aware=True **kwargs: Tool parameters (validated automatically) Returns: ToolResult with success/error status """ try: # Extract parameters (already validated by base class) required_param = kwargs.get("required_param") optional_number = kwargs.get("optional_number", 10) choice_param = kwargs.get("choice_param") # Your tool logic here result = self._do_work(required_param, optional_number, choice_param) return ToolResult( success=True, output=result, metadata={"processed": True}, execution_time_ms=50, ) except Exception as e: return ToolResult( success=False, output=None, error=f"Tool execution failed: {str(e)}" ) def _do_work(self, param1, param2, param3): """Your actual implementation.""" # Example: Database query, API call, computation return {"result": "success", "data": [1, 2, 3]} ``` Register OpenAPI tools dynamically via API (uses the same admin token as MCP): ```bash theme={null} curl -X POST http://localhost:8000/tools/openapi/register \ -H "Authorization: Bearer $MCP_REGISTER_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "name": "petstore", "spec_url": "https://petstore3.swagger.io/api/v3/openapi.json", "auth_type": "none", "operations": ["getPetById"], "rate_limit": 30, "timeout_seconds": 30 }' ``` Response includes registered operations and effective limits. Edit `python/llm-service/llm_service/api/tools.py` around line 228: ```python theme={null} # Add import at top from ..tools.builtin.my_custom_tool import MyCustomTool # Add to registration list in startup_event() @router.on_event("startup") async def startup_event(): registry = get_registry() tools_to_register = [ WebSearchTool, CalculatorTool, FileReadTool, FileWriteTool, PythonWasiExecutorTool, MyCustomTool, # Add your tool here ] for tool_class in tools_to_register: try: registry.register(tool_class) logger.info(f"Registered tool: {tool_class.__name__}") except Exception as e: logger.error(f"Failed to register {tool_class.__name__}: {e}") ``` ```bash theme={null} docker compose -f deploy/compose/docker-compose.yml up -d --force-recreate llm-service ``` ```bash theme={null} # Verify registration curl http://localhost:8000/tools/list | grep my_custom_tool # Get schema curl http://localhost:8000/tools/my_custom_tool/schema | jq . # Execute curl -X POST http://localhost:8000/tools/execute \ -H "Content-Type: application/json" \ -d '{ "tool_name": "my_custom_tool", "parameters": { "required_param": "test", "optional_number": 42, "choice_param": "option1" } }' | jq . ``` ### Advanced: Session-Aware Tools For tools that maintain state across executions: ```python theme={null} class SessionAwareTool(Tool): def _get_metadata(self) -> ToolMetadata: return ToolMetadata( name="session_tool", session_aware=True, # Enable session context ... ) async def _execute_impl( self, session_context: Optional[Dict] = None, **kwargs ) -> ToolResult: # Access session data session_id = session_context.get("session_id") if session_context else None user_id = session_context.get("user_id") if session_context else None # Store/retrieve session-specific data # Example: Redis, database, in-memory cache return ToolResult(success=True, output={"session": session_id}) ``` ## Configuration Reference ### MCP Tool Configuration ```yaml theme={null} mcp_tools: tool_name: enabled: true # Required: Enable/disable tool url: "https://api.example.com" # Required: HTTP endpoint func_name: "function_name" # Required: Remote function name description: "Tool description" # Required: LLM-visible description category: "data" # Required: Tool category cost_per_use: 0.001 # Required: Cost in USD parameters: # Required: Parameter definitions - name: "param1" type: "string" # string|integer|float|boolean|array|object required: true description: "Param description" enum: ["val1", "val2"] # Optional: Allowed values default: "val1" # Optional: Default value headers: # Optional: HTTP headers X-API-Key: "${API_KEY_VAR}" # Use ${} for env vars ``` ### OpenAPI Tool Configuration ```yaml theme={null} openapi_tools: collection_name: enabled: true spec_url: "https://api.example.com/openapi.json" # OR spec_inline auth_type: "none" # none|api_key|bearer|basic auth_config: # Required if auth_type != "none" # For api_key: api_key_name: "X-API-Key" api_key_location: "header" # header|query api_key_value: "$API_KEY" # For bearer: token: "$BEARER_TOKEN" # For basic: username: "$USERNAME" password: "$PASSWORD" category: "api" base_cost_per_use: 0.001 rate_limit: 30 # Requests per minute timeout_seconds: 30 # Request timeout max_response_bytes: 10485760 # Max response size (bytes) operations: # Optional: Filter operations - "operationId1" - "operationId2" tags: # Optional: Filter by tags - "tag1" base_url: "https://override.com" # Optional: Override spec base URL ``` ### Environment Variables **MCP Configuration:** ```bash theme={null} # Domain Security MCP_ALLOWED_DOMAINS=localhost,127.0.0.1,api.example.com # Or * for dev # Circuit Breaker MCP_CB_FAILURES=5 # Failures before circuit opens MCP_CB_RECOVERY_SECONDS=60 # Circuit open duration # Request Limits MCP_MAX_RESPONSE_BYTES=10485760 # 10MB default MCP_RETRIES=3 # Retry attempts MCP_TIMEOUT_SECONDS=10 # Request timeout # Registration Security MCP_REGISTER_TOKEN=your_secret # API registration protection ``` **OpenAPI Configuration:** ```bash theme={null} # Domain Security OPENAPI_ALLOWED_DOMAINS=* # Comma-separated or * for dev OPENAPI_MAX_SPEC_SIZE=5242880 # 5MB spec size limit OPENAPI_FETCH_TIMEOUT=30 # Spec fetch timeout # Request Behavior OPENAPI_RETRIES=2 # Retry attempts (default: 2) ``` ## Testing & Verification ### Health Checks ```bash theme={null} # Check admin service health curl http://localhost:8081/health/ready | jq . curl http://localhost:8081/health/live | jq . # Check LLM service status docker inspect shannon-llm-service-1 --format='{{.State.Health.Status}}' ``` ### List Tools ```bash theme={null} # All tools curl http://localhost:8000/tools/list | jq . # By category curl "http://localhost:8000/tools/list?category=data" | jq . # Exclude dangerous curl "http://localhost:8000/tools/list?exclude_dangerous=true" | jq . # List categories curl http://localhost:8000/tools/categories | jq . ``` ### Execute Tools **Direct execution:** ```bash theme={null} curl -X POST http://localhost:8000/tools/execute \ -H "Content-Type: application/json" \ -d '{ "tool_name": "calculator", "parameters": {"expression": "sqrt(144) + 2^3"} }' | jq . ``` **Batch execution:** ```bash theme={null} curl -X POST http://localhost:8000/tools/batch-execute \ -H "Content-Type: application/json" \ -d '[ {"tool_name": "calculator", "parameters": {"expression": "2+2"}}, {"tool_name": "calculator", "parameters": {"expression": "10*5"}} ]' | jq . ``` **Via workflow:** ```bash theme={null} SESSION_ID="test-$(date +%s)" ./scripts/submit_task.sh "Calculate 2+2 and then multiply by 5" ``` ## Troubleshooting Symptom: Tool doesn't appear in `/tools/list` Debug steps: ```bash theme={null} # 1. Check YAML syntax yamllint config/shannon.yaml # 2. Check logs for errors docker compose logs llm-service | grep -i error # 3. Verify enabled flag grep -A 10 "my_tool" config/shannon.yaml | grep enabled # 4. Force recreate service docker compose -f deploy/compose/docker-compose.yml up -d --force-recreate llm-service # 5. Wait for health sleep 10 docker inspect shannon-llm-service-1 --format='{{.State.Health.Status}}' ``` Symptom: `URL host 'example.com' not in allowed domains` Solutions: 1. Development: Use wildcard ```bash theme={null} # .env MCP_ALLOWED_DOMAINS=* OPENAPI_ALLOWED_DOMAINS=* ``` 2. Production: Add specific domain ```bash theme={null} # .env MCP_ALLOWED_DOMAINS=localhost,127.0.0.1,api.example.com OPENAPI_ALLOWED_DOMAINS=api.example.com,api.github.com ``` Symptom: `ToolResult { success: false, error: "..." }` Debug: ```bash theme={null} # 1. Test tool directly curl -X POST http://localhost:8000/tools/execute \ -H "Content-Type: application/json" \ -d '{"tool_name":"my_tool","parameters":{...}}' | jq . # 2. Check parameter types curl http://localhost:8000/tools/my_tool/schema | jq '.parameters' # 3. Check agent core logs docker logs shannon-agent-core-1 | grep "Tool execution error" # 4. Check LLM service logs docker logs shannon-llm-service-1 | grep my_tool ``` Symptom: `Circuit breaker open for (too many failures)` Debug: ```bash theme={null} # Check recent errors docker logs shannon-llm-service-1 --tail 100 | grep -i "circuit\|failure" # Wait for recovery (default 60s) sleep 60 # Or restart to reset docker compose restart llm-service ``` Prevent:
  • Increase failure threshold: MCP\_CB\_FAILURES=10
  • Increase recovery time: MCP\_CB\_RECOVERY\_SECONDS=120
  • Fix underlying API issues
## Security Best Practices ### Domain Allowlisting ```bash theme={null} # Permissive for testing MCP_ALLOWED_DOMAINS=* OPENAPI_ALLOWED_DOMAINS=* ``` ```bash theme={null} # Specific domains + localhost MCP_ALLOWED_DOMAINS=localhost,127.0.0.1,staging-api.example.com ``` ```bash theme={null} # Explicit allowlist only MCP_ALLOWED_DOMAINS=api.example.com,api.partner.com OPENAPI_ALLOWED_DOMAINS=api.github.com,api.openweathermap.org ``` ### API Key Management Never hardcode API keys in configuration files! **❌ Bad:** ```yaml theme={null} headers: X-API-Key: "sk-1234567890abcdef" ``` **✅ Good:** ```yaml theme={null} headers: X-API-Key: "${WEATHER_API_KEY}" ``` **Store in `.env` (not tracked by git):** ```bash theme={null} # .env WEATHER_API_KEY=sk-real-key-here STOCK_API_KEY=your-stock-key ``` **For production:** Use secrets management * Docker secrets * Kubernetes secrets * HashiCorp Vault * AWS Secrets Manager ### Dangerous Tools Mark tools that modify state or access sensitive resources: ```python theme={null} ToolMetadata( name="file_write", dangerous=True, # Triggers OPA policy checks requires_auth=True, # Requires user authentication ... ) ``` ## Vendor Adapter Pattern **For domain-specific APIs and custom agents** When integrating proprietary or internal APIs that require domain-specific transformations, use the **vendor adapter pattern** to keep vendor logic separate from Shannon's core infrastructure. ### When to Use Use vendor adapters when your API integration requires: * Custom field name aliasing (e.g., `users` → `my:unique_users`) * Request/response transformations * Dynamic parameter injection from session context * Domain-specific validation or normalization * Specialized agent roles with custom system prompts ### Quick Example `python/llm-service/llm_service/tools/vendor_adapters/myvendor.py`: ```python theme={null} class MyVendorAdapter: def transform_body(self, body, operation_id, prompt_params): # Transform field names if isinstance(body.get("metrics"), list): body["metrics"] = [m.replace("users", "my:users") for m in body["metrics"]] # Inject session params if prompt_params and "account_id" in prompt_params: body["account_id"] = prompt_params["account_id"] return body ``` `python/llm-service/llm_service/tools/vendor_adapters/__init__.py`: ```python theme={null} def get_vendor_adapter(name: str): if name.lower() == "myvendor": from .myvendor import MyVendorAdapter return MyVendorAdapter() return None ``` `config/overlays/shannon.myvendor.yaml`: ```yaml theme={null} openapi_tools: myvendor_api: enabled: true spec_url: file:///app/config/openapi_specs/myvendor_api.yaml auth_type: bearer auth_config: vendor: myvendor # Triggers adapter loading token: "${MYVENDOR_API_TOKEN}" category: custom ``` ```bash theme={null} SHANNON_CONFIG_PATH=config/overlays/shannon.myvendor.yaml MYVENDOR_API_TOKEN=your_token_here ``` ### Benefits * ✅ **Clean separation:** Vendor code isolated from Shannon core * ✅ **No core changes:** Shannon infrastructure remains generic * ✅ **Conditional loading:** Graceful fallback if vendor module unavailable * ✅ **Easy testing:** Unit test adapters in isolation * ✅ **Secrets management:** All tokens via environment variables ### Complete Guide For a comprehensive guide including: * Custom agent roles for specialized domains * Session context injection patterns * Testing strategies * Best practices and troubleshooting Learn how to build vendor-specific integrations with the adapter pattern ## Summary **Three ways to add tools:** | Method | Command | Config File | Code Changes | | ------------ | --------------------------------------------------- | ------------------------- | ------------ | | **MCP** | `docker compose up -d --force-recreate llm-service` | `config/shannon.yaml` | None | | **OpenAPI** | `docker compose up -d --force-recreate llm-service` | `config/shannon.yaml` | None | | **Built-in** | `docker compose up -d --force-recreate llm-service` | `api/tools.py` + new file | Python only | **Key takeaways:** * ✅ Zero proto/Rust/Go changes (generic `google.protobuf.Struct` containers) * ✅ Security built-in (domain allowlisting, rate limiting, circuit breakers) * ✅ Cost tracking automatic (set `cost_per_use` in metadata) * ✅ Schema-driven (OpenAI-compatible JSON schemas) ## Next Steps Build vendor-specific integrations Explore all extension methods Complete configuration reference Explore the REST API # Extending Shannon Source: https://docs.shannon.run/en/tutorials/extending-shannon Simple, supported ways to extend Shannon without forking large subsystems ## Overview This guide outlines extensible patterns for customizing Shannon while maintaining upgrade compatibility and clean separation of concerns. System 1 - Pre-built workflows with low overhead Add capabilities via MCP, OpenAPI, or Python Domain-specific integrations without core changes Customize research output formatting ## Extension Methods Comparison | Extension Type | Complexity | Code Changes | Use Case | | --------------------- | ---------- | --------------- | ---------------------------- | | **Templates** | Low | YAML only | Repeatable workflows | | **MCP/OpenAPI Tools** | Low | Config only | External APIs | | **Built-in Tools** | Medium | Python only | Custom logic | | **Vendor Adapters** | Medium | Python + Config | Domain-specific integrations | | **Decomposition** | High | Go + Python | Custom planning logic | For most use cases, **Templates** and **Vendor Adapters** provide the best balance of power and simplicity. ## Extend Decomposition (System 2) **For custom planning and reasoning logic** The orchestrator calls the LLM service endpoint `/agent/decompose` for planning. ### When to Use * Custom task decomposition strategies * Domain-specific planning heuristics * Pre/post-processing of LLM requests * Integration with external planning systems ### Implementation Options **Best for:** Pre/post-processing LLM requests Add heuristics to `go/orchestrator/internal/activities/decompose.go`: ```go theme={null} // Example: Pre-process query before decomposition func PreprocessQuery(query string) string { // Add domain-specific context // Normalize input format // Inject additional constraints return enhancedQuery } // Example: Post-process decomposition response func PostprocessDecomposition(resp *DecompositionResponse) { // Validate subtasks // Add fallback steps // Optimize execution order } ``` **Best for:** Complete custom planning logic Add new endpoint in `python/llm-service/llm_service/api/agent.py`: ```python theme={null} @router.post("/agent/decompose/custom") async def custom_decompose(request: DecomposeRequest): """Custom decomposition logic for domain-specific tasks.""" # Your custom planning logic subtasks = custom_planning_algorithm(request.query) # Return compatible response return DecompositionResponse( subtasks=subtasks, pattern="custom", complexity=calculate_complexity(subtasks) ) ``` Route via feature flag or context key: ```go theme={null} // In orchestrator if ctx.CustomDecomposition { endpoint = "/agent/decompose/custom" } else { endpoint = "/agent/decompose" } ``` Keep response schema compatible with `DecompositionResponse` to avoid breaking orchestrator workflows. ## Add/Customize Templates (System 1) **For repeatable workflows with low overhead** ### When to Use * Predefined workflows (data analysis, code review, etc.) * Quick task execution without AI planning * Common patterns used frequently * Performance-critical paths ### Creating Templates Place templates in your own directory: ```yaml theme={null} # templates/custom/research-workflow.yaml name: "research_workflow" description: "Multi-stage research and analysis workflow" version: "1.0.0" extends: [] # Optional: inherit from other templates defaults: model_tier: "medium" budget_agent_max: 5000 require_approval: false nodes: - id: "search" type: "simple" strategy: "react" tools_allowlist: ["web_search", "calculator"] budget_max: 1000 depends_on: [] - id: "analyze" type: "cognitive" strategy: "chain_of_thought" tools_allowlist: ["python_executor", "calculator"] budget_max: 2000 depends_on: ["search"] - id: "synthesize" type: "simple" strategy: "react" tools_allowlist: ["web_search"] budget_max: 1000 depends_on: ["analyze"] edges: - from: "search" to: "analyze" - from: "analyze" to: "synthesize" metadata: category: "research" author: "your-team" ``` Initialize registry with your template directory: ```go theme={null} // In orchestrator initialization registry := templates.InitTemplateRegistry( "./templates/builtin", "./templates/custom", // Your custom templates ) // Validate all templates if err := registry.Finalize(); err != nil { log.Fatal(err) } ``` Via gRPC API: ```bash theme={null} grpcurl -plaintext -d '{ "query": "Perform research on AI trends", "context": { "template": "research_workflow", "template_version": "1.0.0", "disable_ai": true } }' localhost:50052 shannon.orchestrator.OrchestratorService/SubmitTask ``` Via HTTP Gateway: ```bash theme={null} curl -X POST http://localhost:8080/api/v1/tasks \ -H "Content-Type: application/json" \ -d '{ "query": "Perform research on AI trends", "context": { "template": "research_workflow", "template_version": "1.0.0" } }' ``` ```bash theme={null} # Via gRPC grpcurl -plaintext -d '{}' localhost:50052 \ shannon.orchestrator.OrchestratorService/ListTemplates ``` Note: HTTP Gateway template listing endpoint may not be implemented yet. Use gRPC for template discovery. ### Template Best Practices ```yaml theme={null} # Base template (base.yaml) name: "base_research" version: "1.0.0" defaults: model_tier: "medium" budget_agent_max: 5000 require_approval: false # Child template (advanced_research.yaml) name: "advanced_research" version: "1.0.0" extends: ["base_research"] # Inherits all defaults defaults: budget_agent_max: 10000 # Override specific values ``` ```go theme={null} registry := templates.InitTemplateRegistry("./templates") // Validates: // - YAML syntax // - Required fields // - Parameter types // - Tool references if err := registry.Finalize(); err != nil { log.Fatalf("Template validation failed: %v", err) } ``` ```yaml theme={null} # Explicitly list allowed tools for security tools_allowlist: - "web_search" - "calculator" - "python_executor" # Don't use: # tools_allowlist: ["*"] # Too permissive ``` ## Add Tools Safely **For extending Shannon's capabilities** Shannon supports three tool integration methods: External HTTP APIs with zero code changes Auto-generated from OpenAPI specs Python tools for complex logic ### Security Considerations Always use `tools_allowlist` in templates to restrict which tools can be used. **Good:** ```yaml theme={null} # Template with restricted tools tools_allowlist: - "web_search" - "calculator" - "my_custom_tool" ``` **Bad:** ```yaml theme={null} # Don't allow all tools tools_allowlist: ["*"] ``` ### Keep Experimental Tools Behind Flags ```yaml theme={null} # config/features.yaml experimental_tools: enabled: false # Disabled by default custom_analytics: enabled: ${ENABLE_CUSTOM_ANALYTICS} # Env-based toggle ``` ```python theme={null} # In tool registration if config.get("experimental_tools.enabled"): registry.register(ExperimentalTool) ``` See the complete guide for adding MCP, OpenAPI, and built-in Python tools ## Vendor Extensions **For domain-specific agents and API integrations** The vendor adapter pattern allows you to integrate proprietary APIs and specialized agents without modifying Shannon's core code. ### Architecture ``` Generic Shannon (Open Source) ├── python/llm-service/llm_service/tools/openapi_tool.py # Generic loader ├── python/llm-service/llm_service/roles/presets.py # Generic roles ├── go/orchestrator/internal/activities/agent.go # Generic mirroring └── config/shannon.yaml # Base config Vendor Extensions (Private) ├── config/overlays/shannon.vendor.yaml # Vendor configs ├── config/openapi_specs/vendor_api.yaml # API specs ├── python/llm-service/llm_service/tools/vendor_adapters/ # Transformations │ ├── __init__.py # Registry │ └── vendor.py # VendorAdapter └── python/llm-service/llm_service/roles/vendor/ # Agent roles ├── __init__.py └── custom_agent.py # System prompts ``` ### When to Use Vendor Extensions Use when you need: * Domain-specific API integrations (analytics, CRM, e-commerce) * Custom field name transformations * Specialized agent roles with domain knowledge * Session context injection (account IDs, tenant IDs) * Private/proprietary tool configurations ### Quick Start ```python theme={null} # python/llm-service/llm_service/tools/vendor_adapters/myvendor.py class MyVendorAdapter: def transform_body(self, body, operation_id, prompt_params): # Field aliasing if "metrics" in body: body["metrics"] = [m.replace("users", "mv:users") for m in body["metrics"]] # Inject session context if prompt_params: body.update(prompt_params) return body ``` ```python theme={null} # python/llm-service/llm_service/tools/vendor_adapters/__init__.py def get_vendor_adapter(name: str): if name.lower() == "myvendor": from .myvendor import MyVendorAdapter return MyVendorAdapter() return None ``` ```yaml theme={null} # config/overlays/shannon.myvendor.yaml openapi_tools: myvendor_api: enabled: true spec_url: file:///app/config/openapi_specs/myvendor_api.yaml auth_type: bearer auth_config: vendor: myvendor # Triggers adapter loading token: "${MYVENDOR_API_TOKEN}" category: custom ``` ```python theme={null} # python/llm-service/llm_service/roles/myvendor/custom_agent.py CUSTOM_AGENT_PRESET = { "name": "myvendor_agent", "system_prompt": "You are a specialized agent for...", "allowed_tools": ["myvendor_query", "myvendor_analyze"], "temperature": 0.7, } ``` Register with graceful fallback: ```python theme={null} # roles/presets.py try: from .myvendor.custom_agent import CUSTOM_AGENT_PRESET _PRESETS["myvendor_agent"] = CUSTOM_AGENT_PRESET except ImportError: pass # Shannon works without vendor module ``` ```bash theme={null} SHANNON_CONFIG_PATH=config/overlays/shannon.myvendor.yaml MYVENDOR_API_TOKEN=your_token_here ``` ### Benefits * ✅ **Zero Shannon core changes** - All vendor logic isolated * ✅ **Clean separation** - Generic infrastructure vs. vendor-specific * ✅ **Conditional loading** - Graceful fallback if vendor module unavailable * ✅ **Easy to maintain** - Vendor code in separate directories * ✅ **Testable in isolation** - Unit test adapters independently Comprehensive guide with examples, testing strategies, and best practices ## Human Approval **For gating sensitive operations** Wire `require_approval` through the SubmitTask request for human-in-the-loop control. ### Configuration ```yaml theme={null} # config/features.yaml approvals: enabled: true dangerous_tools: - "file_write" - "code_execution" - "database_query" complexity_threshold: 0.7 # Require approval for complex tasks timeout_seconds: 1800 # 30 minute approval window ``` ### API Usage ```bash theme={null} curl -X POST http://localhost:8080/api/v1/tasks \ -H "Content-Type: application/json" \ -d '{ "query": "Delete all records from users table", "require_approval": true }' ``` ### Approval Flow 1. Task submitted with `require_approval: true` 2. Orchestrator pauses before execution 3. Approval request sent via webhook/UI 4. User approves/rejects via API 5. Workflow continues or terminates **Gateway endpoint (recommended):** ```bash theme={null} curl -X POST "http://localhost:8080/api/v1/approvals/decision" -H "X-API-Key: YOUR_API_KEY" -H "Content-Type: application/json" -d '{ "workflow_id": "", "approval_id": "", "approved": true, "feedback": "Approved for execution" }' ``` Legacy admin endpoint at `http://localhost:8081/approvals/decision` is deprecated. Use the gateway endpoint instead. **Approval gates are enforced in the router before execution.** ## Feature Flags & Config **Runtime configuration without code changes** Many behaviors are controlled via `config/features.yaml` and environment variables, loaded through `GetWorkflowConfig`. ### Common Feature Flags ```yaml theme={null} # config/features.yaml # Template Fallback template_fallback: enabled: true # Fallback to AI if template fails # Tool Selection tool_selection: enabled: true # Auto-select tools based on task max_tools: 5 # Maximum tools per task # Cognitive Patterns patterns: cot_enabled: true # Chain-of-Thought tot_enabled: true # Tree-of-Thoughts react_enabled: true # ReAct # Budget Controls budgets: enforce_token_limits: true enforce_cost_limits: true default_max_tokens: 10000 default_max_cost_usd: 0.50 # Streaming streaming: enabled: true buffer_size: 1000 chunk_size: 512 ``` ### Environment Variable Override ```bash theme={null} # Override via environment TEMPLATE_FALLBACK_ENABLED=1 TOOL_SELECTION_MAX_TOOLS=10 DEFAULT_MAX_TOKENS=20000 # Start services with overrides docker compose up -d ``` ### Dynamic Config Loading ```go theme={null} // In orchestrator config := GetWorkflowConfig() if config.TemplateFallbackEnabled { // Use AI fallback } if config.BudgetsEnforceTokenLimits { // Apply token budget } ``` ## Synthesis Templates (Output Customization) **For customizing how Shannon formats final research answers** Synthesis templates control how multi-agent research results are formatted and presented. They're particularly useful for Deep Research workflows. ### When to Use * Customize output format for specific domains (market research, academic, executive summaries) * Enforce citation styles * Control answer structure and length * Inject domain-specific formatting rules ### Template Methods | Method | Context Parameter | Use Case | | ----------------- | ------------------------------------ | ------------------------------- | | Named template | `synthesis_template: "name"` | Reusable `.tmpl` files | | Verbatim override | `synthesis_template_override: "..."` | One-time custom format | | Min length | `synthesis_min_length: N` | Enforce minimum character count | ### Using Named Templates Create templates in `config/templates/synthesis/`: ```go theme={null} {{/* config/templates/synthesis/market_research.tmpl */}} {{template "_base.tmpl"}} ## Executive Summary Provide a 2-3 sentence summary of key findings. ## Market Analysis - Key trends - Competitive landscape - Opportunities and risks ## Data Sources Use [n] citation format for all claims. ## Recommendations Actionable insights based on the analysis. ``` Use via API: ```bash theme={null} curl -X POST http://localhost:8080/api/v1/tasks \ -H "Content-Type: application/json" \ -d '{ "query": "Analyze the electric vehicle market in 2025", "context": { "force_research": true, "synthesis_template": "market_research" } }' ``` ### Verbatim Override For one-time custom formatting without creating a template file: ```bash theme={null} curl -X POST http://localhost:8080/api/v1/tasks \ -H "Content-Type: application/json" \ -d '{ "query": "Compare major cloud providers", "context": { "force_research": true, "synthesis_template_override": "You are a cloud infrastructure analyst.\n\n## Analysis Format\n- Start with a comparison table\n- Use [n] citations\n- Include cost analysis section\n- End with recommendations" } }' ``` When using `synthesis_template_override`, you bypass the base template's citation contract. You must include citation rules (`[n]` format) in your override text. ### Minimum Length Control Enforce a minimum output length: ```bash theme={null} curl -X POST http://localhost:8080/api/v1/tasks \ -H "Content-Type: application/json" \ -d '{ "query": "Comprehensive analysis of AI governance", "context": { "force_research": true, "synthesis_min_length": 5000 } }' ``` ### Template Selection Logic Selection is based on context and workflow signals: 1. If `context.synthesis_template` is set → use that named template. 2. Else if any of: * `context.workflow_type == "research"` * `context.force_research == true` * `context.synthesis_style == "comprehensive"` * `context.research_areas` is non-empty → use `research_comprehensive.tmpl`. 3. Else if `context.synthesis_style == "concise"` → use `research_concise.tmpl`. 4. Otherwise → use `normal_default.tmpl`. ### Available Templates | Template | Description | | ----------------------------- | ---------------------------------------------------------- | | `_base.tmpl` | Protected base with citation rules | | `normal_default.tmpl` | Default non-research synthesis (minimal structure) | | `research_comprehensive.tmpl` | Detailed research format with strong coverage requirements | | `research_concise.tmpl` | Shorter research synthesis with lighter word requirements | | `research_with_facts.tmpl` | Research format optimized for fact extraction metadata | | `test_bullet_summary.tmpl` | Example bullet-point summary template | ### Best Practices 1. **Always extend `_base.tmpl`** - Ensures citation contract is maintained 2. **Use named templates** for recurring formats 3. **Use override** for one-off customizations 4. **Test templates** with sample queries before production use Templates are located in `config/templates/synthesis/`. See `README.md` in that directory for template authoring guidelines. ## Best Practices Summary * Generic infrastructure: Committed to open source * Vendor-specific code: Kept private in separate directories * Configuration overlays: Domain-specific settings isolated * Conditional imports: Graceful fallback for optional modules * Use stable interfaces (ToolRegistry, TemplateRegistry, etc.) * Avoid forking core subsystems * Keep customizations in separate directories * Use feature flags for experimental changes * Allowlist tools in templates * Enable approvals for dangerous operations * Use domain allowlisting for external APIs * Keep secrets in environment variables * Unit test vendor adapters in isolation * Integration test with Shannon services * Use replay testing for workflow determinism * Validate templates with `registry.Finalize()` ## Extension Decision Tree Extension Decision Tree ## Next Steps Add MCP, OpenAPI, and built-in tools Build domain-specific integrations Complete configuration reference Understanding Shannon's architecture # Human-in-the-Loop Review Source: https://docs.shannon.run/en/tutorials/hitl-review Use Shannon's HITL review system to review, refine, and approve AI-generated research plans before execution. # Human-in-the-Loop Review Shannon's Human-in-the-Loop (HITL) review system lets you **review and refine an AI-generated research plan** before the workflow begins executing. This is particularly valuable for deep research tasks where you want to steer the research direction, add constraints, or ensure the agent focuses on what matters most. ## What You'll Learn * How the HITL review cycle works (plan generation, feedback, approval) * Enabling HITL review on task submission * Interacting with the review API (get state, send feedback, approve) * Optimistic concurrency control with version tracking * SSE events emitted during the review cycle * Python SDK usage for HITL review * Configuration options and timeouts ## Prerequisites * Shannon stack running (Docker Compose) * Gateway reachable at `http://localhost:8080` * Auth defaults: * Docker Compose: authentication is disabled by default (`GATEWAY_SKIP_AUTH=1`). * Local builds: authentication is enabled by default. Set `GATEWAY_SKIP_AUTH=1` to disable auth, or include an API key header `-H "X-API-Key: $API_KEY"`. ## How HITL Review Works The HITL review cycle inserts a human checkpoint between task submission and research execution: Submit a research task with `require_review: true` (or `review_plan: "manual"`) in the context. The LLM service generates a research plan based on the query. The workflow pauses and waits for human input. You review the proposed plan via the review API. Send feedback to refine the plan iteratively (up to 10 rounds). Once satisfied, approve the plan. The workflow resumes and executes the confirmed research direction. The ResearchWorkflow runs with the approved plan injected as context, producing a focused, citation-backed report. ### Architecture ``` User Gateway LLM Service Orchestrator (Temporal) | | | | |-- POST /tasks -------->| | | | (require_review) |-- gRPC SubmitTask ---->| | | | |-- GenerateResearchPlan | | | | (Activity) | | | Redis state init <----| | | | | | | SSE: RESEARCH_PLAN_READY | workflow.Select() | |<-----------------------------------------------| (waiting for signal) | | | | | |-- GET /review -------->| (read Redis state) | | |<-- plan + version -----| | | | | | | |-- POST /review ------->| feedback | | | (action: feedback) |-- call /research-plan->| | | |<-- updated plan -------| | |<-- new plan + version -| | | | | | | |-- POST /review ------->| | | | (action: approve) |-- gRPC Signal -------->| (unblock workflow) | |<-- approved ----------| | | | | |-- ResearchWorkflow --->| ``` ## Enabling HITL Review Add `require_review: true` to the task context when submitting a research task: ```bash curl theme={null} curl -X POST http://localhost:8080/api/v1/tasks \ -H "Content-Type: application/json" \ -d '{ "query": "Research the competitive landscape of AI agent frameworks in 2025", "context": { "force_research": true, "require_review": true } }' ``` ```bash curl (stream endpoint) theme={null} curl -s -X POST http://localhost:8080/api/v1/tasks/stream \ -H "Content-Type: application/json" \ -d '{ "query": "Research the competitive landscape of AI agent frameworks in 2025", "context": { "force_research": true, "require_review": true } }' | jq ``` ```python Python SDK theme={null} from shannon import ShannonClient client = ShannonClient(base_url="http://localhost:8080") handle = client.submit_task( "Research the competitive landscape of AI agent frameworks in 2025", context={ "force_research": True, "require_review": True, }, ) ``` HITL review requires `force_research: true` because the review cycle is part of the ResearchWorkflow. Without it, the orchestrator may route the task to a different workflow that does not support review. The alternative context key `review_plan: "manual"` is also accepted and behaves identically. The desktop app uses `require_review: true` when the user disables auto-approve for deep research. ## Retrieving Review State After submission, poll for the review plan to become ready. The workflow generates an initial plan via the LLM and stores it in Redis. ```bash curl theme={null} # Get current review state curl -s http://localhost:8080/api/v1/tasks/{workflow_id}/review \ -H "X-API-Key: $API_KEY" | jq ``` ```python Python SDK theme={null} state = client.get_review_state(handle.workflow_id) print(f"Status: {state.status}") print(f"Round: {state.round}, Version: {state.version}") print(f"Plan: {state.current_plan}") for r in state.rounds: print(f" [{r.role}] {r.message[:100]}...") ``` ### Response ```json theme={null} { "status": "reviewing", "round": 1, "version": 1, "current_plan": "Based on your query, I propose the following research plan...", "rounds": [ { "role": "assistant", "message": "Based on your query, I propose the following research plan...", "timestamp": "2025-01-15T10:30:00Z" } ], "query": "Research the competitive landscape of AI agent frameworks in 2025" } ``` The response includes an `ETag` header containing the current version number, used for optimistic concurrency control. | Field | Type | Description | | -------------- | ------ | ------------------------------------------------------------- | | `status` | string | `"reviewing"` or `"approved"` | | `round` | int | Current conversation round (starts at 1) | | `version` | int | Monotonic version for concurrency control | | `current_plan` | string | The latest actionable plan (set when LLM intent is `"ready"`) | | `rounds` | array | Full conversation history (role, message, timestamp) | | `query` | string | Original task query | ## Sending Feedback Refine the plan by sending feedback. The gateway forwards your message to the LLM, which generates an updated plan incorporating your input. ```bash curl theme={null} curl -X POST http://localhost:8080/api/v1/tasks/{workflow_id}/review \ -H "Content-Type: application/json" \ -H "X-API-Key: $API_KEY" \ -H "If-Match: 1" \ -d '{ "action": "feedback", "message": "Focus more on open-source frameworks and include pricing comparisons" }' ``` ```python Python SDK theme={null} updated = client.submit_review_feedback( handle.workflow_id, "Focus more on open-source frameworks and include pricing comparisons", version=state.version, # optimistic concurrency ) print(f"New version: {updated.version}") print(f"Updated plan: {updated.current_plan}") ``` ### Feedback Response ```json theme={null} { "status": "reviewing", "plan": { "message": "I've refined the research plan to focus on open-source frameworks...", "round": 2, "version": 2, "intent": "ready" } } ``` The `intent` field indicates the LLM's assessment: * `"feedback"` -- the LLM is asking clarifying questions (no actionable plan yet) * `"ready"` -- the LLM has proposed an actionable research direction The response includes an updated `ETag` header. ### Concurrency Control The `If-Match` header (curl) or `version` parameter (SDK) enables optimistic concurrency. If another request modified the state since you last read it, the server returns `409 Conflict`: ```json theme={null} { "error": "Conflict: state has been modified" } ``` A distributed Redis lock also prevents two feedback requests from racing during the LLM call. ### Round Limits A maximum of **10 feedback rounds** is enforced. At the final round, the LLM is instructed to produce a definitive plan. Beyond this limit, further feedback is rejected and you must approve: ```json theme={null} { "error": "Maximum review rounds reached. Please approve the plan." } ``` ## Approving the Plan Once the plan looks good, approve it to resume the workflow: ```bash curl theme={null} curl -X POST http://localhost:8080/api/v1/tasks/{workflow_id}/review \ -H "Content-Type: application/json" \ -H "X-API-Key: $API_KEY" \ -H "If-Match: 2" \ -d '{"action": "approve"}' ``` ```python Python SDK theme={null} result = client.approve_review(handle.workflow_id, version=updated.version) print(result) # {"status": "approved", "message": "Research started"} ``` ### Approval Response ```json theme={null} { "status": "approved", "message": "Research started" } ``` After approval: 1. The gateway sends a Temporal Signal to the waiting workflow 2. The confirmed plan and review conversation are injected into the task context 3. The ResearchWorkflow proceeds with the approved research direction 4. SSE emits `RESEARCH_PLAN_APPROVED` You cannot approve a plan if `current_plan` is empty. The LLM must have produced at least one plan with intent `"ready"` before approval is possible. If you try to approve without a plan, the server returns: ```json theme={null} { "error": "No research plan to approve. Please provide feedback to generate a plan first." } ``` ## SSE Events The HITL review cycle emits dedicated SSE events that you can consume via the streaming endpoint: ```bash theme={null} curl -N "http://localhost:8080/api/v1/stream/sse?workflow_id={workflow_id}" ``` | Event Type | Description | Payload | | ------------------------ | ------------------------------------------ | ---------------------------- | | `RESEARCH_PLAN_READY` | Initial plan generated, waiting for review | `round`, `intent` | | `REVIEW_USER_FEEDBACK` | User feedback submitted | `round`, `version` | | `RESEARCH_PLAN_UPDATED` | Plan updated after feedback | `round`, `version`, `intent` | | `RESEARCH_PLAN_APPROVED` | Plan approved, research starting | -- | These events are published to the Redis event stream, making the review conversation visible in session history and on page reload. ## Configuration ### Review Timeout The workflow waits up to **15 minutes** (default) for the review to complete. If no approval is received within this window, the workflow times out: ```json theme={null} { "status": "TASK_STATUS_COMPLETED", "result": "", "error_message": "research plan review timed out" } ``` You can customize the timeout via the `review_timeout` context parameter (in seconds): ```bash theme={null} curl -X POST http://localhost:8080/api/v1/tasks \ -H "Content-Type: application/json" \ -d '{ "query": "...", "context": { "force_research": true, "require_review": true, "review_timeout": 1800 } }' ``` ### Approval Workflow (separate feature) Shannon also has a separate **approval workflow** for non-research tasks. This is configured in `features.yaml` and triggers based on complexity thresholds or dangerous tool usage: ```yaml theme={null} # config/features.yaml workflows: approval: enabled: false # Enable approval gate complexity_threshold: 0.5 # Complexity >= this triggers approval dangerous_tools: - file_system - code_execution - bash ``` The approval workflow uses a different endpoint (`POST /api/v1/approvals/decision`) and is independent of the HITL research review. See the [Approve Task API reference](/api/rest/approve-task) for details. ## Python SDK Reference The Shannon Python SDK provides dedicated methods for the HITL review cycle: ```python theme={null} from shannon import ShannonClient client = ShannonClient(base_url="http://localhost:8080") # 1. Submit with review enabled handle = client.submit_task( "Analyze the market for AI coding assistants", context={"force_research": True, "require_review": True}, ) # 2. Wait for plan to be ready (poll) import time state = None for _ in range(30): try: state = client.get_review_state(handle.workflow_id) if state.status == "reviewing": break except Exception: pass time.sleep(2) print(f"Initial plan (round {state.round}):") print(state.current_plan or state.rounds[-1].message) # 3. Send feedback updated = client.submit_review_feedback( handle.workflow_id, "Include Cursor, GitHub Copilot, and Windsurf. Compare pricing tiers.", version=state.version, ) print(f"Refined plan (round {updated.round}):") print(updated.current_plan) # 4. Approve result = client.approve_review(handle.workflow_id, version=updated.version) print(result) # 5. Wait for research completion final = client.wait(handle.task_id, timeout=600) print(final.result) client.close() ``` ### Async SDK ```python theme={null} from shannon import AsyncShannonClient async def review_workflow(): client = AsyncShannonClient(base_url="http://localhost:8080") handle = await client.submit_task( "Analyze AI agent frameworks", context={"force_research": True, "require_review": True}, ) # Poll for plan state = await client.get_review_state(handle.workflow_id) # Send feedback updated = await client.submit_review_feedback( handle.workflow_id, "Focus on LangGraph vs CrewAI", version=state.version, ) # Approve await client.approve_review(handle.workflow_id, version=updated.version) await client.close() ``` ## Best Practices Always pass the `If-Match` header (or `version` parameter in the SDK) to prevent race conditions. This is especially important when multiple users or tabs may interact with the same review. The default 15-minute timeout works for most interactive use cases. For asynchronous review workflows (e.g., email-based approval), increase `review_timeout` accordingly. Before approving, ensure the LLM has produced a plan with intent `"ready"`. Feedback rounds with intent `"feedback"` indicate the LLM needs more information. HITL review works with all research strategy presets (`quick`, `standard`, `deep`, `academic`). The approved plan guides the subsequent research execution. ## Troubleshooting The review state is stored in Redis with a TTL of 20 minutes (review timeout + 5-minute buffer). If the review session expires, you will receive a 404 error. Re-submit the task to start a new review. This means another request modified the review state since you last read it, or another feedback request is currently in progress. Re-fetch the current state with `GET /review`, then retry with the latest version. The LLM has only asked clarifying questions (intent: `"feedback"`) and has not yet produced an actionable plan. Send at least one feedback message so the LLM generates a concrete research direction. The review took longer than the configured timeout (default: 15 minutes). Increase `review_timeout` in the task context, or approve more quickly. ## Next Steps Build comprehensive research reports Approval workflow for non-research tasks Complete API reference for review endpoints # Tutorials Source: https://docs.shannon.run/en/tutorials/index Learn Shannon through hands-on examples and practical guides ## Welcome to Shannon Tutorials Learn how to build powerful AI agent applications with Shannon through practical, hands-on tutorials. Each tutorial includes complete working code, step-by-step instructions, and best practices. This section is actively being developed. New tutorials are added regularly based on community feedback and common use cases. ## Available Tutorials ### Building Agents Build a multi-source research agent with memory, citations, verification, and adaptive patterns
**Status**: ✅ Available
### Extension & Integration Complete guide to extending Shannon with MCP, OpenAPI, and built-in Python tools
**Status**: ✅ Available
Build vendor-specific integrations with the adapter pattern for domain-specific APIs
**Status**: ✅ Available
Create deterministic, zero-token workflows using YAML templates with DAG support
**Status**: ✅ Available
Explore all extension methods: templates, tools, vendor adapters, and custom decomposition
**Status**: ✅ Available
## What You'll Learn Across these tutorials, you'll learn: * **Agent Design**: How to structure agent tasks and workflows * **Tool Integration**: Connecting agents to external APIs and services * **Session Management**: Building stateful, multi-turn conversations * **Error Handling**: Robust patterns for production agents * **Cost Optimization**: Budget management and token efficiency * **Monitoring**: Tracking agent performance and behavior ## Prerequisites Before starting these tutorials, make sure you have: 1. **Shannon Installed**: Follow the [Installation Guide](/en/quickstart/installation) 2. **Basic Python Knowledge**: Familiarity with Python 3.9+ 3. **API Keys**: At least one LLM provider API key (OpenAI, Anthropic, etc.) 4. **Docker**: For running Shannon services locally ## Tutorial Structure Each tutorial follows a consistent structure: 1. **Overview**: What you'll build and why 2. **Prerequisites**: Required setup and dependencies 3. **Step-by-Step**: Detailed implementation guide 4. **Code Examples**: Complete working code you can run 5. **Testing**: How to verify your implementation 6. **Next Steps**: Ideas for extending the tutorial ## Contributing Tutorials Have an idea for a tutorial? We welcome community contributions! 1. Check existing [GitHub issues](https://github.com/Kocoro-lab/Shannon/issues?q=label%3Atutorial) 2. Propose new tutorials in [GitHub Discussions](https://github.com/Kocoro-lab/Shannon/discussions) 3. Submit tutorial PRs following our [contribution guidelines](https://github.com/Kocoro-lab/Shannon/blob/main/CONTRIBUTING.md) ## Get Help If you get stuck on any tutorial: * Search [GitHub Discussions](https://github.com/Kocoro-lab/Shannon/discussions) for similar questions * File an issue if you find bugs or unclear instructions ## What's Next? New to Shannon? Start here Understand Shannon's fundamentals Explore the REST API Use the SDK in your applications # Model Selection & Routing Source: https://docs.shannon.run/en/tutorials/model-selection How Shannon selects models based on tiers, priority, and overrides ## Overview Shannon automatically selects the optimal LLM model for each task based on: 1. **Task complexity** (analyzed during decomposition) 2. **Explicit tier requests** (`model_tier` parameter) 3. **Model/provider overrides** (`model_override`, `provider_override`) 4. **Priority rankings** (defined in `config/models.yaml`) 5. **Budget constraints** and token limits This guide explains how model selection works and how to control it. ## Model Tiers Shannon organizes models into three tiers: | Tier | Target Usage | Characteristics | Cost Range | | ---------- | ------------ | ------------------------------------- | ------------------------ | | **Small** | 50% | Fast, cost-optimized, basic reasoning | \$0.0001-0.0002/1K input | | **Medium** | 40% | Balanced capability/cost | \$0.002-0.006/1K input | | **Large** | 10% | Heavy reasoning, complex tasks | \$0.02-0.025/1K input | **Note**: Percentages are target distributions, not enforced quotas. Actual usage depends on your workload. ## Selection Flow Model Selection Flow ## Priority Ranking Within each tier, models are ranked by priority (lower number = higher priority). Shannon attempts models in priority order until one succeeds. **Example from `config/models.yaml`**: ```yaml theme={null} model_tiers: small: providers: - provider: openai model: gpt-5-nano-2025-08-07 priority: 1 # Try first - provider: anthropic model: claude-haiku-4-5-20251001 priority: 2 # Fallback if OpenAI fails - provider: xai model: grok-3-mini priority: 3 # Default small tier model for xAI - provider: xai model: grok-4-fast-non-reasoning priority: 4 # Alternative fast model ``` **Fallback Behavior**: * If priority 1 fails (rate limit, API error), Shannon tries priority 2 * Continues until a model succeeds or all options exhausted * Failures are logged to orchestrator logs ## Parameter Precedence When multiple parameters specify model selection, the precedence is: 1. **`model_override`** (highest priority) → Forces specific model 2. **`provider_override`** → Limits to one provider's models 3. **`model_tier`** → Uses requested tier 4. **Auto-detected complexity** (lowest priority) → Default behavior ### Top-Level vs Context Parameters Top-level parameters **always override** context parameters: ```json theme={null} { "query": "Analyze data", "model_tier": "large", // Top-level (WINS) "context": { "model_tier": "small" // Context (IGNORED) } } ``` ## Usage Examples ### Auto-Selection (Default) ```bash theme={null} curl -X POST http://localhost:8080/api/v1/tasks \ -H "X-API-Key: sk_test_123456" \ -d '{"query": "What is 2+2?"}' ``` Shannon analyzes complexity → Selects small tier → Uses `gpt-5-nano-2025-08-07` (priority 1 in small tier) ### Force Specific Tier ```bash theme={null} curl -X POST http://localhost:8080/api/v1/tasks \ -H "X-API-Key: sk_test_123456" \ -d '{ "query": "Complex analysis task", "model_tier": "large" }' ``` Uses large tier → `gpt-5.1-2025-11-01` (priority 1 in large tier) ### Override to Specific Model ```bash theme={null} curl -X POST http://localhost:8080/api/v1/tasks \ -H "X-API-Key: sk_test_123456" \ -d '{ "query": "Analysis", "model_override": "claude-sonnet-4-5-20250929" }' ``` Forces Anthropic Claude Sonnet, ignoring tier/priority. ### Force Provider ```bash theme={null} curl -X POST http://localhost:8080/api/v1/tasks \ -H "X-API-Key: sk_test_123456" \ -d '{ "query": "Analysis", "model_tier": "medium", "provider_override": "anthropic" }' ``` Uses medium tier **but only Anthropic models** → `claude-sonnet-4-5-20250929` ### Python SDK Examples ```python theme={null} from shannon import ShannonClient client = ShannonClient(api_key="sk_test_123456") # Auto-selection task = client.tasks.submit(query="Simple task") # Force tier task = client.tasks.submit( query="Complex analysis", model_tier="large" ) # Force model task = client.tasks.submit( query="Research task", model_override="gpt-5.1-2025-11-01" ) # Force provider + tier task = client.tasks.submit( query="Analysis", model_tier="medium", provider_override="openai" ) ``` ## Cost Optimization Strategies ### 1. Start Small, Escalate if Needed ```python theme={null} # Try small tier first task = client.tasks.submit(query="Analyze Q4 data", model_tier="small") status = client.tasks.get(task.task_id) # If result is insufficient, retry with large if not_satisfactory(status.result): task = client.tasks.submit(query="Analyze Q4 data deeply", model_tier="large") ``` ### 2. Provider-Specific Optimization ```python theme={null} # Use cheaper provider for bulk tasks for item in bulk_data: client.tasks.submit( query=f"Summarize {item}", model_tier="small", provider_override="deepseek" # Cheaper than OpenAI ) ``` ### 3. Session-Based Escalation ```python theme={null} session_id = "analysis-session-123" # Start with small model client.tasks.submit( query="Initial analysis", session_id=session_id, model_tier="small" ) # Follow-up with larger model (inherits context) client.tasks.submit( query="Deeper insights", session_id=session_id, model_tier="large" ) ``` ## Research Tiered Model Architecture Added in Shannon v0.3.0. This feature achieves **50-70% cost reduction** compared to uniform large-model usage for research workflows. Shannon's research workflows automatically assign different model tiers to different stages of execution, using expensive models only where quality matters most. ### How It Works | Research Stage | Default Tier | Role | Rationale | | --------------------------------- | ---------------------- | ---------------------- | ----------------------------------------------------------------------------------------- | | **Exploration / Research agents** | `small` | `research_agent` | Gathering information is breadth-oriented; small models are sufficient and cost-effective | | **Synthesis / Final output** | `large` (configurable) | `synthesis_agent` | Combining and reasoning over findings requires stronger models | | **Quick strategy** | `small` | `quick_research_agent` | Forces parallel execution with small models for maximum speed | ### Configuration The synthesis tier is configurable via `synthesis_model_tier` in your task request or `config/shannon.yaml`: ```yaml theme={null} # config/shannon.yaml research: default_research_tier: small # Tier for exploration agents synthesis_model_tier: large # Tier for final synthesis ``` You can also override per-request: ```bash theme={null} curl -X POST http://localhost:8080/api/v1/tasks \ -H "X-API-Key: sk_test_123456" \ -d '{ "query": "Research the impact of LLM scaling laws", "strategy": "research", "context": { "synthesis_model_tier": "medium" } }' ``` ### Quick Research Strategy The `quick` strategy forces all research agents to run in parallel using small models, optimizing for speed and cost over depth: ```python theme={null} task = client.tasks.submit( query="Quick overview of recent ML papers", strategy="quick" # Parallel execution, small models, quick_research_agent role ) ``` ### Cost Comparison **Example: 5-agent research workflow** | Approach | Model Usage | Estimated Cost | | --------------- | --------------------- | -------------- | | Uniform `large` | 5 x large | \~\$0.50 | | Tiered (v0.3.0) | 4 x small + 1 x large | \~\$0.15-0.25 | | Quick strategy | 5 x small (parallel) | \~\$0.05 | Result: **50-70% cost reduction** with tiered architecture, maintaining synthesis quality. ## Complexity Analysis Shannon analyzes task complexity using several factors: * **Query length** and specificity * **Number of sub-tasks** identified * **Tool usage** requirements * **Context depth** needed * **Reasoning intensity** (keywords like "analyze", "compare", "synthesize") **Complexity Thresholds** (configurable): * `< 0.3` → Small tier (simple Q\&A, basic tasks) * `0.3 - 0.7` → Medium tier (multi-step, moderate reasoning) * `> 0.7` → Large tier (complex research, heavy reasoning) ## Monitoring & Debugging ### Check Which Model Was Used ```bash theme={null} TASK_ID="task-abc123" curl http://localhost:8080/api/v1/tasks/$TASK_ID \ -H "X-API-Key: sk_test_123456" | jq '{model_used, provider, usage}' ``` Response: ```json theme={null} { "model_used": "gpt-5-nano-2025-08-07", "provider": "openai", "usage": { "total_tokens": 245, "input_tokens": 150, "output_tokens": 95, "estimated_cost": 0.000053 } } ``` ### Prometheus Metrics ```bash theme={null} # Model usage by tier shannon_llm_requests_total{tier="small"} shannon_llm_requests_total{tier="medium"} shannon_llm_requests_total{tier="large"} # Provider distribution shannon_llm_requests_total{provider="openai"} shannon_llm_requests_total{provider="anthropic"} # Tier drift (when requested tier unavailable) shannon_tier_drift_total{requested="large", actual="medium"} ``` ### Orchestrator Logs ```bash theme={null} docker compose -f deploy/compose/docker-compose.yml logs orchestrator | grep "Model selected" ``` Look for: * `"Model selected: gpt-5-nano-2025-08-07 (small tier, priority 1)"` * `"Falling back to priority 2: claude-haiku-4-5-20251001"` * `"Falling back to priority 3: grok-3-mini (xAI)"` * `"Tier override: user requested large → using gpt-5.1-2025-11-01"` ## Configuration Model tiers and priorities are defined in `config/models.yaml`: ```yaml theme={null} model_tiers: small: providers: - provider: openai model: gpt-5-nano-2025-08-07 priority: 1 - provider: anthropic model: claude-haiku-4-5-20251001 priority: 2 selection_strategy: mode: priority # priority | round-robin | least-cost fallback_enabled: true max_retries: 3 ``` **Selection Modes**: * `priority` (default): Try models in priority order * `round-robin`: Distribute load evenly across same-priority models * `least-cost`: Always select cheapest model in tier ## Troubleshooting ### Issue: Wrong tier selected **Symptoms**: Task uses medium tier when you expected small **Solutions**: 1. Explicitly set `model_tier: "small"` in request 2. Check complexity score in orchestrator logs 3. Verify query isn't triggering complexity heuristics (avoid words like "analyze deeply") ### Issue: Specific model not used **Symptoms**: Request `model_override: "gpt-5.1-2025-11-01"` but gets different model **Solutions**: 1. Verify model is in `config/models.yaml` under `model_catalog` 2. Check API key for provider is set in `.env` (e.g., `OPENAI_API_KEY`, `XAI_API_KEY`) 3. Verify model ID uses canonical name (not alias) 4. Check orchestrator logs for fallback messages 5. Ensure the model is available in your API plan (e.g., GPT-5.1 requires appropriate tier) ### Issue: High costs **Symptoms**: Costs higher than expected **Solutions**: 1. Check actual tier distribution via Prometheus 2. Add explicit `model_tier: "small"` to requests 3. Review `shannon_tier_drift_total` for unwanted escalations 4. Set `MAX_COST_PER_REQUEST` in `.env` to enforce budget ### Issue: Rate limiting **Symptoms**: Frequent 429 errors, slow fallback cascade **Solutions**: 1. Add more providers to tier priority list 2. Enable `round-robin` mode to distribute load 3. Increase `RATE_LIMIT_WINDOW` for affected providers 4. Consider cheaper providers (DeepSeek, Groq) as fallbacks ## Best Practices 1. **Default to Auto-Selection**: Let Shannon's complexity analysis work 2. **Override Sparingly**: Use `model_override` only when required 3. **Start Small**: Set `model_tier: "small"` for cost-sensitive workloads 4. **Monitor Distribution**: Track tier usage via metrics 5. **Configure Fallbacks**: Ensure each tier has 3+ providers 6. **Test Priority Order**: Verify your preferred models are priority 1 7. **Budget Enforcement**: Set `MAX_COST_PER_REQUEST` for safety ## Related Documentation List available models and pricing Task submission with model parameters Environment variables and YAML config View model usage and costs # OpenAPI Tools Source: https://docs.shannon.run/en/tutorials/openapi-tools Complete reference for configuring OpenAPI 3.x tools in Shannon ## Overview Shannon can automatically generate tools from OpenAPI 3.x specifications, allowing you to integrate any REST API without writing code. The OpenAPI loader: * ✅ Parses OpenAPI 3.0/3.1 specs * ✅ Generates one tool per operation * ✅ Handles authentication (Bearer, API Key, Basic) * ✅ Supports path/query/header parameters * ✅ Includes circuit breaker and rate limiting * ✅ Validates requests against schema * ✅ Resolves `$ref` references locally **Quick Start**: See [Adding Custom Tools Guide](/en/tutorials/custom-tools#adding-openapi-tools) for step-by-step instructions. ## Configuration Reference ### Basic Configuration ```yaml theme={null} # config/shannon.yaml or config/overlays/shannon.myvendor.yaml openapi_tools: tool_collection_name: enabled: true | false # Enable/disable this tool collection spec_path: string # Path to OpenAPI spec file spec_url: string # OR: URL to fetch spec from spec_inline: string # OR: Inline YAML/JSON spec auth_type: none | api_key | bearer | basic auth_config: object # Auth configuration (see below) category: string # Tool category (e.g., "analytics", "data") base_cost_per_use: float # Estimated cost per operation (USD) rate_limit: integer # Requests per minute (default: 30) timeout_seconds: float # Request timeout (default: 30) max_response_bytes: integer # Max response size (default: 10MB) operations: [string] # Optional: Filter by operationId tags: [string] # Optional: Filter by tags base_url: string # Optional: Override spec's base URL ``` ### Field Descriptions | Field | Type | Required | Default | Description | | -------------------- | -------------- | --------------- | ---------- | ------------------------------------------------------ | | `enabled` | boolean | Yes | - | Enable this tool collection | | `spec_path` | string | One of spec\_\* | - | Local file path to OpenAPI spec | | `spec_url` | string | One of spec\_\* | - | URL to fetch spec from (supports relative server URLs) | | `spec_inline` | string | One of spec\_\* | - | Inline YAML/JSON spec content | | `auth_type` | enum | Yes | `none` | Authentication method | | `auth_config` | object | If auth ≠ none | `{}` | Auth configuration (varies by type) | | `category` | string | No | `"api"` | Tool category for organization | | `base_cost_per_use` | float | No | `0.001` | Estimated cost per invocation (USD) | | `rate_limit` | integer | No | `30` | Max requests per minute per tool | | `timeout_seconds` | float | No | `30.0` | HTTP request timeout | | `max_response_bytes` | integer | No | `10485760` | Max response size (10MB) | | `operations` | array\[string] | No | All | Filter to specific operationIds | | `tags` | array\[string] | No | All | Filter operations by tags | | `base_url` | string | No | From spec | Override API base URL | ## Authentication Types ### No Authentication ```yaml theme={null} openapi_tools: public_api: enabled: true spec_url: https://api.example.com/openapi.json auth_type: none ``` ### Bearer Token Used by: GitHub, GitLab, most modern APIs ```yaml theme={null} openapi_tools: github_api: enabled: true spec_url: https://raw.githubusercontent.com/github/rest-api-description/main/descriptions/api.github.com/api.github.com.json auth_type: bearer auth_config: token: "${GITHUB_TOKEN}" # From environment variable # OR: # token: "ghp_xxxxxxxxxxxxx" # Hardcoded (not recommended) ``` **Environment Variable**: ```bash theme={null} GITHUB_TOKEN=ghp_your_token_here ``` **Headers sent**: ``` Authorization: Bearer ghp_your_token_here ``` ### API Key in Header Used by: OpenAI, Anthropic, many SaaS APIs ```yaml theme={null} openapi_tools: openai_api: enabled: true spec_url: https://raw.githubusercontent.com/openai/openai-openapi/master/openapi.yaml auth_type: api_key auth_config: api_key_name: Authorization # Header name api_key_location: header # "header" or "query" api_key_value: "Bearer ${OPENAI_API_KEY}" # Value with prefix ``` **Environment Variable**: ```bash theme={null} OPENAI_API_KEY=sk-your-key-here ``` **Headers sent**: ``` Authorization: Bearer sk-your-key-here ``` ### API Key in Query Parameter Used by: OpenWeather, some legacy APIs ```yaml theme={null} openapi_tools: weather_api: enabled: true spec_url: https://api.openweathermap.org/data/3.0/openapi.json auth_type: api_key auth_config: api_key_name: appid # Query param name api_key_location: query # "query" not "header" api_key_value: "${OPENWEATHER_KEY}" ``` **Request URL**: ``` GET https://api.openweathermap.org/data/3.0/weather?appid=your_key_here&q=London ``` ### Basic Authentication Used by: Legacy APIs, internal services ```yaml theme={null} openapi_tools: legacy_api: enabled: true spec_path: config/openapi_specs/legacy_api.yaml auth_type: basic auth_config: username: "${API_USERNAME}" password: "${API_PASSWORD}" ``` **Environment Variables**: ```bash theme={null} API_USERNAME=admin API_PASSWORD=secret123 ``` **Headers sent**: ``` Authorization: Basic YWRtaW46c2VjcmV0MTIz ``` ### Custom Headers For vendor-specific authentication: ```yaml theme={null} openapi_tools: custom_api: enabled: true spec_url: https://api.custom.com/v1/openapi.json auth_type: api_key auth_config: vendor: myvendor # Triggers vendor adapter api_key_name: X-API-Key api_key_location: header api_key_value: "${CUSTOM_API_KEY}" extra_headers: X-Account-ID: "{{body.account_id}}" # Dynamic from request body X-User-ID: "${CUSTOM_USER_ID}" # Static from environment X-Timestamp: "{{timestamp}}" # Dynamic template ``` **Dynamic Header Templates**: * `"${ENV_VAR}"` - Resolved from environment * `"{{body.field}}"` - Resolved from request body at runtime * Static strings - Used as-is ## Advanced Features ### Operation Filtering **By operationId** (recommended): ```yaml theme={null} openapi_tools: petstore: spec_url: https://petstore3.swagger.io/api/v3/openapi.json operations: - getPetById # Only generate tool for this operation - findPetsByStatus - addPet ``` **By tags**: ```yaml theme={null} openapi_tools: petstore: spec_url: https://petstore3.swagger.io/api/v3/openapi.json tags: - pet # Only operations tagged "pet" - user ``` ### Base URL Override Override the base URL from the spec: ```yaml theme={null} openapi_tools: api_staging: spec_url: https://api.example.com/openapi.json base_url: https://staging-api.example.com # Use staging instead ``` **Use cases**: * Testing against staging/dev environments * Internal proxies or gateways * Local development ### Rate Limiting Protect external APIs from overload: ```yaml theme={null} openapi_tools: expensive_api: spec_url: https://expensive.api.com/openapi.json rate_limit: 10 # Only 10 requests per minute timeout_seconds: 60 # 60 second timeout for slow operations ``` **Per-tool limits**: Each operation generated from the spec inherits this limit. **Behavior**: * Enforced via token bucket algorithm * Shared across all tool instances (single Shannon instance) * Returns error if limit exceeded ### Circuit Breaker Automatic failure protection: **Configuration** (via environment): ```bash theme={null} # Circuit breaker opens after 5 failures # Stays open for 60 seconds before allowing retry # (These are defaults, not directly configurable per tool yet) ``` **States**: 1. **Closed** (normal): All requests pass through 2. **Open** (failing): All requests immediately rejected 3. **Half-open** (testing): One trial request allowed **Behavior**: * After 5 consecutive failures → opens circuit * Circuit stays open for 60 seconds * Then allows one trial request (half-open) * Success → closes circuit * Failure → reopens for another 60 seconds ### Response Size Limits Prevent memory exhaustion: ```yaml theme={null} openapi_tools: api_with_large_responses: spec_url: https://api.example.com/openapi.json max_response_bytes: 52428800 # 50MB limit ``` **Behavior**: * Responses larger than limit are truncated * Error returned with truncated marker ## Troubleshooting ### Tool Not Registered **Symptom**: Tool doesn't appear in `/tools/list` **Debug**: ```bash theme={null} # 1. Check if spec is valid curl -X POST http://localhost:8000/tools/openapi/validate \ -H "Content-Type: application/json" \ -d '{"spec_url": "https://api.example.com/openapi.json"}' # 2. Check logs docker logs shannon-llm-service-1 | grep -i "openapi" # 3. Verify enabled flag grep -A 5 "my_tool" config/shannon.yaml | grep enabled ``` **Common causes**: * `enabled: false` in config * Invalid OpenAPI spec * Domain not in `OPENAPI_ALLOWED_DOMAINS` * Spec fetch timeout * Circular `$ref` references ### Domain Validation Error **Symptom**: `URL host 'example.com' not in allowed domains` **Fix**: ```bash theme={null} # Development: Allow all OPENAPI_ALLOWED_DOMAINS=* # Production: Specific domains OPENAPI_ALLOWED_DOMAINS=api.github.com,api.example.com,api.partner.com ``` **In docker-compose.yml**: ```yaml theme={null} services: llm-service: environment: - OPENAPI_ALLOWED_DOMAINS=api.example.com ``` ### Spec Fetch Timeout **Symptom**: `Failed to fetch OpenAPI spec: timeout` **Fix**: ```bash theme={null} # Increase timeout (default: 30s) OPENAPI_FETCH_TIMEOUT=60 # Or use local spec file instead openapi_tools: my_api: spec_path: config/openapi_specs/my_api.yaml # Local file ``` ### Circuit Breaker Triggered **Symptom**: `Circuit breaker open for https://api.example.com` **Debug**: ```bash theme={null} # Check recent errors docker logs shannon-llm-service-1 --tail 100 | grep -i "circuit\|failure" ``` **Fix**: * Wait 60 seconds for automatic recovery * Fix underlying API issues * Increase timeout if API is slow: ```yaml theme={null} timeout_seconds: 60 ``` ### Rate Limit Exceeded **Symptom**: `Rate limit exceeded for tool my_tool` **Fix**: ```yaml theme={null} # Increase limit openapi_tools: my_api: rate_limit: 120 # 120 requests per minute ``` ### Authentication Failures **Symptom**: `401 Unauthorized` or `403 Forbidden` **Debug**: ```bash theme={null} # Check environment variables env | grep API_KEY # Test token manually curl -H "Authorization: Bearer $YOUR_TOKEN" https://api.example.com/endpoint ``` **Common causes**: * Environment variable not set * Token expired * Wrong auth type (should be `bearer` not `api_key`) * Missing `Bearer` prefix for API key auth ## Examples ### Example 1: GitHub API ```yaml theme={null} openapi_tools: github: enabled: true spec_url: https://raw.githubusercontent.com/github/rest-api-description/main/descriptions/api.github.com/api.github.com.json auth_type: bearer auth_config: token: "${GITHUB_TOKEN}" category: development rate_limit: 60 operations: - repos/get - repos/list-for-user - issues/list-for-repo ``` **Usage**: ```bash theme={null} ./scripts/submit_task.sh "List all repositories for user octocat" ``` ### Example 2: OpenWeather API ```yaml theme={null} openapi_tools: openweather: enabled: true spec_url: https://api.openweathermap.org/data/3.0/openapi.json auth_type: api_key auth_config: api_key_name: appid api_key_location: query api_key_value: "${OPENWEATHER_API_KEY}" category: data rate_limit: 60 ``` **Usage**: ```bash theme={null} ./scripts/submit_task.sh "What's the weather forecast for Tokyo?" ``` ### Example 3: Internal API with Vendor Adapter ```yaml theme={null} openapi_tools: internal_analytics: enabled: true spec_path: config/openapi_specs/internal_analytics.yaml auth_type: bearer auth_config: vendor: mycompany # Triggers vendor adapter token: "${ANALYTICS_API_TOKEN}" extra_headers: X-Tenant-ID: "{{body.tenant_id}}" category: analytics base_cost_per_use: 0.005 ``` **Vendor Adapter** (`python/llm-service/llm_service/tools/vendor_adapters/mycompany.py`): ```python theme={null} class MyCompanyAdapter: def transform_body(self, body, operation_id, prompt_params): # Add tenant context if prompt_params and "tenant_id" in prompt_params: body["tenant_id"] = prompt_params["tenant_id"] # Transform metric names if "metrics" in body: body["metrics"] = [f"myco:{m}" for m in body["metrics"]] return body ``` ## Security Best Practices ```yaml theme={null} # ❌ Bad auth_config: token: "sk-1234567890" # ✅ Good auth_config: token: "${API_TOKEN}" ``` ```bash theme={null} # Production OPENAPI_ALLOWED_DOMAINS=api.trusted.com,api.partner.com # Not: OPENAPI_ALLOWED_DOMAINS=* ``` ```yaml theme={null} rate_limit: 60 # Match your API provider's limits ``` * Shannon automatically upgrades HTTP to HTTPS for external APIs * localhost/127.0.0.1 allowed on HTTP for development ```yaml theme={null} max_response_bytes: 10485760 # 10MB default ``` ## See Also Complete tool integration guide Domain-specific integrations Other extension methods Test examples and validation ## Quick Reference ```bash theme={null} # Validate spec curl -X POST http://localhost:8000/tools/openapi/validate \ -d '{"spec_url": "https://api.example.com/openapi.json"}' | jq . # List registered tools curl http://localhost:8000/tools/list | jq . # Get tool schema curl http://localhost:8000/tools/myTool/schema | jq . # Execute tool curl -X POST http://localhost:8000/tools/execute \ -d '{"tool_name":"myTool","parameters":{...}}' | jq . # Test via workflow ./scripts/submit_task.sh "Your query using the tool" ``` **Need Help?** * Report issues: [GitHub Issues](https://github.com/Kocoro-lab/Shannon/issues) * Examples: `tests/e2e/06_openapi_petstore_test.sh` # Build a Deep Research Agent Source: https://docs.shannon.run/en/tutorials/research-assistant Use Shannon's ResearchWorkflow to build SOTA multi-agent deep research with memory, entity filtering, gap filling, citations, verification, and adaptive patterns. # Deep Research Agent This tutorial shows how to use Shannon's **ResearchWorkflow** for comprehensive, citation-backed research reports. ## What You'll Learn * **Multi-stage workflow**: Memory retrieval → Refine → Decompose → Execute → Citations → Gap Filling → Synthesis → Verification * **Adaptive patterns**: React, Parallel, and Hybrid execution based on complexity * **Advanced features**: Entity filtering, gap-filling, context compression, react-per-task mode * **Quality controls**: Citation requirements, coverage enforcement, claim verification * **Language matching**: Automatically responds in the user's query language ## Prerequisites * Shannon stack running (Docker Compose) * Gateway reachable at `http://localhost:8080` * Auth defaults: * Docker Compose: authentication is disabled by default (`GATEWAY_SKIP_AUTH=1`). * Local builds: authentication is enabled by default. Set `GATEWAY_SKIP_AUTH=1` to disable auth, or include an API key header `-H "X-API-Key: $API_KEY"`. ## Workflow Architecture The ResearchWorkflow combines multiple stages to produce high-quality, cited research: Research Workflow **Key Features:** * **Memory-aware**: Uses conversation history for contextual research * **Entity-focused**: Filters results when entity is detected (e.g., specific company/product) * **Self-correcting**: Gap filling ensures comprehensive coverage * **Quality-enforced**: Per-area coverage validation (≥600 chars, ≥2 citations per section) ## Quick Start (HTTP) ```bash theme={null} # Submit a research task (default “standard” strategy) curl -X POST http://localhost:8080/api/v1/tasks \ -H "Content-Type: application/json" \ -d '{ "query": "Latest breakthroughs in quantum error correction", "context": { "force_research": true } }' # Submit and get a ready-to-use SSE stream URL (recommended) # Tip: include force_research to route to ResearchWorkflow (otherwise router may select Supervisor) curl -s -X POST http://localhost:8080/api/v1/tasks/stream \ -H "Content-Type: application/json" \ -d '{ "query": "What are the main transformer architecture trends in 2025?", "context": { "force_research": true } }' | jq ``` ### Stream Events (SSE) ```bash theme={null} # After the /tasks/stream call, use "stream_url" from the response: curl -N "http://localhost:8080/api/v1/stream/sse?workflow_id=task-..." ``` Watch for: * `LLM_OUTPUT`: final synthesis text * `DATA_PROCESSING`: progress/usage * `WORKFLOW_COMPLETED`: completion ## Execution Patterns Shannon selects the optimal execution pattern based on task complexity and dependencies: ### React Pattern (complexity \< 0.5) Iterative reason→act→observe loop for simpler research. ```bash theme={null} # Example: Basic conceptual query triggers React curl -X POST http://localhost:8080/api/v1/tasks \ -H "Content-Type: application/json" \ -d '{ "query": "What is quantum computing?", "context": { "force_research": true } }' ``` **Behavior**: Single agent performs iterative reasoning, web searches, and observation cycles (default 5 iterations). *** ### Parallel Pattern (no dependencies) Concurrent subtask execution for multi-faceted research. ```bash theme={null} # Example: Multi-aspect comparison triggers Parallel curl -X POST http://localhost:8080/api/v1/tasks \ -H "Content-Type: application/json" \ -d '{ "query": "Compare LangChain and AutoGen frameworks: architecture, pricing, community, performance", "context": { "force_research": true } }' ``` **Behavior**: Decomposition creates independent subtasks (e.g., "Analyze LangChain architecture", "Analyze AutoGen pricing"), executed concurrently with web\_search injection for citations. *** ### Hybrid Pattern (has dependencies) Topological sort + fan-in/fan-out for sequential research with dependencies. ```bash theme={null} # Example: Query with logical dependencies triggers Hybrid curl -X POST http://localhost:8080/api/v1/tasks \ -H "Content-Type: application/json" \ -d '{ "query": "Analyze company X market strategy, then evaluate competitive response, then forecast next moves", "context": { "force_research": true } }' ``` **Behavior**: Executes subtasks in dependency order, passing previous results downstream. *** ### React Per Task (deep research mode) Mini ReAct loops per subtask for high-complexity research. **Trigger conditions:** 1. Manual: `context.react_per_task = true` 2. Auto-enable: complexity > 0.7 AND strategy in `{deep, academic}` ```bash theme={null} # Example: Enable react_per_task for iterative per-area research curl -X POST http://localhost:8080/api/v1/tasks \ -H "Content-Type: application/json" \ -d '{ "query": "Comprehensive analysis of transformer architecture evolution 2017-2025", "context": { "force_research": true, "react_per_task": true, "react_max_iterations": 6 }, "research_strategy": "deep" }' ``` **Behavior**: Each parallel subtask runs its own ReAct loop (reason→act→observe), enabling iterative exploration per research area. More thorough but higher token cost. *** ## Strategy Presets Presets provide opinionated defaults for different research depths. The Gateway validates and maps these to workflow context. ```bash theme={null} # Quick strategy (minimal iterations) curl -X POST http://localhost:8080/api/v1/tasks \ -H "Content-Type: application/json" \ -d '{ "query": "What is quantum error correction?", "context": { "force_research": true }, "research_strategy": "quick" }' # Deep strategy with overrides curl -X POST http://localhost:8080/api/v1/tasks \ -H "Content-Type: application/json" \ -d '{ "query": "Compare LangChain and AutoGen frameworks", "context": { "force_research": true, "react_per_task": true, "react_max_iterations": 6 }, "research_strategy": "deep", "enable_verification": true }' # Academic strategy (auto-enables react_per_task for complexity > 0.7) curl -X POST http://localhost:8080/api/v1/tasks \ -H "Content-Type: application/json" \ -d '{ "query": "Survey of attention mechanisms in modern NLP architectures", "context": { "force_research": true }, "research_strategy": "academic" }' ``` **Quick strategy behavior:** uses the lightweight `quick_research_agent` prompt, always takes the parallel execution path, and skips the iterative coverage loop and gap-filling stages—optimized for fast summaries where depth is not required. **Available Presets**: `quick` | `standard` | `deep` | `academic` | Strategy | react\_max\_iterations | max\_concurrent\_agents | verification | gap\_filling | | -------- | ---------------------- | ----------------------- | ------------ | ------------------------------------ | | quick | 2 | 3 | ✗ | ✗ | | standard | 3 | 5 | ✓ | ✓ (max\_gaps: 3, max\_iterations: 2) | | deep | 4 | 6 | ✓ | ✓ (max\_gaps: 2, max\_iterations: 2) | | academic | 5 | 8 | ✓ | ✓ (max\_gaps: 3, max\_iterations: 2) | **Core Parameters**: * `max_concurrent_agents` (1–20): Controls maximum concurrency for parallel subtasks (only applies when multiple subtasks are executed) * `react_max_iterations` (2–8, default 5): Controls ReAct loop depth per task (independent parameter, no prerequisites) **Advanced Flags**: * `react_per_task` (bool): Enable mini ReAct loops per subtask (auto-enabled for complexity > 0.7 + deep/academic) * `enable_verification` (bool): Enable claim verification against citations * `budget_agent_max` (int): Optional per-agent token budget with enforcement **Gap Filling Configuration**: * `gap_filling_enabled` (bool): Enable/disable gap filling * `gap_filling_max_gaps` (1–20): Maximum number of gaps to detect * `gap_filling_max_iterations` (1–5): Maximum retry iterations per gap * `gap_filling_check_citations` (bool): Check citation density as gap indicator **Default preset**: `standard` (if not specified). Presets seed flags only if not already set in context. The Gateway accepts top‑level `research_strategy` on both `/api/v1/tasks` and `/api/v1/tasks/stream` and maps it into context. Preset defaults are loaded from `config/research_strategies.yaml`. Quick strategy skips the iterative coverage loop and gap-filling stages. ## Python SDK ### CLI ```bash theme={null} python -m shannon.cli --base-url http://localhost:8080 \ submit "Latest quantum computing breakthroughs" \ --force-research \ --research-strategy deep --enable-verification ``` ### Programmatic ```python theme={null} from shannon import ShannonClient client = ShannonClient(base_url="http://localhost:8080") handle = client.submit_task( "Compare LangChain and AutoGen frameworks", context={ "force_research": True, "research_strategy": "deep", "react_max_iterations": 6, "enable_verification": True, }, ) final = client.wait(handle.task_id) print(final.result) client.close() ``` ## Response Format Typical status payloads returned by `GET /api/v1/tasks/{id}` include the synthesized result, metadata (citations, verification), model/provider, and timestamps. ```json theme={null} { "task_id": "task-00000000-0000-0000-0000-000000000000", "status": "TASK_STATUS_COMPLETED", "result": "... final synthesis text ...", "metadata": { "citations": [ { "url": "https://example.com/article", "title": "Source Title", "source": "example.com", "quality_score": 0.92, "credibility_score": 0.85 } ], "verification": { "total_claims": 10, "overall_confidence": 0.78, "supported_claims": 7, "unsupported_claims": [ "Claim text lacking sufficient evidence ..." ], "claim_details": [ { "claim": "Concrete claim text ...", "confidence": 0.81, "supporting_citations": [1, 3], "conflicting_citations": [] } ] } }, "created_at": "2025-11-07T12:00:00Z", "updated_at": "2025-11-07T12:02:00Z", "usage": { "input_tokens": 1234, "output_tokens": 5678, "total_tokens": 6912, "estimated_cost": 0.0234 }, "model_used": "gpt-5-mini-2025-08-07", "provider": "openai" } ``` Notes: * `result` is the final synthesized markdown/text. * `metadata.citations` lists collected sources (all are included in the Sources section of the result). * `metadata.verification` appears when verification is enabled and completed. * In Gateway status responses, `created_at` / `updated_at` reflect response time; authoritative run timing and totals are persisted in the database (events/timeline). * `usage` summarizes token usage and may include cost. Gateway status may expose `estimated_cost`; workflow metadata may include `cost_usd`. * `model_used` and `provider` reflect the model/provider chosen during synthesis. ## Advanced Features ### Memory Retrieval **Hierarchical Memory** (priority): * Combines recent (last 5 messages) + semantic (top 5 relevant, similarity ≥ 0.75) * Enables coherent multi-turn research with conversation context * Fallback: Session memory (last 20 messages) **Context Compression**: * Auto-triggers for sessions >20 messages * LLM summarizes conversation (target: 37.5% of window) * Prevents context window overflow in long conversations *** ### Entity Filtering When a specific entity is detected (e.g., company "Acme Analytics"): **Query Refinement**: * Detects canonical name, exact search queries, official domains, disambiguation terms * Example: `canonical_name: "Acme Analytics"`, `official_domains: ["acme.com"]` **Citation Filtering**: * Scoring: domain match +0.6, alias in URL +0.4, text match +0.4 * Threshold: 0.3 (any single match passes) * Safety floor: minKeep=8 (backfilled by quality × credibility) * Official domains always preserved (bypass threshold) **Result Pruning**: * Filters off-entity tool results (e.g., removes "Mind Inc" when searching for "Acme Mind") * Keeps reasoning-only outputs; prunes off-entity tool-driven results *** ### Gap Filling **Auto-detection** (max 2 iterations): * Missing section headings (`### Area Name`) * Gap indicator phrases ("limited information", "insufficient data") * Low citation density (\< 2 inline citations per section) **Resolution**: 1. Build targeted queries: `"Find detailed information about: "` 2. Execute focused ReAct loops (max 3 iterations per gap) 3. Re-collect citations (global deduplication with original citations) 4. Re-synthesize with combined evidence using large tier ```text theme={null} Query: "Analyze company X market strategy, competitive landscape, and leadership" Initial synthesis coverage: - Market strategy: ✓ 800 chars, 3 citations - Competitive landscape: ✓ 650 chars, 2 citations - Leadership: ✗ 200 chars, 0 citations ← Gap detected Gap filling triggers: 1. Targeted query: "Find detailed information about: company X leadership" 2. ReAct loop executes focused search 3. Re-synthesize with combined evidence ``` ### Citations **Collection**: * Extracted from `web_search` and `web_fetch` tool outputs * URL/DOI normalization and deduplication * Scoring: quality (recency + completeness) × credibility (domain reputation) * Diversity enforcement (max 3 per domain) **Enforcement**: * Minimum 6 inline citations per report (clamped by available, floor 3) * Per-area requirement: ≥2 inline citations per section * Sources section lists all collected citations: * "Used inline" (cited in text) * "Additional source" (collected but not cited) *** ### Synthesis Continuation **Trigger**: Model stops early with incomplete output **Detection** (`looksComplete` validation): 1. Must end with sentence punctuation (`.`, `!`, `?`, `。`) 2. No dangling conjunctions (`and, but, however, therefore...`) 3. Every research area has subsection with ≥600 chars and ≥2 citations **Continuation**: * Adaptive margin: min(25% of effective\_max\_completion, 300 tokens) * Prompt: "Continue from last sentence; maintain headings and citation style" * Uses large tier for quality (gpt-4.1/opus) *** ### Verification **Claim Extraction**: * Identifies factual assertions from synthesis * Cross-references against collected citations **Confidence Scoring**: * Weighted by citation credibility * Flags conflicts and unsupported claims * Per-claim details: supporting/conflicting citations **Example output**: ```json theme={null} { "verification": { "total_claims": 10, "overall_confidence": 0.78, "supported_claims": 7, "unsupported_claims": ["Claim lacking evidence..."], "claim_details": [ { "claim": "Transformer architecture introduced in 2017", "confidence": 0.95, "supporting_citations": [1, 3], "conflicting_citations": [] } ] } } ``` **Enable**: Set `enable_verification: true` in context. *** ### Language Matching * Detects user query language (heuristic) * Synthesis responds in the same language * Supports English, Chinese, Japanese, Korean, Arabic, Russian, Spanish, French, German (heuristic‑based) * Generic instructions ensure robustness across languages ## Behavior Guarantees Hierarchical memory (recent + semantic) injected when available Automatic for sessions over 20 messages to prevent context overflow Each research area has dedicated subsection with minimum 600 chars and 2 citations Auto-detects and re-searches undercovered areas (max 2 iterations) Response matches user query language Token usage and cost are aggregated and persisted; per-agent budgets enforced when configured Triggers only when synthesis is incomplete and capacity nearly exhausted When entity detected, filters citations and prunes off-entity results ## Tips & Best Practices * Set `context.force_research=true` to ensure routing to ResearchWorkflow * Start with `standard` preset, adjust based on results * Monitor SSE for progress and token usage * Use `quick` preset for simple queries (saves tokens) * Use `deep` or `academic` for comprehensive research (auto-enables react\_per\_task at high complexity) * Set `budget_agent_max` to enforce per-agent token limits * Enable `react_per_task` manually for iterative per-area exploration * Use sessions to enable memory retrieval * Context compression activates automatically for long conversations * Follow-up queries benefit from hierarchical memory (recent + semantic) ## Troubleshooting **Common Issues**: * **Language defaults to English**: Ensure the query clearly uses the target language * **Missing citations**: Check if the `web_search` tool is available; research workflows auto‑inject `web_search` * **Incomplete coverage**: Gap filling should auto‑trigger; ensure `gap_filling_v1` is enabled * **High token usage**: Enable `budget_agent_max` or use `quick` preset; disable `react_per_task` **Validation**: * Invalid `web_search` `search_type`: Sanitized (falls back to `auto`) * Duplicate citations: Handled via URL/DOI normalization * Entity detection: Performed during refinement when present in the query **Version Gates** (internal feature flags): * Memory retrieval: `memory_retrieval_v1` * Session memory fallback: `session_memory_v1` * Context compression: `context_compress_v1` * Gap filling: `gap_filling_v1` ## Next Steps REST API Documentation Adding Custom Tools Vendor-specific Integrations **Related Resources**: * GitHub: [https://github.com/Kocoro-lab/Shannon](https://github.com/Kocoro-lab/Shannon) * Company: [https://kocoro.ai](https://kocoro.ai) # Swarm Multi-Agent Workflow Source: https://docs.shannon.run/en/tutorials/swarm-workflow Step-by-step guide to running persistent multi-agent swarm workflows in Shannon with Lead Agent coordination # Swarm Multi-Agent Workflow This tutorial shows how to use Shannon's **SwarmWorkflow** to deploy persistent, collaborating agents coordinated by an LLM-powered Lead Agent. Agents work in parallel with inter-agent messaging, a shared workspace, and dynamic task reassignment. ## What You'll Learn * How to submit a swarm task via API and Python SDK * How the Lead Agent coordinates agents through events * How to monitor agent progress with SSE streaming * How to configure swarm parameters and budget controls * Real-world use cases and best practices ## Prerequisites * Shannon stack running (Docker Compose) * Gateway reachable at `http://localhost:8080` * Swarm enabled in `config/features.yaml` (enabled by default) * Auth defaults: * Docker Compose: authentication is disabled by default (`GATEWAY_SKIP_AUTH=1`). * Local builds: authentication is enabled by default. Set `GATEWAY_SKIP_AUTH=1` to disable auth, or include an API key header `-H "X-API-Key: $API_KEY"`. ## Quick Start ### Submit a Swarm Task Submit a task with `force_swarm: true` in the context to route it to the SwarmWorkflow: ```bash theme={null} curl -X POST http://localhost:8080/api/v1/tasks \ -H "Content-Type: application/json" \ -d '{ "query": "Compare AI chip markets across US, Japan, and South Korea", "session_id": "swarm-demo-001", "context": { "force_swarm": true } }' ``` **Response:** ```json theme={null} { "task_id": "task-abc123...", "status": "STATUS_CODE_OK", "message": "Task submitted successfully", "created_at": "2025-11-10T10:00:00Z" } ``` ### Stream Progress Events Connect to the SSE stream to watch agents work in real-time: ```bash theme={null} curl -N "http://localhost:8080/api/v1/stream/sse?workflow_id=task-abc123..." ``` You will see events like: ```text theme={null} data: {"type":"WORKFLOW_STARTED","agent_id":"swarm-supervisor","message":"Lead Agent initializing team"} data: {"type":"LEAD_DECISION","agent_id":"swarm-lead","message":"Creating initial plan with 3 tasks"} data: {"type":"TASKLIST_UPDATED","agent_id":"swarm-lead","message":"Task graph updated: 4 tasks (3 research + 1 synthesis)"} data: {"type":"TEAM_STATUS","agent_id":"swarm-lead","message":"Spawned agent takao"} data: {"type":"AGENT_STARTED","agent_id":"takao","message":"Agent takao started"} data: {"type":"TEAM_STATUS","agent_id":"swarm-lead","message":"Spawned agent mitaka"} data: {"type":"AGENT_STARTED","agent_id":"mitaka","message":"Agent mitaka started"} data: {"type":"TEAM_STATUS","agent_id":"swarm-lead","message":"Spawned agent kichijoji"} data: {"type":"AGENT_STARTED","agent_id":"kichijoji","message":"Agent kichijoji started"} data: {"type":"PROGRESS","agent_id":"takao","message":"Agent takao progress: iteration 1/25, action: tool_call"} data: {"type":"AGENT_COMPLETED","agent_id":"takao","message":"Agent takao completed"} data: {"type":"LEAD_DECISION","agent_id":"swarm-lead","message":"Assigning synthesis task to takao"} data: {"type":"AGENT_COMPLETED","agent_id":"mitaka","message":"Agent mitaka completed"} data: {"type":"AGENT_COMPLETED","agent_id":"kichijoji","message":"Agent kichijoji completed"} data: {"type":"LEAD_DECISION","agent_id":"swarm-lead","message":"All tasks complete, finalizing"} data: {"type":"WORKFLOW_COMPLETED","agent_id":"swarm-supervisor","message":"All done"} ``` ### Retrieve the Result ```bash theme={null} curl "http://localhost:8080/api/v1/tasks/task-abc123..." ``` **Response:** ```json theme={null} { "task_id": "task-abc123...", "status": "TASK_STATUS_COMPLETED", "result": "## AI Chip Market Comparison\n\n### United States\nThe US market is dominated by NVIDIA...\n\n### Japan\nJapan focuses on edge AI...\n\n### South Korea\nSouth Korea leverages Samsung...", "metadata": { "workflow_type": "swarm", "total_agents": 3, "total_tokens": 598235, "model_breakdown": [ { "model": "claude-haiku-4-5-20251001", "provider": "anthropic", "executions": 38, "tokens": 297524, "cost_usd": 0.372 }, { "model": "shannon_web_search", "provider": "shannon-scraper", "executions": 12, "tokens": 90000, "cost_usd": 0.048 } ] }, "usage": { "input_tokens": 289164, "output_tokens": 309071, "total_tokens": 598235, "estimated_cost": 0.780 } } ``` ## Submit + Stream in One Call For frontend applications, use the combined submit-and-stream endpoint: ```bash theme={null} curl -s -X POST http://localhost:8080/api/v1/tasks/stream \ -H "Content-Type: application/json" \ -d '{ "query": "Analyze the competitive landscape of cloud AI platforms: AWS, Azure, and GCP", "context": { "force_swarm": true } }' | jq ``` **Response:** ```json theme={null} { "workflow_id": "task-def456...", "task_id": "task-def456...", "stream_url": "/api/v1/stream/sse?workflow_id=task-def456..." } ``` Then connect to the stream URL for real-time events: ```bash theme={null} curl -N "http://localhost:8080/api/v1/stream/sse?workflow_id=task-def456..." ``` ## Python SDK ### Basic Usage ```python theme={null} from shannon import ShannonClient client = ShannonClient(base_url="http://localhost:8080") # Submit a swarm task handle = client.submit_task( "Compare AI chip markets across US, Japan, and South Korea", force_swarm=True, session_id="swarm-demo-001", ) # Wait for completion and get result result = client.wait(handle.task_id) print(result.result) client.close() ``` ### With Streaming ```python theme={null} from shannon import ShannonClient client = ShannonClient(base_url="http://localhost:8080") # Submit and get stream URL in one call handle, stream_url = client.submit_and_stream( "Analyze the competitive landscape of cloud AI platforms", force_swarm=True, ) # Stream events in real-time for event in client.stream(handle.workflow_id): if event.type == "AGENT_STARTED": print(f"Agent started: {event.agent_id}") elif event.type == "LEAD_DECISION": print(f"Lead decision: {event.message}") elif event.type == "PROGRESS": print(f"Progress: {event.message}") elif event.type == "AGENT_COMPLETED": print(f"Agent completed: {event.agent_id}") elif event.type == "WORKFLOW_COMPLETED": print("Swarm workflow completed") break # Get final result result = client.get_status(handle.task_id) print(result.result) client.close() ``` ### With Custom Context ```python theme={null} handle = client.submit_task( "Research renewable energy policies in the EU, US, and China", force_swarm=True, context={ "model_tier": "medium", # Use medium-tier models for agents }, ) ``` ## How Agents Collaborate ### Lead Agent Coordination The Lead Agent acts as an event-driven coordinator. It does not execute tasks itself, but rather plans, assigns, and reassigns work based on events: * When an agent becomes **idle**, the Lead checks for pending tasks with met dependencies and assigns the next one * When an agent **completes**, the Lead evaluates whether to reassign it, shut it down, or revise the plan * On periodic **checkpoints** (every 120s), the Lead reviews overall progress and can adjust the plan * The Lead skips unnecessary LLM calls when there are no idle agents and no actionable pending tasks ### Team Roster Each agent receives a team roster showing all agents and their assignments. This enables agents to know who to contact for specific information: ```text theme={null} ## Your Team (shared session workspace) - **takao (you)**: "Research US AI chip market" - mitaka: "Research Japan AI chip market" - kichijoji: "Research South Korea AI chip market" ``` ### Publishing Findings Agents share discoveries via the shared workspace. These appear in every agent's prompt context: ```text theme={null} ## Shared Findings - takao: NVIDIA dominates US with 80% market share... - mitaka: Japan focuses on edge AI chips with Preferred Networks leading... ``` ### Sending Direct Messages Agents can send direct messages to specific teammates: ```text theme={null} ## Inbox Messages - From mitaka (info): {"message": "Check Samsung's foundry plans for AI chips"} ``` ### Requesting Help When an agent needs additional support, it requests help from the Lead Agent: ```json theme={null} {"action": "request_help", "help_description": "Need help analyzing EU regulatory impact on AI chips", "help_skills": ["web_search"]} ``` The Lead Agent evaluates the request and may spawn a new agent, reassign an existing idle agent, or add the subtask to the pending task queue. ## Configuration ### features.yaml ```yaml theme={null} workflows: swarm: enabled: true max_agents: 10 # Max total agents (initial + dynamic) max_iterations_per_agent: 25 # Max reason-act loops per agent agent_timeout_seconds: 1800 # Per-agent timeout (30 minutes) max_messages_per_agent: 20 # Max P2P messages per agent workspace_snippet_chars: 800 # Max chars per workspace entry in prompt workspace_max_entries: 5 # Max recent entries shown to agents max_total_llm_calls: 200 # Global LLM call budget max_total_tokens: 1000000 # Global token budget (1M) max_wall_clock_minutes: 30 # Max wall-clock time ``` ### Configuration Parameters | Parameter | Default | Range | Description | | -------------------------- | --------- | -------------- | ------------------------------------------------------ | | `enabled` | `true` | `true`/`false` | Enable or disable swarm workflows | | `max_agents` | `10` | 1-50 | Total agent cap including dynamically spawned agents | | `max_iterations_per_agent` | `25` | 1-100 | Maximum reason-act cycles per agent | | `agent_timeout_seconds` | `1800` | 60-7200 | Per-agent wall-clock timeout | | `max_messages_per_agent` | `20` | 1-100 | Cap on P2P messages an agent can send | | `workspace_snippet_chars` | `800` | 100-4000 | Truncation limit for workspace entries in agent prompt | | `workspace_max_entries` | `5` | 1-20 | Number of recent workspace entries shown per topic | | `max_total_llm_calls` | `200` | 10-1000 | Maximum LLM calls across all agents in the swarm | | `max_total_tokens` | `1000000` | 10000-10000000 | Maximum tokens consumed across all agents | | `max_wall_clock_minutes` | `30` | 1-120 | Maximum wall-clock time for the entire swarm execution | ## Real-World Use Cases Agents review, implement, and test code collaboratively with sandboxed execution. Bull/bear analysts, sentiment agents, and a portfolio manager synthesize investment insights. Parallel data pipelines with sandboxed Python execution, JSON querying, and statistical analysis. Monitor competitor websites, pricing, and social media with automatic cross-sharing of discoveries. ### Example: Collaborative Code Review ```bash theme={null} curl -X POST http://localhost:8080/api/v1/tasks \ -H "Content-Type: application/json" \ -d '{ "query": "Review the Python files in /workspace/src for security vulnerabilities, code quality issues, and missing test coverage. Write fixes and add tests.", "context": { "force_swarm": true } }' ``` The Lead Agent creates tasks for each concern (security audit, code quality, test coverage), assigns agents with the `developer` role, and creates a final synthesis task that depends on all reviews completing. ### Example: Multi-Site Price Monitoring ```bash theme={null} curl -X POST http://localhost:8080/api/v1/tasks \ -H "Content-Type: application/json" \ -d '{ "query": "Compare pricing and features of AWS, Azure, and GCP for a startup needing GPU instances, object storage, and managed Kubernetes", "context": { "force_swarm": true, "model_tier": "medium" } }' ``` ## Understanding the Response Metadata The swarm workflow returns metadata with per-model execution breakdown and token usage: ```json theme={null} { "metadata": { "workflow_type": "swarm", "total_agents": 3, "total_tokens": 598235, "model_breakdown": [ { "model": "claude-haiku-4-5-20251001", "provider": "anthropic", "executions": 38, "tokens": 297524, "cost_usd": 0.372 }, { "model": "shannon_web_search", "provider": "shannon-scraper", "executions": 12, "tokens": 90000, "cost_usd": 0.048 } ] }, "usage": { "input_tokens": 289164, "output_tokens": 309071, "total_tokens": 598235, "estimated_cost": 0.780 } } ``` | Field | Description | | --------------------------------------- | --------------------------------------------- | | `metadata.workflow_type` | Always `"swarm"` for swarm workflows | | `metadata.total_agents` | Number of agents that participated | | `metadata.total_tokens` | Total tokens consumed across the entire swarm | | `metadata.model_breakdown[]` | Per-model execution summary | | `metadata.model_breakdown[].model` | Model identifier | | `metadata.model_breakdown[].provider` | Provider name | | `metadata.model_breakdown[].executions` | Number of LLM calls with this model | | `metadata.model_breakdown[].tokens` | Tokens consumed by this model | | `metadata.model_breakdown[].cost_usd` | Estimated cost in USD | | `usage.input_tokens` | Total input tokens across all agents | | `usage.output_tokens` | Total output tokens across all agents | | `usage.total_tokens` | Total tokens (input + output) | | `usage.estimated_cost` | Total estimated cost in USD | ## Tips and Best Practices * Set `context.force_swarm: true` to route to SwarmWorkflow * Start with default configuration and adjust based on results * Monitor SSE events to understand Lead Agent decisions and agent behavior * Use sessions (`session_id`) for multi-turn swarm conversations * Watch for `LEAD_DECISION` events to understand coordination logic * Reduce `max_iterations_per_agent` for faster completion (e.g., 10-15) * Use `model_tier: "small"` for cost-effective exploration * Swarm agents use MEDIUM tier by default for balanced quality and speed * Keep workspace entries concise -- large entries consume prompt tokens * Knowledge deduplication automatically reduces redundant fetches and searches * The Lead skips checkpoint LLM calls when no agents are idle and no tasks are actionable * Set `max_total_llm_calls` to limit total LLM invocations across all agents * Set `max_total_tokens` to cap token consumption for the entire swarm * Set `max_wall_clock_minutes` to enforce a hard time limit * The Lead Agent receives budget info and makes cost-aware decisions automatically * Simple Q\&A tasks (use standard workflow instead) * Tasks with a single clear answer (swarm adds unnecessary overhead) * Cost-sensitive scenarios where token usage matters * Tasks where agent collaboration does not add value ## Troubleshooting **Common Issues**: * **Swarm not triggering**: Ensure `force_swarm: true` is in the `context` object and swarm is enabled in `features.yaml` * **Agents timing out**: Increase `agent_timeout_seconds` for complex tasks (default is 1800s / 30 minutes) * **Too many agents**: Reduce the number of subtasks by simplifying your query, or lower `max_agents` * **High token usage**: Lower `max_iterations_per_agent`, use `model_tier: "small"`, or reduce `max_total_tokens` * **Agents stuck in loops**: Convergence detection (3 consecutive non-tool iterations) catches this automatically * **Budget exceeded**: Check `max_total_llm_calls` and `max_total_tokens` settings; the Lead will attempt graceful shutdown when budget is tight * **Redundant searches**: Knowledge deduplication should catch this; if persisting, check that agents have access to the shared workspace ## Fallback Behavior If the swarm workflow fails (planning error, all agents fail, etc.), Shannon automatically falls back to standard DAG/Supervisor workflow routing. The `force_swarm` flag is removed from context to prevent recursive failures. ## Next Steps Understand swarm architecture in depth Multi-stage research with citations Full API documentation # Workflow Templates Source: https://docs.shannon.run/en/tutorials/templates Create deterministic, zero-token workflows using Shannon's template system ## Overview Shannon's template system enables **deterministic, zero-token workflows** for common patterns. Templates define structured workflows in YAML that execute without AI decomposition, providing significant cost savings for repeatable tasks. Templates bypass LLM calls for workflow routing Predictable, repeatable workflow behavior Per-node token limits with automatic degradation Parallel execution with dependency management ## When to Use Templates **Use templates when:** * Workflows are repeatable with known structure * You want to eliminate decomposition token costs * Predictable execution order is required * Budget control per workflow stage is needed **Use AI decomposition when:** * Task structure is unknown or variable * Complex reasoning about workflow design is needed * One-off or highly dynamic tasks ## Template Structure Templates are YAML files with this structure: ```yaml theme={null} name: "workflow_name" version: "1.0.0" description: "What this workflow does" defaults: model_tier: "medium" # small | medium | large budget_agent_max: 6000 # Total token budget require_approval: false # Human approval gate nodes: - id: "step_1" type: "simple" # Node type strategy: "react" # Execution strategy tools_allowlist: # Permitted tools - "web_search" budget_max: 1500 # Per-node token limit depends_on: [] # Dependencies - id: "step_2" type: "cognitive" strategy: "chain_of_thought" budget_max: 2000 depends_on: ["step_1"] edges: - from: "step_1" to: "step_2" ``` ### Core Fields | Field | Type | Required | Description | | ------------- | ------ | -------- | -------------------------------- | | `name` | string | Yes | Unique workflow identifier | | `version` | string | Yes | Semantic version (e.g., "1.0.0") | | `description` | string | No | Human-readable description | | `defaults` | object | No | Global settings for all nodes | | `nodes` | array | Yes | Workflow execution units | | `edges` | array | No | Explicit dependency connections | ### Default Settings ```yaml theme={null} defaults: model_tier: "medium" # Model size for all nodes budget_agent_max: 6000 # Total workflow token budget require_approval: false # Require human approval before execution ``` ## Node Types Shannon supports four node types for different execution patterns: ### Simple Nodes Single-task execution with direct tool invocation. ```yaml theme={null} - id: "fetch_data" type: "simple" strategy: "react" tools_allowlist: - "web_search" budget_max: 1000 depends_on: [] ``` **Use for:** Data fetching, simple transformations, tool-based operations. ### Cognitive Nodes Complex reasoning with multi-step analysis. ```yaml theme={null} - id: "analyze" type: "cognitive" strategy: "chain_of_thought" budget_max: 2500 depends_on: ["fetch_data"] degrade_to: "react" # Fallback if budget exceeded retries: 1 ``` **Use for:** Analysis, reasoning, synthesis tasks. ### DAG Nodes Parallel execution with internal task dependencies. ```yaml theme={null} - id: "parallel_analysis" type: "dag" budget_max: 5000 depends_on: ["understand"] metadata: tasks: - id: "collect" tools_allowlist: ["web_search"] depends_on: [] - id: "quantitative" tools_allowlist: ["calculator"] depends_on: ["collect"] - id: "qualitative" tools_allowlist: [] depends_on: ["collect"] - id: "cross_validate" depends_on: ["quantitative", "qualitative"] ``` **Use for:** Parallel processing, fan-out/fan-in patterns. ### Supervisor Nodes Hierarchical task decomposition and coordination. ```yaml theme={null} - id: "orchestrate" type: "supervisor" strategy: "reflection" budget_max: 3000 depends_on: ["parallel_analysis"] ``` **Use for:** Result aggregation, quality control, synthesis. ## Execution Strategies Strategies define how nodes process tasks: | Strategy | Token Range | Description | | ------------------ | ----------- | --------------------------------------- | | `react` | \~500-1500 | Tool-based reasoning with action cycles | | `chain_of_thought` | \~1500-3000 | Sequential step-by-step reasoning | | `tree_of_thoughts` | \~3000-8000 | Explores multiple reasoning branches | | `reflection` | \~1000-2000 | Self-evaluation and refinement | | `debate` | \~2000-4000 | Multi-perspective analysis | ### Automatic Degradation When budget constraints are reached, strategies degrade automatically: ``` tree_of_thoughts → chain_of_thought → react ``` Configure explicit degradation: ```yaml theme={null} - id: "explore" type: "cognitive" strategy: "tree_of_thoughts" budget_max: 2500 degrade_to: "chain_of_thought" # Fallback strategy retries: 1 # Retry count on degradation ``` ## Creating Templates Create a YAML file in `config/workflows/examples/` or your custom directory: ```yaml theme={null} # config/workflows/examples/my_workflow.yaml name: "my_workflow" version: "1.0.0" description: "Custom analysis workflow" defaults: model_tier: "medium" budget_agent_max: 5000 require_approval: false nodes: - id: "intake" type: "simple" strategy: "react" tools_allowlist: - "web_search" budget_max: 1500 depends_on: [] - id: "analyze" type: "cognitive" strategy: "chain_of_thought" budget_max: 2000 depends_on: ["intake"] - id: "synthesize" type: "cognitive" strategy: "reflection" budget_max: 1500 depends_on: ["analyze"] edges: - from: "intake" to: "analyze" - from: "analyze" to: "synthesize" ``` Templates are loaded at startup via `InitTemplateRegistry`: ```go theme={null} // In orchestrator initialization registry := templates.InitTemplateRegistry( "./config/workflows/builtin", "./config/workflows/examples", // Your templates ) // Validate all templates if err := registry.Finalize(); err != nil { log.Fatal(err) } ``` ```bash theme={null} docker compose -f deploy/compose/docker-compose.yml up -d --force-recreate orchestrator ``` Via gRPC: ```bash theme={null} grpcurl -plaintext -d '{}' localhost:50052 \ shannon.orchestrator.OrchestratorService/ListTemplates ``` ## Using Templates ### Via HTTP Gateway ```bash theme={null} curl -X POST http://localhost:8080/api/v1/tasks \ -H "Content-Type: application/json" \ -d '{ "query": "Analyze the current AI market trends", "context": { "template": "my_workflow", "template_version": "1.0.0" } }' ``` ### Via gRPC ```bash theme={null} grpcurl -plaintext -d '{ "query": "Analyze the current AI market trends", "context": { "template": "my_workflow", "template_version": "1.0.0", "disable_ai": true } }' localhost:50052 shannon.orchestrator.OrchestratorService/SubmitTask ``` Set `disable_ai: true` to enforce template-only execution without AI fallback. ### Via Python SDK ```python theme={null} from shannon import ShannonClient client = ShannonClient(base_url="http://localhost:8080") handle = client.submit_task( "Analyze the current AI market trends", context={ "template": "my_workflow", "template_version": "1.0.0", }, ) result = client.wait(handle.task_id) print(result.result) ``` ## Template Examples ### Simple Analysis Workflow Two-stage pipeline for quick summarization: ```yaml theme={null} name: "simple_analysis" version: "1.0.0" description: "Quick summarization workflow for short briefs" defaults: model_tier: "medium" budget_agent_max: 3000 require_approval: false nodes: - id: "intake" type: "simple" strategy: "react" budget_max: 1000 depends_on: [] - id: "summarize" type: "cognitive" strategy: "chain_of_thought" budget_max: 1200 depends_on: ["intake"] edges: - from: "intake" to: "summarize" ``` ### Research Summary Workflow Four-stage research pipeline with progressive reasoning: ```yaml theme={null} name: "research_summary" version: "1.0.0" description: "Multi-stage research workflow" defaults: model_tier: "medium" budget_agent_max: 6000 require_approval: false nodes: - id: "discover" type: "simple" strategy: "react" tools_allowlist: - "web_search" budget_max: 1500 depends_on: [] - id: "reason" type: "cognitive" strategy: "chain_of_thought" budget_max: 1500 depends_on: ["discover"] - id: "explore" type: "cognitive" strategy: "tree_of_thoughts" budget_max: 2500 degrade_to: "chain_of_thought" retries: 1 depends_on: ["reason"] - id: "finalize" type: "cognitive" strategy: "reflection" budget_max: 1000 depends_on: ["explore"] edges: - from: "discover" to: "reason" - from: "reason" to: "explore" - from: "explore" to: "finalize" ``` ### Parallel DAG Workflow Complex workflow with parallel execution branches: ```yaml theme={null} name: "parallel_analysis" version: "1.0.0" description: "Parallel analysis with convergence" defaults: model_tier: "medium" budget_agent_max: 8000 require_approval: false nodes: - id: "understand" type: "simple" strategy: "react" budget_max: 1000 depends_on: [] - id: "parallel_analysis" type: "dag" budget_max: 5000 depends_on: ["understand"] metadata: tasks: - id: "collect" tools_allowlist: ["web_search"] depends_on: [] - id: "quantitative" tools_allowlist: ["calculator"] depends_on: ["collect"] - id: "qualitative" tools_allowlist: [] depends_on: ["collect"] - id: "cross_validate" depends_on: ["quantitative", "qualitative"] - id: "report" type: "cognitive" strategy: "reflection" budget_max: 2000 degrade_to: "chain_of_thought" depends_on: ["parallel_analysis"] edges: - from: "understand" to: "parallel_analysis" - from: "parallel_analysis" to: "report" ``` ## Template Inheritance Templates can inherit from parent templates using `extends`: ```yaml theme={null} # base_research.yaml name: "base_research" version: "1.0.0" defaults: model_tier: "medium" budget_agent_max: 5000 require_approval: false ``` ```yaml theme={null} # advanced_research.yaml name: "advanced_research" version: "1.0.0" extends: ["base_research"] # Inherits all defaults defaults: budget_agent_max: 10000 # Override specific values nodes: # ... additional nodes ``` Multiple parents are applied in order, with the derived template's values taking precedence. ## Validation Templates are validated at load time for: * YAML syntax correctness * Required field presence * DAG acyclicity (no circular dependencies) * Budget hierarchy (node budgets ≤ agent budget) * Tool registry existence * Variable reference resolution ```go theme={null} registry := templates.InitTemplateRegistry("./templates") // Validates all templates if err := registry.Finalize(); err != nil { log.Fatalf("Template validation failed: %v", err) } ``` ## Best Practices Each node should do one thing well: ```yaml theme={null} # Good: Focused nodes - id: "search" type: "simple" strategy: "react" tools_allowlist: ["web_search"] - id: "analyze" type: "cognitive" strategy: "chain_of_thought" ``` Restrict tools per node for security and predictability: ```yaml theme={null} # Good: Explicit allowlist tools_allowlist: - "web_search" - "calculator" # Bad: Allowing all tools # tools_allowlist: ["*"] ``` Configure fallback strategies for cost control: ```yaml theme={null} - id: "explore" strategy: "tree_of_thoughts" budget_max: 2500 degrade_to: "chain_of_thought" retries: 1 ``` Extract shared defaults into base templates: ```yaml theme={null} extends: ["base_research"] defaults: budget_agent_max: 8000 # Only override what changes ``` Use semantic versioning and specify version in requests: ```yaml theme={null} name: "my_workflow" version: "1.0.0" # Increment on breaking changes ``` ```json theme={null} { "context": { "template": "my_workflow", "template_version": "1.0.0" } } ``` ## Troubleshooting **Symptom:** `template 'my_workflow' not found` **Solutions:** 1. Check template file exists in registered directory 2. Verify YAML syntax: `yamllint config/workflows/examples/my_workflow.yaml` 3. Restart orchestrator to reload templates 4. Check orchestrator logs for load errors **Symptom:** `Template validation failed: circular dependency` **Solutions:** 1. Review `depends_on` fields for cycles 2. Ensure DAG nodes have acyclic task dependencies 3. Check `edges` don't create loops **Symptom:** Node execution stops early or degrades unexpectedly **Solutions:** 1. Increase `budget_max` for affected nodes 2. Configure `degrade_to` for graceful fallback 3. Review total budget vs. sum of node budgets **Symptom:** `tool 'my_tool' not registered` **Solutions:** 1. Verify tool is registered in tool registry 2. Check tool name spelling in `tools_allowlist` 3. Ensure LLM service has tool loaded ## Synthesis Templates Synthesis templates control how Shannon formats final output after all agents complete their work. These Go templates live in `config/templates/synthesis/` and are rendered by the Orchestrator before returning results. ### Available Templates | Template | Purpose | | ----------------------------- | ---------------------------------- | | `normal_default.tmpl` | Default for non-research tasks | | `research_comprehensive.tmpl` | Detailed research reports | | `research_concise.tmpl` | Brief research summaries | | `research_with_facts.tmpl` | Research with fact-based citations | | `domain_analysis_digest.tmpl` | Domain analysis format | | `swarm_default.tmpl` | Swarm multi-agent output | | `test_bullet_summary.tmpl` | Bullet-point test format | ### Base Contract All synthesis templates inherit behavioral rules from `_base.tmpl`: * When **CitationAgent is enabled**: no inline `[n]` citations in synthesis — Citation Agent adds them separately * When **CitationAgent is disabled**: use inline `[n]` matching the `AvailableCitations` list The `CurrentDate` field is injected into every template, enabling "as of" statements for time-sensitive content. Templates must preserve structured artifacts (tables, code blocks, JSON) — never flatten them to prose. Templates should **not** include a "Sources" section. The system appends source citations automatically. ### Template Data Every synthesis template receives a `SynthesisTemplateData` struct with the following fields: ```go theme={null} type SynthesisTemplateData struct { Query string // Original user query QueryLanguage string // Detected language (e.g., "en", "ja") ResearchAreas []string // Topics explored by agents AvailableCitations string // Formatted citation list CitationCount int // Total citations available MinCitations int // Minimum citations to include LanguageInstruction string // Output language directive AgentResults string // Combined agent output TargetWords int // Target word count IsResearch bool // Whether task is research-type SynthesisStyle string // Selected synthesis style CitationAgentEnabled bool // Whether Citation Agent is active CurrentDate string // Current date for temporal context NewsCount int // Number of news items found } ``` ### Selecting a Synthesis Template The Orchestrator selects synthesis templates based on the task type and configuration: ```bash theme={null} curl -X POST http://localhost:8080/api/v1/tasks \ -H "Content-Type: application/json" \ -d '{ "query": "Research quantum computing trends", "context": { "synthesis_style": "research_comprehensive" } }' ``` ```yaml theme={null} name: "my_research" version: "1.0.0" defaults: model_tier: "medium" synthesis_style: "research_with_facts" ``` If no `synthesis_style` is specified, Shannon automatically selects `research_comprehensive` for research tasks and `normal_default` for all others. ## Example Workflows Shannon ships with 8 example workflow templates in `config/workflows/examples/`. These serve as ready-to-use starting points and reference implementations. ### Workflow Catalog | Template | Description | Nodes | Pattern | | ---------------------------------- | ----------------------- | ----- | ------------------------------- | | `simple_analysis.yaml` | Quick summarization | 2 | Sequential (intake + summarize) | | `research_summary.yaml` | Multi-stage research | 4 | Sequential with degradation | | `research_summary_enterprise.yaml` | Enterprise research | 4+ | Sequential with large models | | `market_analysis.yaml` | Market research | 3+ | Sequential with tools | | `market_analysis_playbook.yaml` | Market playbook variant | 3+ | Playbook-driven | | `complex_dag.yaml` | Multi-parallel DAG | 5+ | DAG with fan-out/fan-in | | `parallel_dag_example.yaml` | Parallel execution demo | 4+ | DAG with parallel branches | | `parallel_items_example.yaml` | Batch item processing | 3+ | Parallel items | ### YAML Structure Reference All example workflows follow this structure: ```yaml theme={null} name: workflow_id description: "Description" defaults: model_tier: small|medium|large # Model size for all nodes budget_agent_max: 3000 # Total token budget require_approval: false # Human-in-the-loop gate nodes: - id: node_id type: simple|cognitive|dag # Execution type strategy: react|chain_of_thought|tree_of_thoughts depends_on: [other_node_ids] # DAG dependencies budget_max: 1000 # Per-node token limit edges: - from: node1 to: node2 ``` ### Pattern Degradation Example workflows demonstrate Shannon's automatic degradation behavior when resources are constrained: ``` DAG → sequential (if insufficient agents available) tree_of_thoughts → chain_of_thought (if budget constrained) multi-node → single-node (fallback for minimal environments) ``` Degradation is automatic and logged. Check Orchestrator logs for `strategy_degraded` events to monitor when fallbacks activate. ### Learning Router When enabled, the **Learning Router** automatically selects workflow templates based on query similarity to past successful executions. This eliminates the need for callers to specify a template explicitly. ```bash theme={null} # Enable learning router via environment ORCHESTRATOR_LEARNING_ROUTER_ENABLED=true # The router will match incoming queries to historical patterns # and select the best-performing template automatically ``` The Learning Router improves over time. For new deployments, explicitly specify templates until enough execution history accumulates (typically 50+ successful tasks). ## Next Steps Add tools for your templates Other extension methods Environment and YAML config Understanding Shannon's design # Vendor Adapters Source: https://docs.shannon.run/en/tutorials/vendor-adapters Build vendor-specific integrations with the adapter pattern ## Overview The **vendor adapter pattern** allows you to integrate domain-specific agents and tools into Shannon without polluting the core codebase. This pattern maintains clean separation between: * **Generic Shannon infrastructure** (committed to open source) * **Vendor-specific implementations** (kept private or in separate repositories) No modifications to Shannon's core codebase required Generic infrastructure vs. vendor-specific logic Vendor logic isolated in separate directories Shannon works even without vendor modules ## When to Use Vendor Adapters Use vendor adapters when integrating proprietary or internal APIs with domain-specific requirements. **Use vendor adapters when:** * Integrating proprietary/internal APIs with domain-specific requirements * Need custom request/response transformations for OpenAPI tools * Building specialized agents for specific business domains * Field naming conventions differ from your internal systems * Require dynamic parameter injection from session context * Need custom authentication or header logic **Example use cases:** * Analytics platforms (metrics aliasing, time range normalization) * E-commerce systems (product field mapping, SKU transformations) * CRM integrations (contact field normalization) * Internal microservices (custom auth tokens, tenant IDs) * Domain-specific data validation ## Architecture ### File Structure ``` Shannon/ ├── config/ │ ├── shannon.yaml # Base config (generic, committed) │ └── overlays/ │ └── shannon.myvendor.yaml # Vendor overlay (not committed) ├── config/openapi_specs/ │ └── myvendor_api.yaml # Vendor API spec (not committed) ├── python/llm-service/llm_service/ │ ├── roles/ │ │ ├── presets.py # Generic roles + conditional import │ │ └── myvendor/ # Vendor role module (not committed) │ │ ├── __init__.py │ │ └── custom_agent.py # Specialized agent role │ └── tools/ │ ├── openapi_tool.py # Generic OpenAPI loader (committed) │ └── vendor_adapters/ # Vendor adapters (not committed) │ ├── __init__.py # Adapter registry │ └── myvendor.py # Vendor-specific transformations ``` ### Component Responsibilities | Component | Responsibility | Committed to OSS | | -------------------------- | ----------------------------------- | ---------------- | | **Config Overlay** | Vendor-specific tool configurations | ❌ No | | **OpenAPI Spec** | API schema definition | ❌ No | | **Vendor Adapter** | Request/response transformations | ❌ No | | **Vendor Role** | Specialized agent system prompts | ❌ No | | **Generic Infrastructure** | Core OpenAPI/role system | ✅ Yes | ## Quick Start Example Let's create a complete vendor integration for a fictional analytics platform called "DataInsight". Create `python/llm-service/llm_service/tools/vendor_adapters/datainsight.py`: ```python theme={null} """Vendor adapter for DataInsight Analytics API.""" from typing import Any, Dict, List, Optional class DataInsightAdapter: """Transforms requests for DataInsight API conventions.""" def transform_body( self, body: Dict[str, Any], operation_id: str, prompt_params: Optional[Dict[str, Any]] = None, ) -> Dict[str, Any]: """ Transform request body for DataInsight API. Args: body: Original request body from LLM operation_id: OpenAPI operation ID prompt_params: Session context parameters (injected by orchestrator) Returns: Transformed body matching DataInsight API expectations """ if not isinstance(body, dict): return body # Inject session context (account_id, user_id from prompt_params) if prompt_params and isinstance(prompt_params, dict): if "account_id" in prompt_params and "account_id" not in body: body["account_id"] = prompt_params["account_id"] if "user_id" in prompt_params and "user_id" not in body: body["user_id"] = prompt_params["user_id"] # Operation-specific transformations if operation_id == "queryMetrics": body = self._transform_query_metrics(body) elif operation_id == "getDimensionValues": body = self._transform_dimension_values(body) return body def _transform_query_metrics(self, body: Dict) -> Dict: """Transform metric query requests.""" # Normalize metric names (support shorthand) metric_aliases = { "users": "di:unique_users", "sessions": "di:total_sessions", "pageviews": "di:page_views", "bounce_rate": "di:bounce_rate", } if isinstance(body.get("metrics"), list): body["metrics"] = [ metric_aliases.get(m, m) for m in body["metrics"] ] # Normalize time range format if "timeRange" in body and isinstance(body["timeRange"], dict): tr = body["timeRange"] # Ensure startTime/endTime (not start/end) if "start" in tr: tr["startTime"] = tr.pop("start") if "end" in tr: tr["endTime"] = tr.pop("end") # Convert sort to expected format if isinstance(body.get("sort"), dict): field = body["sort"].get("field") order = body["sort"].get("order", "DESC").upper() body["sort"] = {"field": field, "direction": order} return body def _transform_dimension_values(self, body: Dict) -> Dict: """Transform dimension value requests.""" dimension_aliases = { "country": "di:geo_country", "device": "di:device_type", "source": "di:traffic_source", } if "dimension" in body: body["dimension"] = dimension_aliases.get( body["dimension"], body["dimension"] ) return body ``` Edit `python/llm-service/llm_service/tools/vendor_adapters/__init__.py`: ```python theme={null} from typing import Optional def get_vendor_adapter(name: str): """Return a vendor adapter instance by name, or None if not available.""" if not name: return None try: if name.lower() == "datainsight": from .datainsight import DataInsightAdapter return DataInsightAdapter() # Add more vendors here # elif name.lower() == "othervendor": # from .othervendor import OtherVendorAdapter # return OtherVendorAdapter() except Exception: return None return None ``` Create `config/overlays/shannon.datainsight.yaml`: ```yaml theme={null} # DataInsight Analytics Integration # Usage: SHANNON_CONFIG_PATH=config/overlays/shannon.datainsight.yaml openapi_tools: datainsight_analytics: enabled: true spec_path: config/openapi_specs/datainsight_api.yaml auth_type: bearer auth_config: vendor: datainsight # This triggers adapter loading token: "${DATAINSIGHT_API_TOKEN}" extra_headers: X-Account-ID: "{{body.account_id}}" # Dynamic from request body X-User-ID: "${DATAINSIGHT_USER_ID}" # Static from env category: analytics base_cost_per_use: 0.002 rate_limit: 60 timeout_seconds: 30 operations: - queryMetrics - getDimensionValues ``` Create `python/llm-service/llm_service/roles/datainsight/analytics_agent.py`: ```python theme={null} """DataInsight Analytics Agent role preset.""" ANALYTICS_AGENT_PRESET = { "name": "datainsight_analytics", "system_prompt": """You are a specialized data analytics agent with access to DataInsight Analytics API. Your mission: Provide actionable insights from web analytics data. ## Available Tools - queryMetrics: Retrieve metrics like users, sessions, pageviews, bounce rate - getDimensionValues: Get dimension values (countries, devices, traffic sources) ## Output Format Always structure your response as: 1. **dataResult** block (JSON) - for visualization 2. **Summary** section - key findings 3. **Insights** section - actionable recommendations ## Best Practices - Always include time range in queries - Use metric aliases: "users", "sessions", "pageviews" (auto-converted) - Request relevant dimensions for context - Provide comparative analysis when possible - Highlight anomalies and trends Remember: Your goal is to help users understand their data and make better decisions.""", "allowed_tools": [ "queryMetrics", "getDimensionValues", ], "temperature": 0.7, "response_format": None, } ``` > Note: `allowed_tools` semantics for `/agent/query`: > > * Omit/`null` → role presets may enable tools > * `[]` → tools disabled > * `["name", …]` → only these tools are available (names must match registered tools) Register in `python/llm-service/llm_service/roles/presets.py`: ```python theme={null} # At the end of _load_presets() function: try: from .datainsight.analytics_agent import ANALYTICS_AGENT_PRESET _PRESETS["datainsight_analytics"] = ANALYTICS_AGENT_PRESET except ImportError: pass # Graceful fallback if vendor module not available ``` Add to `.env`: ```bash theme={null} # DataInsight Configuration SHANNON_CONFIG_PATH=config/overlays/shannon.datainsight.yaml DATAINSIGHT_API_TOKEN=your_bearer_token_here DATAINSIGHT_USER_ID=your_user_id_here # Domain allowlist (dev: use *, prod: specific domains) OPENAPI_ALLOWED_DOMAINS=api.datainsight.com ``` Rebuild and test: ```bash theme={null} # Rebuild services docker compose -f deploy/compose/docker-compose.yml build --no-cache llm-service orchestrator docker compose -f deploy/compose/docker-compose.yml up -d # Wait for health sleep 10 # Test via gRPC SESSION_ID="test-$(date +%s)" grpcurl -plaintext -d '{ "metadata": { "user_id": "test-user", "session_id": "'$SESSION_ID'" }, "query": "Show me user growth trends for the past 30 days", "context": { "role": "datainsight_analytics", "prompt_params": { "account_id": "acct_12345", "user_id": "user_67890" } } }' localhost:50052 shannon.orchestrator.OrchestratorService/SubmitTask ``` ## Component Guide ### 1. Vendor Adapter Class **Purpose:** Transform requests/responses for vendor-specific API conventions **Common transformation patterns:** * **Field aliasing**: `revenue` → `total_revenue` * **Metric prefixing**: `users` → `my:users` * **Time range normalization**: `{start, end}` → `{startTime, endTime}` * **Sort format conversion**: `{field, order}` → `{column, direction}` * **Filter structure reshaping**: list → object with logic operators * **Default injection**: Add missing required fields from session context ### 2. Config Overlay **Purpose:** Define vendor-specific tool configurations without modifying base config **Header values:** * `"${ENV_VAR}"` - Resolved from environment variables * Static strings - Used as-is Dynamic header templating from the request body (e.g., `{{body.field}}`) is not supported. If headers must depend on body/session values, either: * Define those headers as explicit header parameters in the OpenAPI spec and pass them as tool parameters, or * Use a vendor adapter to shape the request body, while headers remain static/env-driven. ### 3. Vendor Role **Purpose:** Specialized agent with domain-specific knowledge and tool restrictions **Template:** ```python theme={null} """Vendor-specific agent role.""" MY_AGENT_PRESET = { "name": "my_agent", "system_prompt": """You are a specialized agent for [domain]. Your mission: [clear objective] ## Available Tools - tool1: [description] - tool2: [description] ## Output Format [specific format requirements] ## Best Practices - [guideline 1] - [guideline 2] Remember: [key instruction]""", "allowed_tools": [ "tool1", "tool2", ], "temperature": 0.7, "response_format": None, # or {"type": "json_object"} } ``` > Note: When you explicitly pass `allowed_tools`, only the listed tools will be available to the LLM. Pass an empty list `[]` to disable tools. ## Best Practices **✅ Good:** Transform field names, inject defaults ```python theme={null} def transform_body(self, body, operation_id, prompt_params): # Generic field normalization if "start_date" in body and "startTime" not in body: body["startTime"] = body.pop("start_date") return body ``` **❌ Bad:** Business logic in adapter ```python theme={null} def transform_body(self, body, operation_id, prompt_params): # Don't do complex business logic here if body["revenue"] > 1000000: body["alert"] = "high_revenue" # Belongs in application layer ``` ```python theme={null} try: from .myvendor import MyVendorAdapter return MyVendorAdapter() except ImportError: pass # Shannon works without vendor module except Exception as e: logger.warning(f"Failed to load vendor adapter: {e}") return None ``` ```python theme={null} def transform_body(self, body, operation_id, prompt_params): """ Transform body for MyVendor API. Transformations: - Metric names: "users" → "mv:unique_users" - Time range: {start, end} → {startTime, endTime} - Sort format: {field, order} → {column, direction} - Inject tenant_id from prompt_params """ ``` **✅ Good:** ```yaml theme={null} auth_config: token: "${MYVENDOR_TOKEN}" ``` **❌ Bad:** ```yaml theme={null} auth_config: token: "sk-1234567890abcdef" # Never hardcode! ``` ```python theme={null} # tests/test_myvendor_adapter.py def test_metric_aliasing(): adapter = MyVendorAdapter() body = {"metrics": ["users", "sessions"]} result = adapter.transform_body(body, "queryMetrics", None) assert result["metrics"] == ["mv:unique_users", "mv:total_sessions"] ``` ```python theme={null} def transform_body(self, body, operation_id, prompt_params): if not isinstance(body, dict): return body # Don't transform non-dict # Validate required fields if operation_id == "queryData" and "metrics" not in body: return body # Let API return validation error ``` ## Testing & Verification ### Unit Test Adapter ```python theme={null} # tests/vendor/test_datainsight_adapter.py import pytest from llm_service.tools.vendor_adapters.datainsight import DataInsightAdapter def test_metric_aliasing(): adapter = DataInsightAdapter() body = {"metrics": ["users", "pageviews"]} result = adapter.transform_body(body, "queryMetrics", None) assert result["metrics"] == ["di:unique_users", "di:page_views"] def test_session_param_injection(): adapter = DataInsightAdapter() body = {} prompt_params = {"account_id": "acct_123", "user_id": "user_456"} result = adapter.transform_body(body, "queryMetrics", prompt_params) assert result["account_id"] == "acct_123" assert result["user_id"] == "user_456" def test_time_range_normalization(): adapter = DataInsightAdapter() body = {"timeRange": {"start": "2025-01-01", "end": "2025-01-31"}} result = adapter.transform_body(body, "queryMetrics", None) assert result["timeRange"]["startTime"] == "2025-01-01" assert result["timeRange"]["endTime"] == "2025-01-31" assert "start" not in result["timeRange"] ``` ### Integration Test ```bash theme={null} #!/bin/bash # tests/e2e/test_datainsight_integration.sh SESSION_ID="test-datainsight-$(date +%s)" # Submit test query grpcurl -plaintext -d '{ "metadata": {"user_id": "test", "session_id": "'$SESSION_ID'"}, "query": "Show user growth for the past week", "context": { "role": "datainsight_analytics", "prompt_params": { "account_id": "test_account", "user_id": "test_user" } } }' localhost:50052 shannon.orchestrator.OrchestratorService/SubmitTask # Check logs for adapter application docker logs shannon-llm-service-1 --tail 100 | grep "datainsight" ``` ## Troubleshooting Symptom: Logs show "Vendor adapter '' applied" (empty string) Fix: ```yaml theme={null} # Ensure vendor name is set in config overlay auth_config: vendor: myvendor # Must match adapter name ``` Symptom: `ImportError: No module named 'myvendor'` Fix: ```python theme={null} # Use try/except in __init__.py try: from .myvendor import MyVendorAdapter return MyVendorAdapter() except ImportError as e: logger.warning(f"Vendor adapter not available: {e}") return None ``` Symptom: API receives original body, not transformed Debug: ```python theme={null} # Add logging in adapter def transform_body(self, body, operation_id, prompt_params): logger.info(f"BEFORE transform: {body}") # ... transformations ... logger.info(f"AFTER transform: {body}") return body ``` Check: 1. Adapter registered in `__init__.py` 2. Vendor name matches in config 3. `auth_config.vendor` field present 4. Adapter returns modified dict (not None) Symptom: `prompt_params` is None in adapter Cause: Orchestrator not sending session context Fix: Ensure context sent in gRPC request: ```json theme={null} { "context": { "role": "my_agent", "prompt_params": { "account_id": "123", "user_id": "456" } } } ``` ## Summary * ✅ Clean separation: generic code vs. vendor-specific * ✅ No Shannon core changes required * ✅ Conditional loading with graceful fallback * ✅ Environment-based secrets management * ✅ Testable in isolation * ✅ Easy to maintain and extend **Three components:** 1. **Vendor Adapter** - Request/response transformations 2. **Config Overlay** - Tool configurations 3. **Vendor Role** - Specialized agent (optional) **Quick reference:** ```bash theme={null} # Structure config/overlays/shannon.myvendor.yaml config/openapi_specs/myvendor_api.yaml python/llm-service/llm_service/tools/vendor_adapters/myvendor.py python/llm-service/llm_service/roles/myvendor/my_agent.py # Environment SHANNON_CONFIG_PATH=config/overlays/shannon.myvendor.yaml MYVENDOR_API_TOKEN=your_token # Test docker compose build --no-cache llm-service orchestrator docker compose up -d ./scripts/submit_task.sh "Your query here" ``` ## Next Steps Learn how to add custom tools Explore other extension methods Complete configuration reference Understand Shannon's architecture