Sample open-source app that automates deep research on Amazon Bedrock AgentCore. Given a user question, the AI agent iteratively queries data sources, cross-references findings, and generates a structured report with citations and visualizations. The app features a frontend with real-time streaming, over 10 configurable data connectors spanning enterprise data on AWS and external APIs, and a modular architecture based on the FAST template. Read more in our blog.
β¨ Key features:
- Multi-source analysis: Search across enterprise data, Internet, and specialized APIs (10+ built-in sources)
- Iterative workflow: AI agent scaffolds report, researches the data, and creates a detailed report
- Data visualization: Agent generates charts and diagrams to enrich reports with quantitative insights
- Real-time report display: Split-pane UI shows the report being built in real-time and allows follow-ups
- Fact-checking and citations: Every factual claim includes inline source citations with the references section
- RL fine-tuning: Train and deploy your own model with reinforcement learning to optimize report quality at lower cost
Evaluation results on standard deep research benchmarks from TTD-DR (correctness %):
| System | HLE-Search | GAIA | Avg. Rank | Includes Diagrams |
|---|---|---|---|---|
| TTD-DR | 33.9 | 69.1 | 1.0 | β |
| OpenAI Deep Research | 29.1 | 67.4 | 2.0 | β |
| AgentCore Deep Research** | 24.0 | 49.6* | 3.5 | β |
| Perplexity Deep Research | 14.5 | 54.5 | 4.0 | β |
| Grok DeeperSearch | 19.3 | 47.9 | 4.5 | β |
| AgentCore Deep Research (all tools)** | 24.0 | 41.7* | 5.5 | β |
| GPT-Researcher | 2.0 | 37.7 | 6.5 | β |
| Open Deep Search | 3.0 | 20.9 | 7.5 | β |
*GAIA evaluated on 127/165 validation questions (file-based questions excluded since the agent only has search tools).
**Our results use Claude Sonnet 4 with Nova Web Grounding + Tavily Web Search (default web search tools), following our scaffoldβresearchβwriteβverify workflow designed for comprehensive reports.
Run the evaluation yourself with uv run test-scripts/eval-agent.py (see eval script for details).
Prerequisites: Node.js 20+, AWS CLI, AWS CDK, Python 3.10+, uv, and Docker. See deployment guide for details.
Deploying AgentCore Deep Research stack requires a few commands:
cd infra-cdk
cp .config_example.yaml config.yaml # Create your config (edit as needed)
npm install
cdk bootstrap # Once per account/region
npm run deployAvailable deploy commands (run from infra-cdk/):
npm run deploy # Backend + frontend
npm run deploy:frontend # Frontend only
cdk deploy # Backend onlySee the deployment guide for detailed instructions.
- Open the application URL (from CDK outputs)
- Log in with Cognito credentials
- Toggle data sources (AlphaVantage, Tavily, Nova, ArXiv, etc.) as needed
- Enter a research question
- Watch as the agent:
- Scaffolds report structure with key themes
- Researches across enabled data sources
- Writes all sections with citations
- Verifies completeness and fills gaps
- Generates charts and diagrams when the report has quantitative data
- Ask follow-up questions and download the report (including any generated charts)
The architecture uses Amazon Cognito in four places:
- User-based login to the frontend web application on CloudFront
- Token-based authentication for the frontend to access AgentCore Runtime
- Token-based authentication for the agents in AgentCore Runtime to access AgentCore Gateway
- Token-based authentication when making API requests to API Gateway.
The application includes multiple Lambda-based tools behind AgentCore Gateway with OAuth authentication:
| Tool | Domain | Description | API Key Required |
|---|---|---|---|
| AlphaVantage Research | Finance | Commodity prices, US economic indicators, and market news with sentiment analysis | Yes |
| ArXiv Search | Science | Search academic papers on arXiv by topic, author, or keywords with category filtering | No |
| ClinicalTrials.gov Search | Life Science | Search clinical studies worldwide by condition, intervention, phase, and recruitment status | No |
| FRED Economic Search | Finance | Search 800,000+ economic time series from the Federal Reserve (GDP, CPI, unemployment, and more) | No |
| Knowledge Base Search | Generic | Query Amazon Bedrock Knowledge Bases (requires configuration) | No |
| Nova Web Grounding | Generic | AWS-powered web search via Amazon Nova with citations | No |
| OpenFDA Drug Search | Life Science | Search FDA drug label database for pharmaceutical information | No |
| PubMed Search | Life Science | Search peer-reviewed biomedical literature for abstracts, journal articles, and meta-analyses | No |
| S3 File Reader | Generic | Read text files and PDFs from S3 (PDFs auto-converted to markdown via pymupdf4llm) | No |
| SEC EDGAR Search | Finance | Search SEC company filings (10-K, 10-Q, 8-K) with optional full-text content retrieval | No |
| Tavily Web Search | Generic | Search the web for current information with relevance scoring and domain filtering | Yes |
The modular architecture makes it easy to integrate additional data sources for developers.
Note: Several tools connect to external (non-AWS) APIs: Tavily, ArXiv, OpenFDA, AlphaVantage, FRED, PubMed, SEC EDGAR, and ClinicalTrials.gov. Of these, Tavily and AlphaVantage require API keys obtained through external registration. All external APIs, whether free or paid, are subject to the terms and conditions of their respective providers. We are not responsible for the availability, accuracy, or usage policies of third-party APIs. Please review each provider's terms before use. See the deployment guide for stack setup instructions and which tools require API keys.
- Frontend: React with TypeScript, Vite, Tailwind CSS, and shadcn components
- Agent: Strands Agents SDK with BedrockModel
- Authentication: AWS Cognito User Pool with OAuth support
- Infrastructure: CDK deployment with Amplify Hosting for frontend and AgentCore backend
Local development requires a deployed stack because the agent depends on AWS services that cannot run locally:
- AgentCore Memory - stores conversation history
- AgentCore Gateway - provides tool access via MCP
- SSM Parameters - stores configuration (Gateway URL, client IDs)
- Secrets Manager - stores Gateway authentication credentials
You must first deploy the stack with npm run deploy (from infra-cdk/), then you can run the frontend and agent locally using Docker Compose while connecting to these deployed AWS resources:
# Set required environment variables (see below for how to find these)
export MEMORY_ID=your-memory-id
export STACK_NAME=your-stack-name
export AWS_DEFAULT_REGION=us-east-1
# Start the full stack locally
cd docker
docker compose up --buildFinding the environment variable values:
STACK_NAME: Use thestack_name_basevalue from yourinfra-cdk/config.yamlMEMORY_ID: Extract from theMemoryArnCloudFormation output (the ID is the last segment after/)aws cloudformation describe-stacks --stack-name <your-stack-name> \ --query 'Stacks[0].Outputs[?OutputKey==`MemoryArn`].OutputValue' --output text # Returns: arn:aws:bedrock-agentcore:region:account:memory/MEMORY_ID
AWS_DEFAULT_REGION: The region where you deployed the stack (e.g.,us-east-1)
See the local development guide for detailed setup instructions.
Distil a frontier model's research behaviour into a small open model with LoRA SFT, then serve it self-hosted. The student learns the full agentic trajectory β which tools to call, in what order, how to build a report incrementally, and how to cite sources β not just what a finished report looks like.
Measured result (98 held-out questions, identical harness for every model: same agent code, same tools, same prompt, same judge, greedy decoding):
| Stage | Score | Rubric | Citation | Format | Training compute |
|---|---|---|---|---|---|
| Qwen3.5-9B base | 0.538 | 0.524 | 0.308 | 0.990 | β |
| Qwen3.5-9B + trajectory SFT | 0.744 | 0.687 | 0.953 | 0.986 | 138 GPU-h |
| Teacher model (frontier) | 0.743 | 0.683 | 0.966 | 1.000 | β |
Trajectory SFT gains +0.208 over the base model (95% CI [+0.174, +0.242], p=1.2e-20, 92 wins / 6 losses). Most of that is provenance: citation validity moves 0.308 β 0.953, measured by a deterministic check against the URLs the tools actually returned, so it needs no judge at all. The base model emits unresolvable bare domains; the distilled model cites pages it genuinely retrieved.
One caveat on the teacher comparison. The judge is the same model used for the RL reward below, chosen because it is cheap enough to run inside a training loop rather than because it is the strongest available evaluator. Scored by a larger independent judge every score drops and the teacher's lead widens.
An earlier version of this pipeline trained on question β final_report pairs.
That teaches report-shaped prose without the research behaviour behind it, which
is a good recipe for confident fabrication. Every paper in this area
(DR-Venus,
DeepSearch-World,
DeepRubric,
RΒ²-Searcher) trains on full multi-step
trajectories with tool observations masked out of the loss β otherwise the
model learns to invent search results.
Masking depends on a detail in the chat template that is easy to miss. TRL's
assistant_only_loss builds its mask from {% generation %} markers in the
template, and most published templates β Qwen3.5's included β have none.
Enabling the flag against such a template does not error: it silently trains on
the whole sequence, observations included, which is precisely the failure being
avoided. Observations are roughly half the tokens in a trajectory, so the loss
curve looks plausible either way; the only visible signal is that loss starts
much higher (4.06 versus 0.56 on a one-step probe).
TRL handles this for recognised model families by swapping in a marked-up
training template (see trl/chat_templates/), so there is nothing to configure.
What this pipeline adds is an assertion: training refuses to start if neither
the model's own template nor TRL provides generation markers, because for an
unlisted family the failure would otherwise be silent.
The same detail bites in reverse at inference time: {% generation %} is a
training-only construct, so the merged checkpoint is written with the published
template restored. Shipping the training template would either break a serving
stack that does not understand the tag, or silently change the prompt format the
model was tuned on.
DATA GENERATION (teacher agent on Bedrock)
ββββββββββββββββββββββββ βββββββββββββββββββββββββββββ ββββββββββββββββββββββ
β sft_generate_data.py βββββΊβ Production agent βββββΊβ AgentCore Gateway β
β captures the full β β (teacher model + tools) β β (Nova, ArXiv, β
β SSE trajectory ββββββ emits tool calls+results β β PubMed, EDGAR...) β
ββββββββββββ¬ββββββββββββ βββββββββββββββββββββββββββββ ββββββββββββββββββββββ
β TRL tool-calling format; observations kept but masked at train time
βΌ
TRAINING (SageMaker ml.g6e.12xlarge β 4Γ L40S 48GB)
ββββββββββββββββββββββββ
β TRL SFTTrainer β LoRA r=32 Ξ±=64, FSDP, max_seq_len 32768,
β assistant_only_loss β observations masked via {% generation %} template
ββββββββββββ¬ββββββββββββ β merged HF checkpoint (model.tar.gz)
βΌ
INFERENCE (SageMaker ml.g6e.16xlarge β 1Γ L40S)
ββββββββββββββββββββββββ βββββββββββββββββββββββββββββ
β vLLM endpoint ββββββ AgentCore Runtime β same agent code and
ββββββββββββββββββββββββ β (same tools as prod) β same tools as production
βββββββββββββββββββββββββββββ
| Stage | Instance | Why not smaller |
|---|---|---|
| Training | ml.g6e.12xlarge (4Γ L40S 48GB) |
A trajectory is ~20K tokens, so max_seq_length must be 32768. That OOMs on 4Γ A10G 24GB (ml.g5.12xlarge). |
| Serving | ml.g6e.16xlarge (1Γ L40S 48GB) |
9B in BF16 plus KV cache for a 64K context. |
GPU capacity for these instance types fluctuates by region, and available
capacity is not the same as having quota β a job can sit Pending for hours in
one region while starting immediately in another. Launching in several regions
and keeping whichever starts first is the pragmatic approach.
# 1. Generate research questions (seeded, so this is reproducible)
uv run test-scripts/sft_generate_questions.py --count 1000 --eval-count 0 --seed 7 \
--output test-scripts/results/rl_train_data_1k.jsonl
# 2. Collect teacher trajectories. Resume-safe; ~550 trajectories/hour at
# --max-concurrent 45. AgentCore parallelises well; Bedrock is the limit.
export EVAL_USERNAME=<cognito-user> EVAL_PASSWORD=<cognito-password>
uv run test-scripts/sft_generate_data.py \
--questions test-scripts/results/rl_train_data_1k.jsonl \
--max-concurrent 45 --observation-chars 1200 \
--output test-scripts/results/sft_traces_1k.jsonl
# 3. Deploy training infra and build the training image
cd infra-cdk && npm run deploy:rl && cd ..
./training/build_and_push.sh sft
# 4. Train (LoRA). ~37h for 1,963 trajectories x 2 epochs on 4x L40S.
# --lora-alpha defaults to 2x rank; do not pin it independently.
uv run test-scripts/sft_train.py \
--data test-scripts/results/sft_traces_2k_fitted.jsonl \
--s3-bucket <sagemaker-bucket-in-training-region> \
--role-arn <RLTrainingRole ARN> \
--hf-model-id Qwen/Qwen3.5-9B \
--lora-rank 32 --epochs 2 --max-seq-length 32768 \
--eval-fraction 0.05 --max-runtime 259200 \
--instance-type ml.g6e.12xlarge
# 5. Serve the merged checkpoint (must be in the agent's region)
uv run test-scripts/deploy_model.py --job-name <training-job> \
--endpoint-name dr-sft --instance-type ml.g6e.16xlarge \
--tensor-parallel-degree 1 --max-model-len 65536 \
--tool-call-parser qwen3_coder --reasoning-parser qwen3 \
--enable-capacity-fallback --region us-west-2
# 6. Point the agent at it, then WAIT ~15 min (AgentCore does not hot-swap
# images mid-session; evaluating too early scores an empty runtime)
uv run test-scripts/deploy_finetuned_agent.py --endpoint-name dr-sft
# 7. Evaluate against the frozen question set, and the base model for comparison
uv run test-scripts/eval-agent.py --benchmark rubric --max-questions 98 \
--parallel 8 --tag sft --model qwen3.5-9b-sft --runtime-arn <finetuned runtime ARN>Sharpen the distilled model against a rubric reward using GRPO, powered by AgentCore RL Toolkit with the verl backend.
RL runs on top of an SFT checkpoint, not the base model, so run that stage first.
Measured result (same 98 questions, same harness and judge as the SFT table above):
| Stage | Score | Rubric | Citation | Format | Training compute |
|---|---|---|---|---|---|
| Qwen3.5-9B + trajectory SFT | 0.744 | 0.687 | 0.953 | 0.986 | 138 GPU-h |
| + GRPO, 50 steps | 0.772 | 0.726 | 0.906 | 1.000 | 168 GPU-h |
| + GRPO, 100 steps | 0.770 | 0.721 | 0.929 | 1.000 | 198 GPU-h |
| + GRPO, 150 steps | 0.782 | 0.737 | 0.919 | 1.000 | 228 GPU-h |
| + GRPO, 200 steps | 0.677 | 0.604 | 0.946 | 0.980 | 258 GPU-h |
| Teacher model (frontier) | 0.743 | 0.683 | 0.966 | 1.000 | β |
GRPO adds +0.038 over SFT by step 150 (95% CI [+0.016, +0.060], p=0.001),
then over-optimises: by step 200 the score falls to 0.677, which is 0.104 below
step 150 (p<0.0001) and below SFT. The in-training reward was still climbing at
that point, so the reward gave no warning. The failure is not uniform
degradation β median report length barely moves, but reports scoring under 0.5 on
the rubric jump from 4 to 20 out of 98. Checkpoint often (--save-freq) and
evaluate every checkpoint; a rising training reward is not evidence that the
policy is still improving.
TRAINING (SageMaker ml.g6e.12xlarge β 4Γ L40S 48GB)
βββββββββββββββββββββββ ββββββββββββββββββββββββββββ βββββββββββββββββββββββ
β verl GRPO (FSDP) ββββββΊβ AgentCore Runtime ββββββΊβ AgentCore Gateway β
β train.py on Ray β β (RL agent with tools) β β (Tavily, Nova, β
β βββββββ returns rubric rewards β β ArXiv, PubMed...) β
ββββββββββββ¬βββββββββββ ββββββββββββββββββββββββββββ βββββββββββββββββββββββ
β
β rllm-model-gateway captures token IDs + logprobs
β vLLM serves current policy weights
β
βΌ --save-hf β model.tar.gz (HF safetensors + tokenizer)
βββββββββββββββββββββββ
β S3 Bucket β
ββββββββββββ¬βββββββββββ
β
INFERENCE (SageMaker ml.g6e.16xlarge β 1Γ L40S 48GB)
βΌ
βββββββββββββββββββββββ ββββββββββββββββββββββββββββ βββββββββββββββββββββββ
β SageMaker Endpoint βββββββ AgentCore Runtime ββββββΊβ AgentCore Gateway β
β (DJL/vLLM) β β (finetuned agent, same β β (same tools as β
β β β code as production) β β production) β
βββββββββββββββββββββββ ββββββββββββββββββββββββββββ βββββββββββββββββββββββ
β²
β
Eval / Users
(same auth as production)
Each training step: prompts β agent produces full research reports using tools β reports scored against the shared rubric in research_rubric.py β GRPO computes advantages across N samples β model weights updated.
- Deployed deep research stack (
npm run deployfrominfra-cdk/) - AWS account with SageMaker GPU quota (
ml.g6e.12xlargefor training jobs β 4Γ L40S 48GB GPUs) - Cognito user credentials exported as
EVAL_USERNAMEandEVAL_PASSWORD(for eval) - Docker or Finch installed (for building the training container)
- AgentCore CLI installed (
npm install -g @aws/agentcore)
Check your GPU quotas:
aws service-quotas list-service-quotas --service-code sagemaker \
--query 'Quotas[?contains(QuotaName, `training`) && (contains(QuotaName, `ml.g`) || contains(QuotaName, `ml.p`))].{Name:QuotaName,Value:Value}' \
--output tableGenerate research questions that exercise tool use (shared with SFT pipeline):
# Generate 500 training + 100 eval questions across multiple domains
uv run test-scripts/sft_generate_questions.py --count 500 --eval-count 100This produces test-scripts/results/rl_train_data.jsonl with one prompt per line:
{"prompt": [{"role": "user", "content": "Research question here"}], "enabled_sources": ["tavily", "nova"], "metadata": {"prompt": "Research question here", "domain": "general", "tools": ["tavily", "nova"]}}The prompt field is a chat-format message list. The enabled_sources field controls which tools the agent uses during training rollouts. Questions span 6 domains (general, finance, science, medical, policy, technology) to ensure broad tool-use coverage.
# 1. Deploy RL training infrastructure (S3 bucket, RL agent runtime, IAM roles)
cd infra-cdk && npm run deploy:rlNote the stack outputs β you'll need RLAgentRuntimeArn and RLBucketName:
aws cloudformation describe-stacks --stack-name deep-research-rl \
--query 'Stacks[0].Outputs[*].[OutputKey,OutputValue]' --output table# 2. Build and push training container to ECR (builds for linux/amd64)
./training/build_and_push.sh rl
# 3. Train with GRPO (launches SageMaker job, runs ~2-4 hours)
uv run test-scripts/rl_train.py \
--data test-scripts/results/rl_train_data.jsonl \
--agent-arn <RLAgentRuntimeArn> \
--s3-bucket <RLBucketName> \
--sft-job-name <completed-sft-job> \
--instance-type ml.g6e.12xlarge
# 4. Deploy fine-tuned model as a SageMaker endpoint (vLLM)
uv run test-scripts/deploy_model.py --job-name <training-job-name> \
--endpoint-name dr-finetuned --instance-type ml.g6e.16xlarge
# 5. Deploy a separate agent with the fine-tuned model
uv run test-scripts/deploy_finetuned_agent.py --endpoint-name dr-finetuned
# 6. Eval fine-tuned agent (runs alongside production agent)
export EVAL_USERNAME=<your-cognito-username>
export EVAL_PASSWORD=<your-cognito-password>
uv run test-scripts/eval-agent.py --benchmark hle-search --max-questions 50 \
--tag finetunedMonitor the training job in the SageMaker console or via CLI:
aws sagemaker describe-training-job --training-job-name <job-name> \
--query '{Status:TrainingJobStatus,SecondaryStatus:SecondaryStatus}'Training runs on the verl backend with the FSDP engine, so the model architecture is read from config.json β there is no per-model script to maintain. Attention comes from HuggingFace transformers, which is what allows unusual head dimensions to train without a fused-kernel fallback.
Reports are scored on a 0β1 scale combining three signals. The weights and all
scoring logic live in patterns/strands-deep-research/research_rubric.py, which
is the single source of truth shared by evaluation and RL:
| Signal | Weight | Method |
|---|---|---|
| Rubric quality | 80% | LLM judge scores 6 criteria, including grounding of claims in sources |
| Citation validity | 10% | Counts distinct well-formed URLs, and rejects citations absent from the URLs the tools actually returned |
| Format compliance | 10% | Each expected section must carry real body text, not just a heading |
| Component | Role | Managed by |
|---|---|---|
| AgentCore Runtime | Runs parallel agent rollouts in isolated microVMs | AWS (serverless) |
| SageMaker Training | GPU cluster for GRPO weight updates | CDK stack |
| rllm-model-gateway | Captures token IDs/logprobs from inference | Training container |
| vLLM | Serves current policy weights during training and inference | Training container / SageMaker endpoint |
| S3 | Data exchange: prompts β rewards β checkpoints | CDK stack |
rl_app.py |
RL-adapted agent (same tools, OpenAIModel instead of BedrockModel) |
This repo |
See test-scripts/rl_train.py for the training script and patterns/strands-deep-research/rl_app.py for the RL-adapted agent.
agentcore-deep-research/
βββ frontend/ # React frontend application
β βββ src/
β β βββ components/ # React components (shadcn/ui)
β β βββ hooks/ # Custom React hooks
β β βββ lib/ # Utility libraries
β β βββ services/ # API service layers
β β βββ types/ # TypeScript type definitions
β βββ public/ # Static assets and aws-exports.json
β βββ package.json
βββ infra-cdk/ # CDK infrastructure code
β βββ lib/ # CDK stack definitions
β βββ bin/ # CDK app entry point
β βββ lambdas/ # Lambda function code
β βββ .config_example.yaml # Example deployment configuration (copy to config.yaml)
β βββ config.yaml # Your deployment configuration (gitignored)
βββ patterns/ # Agent pattern implementations
β βββ strands-deep-research/ # Deep Research agent
β βββ deep_research_agent.py # Main agent with Gateway tools
β βββ report_upload_hook.py # S3 upload for real-time display
β βββ system_prompt.txt # 5-step research workflow
β βββ requirements.txt # Agent dependencies
β βββ Dockerfile # Container configuration
βββ tools/ # Agent tool implementations
β βββ code_interpreter/ # Code interpreter for chart generation
β βββ data_analysis/ # Data analysis advisor prompt
βββ gateway/ # Gateway utilities and tools
β βββ tools/ # Gateway tool implementations
βββ docker/ # Local development Docker setup
β βββ docker-compose.yml # Docker Compose for local stack
βββ scripts/ # Deployment and test scripts
β βββ deploy-frontend.py # Cross-platform frontend deployment
βββ docs/ # Documentation source files
βββ tests/ # Test suite
βββ README.md
Note: this asset represents a proof-of-value for the services included and is not intended as a production-ready solution. You must determine how the AWS Shared Responsibility applies to your specific use case and implement the needed controls to achieve your desired security outcomes. AWS offers a broad set of security tools and configurations to enable our customers.
Ultimately it is your responsibility as the developer to ensure all aspects of the application are secure. We provide security best practices in repository documentation and provide a secure baseline but Amazon holds no responsibility for the security of applications built from this tool.
![]() |
![]() |
![]() |
![]() |
|---|---|---|---|
| Nikita Kozodoi | Aiham Taleb | Jack Butler | Elizaveta Zinovyeva |







