diff --git a/01-tutorials/10-Agent-Registry/01-advanced/kiro-registry-dcr-auth0/images/4_kiro_search.png b/01-tutorials/10-Agent-Registry/01-advanced/kiro-registry-dcr-auth0/images/4_kiro_search.png index 4b26bc229..064617a5d 100644 Binary files a/01-tutorials/10-Agent-Registry/01-advanced/kiro-registry-dcr-auth0/images/4_kiro_search.png and b/01-tutorials/10-Agent-Registry/01-advanced/kiro-registry-dcr-auth0/images/4_kiro_search.png differ diff --git a/01-tutorials/10-Agent-Registry/01-advanced/kiro/kiro-registry-search-publish-workflow/00_kiro_search_publish.ipynb b/01-tutorials/10-Agent-Registry/01-advanced/kiro/kiro-registry-search-publish-workflow/00_kiro_search_publish.ipynb new file mode 100644 index 000000000..03a1db582 --- /dev/null +++ b/01-tutorials/10-Agent-Registry/01-advanced/kiro/kiro-registry-search-publish-workflow/00_kiro_search_publish.ipynb @@ -0,0 +1,704 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Registry-First Development Workflow with Kiro\n", + "\n", + "Discover agentic capabilities from your organization's AWS Agent Registry, identify gaps, build missing agents, and publish them back — __all from the Kiro IDE__ using MCP-based search and Kiro Powers for publisher workflows.\n", + "\n", + "## What This Tutorial Demonstrates\n", + "\n", + "This tutorial walks through a **registry-first development workflow** powered by Kiro:\n", + "\n", + "1. **Search** — Use the AWS Agent Registry MCP server in Kiro to semantically search for existing agents and tools across your organization, directly from the IDE.\n", + "\n", + "2. **Identify gaps** — Map discovered capabilities against your workflow requirements and pinpoint what's missing.\n", + "\n", + "3. **Build & Publish** — Use Kiro Powers (which define the AWS Agent Registry publisher APIs) to build the missing agents and publish them back to the registry, all via the Kiro chat interface.\n", + "\n", + "4. **Invoke** — Resolve runtime ARNs from registry records and invoke agents via AgentCore Runtime.\n", + "\n", + "The key idea: a developer sitting in Kiro can go from \"what agents exist?\" to \"I built and published the missing ones\" without leaving the IDE.\n", + "\n", + "![AWS Agent Registry flow on Kiro](images/KiroDiagram.png)\n", + "\n", + "## What is AWS Agent Registry?\n", + "\n", + "AWS Agent Registry is a centralized, governed catalog for discovering, publishing, and managing AI agents and tools across an organization. It provides semantic search for capability-based discovery, IAM and OAuth access control, rich metadata management (protocol, version, connection info, tool schemas), and a built-in governance workflow (DRAFT → PENDING_APPROVAL → APPROVED) so teams can trust what they find and reuse what others have built. AWS Agent Registry has three pain personas, Publishers - who publish capabilities to the Registry , Admins - who approve the published capabilities and Consumers - who search and access the approved registry records downstream.\n", + "\n", + "![AWS Agent Registry Publisher flow ](images/Publisher-workflow.png)\n", + "\n", + "\n", + "![AWS Agent Registry Consumer flow](images/2-consumerflow.png) \n", + "## What is Dynamic Client Registration?\n", + "\n", + "Dynamic Client Registration is an OAuth and OpenID Connect protocol that lets client applications automatically register with an authorization server instead of requiring manual pre-registration. The DCR protocol is formally defined in RFC 7591, with optional registration management extensions in RFC 7592. It was designed to work within the Open Authorization (OAuth) and OpenID Connect (OIDC) ecosystems. It enables automatic creation of client IDs, secrets, and metadata (redirect URIs, scopes), often used for automation, AI agents, and dynamic scaling.\n", + "\n", + "### Why DCR Matters for Kiro\n", + "\n", + "For Kiro to search the registry as an MCP tool, the registry needs to be configured with a **CUSTOM_JWT authorizer** backed by an OAuth provider (Auth0 in this example). Kiro's MCP client uses the **authorization_code + PKCE** flow — it dynamically registers itself via Dynamic Client Registration (DCR), opens a browser for login, catches the callback on localhost, and exchanges the code for a token. This means zero manual credential management for the developer.\n", + "\n", + "## How Kiro Powers Fit In this Development Workflow\n", + "\n", + "A **Kiro Power** is a curated, pre-packaged bundle of capabilities designed for the Kiro AI-powered IDE that gives the Kiro agent instant, specialized expertise in a specific technology or workflow.\n", + "\n", + "Each power typically bundles three components :\n", + "\n", + "- **POWER.md** — A steering file that acts as an onboarding manual, telling the agent what MCP tools are available and when to use them\n", + "- **MCP server configuration** — The tools and connection details for the Model Context Protocol server\n", + "- **Additional steering or hooks** — Extra guidance files or automated validation hooks [ We use this for publisher of records]\n", + "\n", + "The key innovation is **dynamic context loading**: rather than loading every tool upfront (which can overwhelm the agent), powers activate only when relevant. For example, mention \"database\" and the Neon power loads; switch to deployment and the Netlify power activates while Neon deactivates.\n", + "\n", + "In this sample Kiro Powers package the AWS Agent Registry publisher APIs (`CreateRegistryRecord`, `SubmitRegistryRecordForApproval`, etc.) as steering files that guide Kiro through the publish workflow. When you ask Kiro to \"publish this agent to the registry,\" the Power provides the step-by-step instructions and API calls — so you get a governed publish-and-approve cycle without writing boto3 code yourself.\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Use Case: \n", + "\n", + "To ground this tutorial in a real world usecase , let imagine a scenrio of AnyCompany financial services.\n", + "\n", + "AnyCompany financial services firm has a multi team structure. Core teams include investment management, wealth advisory, trading operations, and compliance. Over the past year, multiple teams have independently built AI agents, MCP servers, and automation tools — but with no shared catalog, no common standard, and no way for one team to discover what another has already built.\n", + "\n", + "The Wealth Advisory team has been asked to build a Quarterly Intelligence Briefing workflow. \n", + "Here's the requirement: \n", + "\n", + "> *\"When a publicly traded company reports quarterly results, automatically generate a comprehensive client-ready investment brief — including what happened, why it matters, how it affects each client's portfolio, and what (if any) action to consider — all within 30 minutes of the earnings release.\"*\n", + "\n", + "In order to build this,the key capabilities needed include :\n", + "* **First**, gathering data — pull raw earnings data, market context, competitive intel.\n", + "* **Second**, analyze and synthesize — run financial analysis compliance tests and generate an investment thesis based on the data.\n", + "* **Third** is generate a perosnlaized investment brief for each client.\n", + "\n", + "\n", + "The Wealth Advisory team has **zero** existing capabilties in house for earnings data ingestion, financial analysis, or compliance screening. Building from scratch would take **couple of months and $1M+**.\n", + "\n", + "But they don't need to build from scratch. **Other teams already have the pieces.** The Wealth Advisory team just needs a way to find them, verify they're approved for use, and wire them together into a flow.\n", + "\n", + "The result: what would have been a 6-month greenfield project becomes a composition exercise — 7 agents discovered from 5 teams, 2 new agents built to fill gaps, and a single orchestrator tying them all together.\n", + "\n", + "\n", + "\n", + "\n", + "![Wealth Advisory Teams Quarterly Briefing Use Case](images/5-UsecaseviaAWSRegistry.png)\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Tutorial Details\n", + "\n", + "| Information | Details |\n", + "|:------------|:--------|\n", + "| Tutorial type | Interactive |\n", + "| AgentCore components | AWS Agent Registry, AgentCore Runtime, MCP Gateway |\n", + "| Record types | MCP, CUSTOM |\n", + "| Approval mode | Auto-approval |\n", + "| Tutorial components | AWS Agent Registry, AgentCore Runtime, Auth0 (OAuth/DCR), Kiro Powers |\n", + "| Tutorial vertical | Financial Services (Wealth Advisory) |\n", + "| Example complexity | Advanced |\n", + "| SDK used | boto3, strands-agents, mcp |\n", + "\n", + "## Steps Involved: \n", + "\n", + "Set Up:\n", + "\n", + "- Create an Auth0 DCR-enabled registry (CUSTOM_JWT authorizer) [DCR set up instructions here](https://github.com/awslabs/agentcore-samples/blob/main/01-tutorials/10-Agent-Registry/01-advanced/kiro-registry-dcr-auth0/DCR_registry_search_mcp_in_kiro.ipynb)\n", + "- Deploy sample agents to AgentCore Runtime and register them as records\n", + "\n", + "Search\n", + "\n", + "- In Kiro Use the registry MCP server to search for existing agents from chat interface.\n", + "- Resolve runtime ARNs from search results and invoke agents from their URIs\n", + "\n", + "\n", + "Build\n", + "\n", + "- Set up and Use Kiro Powers to create and publish new agents to registry from Kiro Chat interface. [Sample Kiro powers available here ](https://github.com/sanaiqbalw/amazon-bedrock-agentcore-samples/tree/br_dcr-registry_for_kiro-mcp-search/01-tutorials/10-Agent-Registry/01-advanced/kiro/kiro-power-publisher-workflow)\n", + "\n", + "## Prerequisites\n", + "\n", + "- AWS credentials configured with appropriate permissions for AgentCore Registry, AgentCore Runtime, and Cognito\n", + "- Python 3.10+ with the following packages:\n", + "\n", + "| Package | Usage |\n", + "|:--------|:------|\n", + "| `boto3` | AWS SDK for registry and AgentCore runtime interactions |\n", + "| `strands-agents` | Agent orchestration framework |\n", + "| `mcp` | MCP client for Gateway tool invocation |\n", + "| `bedrock-agentcore` | AgentCore Runtime SDK for agent deployment |\n", + "\n", + "- **Kiro IDE** with the AWS Agent Registry MCP server configured and the Agent Registry Publisher Power installed\n", + "- **Auth0 tenant** with DCR enabled (for the CUSTOM_JWT authorizer on the registry)\n", + "\n", + "### [Optional — SageMaker AI Only] Create a Machine-to-Machine (M2M) Application\n", + "\n", + "The PKCE flow used by Kiro (and by Step 4 below) requires a localhost HTTP server to catch the OAuth callback. On SageMaker AI, localhost isn't accessible from the browser, so you need to use the **client_credentials** grant instead:\n", + "\n", + "1. In Auth0, navigate to **Applications → Applications**\n", + "2. Click **+ Create Application** → select **Machine to Machine Applications**\n", + "3. Name it (e.g., `Registry M2M Client`) and authorize it to call the registry API\n", + "4. Under **Settings → Credentials**, ensure **Authentication Method** is not `None`\n", + "5. Under **Advanced Settings → Grant Types**, ensure **Client Credentials** is enabled\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "---\n", + "# __Step 1: Registry Setup__" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "---\n", + "## __Step 1.1 Registry Setup__: \n", + "### Create Auth0 DCR Credentials and then create a Registry\n", + "\n", + "Creates registry with CUSTOM_JWT authorizer → polls until READY → adds MCP URL to allowedAudience.\n", + "\n", + "\n", + "- Follow the steps here to set up Auth0 and create a registry [DCR set up instructions here](https://github.com/awslabs/agentcore-samples/blob/main/01-tutorials/10-Agent-Registry/01-advanced/kiro-registry-dcr-auth0/DCR_registry_search_mcp_in_kiro.ipynb)\n", + "\n", + "\n", + "- Update .env with domain url and audience \n", + "\n", + "- Create a registry with auth config using OAUTH DCR created.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Create Registry \n", + "\n", + "from utils import *\n", + "from setup import *\n", + "result = create_registry_with_auth0(name=\"AnyCompanyAgentRegistry\")\n", + "REGISTRY_ID = result[\"registryId\"]\n", + "\n", + "print(f\"Registry: {REGISTRY_ID} | Status: {result['status']}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## __Step 1.2 Registry Setup__ : \n", + "### Add an API on auth0 tenant with identifier as Registry MCP URL\n", + "\n", + "\n", + "- In the Auth0 dashboard, navigate to **Applications → APIs** and create an API using the registry's MCP endpoint URL as **Identifier**.Use the registry_id that was created.\n", + "mcp url: `https://bedrock-agentcore.us-west-2.amazonaws.com/registry//mcp`\n", + "\n", + "- Note: Kiro sends the MCP server URL as the `audience` in the Auth0 authorization request (Otherwise it wont find the service). In this notebook you do not need to manually update the Auth configurations of the Registry. The `create_registry` helper for this notebook has been written to automatically add `https://bedrock-agentcore.us-west-2.amazonaws.com/registry//mcp` to the registry's `allowedAudience` after creation. \n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "---\n", + "## __Step 1.3 Registry Setup__:\n", + "### Deploy Agents to AgentCore Runtime" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "AGENTS_TO_DEPLOY = [\n", + " 'data_retrieval_agent',\n", + " 'market_research_agent',\n", + " 'patent_research_agent',\n", + " 'financial_analysis_agent',\n", + " 'sentiment_analysis_agent',\n", + " 'investment_thesis_generator',\n", + " 'approval_workflow_agent',\n", + " 'client_communication_agent',\n", + " 'reporting_agent',\n", + "]\n", + "\n", + "!cd setup && python3 2_deploy_artifacts_on_agentcore_runtime.py {' '.join(AGENTS_TO_DEPLOY)}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "---\n", + "## __Step 1.4 Registry Setup__:\n", + "### Register the deployed Agents as Records\n", + "\n", + "Runtime ARNs are auto-discovered from AgentCore Runtime and embedded in the record descriptors." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "\n", + "for agent in AGENTS_TO_DEPLOY:\n", + " !cd setup && python3 3_add_records_to_registry.py {agent} --registry-id {REGISTRY_ID}" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Verify: list all records in the registry\n", + "import time\n", + "time.sleep(5)\n", + "from setup import get_cp_client\n", + "cp_client = get_cp_client()\n", + "\n", + "records = cp_client.list_registry_records(registryId=REGISTRY_ID)\n", + "print(f\"Records ({len(records['registryRecords'])}):\\n\")\n", + "for r in records['registryRecords']:\n", + " print(f\" [REGISTRY ID: {REGISTRY_ID}] [{r['status']}] {r['name']} | {r.get('descriptorType', 'N/A')}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "## __Step 2: Search the Registry on Kiro for capabilties__\n", + "\n", + "### This is where your work starts __AS A CONSUMER__ of AWS Agent Registry, when Organization Registry is available\n", + "\n", + "### 2.1 Add AWS Agent Registry mcp to the Kiro mcp.json\n", + "Add the Registry mcp url to Kiro mcp.json (.kiro/settings/mcp.json):\n", + "{ \"mcpServers\" : {\n", + "\"AnyCompanyRegistry\": {\n", + " \"type\": \"http\",\n", + " \"url\": \"https://bedrock-agentcore.us-west-2.amazonaws.com/registry//mcp/\",\n", + " \"disabled\": false\n", + " }\n", + "}\n", + "}\n", + "\n", + "\n", + "### 2.2 Authenticate\n", + "\n", + "Enable and authenticate the MCP on your Kiro/Claude\n", + "When Kiro connects to the MCP server, it will:\n", + "- Discover the Auth0 authorization server via the registry's well-known endpoint\n", + "- Use DCR to auto-register as an OAuth client (POST /oidc/register)\n", + "- Obtain an access token via PKCE authorization code flow\n", + "- Use the token to call the registry search MCP\n", + "\n", + "\n", + "### 2.3 Search from Kiro\n", + "Open Kiro chat and ask it to search the registry:\n", + "\n", + "##### prompt 1: \n", + "\"Use the AnyCompany Registry to find records relevant for financial data analysis\"\n", + "\n", + "##### prompt 2: \n", + "\"Use the AnyCompany Registry to find records relevant for complinace workflow tools\"\n", + "\n", + "##### prompt 3: \n", + "\"Use the AnyCompany Registry to find records relevant for dashboarding and marketing\"\n", + "\n", + "##### prompt 4: \n", + "\"List all the records that were relevant as a python list \"\n", + "\n", + "##### prompt 5: \n", + "\"List all the information and inline content of the records above \"\n", + "\n", + "\n", + "#### Then take that python list and invoke the agents to see what they do.\n", + "\n", + "```\n", + "queries = [\n", + " \"financial data retrieval earnings\",\n", + " \"market research competitor analysis\",\n", + " \"patent intellectual property\",\n", + " \"financial analysis portfolio risk\",\n", + " \"sentiment analysis news social\",\n", + " \"approval workflow compliance\",\n", + " \"reporting dashboard visualization\",\n", + " \"investment thesis synthesis conviction\",\n", + " \"client communication personalized brief\",\n", + "]\n", + "```\n", + "\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "---\n", + "## __Step 3: Resolve Runtime ARNs & Invoke the Agents__\n", + "\n", + "Extract `runtimeArn` from search results metadata in AWS Agent Registry and invoke agents via AgentCore Runtime (IAM auth).\n", + "\n", + "Note: We can do this step in Kiro too, by making sure we have the AgentCore MCP installed." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from utils import resolve_agents_from_registry, invoke_http_agent\n", + "\n", + "queries=[ \"financial data retrieval earnings\",\n", + " \"market research competitor analysis\"] #UPDATE WITH YOUR LIST PLEASE\n", + "discovered = resolve_agents_from_registry(queries, registry_id=REGISTRY_ID)\n", + "\n", + "AGENT_ARNS = {n: info['runtimeArn'] for n, info in discovered.items() if info.get('runtimeArn')}\n", + "\n", + "print(f\"Resolved {len(AGENT_ARNS)} runtime ARNs:\\n\")\n", + "for name, arn in AGENT_ARNS.items():\n", + " print(f\" {name}: {arn}\")\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def invoke_agent(name, prompt):\n", + " \"\"\"Invoke an HTTP agent by name using its runtime ARN.\"\"\"\n", + " return invoke_http_agent(AGENT_ARNS[name], prompt)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Invoke: Market Research Agent\n", + "result = invoke_agent('market_research_agent', 'Analyze the tech sector competitors for NVTK')\n", + "print(\"=== Market Research ===\")\n", + "print(result['result'][:500])" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Invoke: Financial Analysis Agent\n", + "result = invoke_agent('financial_analysis_agent', 'Analyze NVTK financials')\n", + "print(\"=== Financial Analysis ===\")\n", + "print(result['result'][:500])" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Invoke: Sentiment Analysis Agent\n", + "result = invoke_agent('sentiment_analysis_agent', 'Analyze sentiment for NVTK')\n", + "print(\"=== Sentiment Analysis ===\")\n", + "print(result['result'][:500])" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Invoke: Patent Research Agent\n", + "result = invoke_agent('patent_research_agent', 'Analyze NovaTech patent portfolio in AI')\n", + "print(\"=== Patent Research ===\")\n", + "print(result['result'][:500])" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Invoke: Investment Thesis Generator\n", + "result = invoke_agent('investment_thesis_generator', 'Generate investment thesis for NVTK')\n", + "print(\"=== Investment Thesis ===\")\n", + "print(result['result'][:500])" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Invoke: Approval Workflow Agent\n", + "result = invoke_agent('approval_workflow_agent', 'Submit investment thesis for compliance review')\n", + "print(\"=== Compliance Approval ===\")\n", + "print(result['result'][:500])" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Invoke: Client Communication Agent\n", + "result = invoke_agent('data_retrieval_agent', 'Generate growth brief for NVTK thesis')\n", + "print(\"=== Client Brief ===\")\n", + "print(result['result'][:500])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "# __Step 4: Publish New Agents (fill the gaps)__\n", + "\n", + "The Wealth Advisory team builds two new agents:\n", + "- `investment_thesis_generator` — synthesizes all analysis into a unified thesis with conviction rating\n", + "- `client_communication_agent` — generates personalized briefs per client segment\n", + "\n", + "1. Add Investment Thesis Agent \n", + "2. Add Client Communication Agent\n", + "\n", + "\n", + "### 4.1 Create Agent Scripts and deploy and register them from Kiro using Kiro powers\n", + "Follow the instruction on how to use kiro power to create agents in AgentCore Runtime and publish them.\n", + "[Set up kiro powers](https://github.com/sanaiqbalw/amazon-bedrock-agentcore-samples/tree/br_dcr-registry_for_kiro-mcp-search/01-tutorials/10-Agent-Registry/01-advanced/kiro/kiro-power-publisher-workflow)\n", + "This will help you in :\n", + "1. Deploy the 2 new agents to AgentCore Runtime\n", + "2. Deploy the 2 new agents to AgentCore Runtime \n", + "3. Add to Registry, Submit for approval\n", + "\n", + "\n", + "### 4.2 Go to AWS console in Admin role and approve the agent submission.\n", + "\n", + "______________________\n", + "\n", + "\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## __Step 5 Verify the published Agents are now discoverable in Registry__" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Verify they're now discoverable\n", + "results = search_registry(\"investment thesis synthesis conviction\", registry_id)\n", + "print_results(\"investment thesis synthesis conviction\", results)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "\n", + "**The gaps are filled.** Both agents are now in the registry, discoverable by any team at AnyCompany.\n", + "\n", + "---\n", + "## Step 7: Orchestrate — The Full Workflow\n", + "\n", + "Now we compose all 9 agents into the 3-phase Quarterly Intelligence Briefing using Strands Agents.\n", + "\n", + "Each registry agent becomes a `@tool` that the orchestrator can call. The orchestrator is a single Strands `Agent` with a system prompt that describes the 3-phase workflow:\n", + "\n", + "| Phase | Tools | Purpose |\n", + "|:------|:------|:--------|\n", + "| Phase 1: Gather | `query_earnings`, `market_research`, `patent_research` | Collect raw data (parallel) |\n", + "| Phase 2: Analyze & Synthesize | `financial_analysis`, `sentiment_analysis`, `generate_thesis`, `compliance_review` | Deep analysis and thesis generation (sequential) |\n", + "| Phase 3: Deliver | `generate_client_brief`, `generate_report` | Personalized briefs and formatted reports |\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from strands import Agent, tool\n", + "from strands.models import BedrockModel\n", + "\n", + "# ── PHASE 1 tools: GATHER ──\n", + "\n", + "@tool\n", + "def query_earnings(ticker: str, quarter: str) -> str:\n", + " \"\"\"Retrieve quarterly earnings data.\"\"\"\n", + " return invoke_http_agent(AGENTS['data_retrieval_agent'], f'Get earnings data for {ticker} {quarter}')['result']\n", + "\n", + "@tool\n", + "def market_research(ticker: str) -> str:\n", + " \"\"\"Analyze market context and competitors for a ticker.\"\"\"\n", + " return invoke_http_agent(AGENTS['market_research_agent'], f'Analyze the tech sector competitors for {ticker}')['result']\n", + "\n", + "@tool\n", + "def patent_research(company: str) -> str:\n", + " \"\"\"Analyze patent portfolio and IP moats.\"\"\"\n", + " return invoke_http_agent(AGENTS['patent_research_agent'], f'Analyze {company} patent portfolio in AI')['result']\n", + "\n", + "# ── PHASE 2 tools: ANALYZE & SYNTHESIZE ──\n", + "\n", + "@tool\n", + "def financial_analysis(ticker: str) -> str:\n", + " \"\"\"Perform financial analysis including valuation and risk metrics.\"\"\"\n", + " return invoke_http_agent(AGENTS['financial_analysis_agent'], f'Analyze {ticker} financials')['result']\n", + "\n", + "@tool\n", + "def sentiment_analysis(ticker: str) -> str:\n", + " \"\"\"Analyze sentiment from news and social media.\"\"\"\n", + " return invoke_http_agent(AGENTS['sentiment_analysis_agent'], f'Analyze sentiment for {ticker}')['result']\n", + "\n", + "@tool\n", + "def generate_thesis(ticker: str, financial_summary: str, sentiment_summary: str, market_context: str) -> str:\n", + " \"\"\"Synthesize all analysis into a unified investment thesis with conviction rating.\"\"\"\n", + " prompt = f'Generate investment thesis for {ticker}. Financials: {financial_summary[:500]}. Sentiment: {sentiment_summary[:500]}. Market: {market_context[:500]}'\n", + " return invoke_http_agent(AGENTS['investment_thesis_generator'], prompt)['result']\n", + "\n", + "@tool\n", + "def compliance_review(thesis: str) -> str:\n", + " \"\"\"Submit investment thesis for compliance approval.\"\"\"\n", + " return invoke_http_agent(AGENTS['approval_workflow_agent'], f'Submit investment thesis for compliance review: {thesis[:500]}')['result']\n", + "\n", + "# ── PHASE 3 tools: DELIVER ──\n", + "\n", + "@tool\n", + "def generate_client_brief(thesis: str, client_segment: str) -> str:\n", + " \"\"\"Generate personalized client brief for a segment (conservative/growth/institutional).\"\"\"\n", + " return invoke_http_agent(AGENTS['client_communication_agent'], f'Generate {client_segment} brief for thesis: {thesis[:500]}')['result']\n", + "\n", + "@tool\n", + "def generate_report(title: str, content: str) -> str:\n", + " \"\"\"Format and generate a report.\"\"\"\n", + " return invoke_http_agent(AGENTS['reporting_agent'], f'Generate report titled {title}: {content[:500]}')['result']\n", + "\n", + "print('All 9 workflow tools defined.')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Create the orchestrator agent\n", + "orchestrator = Agent(\n", + " model=BedrockModel(model_id='us.anthropic.claude-sonnet-4-20250514-v1:0'),\n", + " tools=[\n", + " query_earnings, market_research, patent_research, # Phase 1: Gather\n", + " financial_analysis, sentiment_analysis, # Phase 2a: Analyze\n", + " generate_thesis, compliance_review, # Phase 2b: Synthesize\n", + " generate_client_brief, generate_report, # Phase 3: Deliver\n", + " ],\n", + " system_prompt=\"\"\"You are the Quarterly Intelligence Briefing orchestrator for AnyCompany Financial Services.\n", + "\n", + "When given a ticker and quarter, execute this 3-phase workflow:\n", + "\n", + "PHASE 1 — GATHER (call these in parallel):\n", + " 1. query_earnings — get raw earnings data\n", + " 2. market_research — get market context and competitor analysis\n", + " 3. patent_research — get IP and competitive moat analysis\n", + "\n", + "PHASE 2 — ANALYZE & SYNTHESIZE (sequential):\n", + " 4. financial_analysis — deep financial analysis\n", + " 5. sentiment_analysis — news and social sentiment\n", + " 6. generate_thesis — synthesize everything into a unified investment thesis\n", + " 7. compliance_review — submit thesis for compliance approval\n", + "\n", + "PHASE 3 — DELIVER:\n", + " 8. generate_client_brief — create personalized briefs for 'growth' segment\n", + " 9. generate_report — format the final report\n", + "\n", + "After all phases, provide a summary of the complete briefing.\"\"\"\n", + ")\n", + "\n", + "print('Orchestrator ready.')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Run the full Quarterly Intelligence Briefing\n", + "result = orchestrator('Generate a Quarterly Intelligence Briefing for NVTK Q1-2025')\n", + "display(Markdown(f'## Quarterly Intelligence Briefing — NVTK Q1-2025\\n\\n{str(result)}'))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "---\n", + "## Step 8: to Cleanup\n", + "\n", + "Deletes all records, the registry, discovered runtimes, gateways, OAuth providers, and Cognito pools." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "cleanup(REGISTRY_ID)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.14.2" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/01-tutorials/10-Agent-Registry/01-advanced/kiro/kiro-registry-search-publish-workflow/README.md b/01-tutorials/10-Agent-Registry/01-advanced/kiro/kiro-registry-search-publish-workflow/README.md new file mode 100644 index 000000000..143c8cfb1 --- /dev/null +++ b/01-tutorials/10-Agent-Registry/01-advanced/kiro/kiro-registry-search-publish-workflow/README.md @@ -0,0 +1,94 @@ +# Registry-First Development Workflow with Kiro + +Discover agentic capabilities from your organization's AWS Agent Registry, identify gaps, build missing agents, and publish them back — __all from the Kiro IDE__ using MCP-based search and Kiro Powers for publisher workflows. + +## What This Tutorial Demonstrates + +This tutorial walks through a **registry-first development workflow** powered by Kiro: + +1. **Search** — Use the AWS Agent Registry MCP server in Kiro to semantically search for existing agents and tools across your organization, directly from the IDE. + +2. **Identify gaps** — Map discovered capabilities against your workflow requirements and pinpoint what's missing. + +3. **Build & Publish** — Use Kiro Powers (which define the AWS Agent Registry publisher APIs) to build the missing agents and publish them back to the registry, all via the Kiro chat interface. + +4. **Invoke** — Resolve runtime ARNs from registry records and invoke agents via AgentCore Runtime. + +The key idea: a developer sitting in Kiro can go from "what agents exist?" to "I built and published the missing ones" without leaving the IDE. + +![AWS Agent Registry flow on Kiro](images/KiroDiagram.png) + +## What is AWS Agent Registry? + +AWS Agent Registry is a centralized, governed catalog for discovering, publishing, and managing AI agents and tools across an organization. It provides semantic search for capability-based discovery, IAM and OAuth access control, rich metadata management (protocol, version, connection info, tool schemas), and a built-in governance workflow (DRAFT → PENDING_APPROVAL → APPROVED) so teams can trust what they find and reuse what others have built. AWS Agent Registry has three pain personas, Publishers - who publish capabilities to the Registry , Admins - who approve the published capabilities and Consumers - who search and access the approved registry records downstream. + +![AWS Agent Registry Publisher flow ](images/Publisher-workflow.png) + + +![AWS Agent Registry Consumer flow](images/2-consumerflow.png) +## What is Dynamic Client Registration? + +Dynamic Client Registration is an OAuth and OpenID Connect protocol that lets client applications automatically register with an authorization server instead of requiring manual pre-registration. The DCR protocol is formally defined in RFC 7591, with optional registration management extensions in RFC 7592. It was designed to work within the Open Authorization (OAuth) and OpenID Connect (OIDC) ecosystems. It enables automatic creation of client IDs, secrets, and metadata (redirect URIs, scopes), often used for automation, AI agents, and dynamic scaling. + +### Why DCR Matters for Kiro + +For Kiro to search the registry as an MCP tool, the registry needs to be configured with a **CUSTOM_JWT authorizer** backed by an OAuth provider (Auth0 in this example). Kiro's MCP client uses the **authorization_code + PKCE** flow — it dynamically registers itself via Dynamic Client Registration (DCR), opens a browser for login, catches the callback on localhost, and exchanges the code for a token. This means zero manual credential management for the developer. + +## How Kiro Powers Fit In this Development Workflow + +A **Kiro Power** is a curated, pre-packaged bundle of capabilities designed for the Kiro AI-powered IDE that gives the Kiro agent instant, specialized expertise in a specific technology or workflow. + +Each power typically bundles three components : + +- **POWER.md** — A steering file that acts as an onboarding manual, telling the agent what MCP tools are available and when to use them +- **MCP server configuration** — The tools and connection details for the Model Context Protocol server +- **Additional steering or hooks** — Extra guidance files or automated validation hooks [ We use this for publisher of records] + +The key innovation is **dynamic context loading**: rather than loading every tool upfront (which can overwhelm the agent), powers activate only when relevant. For example, mention "database" and the Neon power loads; switch to deployment and the Netlify power activates while Neon deactivates. + +In this sample Kiro Powers package the AWS Agent Registry publisher APIs (`CreateRegistryRecord`, `SubmitRegistryRecordForApproval`, etc.) as steering files that guide Kiro through the publish workflow. When you ask Kiro to "publish this agent to the registry," the Power provides the step-by-step instructions and API calls — so you get a governed publish-and-approve cycle without writing boto3 code yourself. + +## Use Case: + +To ground this tutorial in a real world usecase , let imagine a scenrio of AnyCompany financial services. + +AnyCompany financial services firm has a multi team structure. Core teams include investment management, wealth advisory, trading operations, and compliance. Over the past year, multiple teams have independently built AI agents, MCP servers, and automation tools — but with no shared catalog, no common standard, and no way for one team to discover what another has already built. + +The Wealth Advisory team has been asked to build a Quarterly Intelligence Briefing workflow. +Here's the requirement: + +> *"When a publicly traded company reports quarterly results, automatically generate a comprehensive client-ready investment brief — including what happened, why it matters, how it affects each client's portfolio, and what (if any) action to consider — all within 30 minutes of the earnings release."* + +In order to build this,the key capabilities needed include : +* **First**, gathering data — pull raw earnings data, market context, competitive intel. +* **Second**, analyze and synthesize — run financial analysis compliance tests and generate an investment thesis based on the data. +* **Third** is generate a perosnlaized investment brief for each client. + + +The Wealth Advisory team has **zero** existing capabilties in house for earnings data ingestion, financial analysis, or compliance screening. Building from scratch would take **couple of months and $1M+**. + +But they don't need to build from scratch. **Other teams already have the pieces.** The Wealth Advisory team just needs a way to find them, verify they're approved for use, and wire them together into a flow. + +The result: what would have been a 6-month greenfield project becomes a composition exercise — 7 agents discovered from 5 teams, 2 new agents built to fill gaps, and a single orchestrator tying them all together. + + + + +![Wealth Advisory Teams Quarterly Briefing Use Case](images/5-UsecaseviaAWSRegistry.png) + +## Steps Involved: + +Set Up: + +- Create an Auth0 DCR-enabled registry (CUSTOM_JWT authorizer) [DCR set up instructions here](https://github.com/awslabs/agentcore-samples/blob/main/01-tutorials/10-Agent-Registry/01-advanced/kiro-registry-dcr-auth0/DCR_registry_search_mcp_in_kiro.ipynb) +- Deploy sample agents to AgentCore Runtime and register them as records + +Search + +- In Kiro Use the registry MCP server to search for existing agents from chat interface. +- Resolve runtime ARNs from search results and invoke agents from their URIs + + +Build + +- Set up and Use Kiro Powers to create and publish new agents to registry from Kiro Chat interface. [Sample Kiro powers available here ](https://github.com/sanaiqbalw/amazon-bedrock-agentcore-samples/tree/br_dcr-registry_for_kiro-mcp-search/01-tutorials/10-Agent-Registry/01-advanced/kiro/kiro-power-publisher-workflow) diff --git a/01-tutorials/10-Agent-Registry/01-advanced/kiro/kiro-registry-search-publish-workflow/images/2-consumerflow.png b/01-tutorials/10-Agent-Registry/01-advanced/kiro/kiro-registry-search-publish-workflow/images/2-consumerflow.png new file mode 100644 index 000000000..b8496d8c7 Binary files /dev/null and b/01-tutorials/10-Agent-Registry/01-advanced/kiro/kiro-registry-search-publish-workflow/images/2-consumerflow.png differ diff --git a/01-tutorials/10-Agent-Registry/01-advanced/kiro/kiro-registry-search-publish-workflow/images/5-UsecaseviaAWSRegistry.png b/01-tutorials/10-Agent-Registry/01-advanced/kiro/kiro-registry-search-publish-workflow/images/5-UsecaseviaAWSRegistry.png new file mode 100644 index 000000000..00430e7b8 Binary files /dev/null and b/01-tutorials/10-Agent-Registry/01-advanced/kiro/kiro-registry-search-publish-workflow/images/5-UsecaseviaAWSRegistry.png differ diff --git a/01-tutorials/10-Agent-Registry/01-advanced/kiro/kiro-registry-search-publish-workflow/images/KiroDiagram.png b/01-tutorials/10-Agent-Registry/01-advanced/kiro/kiro-registry-search-publish-workflow/images/KiroDiagram.png new file mode 100644 index 000000000..d33fac5dc Binary files /dev/null and b/01-tutorials/10-Agent-Registry/01-advanced/kiro/kiro-registry-search-publish-workflow/images/KiroDiagram.png differ diff --git a/01-tutorials/10-Agent-Registry/01-advanced/kiro/kiro-registry-search-publish-workflow/images/Publisher-workflow.png b/01-tutorials/10-Agent-Registry/01-advanced/kiro/kiro-registry-search-publish-workflow/images/Publisher-workflow.png new file mode 100644 index 000000000..b5fa7759b Binary files /dev/null and b/01-tutorials/10-Agent-Registry/01-advanced/kiro/kiro-registry-search-publish-workflow/images/Publisher-workflow.png differ diff --git a/01-tutorials/10-Agent-Registry/01-advanced/kiro/kiro-registry-search-publish-workflow/setup/.env.example b/01-tutorials/10-Agent-Registry/01-advanced/kiro/kiro-registry-search-publish-workflow/setup/.env.example new file mode 100644 index 000000000..ac46b3128 --- /dev/null +++ b/01-tutorials/10-Agent-Registry/01-advanced/kiro/kiro-registry-search-publish-workflow/setup/.env.example @@ -0,0 +1,4 @@ +AWS_REGION=us-west-2 +# Auth0 OAuth (DCR) — only the domain is needed, client creds are obtained via DCR +AUTH0_DOMAIN=your-tenant.us.auth0.us.com +AUTH0_AUDIENCE=https://bedrock-agentcore.us-west-2.amazonaws.com diff --git a/01-tutorials/10-Agent-Registry/01-advanced/kiro/kiro-registry-search-publish-workflow/setup/2_deploy_artifacts_on_agentcore_runtime.py b/01-tutorials/10-Agent-Registry/01-advanced/kiro/kiro-registry-search-publish-workflow/setup/2_deploy_artifacts_on_agentcore_runtime.py new file mode 100644 index 000000000..8e0faa9c1 --- /dev/null +++ b/01-tutorials/10-Agent-Registry/01-advanced/kiro/kiro-registry-search-publish-workflow/setup/2_deploy_artifacts_on_agentcore_runtime.py @@ -0,0 +1,83 @@ +#!/usr/bin/env python3 +""" +Step 2: Deploy agents to AgentCore Runtime. + +All agents are HTTP protocol with IAM auth (no OAuth on agents). + +Run: python3 2_deploy_artifacts_on_agentcore_runtime.py market_research_agent [financial_analysis_agent ...] +""" + +import argparse +import logging +import os +import shutil +import tempfile +from pathlib import Path + +from bedrock_agentcore_starter_toolkit import Runtime +from setup import REGION + +logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s") +logger = logging.getLogger("deploy") + + +def load_metadata(agent_name: str) -> dict: + mod = __import__(f"seed_agents.{agent_name}", fromlist=["METADATA"]) + return mod.METADATA + + +def deploy_one(agent_name: str) -> dict: + """Package and deploy a single agent to AgentCore Runtime. Returns name and ARN.""" + meta = load_metadata(agent_name) + name = meta["name"] + + logger.info("Deploying: %s (HTTP) — Team: %s", name, meta.get("team", "unknown")) + + agent_file = Path("seed_agents") / meta["entrypoint"] + req_file = Path("seed_agents") / "requirements.txt" + project_dir = Path(tempfile.mkdtemp(prefix=f"ac-{name}-")) + shutil.copy(agent_file, project_dir / meta["entrypoint"]) + if req_file.exists(): + shutil.copy(req_file, project_dir / "requirements.txt") + + original_dir = os.getcwd() + os.chdir(project_dir) + + try: + runtime = Runtime() + runtime.configure( + entrypoint=meta["entrypoint"], + agent_name=name, + protocol="HTTP", + region=REGION, + auto_create_execution_role=True, + auto_create_ecr=True, + requirements_file="requirements.txt" if req_file.exists() else None, + ) + result = runtime.launch(auto_update_on_conflict=True) + logger.info("Deployed: %s — ARN: %s", name, result.agent_arn) + return {"name": name, "agent_arn": result.agent_arn} + finally: + os.chdir(original_dir) + shutil.rmtree(project_dir, ignore_errors=True) + + +def main(): + parser = argparse.ArgumentParser(description="Deploy agent(s) to AgentCore Runtime") + parser.add_argument("agents", nargs="+", help="Agent module names (e.g. market_research_agent)") + args = parser.parse_args() + + results = [] + for name in args.agents: + try: + results.append(deploy_one(name)) + except Exception as e: + logger.error("Failed to deploy %s: %s", name, e) + + logger.info("Deployed %d/%d agents", len(results), len(args.agents)) + for r in results: + logger.info(" %s | %s", r["name"], r["agent_arn"]) + + +if __name__ == "__main__": + main() diff --git a/01-tutorials/10-Agent-Registry/01-advanced/kiro/kiro-registry-search-publish-workflow/setup/3_add_records_to_registry.py b/01-tutorials/10-Agent-Registry/01-advanced/kiro/kiro-registry-search-publish-workflow/setup/3_add_records_to_registry.py new file mode 100644 index 000000000..cfaaf63e0 --- /dev/null +++ b/01-tutorials/10-Agent-Registry/01-advanced/kiro/kiro-registry-search-publish-workflow/setup/3_add_records_to_registry.py @@ -0,0 +1,136 @@ +#!/usr/bin/env python3 +""" +Step 3: Register agents in the AgentCore Registry. + +All agents are registered as CUSTOM descriptorType with inline metadata. +Runtime ARNs are auto-discovered from AgentCore Runtime if not passed via --arn. + +Run: python3 3_add_records_to_registry.py market_research_agent [--arn ] +""" + +import argparse +import json +import logging +import time + +import boto3 +import requests as http_requests +from botocore.auth import SigV4Auth +from botocore.awsrequest import AWSRequest + +from setup import get_cp_client, REGION + +logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s") +logger = logging.getLogger("register") + + +def load_metadata(agent_name: str) -> dict: + mod = __import__(f"seed_agents.{agent_name}", fromlist=["METADATA"]) + return mod.METADATA + + +def discover_runtime_arn(agent_name: str) -> str: + """Auto-discover runtime ARN by listing agents on AgentCore Runtime control plane (paginated).""" + ac_cp = boto3.client("bedrock-agentcore-control", region_name=REGION) + try: + resp = ac_cp.list_agent_runtimes() + while True: + for agent in resp.get("agentRuntimes", []): + if agent["agentRuntimeName"] == agent_name: + logger.info("Auto-discovered ARN for %s: %s", agent_name, agent["agentRuntimeArn"]) + return agent["agentRuntimeArn"] + nt = resp.get("nextToken") + if not nt: + break + resp = ac_cp.list_agent_runtimes(nextToken=nt) + except Exception as e: + logger.warning("Could not auto-discover ARN for %s: %s", agent_name, e) + return None + + +def _create_record_raw(registry_id, body: dict) -> str: + """Create a registry record via signed HTTP.""" + cp_client = get_cp_client() + url = f"{cp_client.meta.endpoint_url}/registries/{registry_id}/records" + data = json.dumps(body) + req = AWSRequest(method="POST", url=url, data=data, headers={"Content-Type": "application/json"}) + creds = boto3.Session(region_name=REGION).get_credentials().get_frozen_credentials() + SigV4Auth(creds, "bedrock-agentcore", REGION).add_auth(req) + resp = http_requests.post(url, headers=dict(req.headers), data=data) + resp.raise_for_status() + return resp.json()["recordArn"].split("/")[-1] + + +def wait_for_draft(cp, registry_id, record_id, max_wait=30): + for _ in range(max_wait // 3): + time.sleep(3) + rec = cp.get_registry_record(registryId=registry_id, recordId=record_id) + if rec["status"] not in ("CREATING", "UPDATING"): + return rec["status"] + return "CREATING" + + +def approve(cp, registry_id, record_id): + cp.submit_registry_record_for_approval(registryId=registry_id, recordId=record_id) + creds = boto3.Session(region_name=REGION).get_credentials().get_frozen_credentials() + url = f"{cp.meta.endpoint_url}/registries/{registry_id}/records/{record_id}/status" + body = json.dumps({"status": "APPROVED", "statusReason": "Approved"}) + req = AWSRequest(method="PATCH", url=url, data=body, headers={"Content-Type": "application/json"}) + SigV4Auth(creds, "bedrock-agentcore", REGION).add_auth(req) + http_requests.request(method="PATCH", url=url, headers=dict(req.headers), data=body).raise_for_status() + + +def register_agent(cp, registry_id, meta: dict, runtime_arn: str = None) -> str: + """Create a CUSTOM registry record for an agent and approve it.""" + inline = { + "name": meta["name"], + "description": meta["description"], + "version": meta.get("version", "1.0.0"), + "protocol": "HTTP", + "team": meta.get("team", ""), + "capabilities": meta.get("capabilities", []), + "tools": meta["tools"], + } + if runtime_arn: + inline["runtimeArn"] = runtime_arn + + record_id = _create_record_raw(registry_id, { + "name": meta["name"], + "description": meta["description"], + "descriptorType": "CUSTOM", + "recordVersion": meta.get("version", "1.0"), + "descriptors": {"custom": {"inlineContent": json.dumps(inline)}}, + }) + logger.info("Created: %s -> %s", meta["name"], record_id) + + status = wait_for_draft(cp, registry_id, record_id) + logger.info("Status: %s", status) + approve(cp, registry_id, record_id) + logger.info("Approved: %s", meta["name"]) + return record_id + + +def main(): + parser = argparse.ArgumentParser(description="Register agent(s) in AgentCore Registry") + parser.add_argument("agent", help="Agent module name") + parser.add_argument("--arn", help="Runtime ARN (auto-discovered if omitted)") + parser.add_argument("--registry-id", help="Override REGISTRY_ID from .env") + args = parser.parse_args() + + registry_id = args.registry_id + if not registry_id: + logger.error("No REGISTRY_ID set. Run 1_create_registry.py first.") + return + + meta = load_metadata(args.agent) + cp = get_cp_client() + + runtime_arn = args.arn or discover_runtime_arn(meta["name"]) + + logger.info("Registering: %s — Team: %s — ARN: %s", meta["name"], meta.get("team", "unknown"), runtime_arn or "NONE") + record_id = register_agent(cp, registry_id, meta, runtime_arn=runtime_arn) + logger.info("Done: %s | Record: %s", meta["name"], record_id) + + +if __name__ == "__main__": + main() diff --git a/01-tutorials/10-Agent-Registry/01-advanced/kiro/kiro-registry-search-publish-workflow/setup/seed_agents/__init__.py b/01-tutorials/10-Agent-Registry/01-advanced/kiro/kiro-registry-search-publish-workflow/setup/seed_agents/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/01-tutorials/10-Agent-Registry/01-advanced/kiro/kiro-registry-search-publish-workflow/setup/seed_agents/approval_workflow_agent.py b/01-tutorials/10-Agent-Registry/01-advanced/kiro/kiro-registry-search-publish-workflow/setup/seed_agents/approval_workflow_agent.py new file mode 100644 index 000000000..c4a65229a --- /dev/null +++ b/01-tutorials/10-Agent-Registry/01-advanced/kiro/kiro-registry-search-publish-workflow/setup/seed_agents/approval_workflow_agent.py @@ -0,0 +1,63 @@ +"""Approval Workflow Agent — compliance gate for POC (auto-approves).""" + +METADATA = { + "name": "approval_workflow_agent", + "description": "Agent that orchestrates multi-step approval workflows with escalation and notification", + "protocol": "HTTP", + "entrypoint": "approval_workflow_agent.py", + "version": "1.0.0", + "team": "Compliance", + "capabilities": ["approval-routing", "compliance-check", "escalation"], + "tools": [ + {"name": "submit_for_compliance_review", "description": "Submit document for compliance review"}, + {"name": "check_approval_status", "description": "Check status of a compliance review"}, + ], +} + +from strands import Agent, tool # noqa: E402 +from strands.models import BedrockModel # noqa: E402 +from bedrock_agentcore.runtime import BedrockAgentCoreApp # noqa: E402 + +app = BedrockAgentCoreApp() + + +@tool +def submit_for_compliance_review(document_type: str, content_summary: str) -> str: + """Submit a document for compliance review.""" + return f"""{{"status": "APPROVED", +"review_id": "CR-2025-00847", +"document_type": "{document_type}", +"reviewer": "Compliance Bot v2", +"checks_passed": [ + "No forward-looking statements without disclaimers", + "Risk disclosures present", + "No material non-public information detected", + "Suitability disclaimers included" +], +"flags": [], +"approved_at": "2025-01-30T16:30:00Z", +"notes": "Auto-approved — all compliance checks passed"}}""" + + +@tool +def check_approval_status(review_id: str) -> str: + """Check the status of a compliance review.""" + return f"""{{"review_id": "{review_id}", "status": "APPROVED", +"approved_at": "2025-01-30T16:30:00Z"}}""" + + +agent = Agent( + model=BedrockModel(model_id="us.anthropic.claude-sonnet-4-20250514-v1:0"), + tools=[submit_for_compliance_review, check_approval_status], + system_prompt="You are a compliance workflow agent. Submit documents for review and report approval status.", +) + + +@app.entrypoint +def invoke(payload, context=None): + result = agent(payload.get("prompt", "Submit investment thesis for compliance review")) + return {"result": str(result)} + + +if __name__ == "__main__": + app.run() diff --git a/01-tutorials/10-Agent-Registry/01-advanced/kiro/kiro-registry-search-publish-workflow/setup/seed_agents/client_communication_agent.py b/01-tutorials/10-Agent-Registry/01-advanced/kiro/kiro-registry-search-publish-workflow/setup/seed_agents/client_communication_agent.py new file mode 100644 index 000000000..1e92d7265 --- /dev/null +++ b/01-tutorials/10-Agent-Registry/01-advanced/kiro/kiro-registry-search-publish-workflow/setup/seed_agents/client_communication_agent.py @@ -0,0 +1,69 @@ +"""Client Communication Agent — generates personalized briefs. NEW agent built by Wealth Advisory.""" + +METADATA = { + "name": "client_communication_agent", + "description": "Generates personalized client briefs tailored to risk profile, portfolio holdings, and communication preferences", + "protocol": "HTTP", + "entrypoint": "client_communication_agent.py", + "version": "1.0.0", + "team": "Wealth Advisory", + "capabilities": ["audience-segmentation", "tone-adaptation", "portfolio-aware-messaging"], + "tools": [ + {"name": "generate_client_brief", "description": "Generate personalized investment brief for a client segment"}, + ], +} + +from strands import Agent, tool # noqa: E402 +from strands.models import BedrockModel # noqa: E402 +from bedrock_agentcore.runtime import BedrockAgentCoreApp # noqa: E402 + +app = BedrockAgentCoreApp() + + +@tool +def generate_client_brief(thesis: str, client_segment: str) -> str: + """Generate personalized investment brief for a client segment.""" + briefs = { + "conservative": """{ +"segment": "conservative", +"subject": "NVTK Quarterly Update — Steady Growth with Managed Risk", +"tone": "reassuring", +"key_message": "NovaTech continues to deliver consistent, predictable growth. The 5.5% revenue increase and expanding margins from services provide a stable foundation. We recommend maintaining your current position.", +"risk_emphasis": "HIGH — detailed risk section with downside scenarios", +"action": "HOLD — maintain current allocation, no changes recommended", +"portfolio_impact": "Your NVTK position (4.2% of portfolio) remains within target allocation range"}""", + "growth": """{ +"segment": "growth", +"subject": "NVTK — Strong Conviction BUY on AI Catalyst", +"tone": "opportunity-focused", +"key_message": "NovaTech's Q1 beat signals the beginning of an AI-driven growth cycle. With conviction rating 8/10 and $89 price target (14% upside), this is an opportunity to increase exposure before the market fully prices in the AI strategy.", +"risk_emphasis": "MODERATE — risks noted but framed as entry opportunities", +"action": "BUY — consider increasing allocation by 1-2%", +"portfolio_impact": "Increasing NVTK from 6.1% to 7.5% keeps portfolio within growth mandate"}""", + "institutional": """{ +"segment": "institutional", +"subject": "NVTK Q1 FY2025 — Comprehensive Analysis & Thesis", +"tone": "technical", +"key_message": "Full quantitative analysis attached. DCF fair value $85 vs current $78.42. Conviction 8/10 driven by cross-source alignment across fundamentals, sentiment, and IP moat assessment.", +"risk_emphasis": "FULL — complete risk matrix with probability-weighted scenarios", +"action": "OVERWEIGHT — increase by 50bps relative to benchmark", +"portfolio_impact": "Appendix includes full attribution analysis and tracking error impact"}""" + } + return briefs.get(client_segment, briefs["growth"]) + + +agent = Agent( + model=BedrockModel(model_id="us.anthropic.claude-sonnet-4-20250514-v1:0"), + tools=[generate_client_brief], + system_prompt="You are a client communications specialist. Generate personalized investment briefs tailored to different client segments (conservative, growth, institutional).", +) + + +@app.entrypoint +def invoke(payload, context=None): + result = agent(payload.get("prompt", "Generate briefs for all client segments for NVTK")) + return {"result": str(result)} + + +if __name__ == "__main__": + app.run() diff --git a/01-tutorials/10-Agent-Registry/01-advanced/kiro/kiro-registry-search-publish-workflow/setup/seed_agents/code_review_agent.py b/01-tutorials/10-Agent-Registry/01-advanced/kiro/kiro-registry-search-publish-workflow/setup/seed_agents/code_review_agent.py new file mode 100644 index 000000000..0ab9fb9f4 --- /dev/null +++ b/01-tutorials/10-Agent-Registry/01-advanced/kiro/kiro-registry-search-publish-workflow/setup/seed_agents/code_review_agent.py @@ -0,0 +1,66 @@ +"""Code Review Agent — analyzes code for quality, security, and best practices.""" + +METADATA = { + "name": "code_review_agent", + "description": "Agent for automated code review, security scanning, and best practice enforcement across repositories", + "protocol": "HTTP", + "entrypoint": "code_review_agent.py", + "version": "1.0.0", + "team": "Engineering", + "capabilities": ["code-review", "security-scanning", "best-practices", "static-analysis"], + "tools": [ + {"name": "review_code", "description": "Review code for quality issues, bugs, and improvements"}, + {"name": "security_scan", "description": "Scan code for security vulnerabilities and compliance issues"}, + ], +} + +from strands import Agent, tool # noqa: E402 +from strands.models import BedrockModel # noqa: E402 +from bedrock_agentcore.runtime import BedrockAgentCoreApp # noqa: E402 + +app = BedrockAgentCoreApp() + + +@tool +def review_code(code_snippet: str, language: str = "python") -> str: + """Review code for quality issues, bugs, and improvements.""" + return f"""{{"language": "{language}", +"issues_found": 3, +"severity_summary": {{"critical": 0, "warning": 2, "info": 1}}, +"findings": [ + {{"severity": "warning", "line": 12, "message": "Unused variable 'temp_result'", "suggestion": "Remove or use the variable"}}, + {{"severity": "warning", "line": 28, "message": "Function exceeds 50 lines — consider refactoring", "suggestion": "Extract helper functions"}}, + {{"severity": "info", "line": 5, "message": "Missing type hints on function parameters", "suggestion": "Add type annotations"}} +], +"overall_quality": "GOOD", +"recommendation": "Address warnings before merging"}}""" + + +@tool +def security_scan(code_snippet: str, language: str = "python") -> str: + """Scan code for security vulnerabilities and compliance issues.""" + return f"""{{"language": "{language}", +"vulnerabilities_found": 1, +"severity_summary": {{"critical": 0, "high": 0, "medium": 1, "low": 0}}, +"findings": [ + {{"severity": "medium", "type": "CWE-798", "message": "Potential hardcoded credential detected", "suggestion": "Use environment variables or secrets manager"}} +], +"compliance": {{"owasp_top10": "PASS", "cwe_top25": "PASS_WITH_WARNINGS"}}, +"recommendation": "Resolve medium-severity finding before deployment"}}""" + + +agent = Agent( + model=BedrockModel(model_id="us.anthropic.claude-sonnet-4-20250514-v1:0"), + tools=[review_code, security_scan], + system_prompt="You are a senior code reviewer. Use your tools to analyze code for quality, security, and best practices.", +) + + +@app.entrypoint +def invoke(payload, context=None): + result = agent(payload.get("prompt", "Review the submitted code")) + return {"result": str(result)} + + +if __name__ == "__main__": + app.run() diff --git a/01-tutorials/10-Agent-Registry/01-advanced/kiro/kiro-registry-search-publish-workflow/setup/seed_agents/data_retrieval_agent.py b/01-tutorials/10-Agent-Registry/01-advanced/kiro/kiro-registry-search-publish-workflow/setup/seed_agents/data_retrieval_agent.py new file mode 100644 index 000000000..ce93f211a --- /dev/null +++ b/01-tutorials/10-Agent-Registry/01-advanced/kiro/kiro-registry-search-publish-workflow/setup/seed_agents/data_retrieval_agent.py @@ -0,0 +1,64 @@ +"""Data Retrieval Agent — returns static financial data for POC.""" + +METADATA = { + "name": "data_retrieval_agent", + "description": "Agent for retrieving financial data from databases, APIs, and data lakes", + "protocol": "HTTP", + "entrypoint": "data_retrieval_agent.py", + "version": "1.0.0", + "team": "Data Engineering", + "capabilities": ["earnings-data", "price-history", "sec-filings"], + "tools": [ + {"name": "query_earnings", "description": "Retrieve quarterly earnings data for a ticker"}, + {"name": "query_price_history", "description": "Retrieve recent price history"}, + {"name": "query_sec_filings", "description": "Retrieve recent SEC filings"}, + ], +} + +from strands import Agent, tool # noqa: E402 +from strands.models import BedrockModel # noqa: E402 +from bedrock_agentcore.runtime import BedrockAgentCoreApp # noqa: E402 + +app = BedrockAgentCoreApp() + + +@tool +def query_earnings(ticker: str, quarter: str) -> str: + """Retrieve quarterly earnings data for a ticker.""" + return f"""{{"ticker": "{ticker}", "quarter": "{quarter}", +"revenue": "$14.2B", "eps": "$3.87", "revenue_growth": "5.5%", +"net_income": "$3.1B", "gross_margin": "44.8%", +"guidance": "Q2 revenue $13.5-14.5B"}}""" + + +@tool +def query_price_history(ticker: str, days: int = 30) -> str: + """Retrieve recent price history for a ticker.""" + return f"""{{"ticker": "{ticker}", "period": "{days}d", +"current": "$78.42", "high_30d": "$82.15", "low_30d": "$73.60", +"avg_volume": "12.8M", "change_30d": "+4.8%"}}""" + + +@tool +def query_sec_filings(ticker: str) -> str: + """Retrieve recent SEC filings for a ticker.""" + return f"""{{"ticker": "{ticker}", +"filings": [{{"type": "10-Q", "date": "2025-01-30", "summary": "Quarterly report filed"}}, +{{"type": "8-K", "date": "2025-01-28", "summary": "Earnings press release"}}]}}""" + + +agent = Agent( + model=BedrockModel(model_id="us.anthropic.claude-sonnet-4-20250514-v1:0"), + tools=[query_earnings, query_price_history, query_sec_filings], + system_prompt="You are a data retrieval specialist. Use your tools to fetch financial data.", +) + + +@app.entrypoint +def invoke(payload, context=None): + result = agent(payload.get("prompt", "Get NVTK earnings data")) + return {"result": str(result)} + + +if __name__ == "__main__": + app.run() diff --git a/01-tutorials/10-Agent-Registry/01-advanced/kiro/kiro-registry-search-publish-workflow/setup/seed_agents/financial_analysis_agent.py b/01-tutorials/10-Agent-Registry/01-advanced/kiro/kiro-registry-search-publish-workflow/setup/seed_agents/financial_analysis_agent.py new file mode 100644 index 000000000..5289839ac --- /dev/null +++ b/01-tutorials/10-Agent-Registry/01-advanced/kiro/kiro-registry-search-publish-workflow/setup/seed_agents/financial_analysis_agent.py @@ -0,0 +1,71 @@ +"""Financial Analysis Agent — returns static financial analysis for POC.""" + +METADATA = { + "name": "financial_analysis_agent", + "description": "Agent that performs financial analysis including portfolio valuation, risk metrics, and trend analysis", + "protocol": "HTTP", + "entrypoint": "financial_analysis_agent.py", + "version": "1.0.0", + "team": "Finance", + "capabilities": ["portfolio-valuation", "risk-assessment", "trend-analysis"], + "tools": [ + {"name": "portfolio_valuation", "description": "Calculate valuation metrics"}, + {"name": "risk_assessment", "description": "Compute risk metrics"}, + {"name": "trend_analysis", "description": "Analyze earnings and revenue trends"}, + ], +} + +from strands import Agent, tool # noqa: E402 +from strands.models import BedrockModel # noqa: E402 +from bedrock_agentcore.runtime import BedrockAgentCoreApp # noqa: E402 + +app = BedrockAgentCoreApp() + + +@tool +def portfolio_valuation(ticker: str) -> str: + """Calculate valuation metrics for a stock.""" + return f"""{{"ticker": "{ticker}", +"pe_ratio": 31.2, "forward_pe": 28.5, "peg_ratio": 1.8, +"price_to_book": 5.4, "ev_ebitda": 24.1, +"dcf_fair_value": "$85.00", "current_price": "$78.42", +"upside_potential": "6.5%", +"valuation_signal": "SLIGHTLY_UNDERVALUED"}}""" + + +@tool +def risk_assessment(ticker: str) -> str: + """Compute risk metrics for a stock.""" + return f"""{{"ticker": "{ticker}", +"beta": 1.24, "sharpe_ratio": 1.45, "max_drawdown": "-12.3%", +"var_95": "-3.2%", "volatility_30d": "22.1%", +"risk_rating": "MODERATE", +"risk_factors": ["International revenue exposure (18%)", "Regulatory scrutiny", "AI capex ramp"]}}""" + + +@tool +def trend_analysis(ticker: str) -> str: + """Analyze earnings and revenue trends.""" + return f"""{{"ticker": "{ticker}", +"revenue_trend": "5 consecutive quarters of growth", +"eps_trend": "Accelerating — 8.2% YoY growth", +"margin_trend": "Expanding — services mix shift driving gross margin improvement", +"guidance_vs_consensus": "In-line to slightly above", +"analyst_consensus": "BUY (32 buy, 8 hold, 2 sell)"}}""" + + +agent = Agent( + model=BedrockModel(model_id="us.anthropic.claude-sonnet-4-20250514-v1:0"), + tools=[portfolio_valuation, risk_assessment, trend_analysis], + system_prompt="You are a financial analyst. Use your tools to provide comprehensive financial analysis.", +) + + +@app.entrypoint +def invoke(payload, context=None): + result = agent(payload.get("prompt", "Analyze NVTK financials")) + return {"result": str(result)} + + +if __name__ == "__main__": + app.run() diff --git a/01-tutorials/10-Agent-Registry/01-advanced/kiro/kiro-registry-search-publish-workflow/setup/seed_agents/investment_thesis_generator.py b/01-tutorials/10-Agent-Registry/01-advanced/kiro/kiro-registry-search-publish-workflow/setup/seed_agents/investment_thesis_generator.py new file mode 100644 index 000000000..0041d30fe --- /dev/null +++ b/01-tutorials/10-Agent-Registry/01-advanced/kiro/kiro-registry-search-publish-workflow/setup/seed_agents/investment_thesis_generator.py @@ -0,0 +1,60 @@ +"""Investment Thesis Generator — synthesizes all analysis into unified thesis. NEW agent built by Wealth Advisory.""" + +METADATA = { + "name": "investment_thesis_generator", + "description": "Synthesizes financial analysis, sentiment scores, and market context into a unified investment thesis with conviction ratings", + "protocol": "HTTP", + "entrypoint": "investment_thesis_generator.py", + "version": "1.0.0", + "team": "Wealth Advisory", + "capabilities": ["multi-source-synthesis", "conviction-scoring", "risk-identification"], + "tools": [ + {"name": "generate_thesis", "description": "Generate unified investment thesis from multiple analysis inputs"}, + {"name": "score_conviction", "description": "Calculate conviction rating based on cross-source alignment"}, + ], +} + +from strands import Agent, tool # noqa: E402 +from strands.models import BedrockModel # noqa: E402 +from bedrock_agentcore.runtime import BedrockAgentCoreApp # noqa: E402 + +app = BedrockAgentCoreApp() + + +@tool +def generate_thesis(ticker: str, financial_summary: str, sentiment_summary: str, market_context: str) -> str: + """Generate unified investment thesis from multiple analysis inputs.""" + return f"""{{"ticker": "{ticker}", +"thesis": "NVTK presents a compelling risk-adjusted opportunity. Strong Q1 earnings beat (+5.5% revenue growth) combined with bullish sentiment (0.78 confidence) and expanding margins from services mix shift support a BUY thesis. The on-device AI strategy creates a durable competitive moat backed by 47 recent patent filings. Key risk: international revenue exposure (18%) amid geopolitical uncertainty.", +"conviction_rating": 8, +"conviction_rationale": "High cross-source alignment — financials, sentiment, and competitive position all point bullish. Deducted 2 points for international risk and elevated valuation.", +"recommendation": "BUY", +"time_horizon": "12-18 months", +"price_target": "$89", +"risk_factors": ["International revenue exposure (18%)", "Regulatory risk", "AI capex ramp impact on near-term margins"], +"supporting_evidence": ["Revenue beat consensus by 2.1%", "8/10 top analysts bullish", "Patent moat rated STRONG"]}}""" + + +@tool +def score_conviction(data_alignment: str) -> str: + """Calculate conviction rating based on cross-source alignment.""" + return """{"score": 8, "max": 10, +"factors": {"financial_strength": 9, "sentiment_alignment": 8, "competitive_moat": 9, "risk_adjusted": 7}, +"methodology": "Weighted average across 4 dimensions with risk penalty"}""" + + +agent = Agent( + model=BedrockModel(model_id="us.anthropic.claude-sonnet-4-20250514-v1:0"), + tools=[generate_thesis, score_conviction], + system_prompt="You are an investment thesis synthesizer. Combine financial analysis, sentiment data, and market context into a unified investment thesis with conviction ratings.", +) + + +@app.entrypoint +def invoke(payload, context=None): + result = agent(payload.get("prompt", "Generate investment thesis for NVTK")) + return {"result": str(result)} + + +if __name__ == "__main__": + app.run() diff --git a/01-tutorials/10-Agent-Registry/01-advanced/kiro/kiro-registry-search-publish-workflow/setup/seed_agents/market_research_agent.py b/01-tutorials/10-Agent-Registry/01-advanced/kiro/kiro-registry-search-publish-workflow/setup/seed_agents/market_research_agent.py new file mode 100644 index 000000000..8aeec6cf1 --- /dev/null +++ b/01-tutorials/10-Agent-Registry/01-advanced/kiro/kiro-registry-search-publish-workflow/setup/seed_agents/market_research_agent.py @@ -0,0 +1,62 @@ +"""Market Research Agent — returns static market context for POC.""" + +METADATA = { + "name": "market_research_agent", + "description": "Agent for conducting market research, competitor analysis, and industry trend reports", + "protocol": "HTTP", + "entrypoint": "market_research_agent.py", + "version": "1.0.0", + "team": "Strategy", + "capabilities": ["market-research", "competitor-analysis", "trend-reports"], + "tools": [ + {"name": "competitor_analysis", "description": "Analyze competitors in a sector"}, + {"name": "industry_trends", "description": "Get industry trend analysis"}, + ], +} + +from strands import Agent, tool # noqa: E402 +from strands.models import BedrockModel # noqa: E402 +from bedrock_agentcore.runtime import BedrockAgentCoreApp # noqa: E402 + +app = BedrockAgentCoreApp() + + +@tool +def competitor_analysis(sector: str) -> str: + """Analyze competitors in a sector.""" + return f"""{{"sector": "{sector}", +"competitors": [ + {{"name": "Axiom Systems", "market_share": "19.4%", "trend": "stable"}}, + {{"name": "Pinnacle Digital", "market_share": "12.1%", "trend": "growing"}}, + {{"name": "Vertex Labs", "market_share": "8.7%", "trend": "growing"}} +], +"key_moves": ["Axiom expanding AI chip fab", "Pinnacle launching enterprise AI tier"]}}""" + + +@tool +def industry_trends(sector: str) -> str: + """Get industry trend analysis.""" + return f"""{{"sector": "{sector}", +"trends": [ + {{"trend": "On-device AI", "impact": "HIGH", "timeline": "12-18 months"}}, + {{"trend": "Edge computing integration", "impact": "MEDIUM", "timeline": "24-36 months"}}, + {{"trend": "Sustainability regulations", "impact": "MEDIUM", "timeline": "6-12 months"}} +], +"macro_outlook": "Cautiously optimistic — enterprise spending resilient, AI adoption accelerating"}}""" + + +agent = Agent( + model=BedrockModel(model_id="us.anthropic.claude-sonnet-4-20250514-v1:0"), + tools=[competitor_analysis, industry_trends], + system_prompt="You are a market research analyst. Use your tools to provide market context and competitive intelligence.", +) + + +@app.entrypoint +def invoke(payload, context=None): + result = agent(payload.get("prompt", "Analyze the tech sector")) + return {"result": str(result)} + + +if __name__ == "__main__": + app.run() diff --git a/01-tutorials/10-Agent-Registry/01-advanced/kiro/kiro-registry-search-publish-workflow/setup/seed_agents/patent_research_agent.py b/01-tutorials/10-Agent-Registry/01-advanced/kiro/kiro-registry-search-publish-workflow/setup/seed_agents/patent_research_agent.py new file mode 100644 index 000000000..0762b3336 --- /dev/null +++ b/01-tutorials/10-Agent-Registry/01-advanced/kiro/kiro-registry-search-publish-workflow/setup/seed_agents/patent_research_agent.py @@ -0,0 +1,60 @@ +"""Patent Research Agent — returns static IP intelligence for POC.""" + +METADATA = { + "name": "patent_research_agent", + "description": "Agent for searching patents, prior art, and intellectual property databases", + "protocol": "HTTP", + "entrypoint": "patent_research_agent.py", + "version": "1.0.0", + "team": "R&D / Legal", + "capabilities": ["patent-search", "prior-art-analysis", "ip-landscape"], + "tools": [ + {"name": "search_patents", "description": "Search patent filings for a company"}, + {"name": "ip_landscape", "description": "Generate IP landscape report"}, + ], +} + +from strands import Agent, tool # noqa: E402 +from strands.models import BedrockModel # noqa: E402 +from bedrock_agentcore.runtime import BedrockAgentCoreApp # noqa: E402 + +app = BedrockAgentCoreApp() + + +@tool +def search_patents(company: str, domain: str) -> str: + """Search patent filings for a company in a technology domain.""" + return f"""{{"company": "{company}", "domain": "{domain}", +"recent_filings": 47, "granted_last_year": 31, +"top_areas": ["neural engine architectures", "on-device ML inference", "privacy-preserving computation"], +"notable_patents": [ + {{"id": "US-2025-0012345", "title": "Efficient transformer inference on edge SoC", "filed": "2024-11-15"}}, + {{"id": "US-2025-0012890", "title": "Federated learning with differential privacy", "filed": "2024-10-22"}} +]}}""" + + +@tool +def ip_landscape(domain: str) -> str: + """Generate IP landscape report for a technology area.""" + return f"""{{"domain": "{domain}", +"total_patents_filed_2024": 12840, +"top_assignees": ["NovaTech (1,247)", "Axiom Systems (1,102)", "Pinnacle Digital (987)", "Vertex Labs (834)"], +"emerging_areas": ["neuromorphic computing", "quantum-classical hybrid ML"], +"moat_assessment": "STRONG — deep patent portfolio in on-device AI creates significant barriers to entry"}}""" + + +agent = Agent( + model=BedrockModel(model_id="us.anthropic.claude-sonnet-4-20250514-v1:0"), + tools=[search_patents, ip_landscape], + system_prompt="You are an IP research analyst. Use your tools to assess patent landscapes and competitive tech moats.", +) + + +@app.entrypoint +def invoke(payload, context=None): + result = agent(payload.get("prompt", "Analyze NovaTech's patent portfolio in AI")) + return {"result": str(result)} + + +if __name__ == "__main__": + app.run() diff --git a/01-tutorials/10-Agent-Registry/01-advanced/kiro/kiro-registry-search-publish-workflow/setup/seed_agents/reporting_agent.py b/01-tutorials/10-Agent-Registry/01-advanced/kiro/kiro-registry-search-publish-workflow/setup/seed_agents/reporting_agent.py new file mode 100644 index 000000000..88583c820 --- /dev/null +++ b/01-tutorials/10-Agent-Registry/01-advanced/kiro/kiro-registry-search-publish-workflow/setup/seed_agents/reporting_agent.py @@ -0,0 +1,63 @@ +"""Reporting Agent — formats and generates reports for POC.""" + +METADATA = { + "name": "reporting_agent", + "description": "Agent for generating reports, dashboards, and data visualizations", + "protocol": "HTTP", + "entrypoint": "reporting_agent.py", + "version": "1.0.0", + "team": "BI", + "capabilities": ["report-generation", "dashboards", "distribution"], + "tools": [ + {"name": "generate_report", "description": "Generate a formatted report from content"}, + {"name": "create_dashboard", "description": "Create a dashboard with charts and metrics"}, + {"name": "distribute_report", "description": "Distribute a report to specified channels"}, + ], +} + +from strands import Agent, tool # noqa: E402 +from strands.models import BedrockModel # noqa: E402 +from bedrock_agentcore.runtime import BedrockAgentCoreApp # noqa: E402 + +app = BedrockAgentCoreApp() + + +@tool +def generate_report(title: str, content: str, format: str = "pdf") -> str: + """Generate a formatted report from content.""" + return f"""{{"status": "generated", "title": "{title}", "format": "{format}", +"pages": 4, "url": "s3://novacorp-reports/{title.replace(' ', '_').lower()}.{format}", +"sections": ["Executive Summary", "Analysis", "Recommendations", "Appendix"]}}""" + + +@tool +def create_dashboard(title: str, metrics: str) -> str: + """Create a dashboard with charts and metrics.""" + return f"""{{"status": "created", "title": "{title}", +"url": "https://dashboards.novacorp.internal/{title.replace(' ', '-').lower()}", +"widgets": ["revenue_chart", "sentiment_gauge", "risk_heatmap", "conviction_score"]}}""" + + +@tool +def distribute_report(report_url: str, channels: str) -> str: + """Distribute a report to specified channels.""" + return f"""{{"status": "distributed", "report": "{report_url}", +"channels": ["email:wealth-advisory@novacorp.com", "slack:#investment-briefs", "portal:client-hub"], +"recipients": 142, "delivered_at": "2025-01-30T16:45:00Z"}}""" + + +agent = Agent( + model=BedrockModel(model_id="us.anthropic.claude-sonnet-4-20250514-v1:0"), + tools=[generate_report, create_dashboard, distribute_report], + system_prompt="You are a reporting specialist. Use your tools to generate and distribute reports.", +) + + +@app.entrypoint +def invoke(payload, context=None): + result = agent(payload.get("prompt", "Generate a quarterly report")) + return {"result": str(result)} + + +if __name__ == "__main__": + app.run() diff --git a/01-tutorials/10-Agent-Registry/01-advanced/kiro/kiro-registry-search-publish-workflow/setup/seed_agents/requirements.txt b/01-tutorials/10-Agent-Registry/01-advanced/kiro/kiro-registry-search-publish-workflow/setup/seed_agents/requirements.txt new file mode 100644 index 000000000..825d7e6bd --- /dev/null +++ b/01-tutorials/10-Agent-Registry/01-advanced/kiro/kiro-registry-search-publish-workflow/setup/seed_agents/requirements.txt @@ -0,0 +1,4 @@ +strands-agents[otel]>=1.0.0 +boto3>=1.35.0 +bedrock-agentcore>=1.0.0 +aws-opentelemetry-distro diff --git a/01-tutorials/10-Agent-Registry/01-advanced/kiro/kiro-registry-search-publish-workflow/setup/seed_agents/sentiment_analysis_agent.py b/01-tutorials/10-Agent-Registry/01-advanced/kiro/kiro-registry-search-publish-workflow/setup/seed_agents/sentiment_analysis_agent.py new file mode 100644 index 000000000..4b84c5868 --- /dev/null +++ b/01-tutorials/10-Agent-Registry/01-advanced/kiro/kiro-registry-search-publish-workflow/setup/seed_agents/sentiment_analysis_agent.py @@ -0,0 +1,74 @@ +"""Sentiment Analysis Agent — returns static sentiment scores for POC.""" + +METADATA = { + "name": "sentiment_analysis_agent", + "description": "Agent for analyzing sentiment in customer feedback, reviews, and social media", + "protocol": "HTTP", + "entrypoint": "sentiment_analysis_agent.py", + "version": "1.0.0", + "team": "Marketing / CX", + "capabilities": ["sentiment-analysis", "social-monitoring", "analyst-tracking"], + "tools": [ + {"name": "analyze_news_sentiment", "description": "Analyze sentiment from news articles"}, + {"name": "analyze_social_sentiment", "description": "Monitor social media sentiment"}, + {"name": "analyze_analyst_sentiment", "description": "Analyze Wall Street analyst sentiment"}, + ], +} + +from strands import Agent, tool # noqa: E402 +from strands.models import BedrockModel # noqa: E402 +from bedrock_agentcore.runtime import BedrockAgentCoreApp # noqa: E402 + +app = BedrockAgentCoreApp() + + +@tool +def analyze_news_sentiment(ticker: str) -> str: + """Analyze sentiment from news articles about a company.""" + return f"""{{"ticker": "{ticker}", +"articles_analyzed": 127, +"overall_sentiment": "BULLISH", "confidence": 0.78, +"breakdown": {{"very_bullish": 34, "bullish": 48, "neutral": 29, "bearish": 12, "very_bearish": 4}}, +"key_themes": ["strong earnings beat", "AI strategy praised", "services growth momentum"], +"negative_themes": ["international headwinds", "regulatory concerns"]}}""" + + +@tool +def analyze_social_sentiment(ticker: str) -> str: + """Monitor and analyze social media sentiment.""" + return f"""{{"ticker": "{ticker}", +"posts_analyzed": 8420, "period": "7d", +"sentiment_score": 0.72, "trend": "IMPROVING", +"platforms": {{"twitter": 0.68, "reddit": 0.75, "stocktwits": 0.71}}, +"viral_topics": ["NovaTech AI platform rollout", "Q1 earnings surprise"], +"influencer_sentiment": "Predominantly positive — 8/10 top finance influencers bullish"}}""" + + +@tool +def analyze_analyst_sentiment(ticker: str) -> str: + """Analyze Wall Street analyst sentiment.""" + return f"""{{"ticker": "{ticker}", +"upgrades_30d": 5, "downgrades_30d": 1, +"avg_price_target": "$89.50", "high_target": "$105.00", "low_target": "$65.00", +"consensus": "OUTPERFORM", +"notable_calls": [ + {{"analyst": "Meridian Capital", "action": "Reiterate Overweight", "target": "$92"}}, + {{"analyst": "Crestview Partners", "action": "Upgrade to Buy", "target": "$88"}} +]}}""" + + +agent = Agent( + model=BedrockModel(model_id="us.anthropic.claude-sonnet-4-20250514-v1:0"), + tools=[analyze_news_sentiment, analyze_social_sentiment, analyze_analyst_sentiment], + system_prompt="You are a sentiment analyst. Use your tools to provide comprehensive sentiment analysis across news, social media, and analyst coverage.", +) + + +@app.entrypoint +def invoke(payload, context=None): + result = agent(payload.get("prompt", "Analyze sentiment for NVTK")) + return {"result": str(result)} + + +if __name__ == "__main__": + app.run() diff --git a/01-tutorials/10-Agent-Registry/01-advanced/kiro/kiro-registry-search-publish-workflow/setup/setup.py b/01-tutorials/10-Agent-Registry/01-advanced/kiro/kiro-registry-search-publish-workflow/setup/setup.py new file mode 100644 index 000000000..92d8aa39b --- /dev/null +++ b/01-tutorials/10-Agent-Registry/01-advanced/kiro/kiro-registry-search-publish-workflow/setup/setup.py @@ -0,0 +1,281 @@ +""" +Setup: Shared config + Auth0 DCR OAuth (Authorization Code + PKCE). + +Loads .env for registry endpoints and Auth0 domain. +get_oauth_token() does: + 1. DCR-registers a public client (if needed) + 2. Opens browser for Auth0 login (authorization_code + PKCE) + 3. Catches callback on localhost, exchanges code for token + 4. Caches token until expiry + +Import: from setup import get_cp_client, get_oauth_token, create_registry_with_auth0 +""" + +import base64 +import hashlib +import json +import logging +import os +import secrets +import time +import webbrowser +from http.server import HTTPServer, BaseHTTPRequestHandler +from pathlib import Path +from urllib.parse import urlencode, urlparse, parse_qs + +import boto3 +import requests +from dotenv import load_dotenv + +load_dotenv(dotenv_path=Path(__file__).parent / ".env") + +logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s") +logger = logging.getLogger("poc") + +# ── Config from .env ───────────────────────────────────────────────────────── + +REGION = os.getenv("AWS_REGION", "us-west-2") +AUTH0_DOMAIN = os.getenv("AUTH0_DOMAIN") + +# ── Token + client cache ───────────────────────────────────────────────────── + +CALLBACK_PORT = 65358 +CALLBACK_URL = f"http://localhost:{CALLBACK_PORT}/callback" + +_cache = {"token": None, "expires_at": 0, "client_id": None} + + +# ── Helpers ────────────────────────────────────────────────────────────────── + +def get_cp_client(): + return boto3.client("bedrock-agentcore-control", region_name=REGION) + + +def get_dp_client(): + return boto3.client("bedrock-agentcore", region_name=REGION) + + +def _auth0_url(path): + return f"https://{AUTH0_DOMAIN}{path}" + + +# ── DCR + PKCE OAuth flow ──────────────────────────────────────────────────── + +def dcr_register(client_name="registry-notebook-client"): + """Register a public client via Auth0 DCR for authorization_code + PKCE.""" + resp = requests.post(_auth0_url("/oidc/register"), json={ + "client_name": client_name, + "redirect_uris": [CALLBACK_URL], + "grant_types": ["authorization_code"], + "response_types": ["code"], + "token_endpoint_auth_method": "none", + }) + resp.raise_for_status() + data = resp.json() + logger.info("DCR registered client: %s", data["client_id"]) + return data["client_id"] + + +def _get_or_register_client(): + if _cache["client_id"]: + return _cache["client_id"] + cid = dcr_register() + _cache["client_id"] = cid + return cid + + +def _generate_pkce(): + """Generate PKCE code_verifier and code_challenge.""" + verifier = secrets.token_urlsafe(64)[:128] + digest = hashlib.sha256(verifier.encode("ascii")).digest() + challenge = base64.urlsafe_b64encode(digest).rstrip(b"=").decode("ascii") + return verifier, challenge + + +def _authorize_via_browser(client_id, audience, code_verifier, code_challenge, timeout=120): + """Open browser for Auth0 login, catch the callback code on localhost.""" + import subprocess + state = secrets.token_urlsafe(32) + auth_code = [None] + error = [None] + + class CallbackHandler(BaseHTTPRequestHandler): + def do_GET(self): + qs = parse_qs(urlparse(self.path).query) + if qs.get("state", [None])[0] != state: + error[0] = "State mismatch" + elif "error" in qs: + error[0] = qs["error"][0] + ": " + qs.get("error_description", [""])[0] + else: + auth_code[0] = qs.get("code", [None])[0] + self.send_response(200) + self.send_header("Content-Type", "text/html") + self.end_headers() + msg = "Authentication successful! You can close this tab." if auth_code[0] else f"Error: {error[0]}" + self.wfile.write(f"

{msg}

".encode()) + + def log_message(self, *args): + pass + + server = HTTPServer(("localhost", CALLBACK_PORT), CallbackHandler) + server.timeout = timeout + + authorize_url = _auth0_url("/authorize") + "?" + urlencode({ + "response_type": "code", + "client_id": client_id, + "redirect_uri": CALLBACK_URL, + "audience": audience, + "scope": "openid", + "state": state, + "code_challenge": code_challenge, + "code_challenge_method": "S256", + }) + + # Print clickable link as fallback, then try to open browser + print(f"\n Auth0 login required. If browser doesn't open, click:\n{authorize_url}\n") + try: + subprocess.Popen(["open", authorize_url], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) + except Exception: + try: + webbrowser.open(authorize_url) + except Exception: + pass # user can click the printed URL + + server.handle_request() + server.server_close() + + if error[0]: + raise RuntimeError(f"Auth0 authorization failed: {error[0]}") + if not auth_code[0]: + raise RuntimeError("No authorization code received (timeout?)") + + return auth_code[0] + + +def _exchange_code(client_id, code, code_verifier): + """Exchange authorization code for access token.""" + resp = requests.post(_auth0_url("/oauth/token"), json={ + "grant_type": "authorization_code", + "client_id": client_id, + "code": code, + "redirect_uri": CALLBACK_URL, + "code_verifier": code_verifier, + }) + resp.raise_for_status() + return resp.json() + + +def get_oauth_token(force_refresh=False, audience=None): + """Get a valid Auth0 bearer token via DCR + authorization_code PKCE. + + First call opens a browser for login. Token is cached until expiry. + """ + if not audience: + raise ValueError("audience is required — pass the registry MCP URL.") + + if not force_refresh and _cache["token"] and time.time() < _cache["expires_at"] - 60: + return _cache["token"] + + client_id = _get_or_register_client() + code_verifier, code_challenge = _generate_pkce() + + # Browser popup — user authenticates once + code = _authorize_via_browser(client_id, audience, code_verifier, code_challenge) + data = _exchange_code(client_id, code, code_verifier) + + _cache["token"] = data["access_token"] + + # Parse expiry from JWT or response + try: + payload = data["access_token"].split(".")[1] + payload += "=" * (4 - len(payload) % 4) + decoded = json.loads(base64.b64decode(payload)) + _cache["expires_at"] = decoded.get("exp", time.time() + 3600) + except Exception: + _cache["expires_at"] = time.time() + data.get("expires_in", 3600) + + logger.info("OAuth token acquired (expires in %ds)", _cache["expires_at"] - time.time()) + return _cache["token"] + + +# ── Registry management ───────────────────────────────────────────────────── + +def create_registry_with_auth0(name="auth0-oauth-registry", + description="Registry with Auth0 OAuth authentication", + initial_audience="AWS-REGISTRY-SAMPLE-DCR-V0", + poll_interval=5, max_wait=150): + """Create a registry with Auth0 CUSTOM_JWT authorizer. + + initial_audience is the Auth0 API identifier used at creation time. + After READY, the MCP URL is added to allowedAudience automatically. + Sets module-level REGISTRY_ID and AUTH0_AUDIENCE. + """ + cp = get_cp_client() + discovery_url = f"https://{AUTH0_DOMAIN}/.well-known/openid-configuration" + + logger.info("Creating registry '%s' with CUSTOM_JWT authorizer...", name) + resp = cp.create_registry( + name=name, + description=description, + authorizerType="CUSTOM_JWT", + authorizerConfiguration={ + "customJWTAuthorizer": { + "discoveryUrl": discovery_url, + "allowedAudience": [initial_audience], + } + }, + ) + registry_arn = resp["registryArn"] + registry_id = registry_arn.split("/")[-1] + logger.info("Created registry %s (%s)", registry_id, registry_arn) + + # Wait for READY + status = "UNKNOWN" + elapsed = 0 + while elapsed < max_wait: + time.sleep(poll_interval) + elapsed += poll_interval + info = cp.get_registry(registryId=registry_id) + status = info.get("status", "UNKNOWN") + logger.info("[%ds] status=%s", elapsed, status) + if status == "READY": + break + + if status != "READY": + logger.warning("Registry not READY after %ds (status=%s) — skipping audience update", max_wait, status) + else: + # Add MCP URL to allowedAudience + update_registry_audience_with_mcp_url(registry_id) + logger.info("Added MCP URL to allowedAudience") + + return {"registryId": registry_id, "registryArn": registry_arn, "status": status} + + +def update_registry_audience_with_mcp_url(registry_id=None): + """Add the MCP endpoint URL to the registry's allowedAudience.""" + + cp = get_cp_client() + registry = cp.get_registry(registryId=registry_id) + jwt_config = registry["authorizerConfiguration"]["customJWTAuthorizer"] + dp = get_dp_client() + mcp_url = f"{dp.meta.endpoint_url}/registry/{registry_id}/mcp" + audience = list(set(jwt_config.get("allowedAudience", []) + [mcp_url])) + cp.update_registry( + registryId=registry_id, + authorizerConfiguration={ + "optionalValue": { + "customJWTAuthorizer": { + "discoveryUrl": jwt_config["discoveryUrl"], + "allowedAudience": audience, + } + } + }, + ) + while True: + status = cp.get_registry(registryId=registry_id)["status"] + if status != "UPDATING": + break + time.sleep(2) + return cp.get_registry(registryId=registry_id) + + diff --git a/01-tutorials/10-Agent-Registry/01-advanced/kiro/kiro-registry-search-publish-workflow/utils.py b/01-tutorials/10-Agent-Registry/01-advanced/kiro/kiro-registry-search-publish-workflow/utils.py new file mode 100644 index 000000000..152089bf8 --- /dev/null +++ b/01-tutorials/10-Agent-Registry/01-advanced/kiro/kiro-registry-search-publish-workflow/utils.py @@ -0,0 +1,172 @@ +import sys +import os +sys.path.insert(0, os.path.join(os.getcwd(), 'setup')) + +import boto3 +import json +import logging +import uuid +import time +import requests +from setup import REGION, get_cp_client, get_dp_client, get_oauth_token + +ACCOUNT_ID = boto3.client('sts', region_name=REGION).get_caller_identity()['Account'] + +# Clients +cp_client = get_cp_client() +dp_client = get_dp_client() +ac_client = boto3.client("bedrock-agentcore", region_name=REGION) +DP_ENDPOINT = dp_client.meta.endpoint_url + + +def search_registry(query, registry_id=None, max_results=5): + """Search the Auth0 OAuth-protected registry.""" + rid = registry_id + audience = f"{DP_ENDPOINT}/registry/{rid}/mcp" + token = get_oauth_token(audience=audience) + resp = requests.post( + f"{DP_ENDPOINT}/registry-records/search", + headers={"Authorization": f"Bearer {token}", "Content-Type": "application/json"}, + json={ + "searchQuery": query, + "registryIds": [f"arn:aws:bedrock-agentcore:{REGION}:{ACCOUNT_ID}:registry/{rid}"], + "maxResults": max_results, + }, + ) + return resp.json() + + +def get_inline_metadata(record): + """Extract inline metadata from a registry record's descriptors.""" + descs = record.get('descriptors', {}) + dtype = record.get('descriptorType', '') + raw = None + if dtype == 'CUSTOM': + raw = (descs.get('custom') or {}).get('inlineContent') + elif dtype == 'MCP': + raw = (descs.get('mcp', {}).get('server') or {}).get('inlineContent') + elif dtype == 'A2A': + raw = (descs.get('a2a') or {}).get('inlineContent') + elif dtype == 'AGENT_SKILLS': + raw = (descs.get('agentSkills') or {}).get('inlineContent') + if raw: + try: + return json.loads(raw) if isinstance(raw, str) else raw + except json.JSONDecodeError: + return raw + return None + + +def print_results(query, results): + """Pretty-print search results with inline metadata.""" + print(f"Search: '{query}'") + for r in results.get('registryRecords', []): + print(f" → {r['name']} ({r['descriptorType']}) — {r.get('description', '')[:80]}") + meta = get_inline_metadata(r) + if meta: + if isinstance(meta, dict): + for k, v in meta.items(): + val = json.dumps(v) if isinstance(v, (dict, list)) else str(v) + if len(val) > 120: + val = val[:120] + '...' + print(f" {k}: {val}") + else: + print(f" metadata: {str(meta)[:200]}") + print() + + +def invoke_http_agent(runtime_arn, prompt): + """Invoke an HTTP agent on AgentCore Runtime (IAM auth).""" + resp = ac_client.invoke_agent_runtime( + agentRuntimeArn=runtime_arn, + runtimeSessionId=str(uuid.uuid4()) + "-notebook-session-pad", + payload=json.dumps({"prompt": prompt}), + qualifier="DEFAULT", + ) + return json.loads(resp["response"].read().decode("utf-8")) + + +def resolve_agents_from_registry(queries, registry_id): + """Search registry and extract runtime ARNs / MCP endpoints from descriptors.""" + agents = {} + for q in queries: + for r in search_registry(q, registry_id).get("registryRecords", []): + name = r["name"] + if name in agents: + continue + dtype = r["descriptorType"] + descs = r.get("descriptors", {}) + if dtype == "CUSTOM": + raw = (descs.get("custom") or {}).get("inlineContent") + if raw: + desc = json.loads(raw) if isinstance(raw, str) else raw + agents[name] = {"type": "HTTP", "metadata": desc} + if desc.get("runtimeArn"): + agents[name]["runtimeArn"] = desc["runtimeArn"] + elif dtype == "MCP": + mcp_desc = descs.get("mcp", {}) + server_raw = (mcp_desc.get("server") or {}).get("inlineContent") + tools_raw = (mcp_desc.get("tools") or {}).get("inlineContent") + server_meta = json.loads(server_raw) if server_raw and isinstance(server_raw, str) else server_raw + tools_meta = json.loads(tools_raw) if tools_raw and isinstance(tools_raw, str) else tools_raw + info = {"type": "MCP"} + if server_meta: + info["server"] = server_meta + url = None + if isinstance(server_meta, str): + url = server_meta + elif isinstance(server_meta, dict): + remotes = server_meta.get("remotes", []) + if remotes: + url = remotes[0].get("url") + if not url: + url = server_meta.get("url") or server_meta.get("endpoint") + if url: + info["mcpUrl"] = url + if tools_meta: + info["tools"] = tools_meta + agents[name] = info + return agents + + +def cleanup(registry_id=None): + """Delete all records, then the registry. Also cleans up runtimes and gateways found in descriptors.""" + logger = logging.getLogger("poc") + registry_id = registry_id + cp = get_cp_client() + ac = boto3.client("bedrock-agentcore-control", region_name=REGION) + + runtime_arns = set() + + # 1. List records, extract runtime ARNs, delete records + try: + records = cp.list_registry_records(registryId=registry_id).get('registryRecords', []) + for rec in records: + rid = rec['recordId'] + try: + detail = cp.get_registry_record(registryId=registry_id, recordId=rid) + meta = get_inline_metadata(detail) + if isinstance(meta, dict) and meta.get('runtimeArn'): + runtime_arns.add(meta['runtimeArn']) + except Exception: + pass + cp.delete_registry_record(registryId=registry_id, recordId=rid) + logger.info('Deleted record %s (%s)', rec.get('name', rid), rid) + + time.sleep(5) + cp.delete_registry(registryId=registry_id) + logger.info('Registry %s deleted', registry_id) + except Exception as e: + logger.warning('Registry cleanup: %s', e) + + # 2. Delete agent runtimes discovered from records + for arn in runtime_arns: + try: + rt_id = arn.split('/')[-1] + ac.delete_agent_runtime(agentRuntimeId=rt_id) + logger.info('Deleted runtime %s', rt_id) + except Exception as e: + logger.warning('Runtime %s: %s', arn, e) + + logger.info('Cleanup complete') + diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 0d2213550..ae7ee8a62 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -111,3 +111,4 @@ - Richa Gupta (richagpt) - Chandra Dhandapani - Anant Murarka (anantmu) +- Sana Iqbal