Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

18 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

πŸ” AWS Customer Intelligence Orchestrator

Multi-agent research system that turns a company domain into an AWS sales-opportunity report.

Python Strands Agents A2A Protocol Amazon Bedrock Docker

Three specialized agents Β· one shared container image Β· agent-to-agent over HTTP


πŸ“– Overview

Give it a prompt like "Analyze atscale.com for AWS opportunities" and it produces a polished HTML customer-intelligence report β€” executive summary, company snapshot, AWS opportunities mapped to business challenges, and strategic recommendations β€” saved locally and optionally uploaded to S3.

Under the hood, a Deep Intel orchestrator fans out to two specialist research agents in parallel over the A2A protocol, validates their findings against typed contracts, and renders the report deterministically from a template. Every agent runs on Amazon Bedrock and is served with bedrock-agentcore's A2A runtime β€” the same binary runs locally under Docker Compose or in the cloud as AgentCore Runtimes.

flowchart TD
    U([Client request]) -->|A2A JSON-RPC| DI

    subgraph Agents["One shared image Β· selected by AGENT_ID"]
        DI["🧠 Deep Intel<br/><i>orchestrator</i>"]
        AR["πŸ”Ž AWS Research<br/><i>worker</i>"]
        BI["🏒 Business Intel<br/><i>worker</i>"]
    end

    DI -->|A2A, parallel| AR
    DI -->|A2A, parallel| BI
    AR -->|Tavily MCP| WEB[(Web research)]
    BI -->|Tavily MCP| WEB
    AR -->|"AwsFindings (structured output)"| DI
    BI -->|"CompanyProfile (structured output)"| DI
    DI -->|ReportContent β†’ Jinja2| HTML[/"πŸ“„ HTML report<br/>local + S3"/]
Loading

✨ Key Features

  • 🐳 One image, three agents β€” every container runs the same code; the AGENT_ID env var (deep_intel | aws_research | business_intel) decides which agent a process becomes at boot.
  • πŸ”— A2A-native β€” each agent is an independent A2A service with an agent card, served via bedrock_agentcore.runtime.serve_a2a (AgentCore contract: port 9000 at /, /ping health, card at /.well-known/agent-card.json).
  • ⚑ Guaranteed parallelism β€” the orchestrator fans out to both workers with asyncio.gather inside a single tool, not by hoping the model emits concurrent tool calls.
  • 🧾 Schema-enforced output β€” workers produce their typed contracts (AwsFindings, CompanyProfile) via Strands structured output: enforced by the provider at decode time, never parsed out of prose.
  • πŸ–¨οΈ Deterministic HTML β€” the LLM composes only typed ReportContent; a Jinja2 template owns all markup/CSS. Reports are always valid, consistent, and injection-safe (autoescaped).
  • πŸ“‘ Streaming where it matters β€” the orchestrator streams its response over A2A SSE; workers return a single structured-JSON artifact by design.
  • πŸ—‚οΈ Registry-driven capability β€” every agent's skills, output schema, discovery env vars, and MCP servers are declared in one place (agents/registry.py). Adding a capability is a data change, not a new service class.
  • πŸ“š Skills as Markdown β€” research methodology lives in agents/skills/*/SKILL.md, loaded on demand via progressive disclosure. Edit worker behavior without touching Python.
  • ☁️ IaC included β€” CDK stacks deploy the three agents as AgentCore Runtimes with per-agent IAM roles and worker-ARN injection.

πŸš€ Quick Start

Prerequisites

  • Python 3.10+ (containers use 3.12) and uv
  • AWS credentials with Bedrock access
  • A Tavily MCP endpoint URL (web research for the workers)

Installation

git clone <this-repo> && cd jarvis-strands-github-mcp
uv sync

Configuration

Create a .env file:

# Web research (required β€” workers call Tavily via MCP)
TAVILY_MCP_URL=https://your-tavily-mcp-endpoint
TAVILY_MCP_TOKEN=your_token            # optional, if the endpoint needs auth

# Bedrock model (required β€” no default). If your org gates Bedrock behind
# application inference profiles, use the profile ARN here.
MODEL=us.anthropic.claude-sonnet-5

# AWS credentials (or use a profile / instance role)
AWS_ACCESS_KEY_ID=...
AWS_SECRET_ACCESS_KEY=...
AWS_REGION=us-east-1

# Reporting (optional)
S3_BUCKET=your-reports-bucket
πŸ“‹ Full environment variable reference
Variable Required Default Description
AGENT_ID βœ… β€” Agent this process serves: deep_intel | aws_research | business_intel
TAVILY_MCP_URL βœ… β€” Tavily MCP server URL
MODEL βœ… β€” Bedrock model id, inference-profile ARN, or application-inference-profile ARN
TAVILY_MCP_TOKEN ❌ β€” Auth token for the Tavily MCP endpoint
TAVILY_MCP_AUTH_HEADER ❌ Authorization Header name for the MCP token
TAVILY_MCP_AUTH_PREFIX ❌ Bearer Prefix prepended to the MCP token
TAVILY_MCP_TRANSPORT ❌ streamable_http MCP transport
AWS_REGION ❌ us-east-1 AWS region for Bedrock / S3
MAX_TOKENS ❌ 8192 Max tokens per model call
BEDROCK_READ_TIMEOUT ❌ 300 Bedrock read timeout (s)
BEDROCK_CONNECT_TIMEOUT ❌ 30 Bedrock connect timeout (s)
BEDROCK_MAX_ATTEMPTS ❌ 3 Bedrock retry attempts
REPORT_OUTPUT_DIR ❌ reports Local output directory
S3_BUCKET ❌ β€” Enables S3 upload when set
S3_PREFIX ❌ aws-intel-reports S3 key prefix
ORCHESTRATOR_LOG_LEVEL ❌ INFO DEBUG surfaces tool inputs/results
ORCHESTRATOR_LOG_FILE ❌ orchestrator.log Log file path
PORT ❌ 9000 Local port override
AGENT_BASE_URL ⚠️ http://localhost:<PORT> URL other agents use to reach this one β€” advertised in the agent card. Set per-service (compose does this); never set globally
DEEP_INTEL_URL / AWS_RESEARCH_URL / BUSINESS_INTEL_URL ❌ β€” Explicit peer URLs (local/docker discovery)
AWS_RESEARCH_AGENT_ARN / BUSINESS_INTEL_AGENT_ARN ❌ β€” AgentCore runtime ARNs (deployed discovery fallback)

Run all three agents

docker compose up --build
Service Host port AGENT_ID
Deep Intel (orchestrator) 9001 deep_intel
AWS Research 9002 aws_research
Business Intel 9003 business_intel

Send a request

Any A2A/JSON-RPC client works β€” no SDK required:

curl -s -X POST http://localhost:9001/ \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "id": "1",
    "method": "message/send",
    "params": {
      "message": {
        "kind": "message",
        "messageId": "'"$(uuidgen)"'",
        "role": "user",
        "parts": [{"kind": "text", "text": "Analyze stripe.com for AWS opportunities"}]
      }
    }
  }'

The response is a brief findings summary with the report location; the full HTML lands in reports/<Company>_<timestamp>.html (and S3 when configured).

Run a single agent without Docker
AGENT_ID=aws_research PORT=9002 uv run python -m agents.server

Health: GET /ping Β· Agent card: GET /.well-known/agent-card.json

πŸ—οΈ How It Works

sequenceDiagram
    autonumber
    participant C as Client
    participant DI as Deep Intel
    participant AR as AWS Research
    participant BI as Business Intel

    C->>DI: message/send (A2A JSON-RPC)
    DI->>DI: gather_research tool
    par parallel fan-out (asyncio.gather)
        DI->>AR: A2A message
        AR->>AR: Skill + Tavily MCP research
        AR-->>DI: AwsFindings (structured JSON)
    and
        DI->>BI: A2A message
        BI->>BI: Skill + Tavily MCP research
        BI-->>DI: CompanyProfile (structured JSON)
    end
    DI->>DI: validate (Pydantic) + cross-check sources
    DI->>DI: save_report β†’ Jinja2 render β†’ disk + S3
    DI-->>C: summary + report location (streamed over SSE)
Loading
  1. Deep Intel receives the prompt and calls its gather_research tool, which fans out to both workers in parallel and validates each response against its Pydantic contract.
  2. Workers research using the Tavily MCP tools, guided by their Skill; their final answer is produced via structured output, so the returned artifact is schema-valid JSON by construction.
  3. Deep Intel cross-checks the sources, composes typed ReportContent (content only β€” no markup), and its save_report tool renders the HTML through the Jinja2 template and persists it.

Design decisions worth knowing

  • A2A clients send messages to card.url, not to the endpoint you configure (that's only used to fetch the card). Each agent therefore advertises the address peers can reach it at (AGENT_BASE_URL; AgentCore overwrites it with the real runtime URL when deployed).
  • Workers don't stream. In A2A-compliant streaming mode, mid-loop narration would stream into the artifact and displace the final structured JSON β€” so workers use single-artifact mode, while the orchestrator streams to the client.
  • Per-conversation isolation. StrandsA2AExecutor builds a fresh agent per A2A context_id (LRU-cached); the expensive Tavily MCP client is started once per process and shared.

πŸ“‚ Project Structure

Expand
Dockerfile                       # Single image for all agents
docker-compose.yml               # Three agent services (host ports 9001-9003)
agents/
β”œβ”€β”€ server.py                    # Entrypoint: AGENT_ID β†’ registry spec β†’ serve_a2a (port 9000)
β”œβ”€β”€ registry.py                  # ⭐ Source of truth: agent specs, schemas, skills, MCP servers, discovery
β”œβ”€β”€ a2a_client.py                # Thin A2A client for parallel worker calls (+ deferred auth seam)
β”œβ”€β”€ deep_intel/
β”‚   β”œβ”€β”€ orchestrator.py          # Orchestrator agent: gather_research + save_report tools
β”‚   └── reporting.py             # Jinja2 rendering, disk storage, optional S3 upload
β”œβ”€β”€ skills/                      # Worker methodologies (progressive-disclosure Skills)
β”‚   β”œβ”€β”€ aws-opportunity-research/SKILL.md
β”‚   └── company-intelligence/SKILL.md
└── shared/
    β”œβ”€β”€ research_agent.py        # Unified worker engine (skill + schema + role line)
    β”œβ”€β”€ schemas.py               # Typed contracts: AwsFindings, CompanyProfile, ReportContent
    β”œβ”€β”€ bedrock.py               # BedrockModel + timeout/retry construction
    β”œβ”€β”€ templates/report.html.j2 # Report layout/CSS (the LLM never writes markup)
    β”œβ”€β”€ mcp/                     # MCP client builders (Tavily via streamable HTTP)
    └── core/                    # Config (fail-fast), logging, exceptions
cdk/                             # AgentCore Runtime deployment (see cdk/README.md)
test/                            # Contract + renderer + tool-path tests
reports/                         # Generated HTML reports (bind-mounted)

☁️ Deployment (Amazon Bedrock AgentCore)

The cdk/ app deploys the three agents as AgentCore Runtimes on the A2A protocol β€” one shared ECR image, one runtime per AGENT_ID:

cdk deploy JarvisAgentsBase        # ECR repo + reports S3 bucket
# CI pushes the image (.github/workflows/ci-ecr.yml)
cdk deploy JarvisAgentsRuntime -c imageTag=<git-sha> -c model=<model-or-profile-arn> -c tavilyMcpUrl=<url>

Workers deploy first; their runtime ARNs are injected into Deep Intel's environment and granted bedrock-agentcore:InvokeAgentRuntime. Rolling out new code is decoupled from CDK: CI pushes a new image, then update-agent-runtime (the ECS force-new-deployment analog). Inbound auth supports a custom JWT authorizer (e.g. Microsoft Entra) via CDK context, falling back to IAM SigV4. See cdk/README.md.

πŸ§ͺ Testing

uv run pytest

Covers the worker→orchestrator typed contract (_coerce), the report renderer (valid HTML, autoescaping, optional sections), and save_report through the real Strands tool-invocation path.

πŸ—ΊοΈ Roadmap

  • Inter-agent auth for deployed runtimes β€” machine OAuth (client-credentials) in a2a_client.py:_auth_token; deployed orchestratorβ†’worker calls 403 until wired
  • Secrets hygiene β€” move TAVILY_MCP_TOKEN / OAuth client secret to AWS Secrets Manager
  • Tool-error observability β€” WARNING-level hook for failed tool calls
  • Persistent task store β€” tasks are in-memory today and don't survive restarts

πŸ™Œ Acknowledgments

Built on the Strands Agents SDK, the A2A protocol, Amazon Bedrock AgentCore, and Tavily web research via MCP.

πŸ“œ License

License to be determined.

About

opensource demo for stands agent to interact with Jarvis mcp server

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages