diff --git a/src/components/landing/CodeBlock.astro b/src/components/landing/CodeBlock.astro new file mode 100644 index 00000000..bb712007 --- /dev/null +++ b/src/components/landing/CodeBlock.astro @@ -0,0 +1,198 @@ +--- +/** + * Theme-aware syntax-highlighted code block for the landing page. + * Renders both light and dark variants at build time, toggles via CSS on [data-theme]. + * Includes a copy-to-clipboard button. + */ +import { Code } from 'astro:components' + +interface Props { + code: string + lang?: string + filename?: string +} + +const { code, lang = 'python', filename } = Astro.props +--- + +
+ {filename && ( +
+ {filename} + +
+ )} + {!filename && ( + + )} +
+ +
+
+ +
+
+ + + + diff --git a/src/components/landing/CredibilityStrip.astro b/src/components/landing/CredibilityStrip.astro new file mode 100644 index 00000000..5accab55 --- /dev/null +++ b/src/components/landing/CredibilityStrip.astro @@ -0,0 +1,181 @@ +--- +/** + * Credibility strip: company logos + deployment targets. + * Sits between feature cards and testimonials as a trust bridge. + */ + +// Import logos directly — Astro handles SVG/PNG optimization +import smartsheetLogo from '../../content/testimonials/smartsheet-logo.svg' +import smartsheetLogoDark from '../../content/testimonials/smartsheet-logo-white.svg' +import swisscomLogo from '../../content/testimonials/swisscom-logo.svg' +import eightcapLogo from '../../content/testimonials/eightcap-logo.svg' +import zafranLogo from '../../content/testimonials/zafran-logo.svg' +import jitLogo from '../../content/testimonials/jit_logo.svg' +import tavilyLogo from '../../content/testimonials/tavily-logo.svg' + +const logos = [ + { name: 'Smartsheet', light: smartsheetLogo, dark: smartsheetLogoDark }, + { name: 'Swisscom', light: swisscomLogo, dark: swisscomLogo }, + { name: 'Eightcap', light: eightcapLogo, dark: eightcapLogo }, + { name: 'Zafran', light: zafranLogo, dark: zafranLogo }, + { name: 'Jit', light: jitLogo, dark: jitLogo }, + { name: 'Tavily', light: tavilyLogo, dark: tavilyLogo }, +] + +const deployTargets = ['AgentCore', 'Lambda', 'Fargate', 'EKS', 'Docker', 'Terraform'] +--- + +
+
+
+ Trusted in production by +
+ {logos.map((logo) => ( + + + ))} +
+
+
+ Deploy anywhere +
+ {deployTargets.map((target, i) => ( + <> + {i > 0 && } + {target} + + ))} +
+
+
+
+ + diff --git a/src/components/landing/FeatureCards.astro b/src/components/landing/FeatureCards.astro new file mode 100644 index 00000000..84a4e01c --- /dev/null +++ b/src/components/landing/FeatureCards.astro @@ -0,0 +1,134 @@ +--- +/** + * 8 feature cards in 2-column grid with mini code snippets. + */ +import CodeBlock from './CodeBlock.astro' +import { features } from '../../data/feature-cards' +--- + +
+
+

Everything you need to build agents

+
+ {features.map((feature) => ( +
+

{feature.title}

+

{feature.description}

+
+ +
+
+ ))} +
+
+
+ + diff --git a/src/components/landing/HeroSection.astro b/src/components/landing/HeroSection.astro new file mode 100644 index 00000000..19b22f6c --- /dev/null +++ b/src/components/landing/HeroSection.astro @@ -0,0 +1,302 @@ +--- +/** + * Hero section: headline + subtext on left, code snippet on right. + */ +import CodeBlock from './CodeBlock.astro' +import { getStarCount } from '../../util/github' + +interface Props { + baseUrl: string +} + +const { baseUrl } = Astro.props +const withBase = (path: string) => { + const b = baseUrl.endsWith('/') ? baseUrl.slice(0, -1) : baseUrl + return b + path +} + +const stars = await getStarCount() + +const heroCode = `from strands import Agent, tool + +@tool +def weather(city: str) -> dict: + """Get current weather for a city.""" + return fetch_weather(city) + +agent = Agent(tools=[weather]) +agent("What's the weather in Seattle?")` +--- + +
+
+
+
+

+ Build AI agents
+ you can actually steer. +

+

+ Any model. Any cloud. Open source for Python and TypeScript. +

+ +
+
+ pip install strands-agents + +
+
+ npm install @strands-agents/sdk + +
+
+

+ {stars} GitHub stars · Built from production systems inside Amazon · Works with any model +

+
+
+ +
+
+
+ + + + diff --git a/src/components/landing/ModelDrivenSection.astro b/src/components/landing/ModelDrivenSection.astro new file mode 100644 index 00000000..39b615d9 --- /dev/null +++ b/src/components/landing/ModelDrivenSection.astro @@ -0,0 +1,116 @@ +--- +/** + * "Model-driven by design" section with explanatory text and a large code example. + */ +import CodeBlock from './CodeBlock.astro' + +const researchCode = `from strands import Agent, tool +from strands_tools import http_request +from pathlib import Path + +@tool +def save_report(title: str, content: str) -> str: + """Save a research report to disk.""" + Path(f"reports/{title}.md").write_text(content) + return f"Saved {title}.md" + +# The model decides how to classify, research, and draft +agent = Agent( + system_prompt="""You are a research assistant. Classify the topic, + research it using the web, then save a summary report.""", + tools=[http_request, save_report], +) + +agent("Summarize recent AI agent papers")` +--- + +
+
+

Write code, not pipelines

+

+ Early agent frameworks wrapped models in orchestration logic because models couldn't reason reliably. Now they can. Strands gives you back control: define your tools as functions, write a system prompt, and the agent loop handles execution. No step definitions, no workflow graphs. Just code. +

+
+ +
+

+ The model handles orchestration. When it makes a mistake, a plugin handles recovery. Your agent code stays the same. +

+
+
+ + diff --git a/src/components/landing/SteeringSection.astro b/src/components/landing/SteeringSection.astro new file mode 100644 index 00000000..2b29028e --- /dev/null +++ b/src/components/landing/SteeringSection.astro @@ -0,0 +1,239 @@ +--- +/** + * Steering narrative section: Problem → Solution → Proof. + * Inspired by Clare's steering blog post. + */ +import CodeBlock from './CodeBlock.astro' +import { pathWithBase } from '../../util/links' + +const messyPrompt = `agent = Agent( + system_prompt="""You are a report generator. Always use markdown + tables for comparisons. Never use bullet lists for data. Format + currency as $X,XXX.XX. Include a summary section at the top. + When comparing more than 3 items, split into sub-tables. + Use ISO 8601 dates. Cite sources with inline links. + If the user asks about competitors, stay neutral. + Never share internal pricing...""" + # The model will follow some of these. Guess which ones. +)` + +const steeringCode = `from strands import Agent +from strands.vended_plugins.steering import ( + SteeringHandler, ToolSteeringAction, +) + +class NoPricingLeaks(SteeringHandler): + async def steer_before_tool(self, *, agent, tool_use, **kwargs): + if tool_use["name"] == "send_email": + if "internal pricing" in str(tool_use["input"]): + return ToolSteeringAction.guide( + "Contains internal pricing. Redact before sending." + ) + return ToolSteeringAction.proceed() + +agent = Agent( + tools=[send_email, generate_report], + plugins=[NoPricingLeaks()], +)` +--- + +
+
+ + +
+

Your agent ignored your instructions again

+

+ You wrote the rules. The model skipped them. Longer prompts make it worse: by line 40, the model is guessing which instructions still matter. Hard-coded workflows are the other extreme: predictable but brittle, and they strip away the reasoning that makes agents useful. +

+
+ +
+
+ + +
+

Middleware for the agent loop

+

+ Steering hooks intercept the agent loop the same way middleware intercepts HTTP requests. Before a tool call, check the inputs. After a model response, validate the output. Each handler is a Python function you can read, test, and debug. +

+
+ +
+
+ + +
+
+
+ 100% + task accuracy with steering +
+
+ Prompt-only agents scored 82.5%. Hard-coded workflows scored 80.8%. Steered agents recovered from every mistake. +
+
+ + Read the blog post → + +
+ +
+
+ + diff --git a/src/components/overrides/Header.astro b/src/components/overrides/Header.astro index 8e4a5824..23e3f982 100644 --- a/src/components/overrides/Header.astro +++ b/src/components/overrides/Header.astro @@ -283,6 +283,14 @@ const { siteTitleHref } = Astro.locals.starlightRoute; white-space: nowrap; } + /* Narrow desktop: compact nav tabs */ + @media (min-width: 50rem) and (max-width: 68.75rem) { + .nav-tab { + padding: 0.25rem 0.5rem; + font-size: 0.875rem; + } + } + /* Hover state with Strands green highlight */ .nav-tab:hover { color: var(--strands-dark-text); diff --git a/src/data/feature-cards.ts b/src/data/feature-cards.ts new file mode 100644 index 00000000..e09c5606 --- /dev/null +++ b/src/data/feature-cards.ts @@ -0,0 +1,117 @@ +export const features = [ + // Give it tools + { + title: "Tools from Any Function", + description: "Turn any function into an agent tool with @tool. The docstring becomes the LLM's tool description. No schema files, no registration boilerplate.", + code: `# Any function becomes a tool +@tool +def search_db(query: str) -> list: + """Search the product database.""" + return db.search(query)`, + }, + { + title: "Native MCP Support", + description: "Connect to any MCP server. Use thousands of community tools without writing integration code.", + code: `# Connect to any MCP server +from strands.tools.mcp import MCPClient +from mcp import stdio_client, StdioServerParameters + +mcp = MCPClient(lambda: stdio_client( + StdioServerParameters( + command="uvx", + args=["my-mcp-server"], + ) +))`, + }, + // Let it scale + { + title: "Multi-Agent Systems", + description: "Compose agents with graphs, swarms, workflows, or simple agent-as-tool patterns. Built-in A2A protocol support for distributed systems.", + code: `# Agents as tools for other agents +@tool +def research(query: str) -> str: + """Research a topic thoroughly.""" + agent = Agent(tools=[search_web]) + return str(agent(query)) + +writer = Agent(tools=[research]) +writer("Write a post about AI agents")`, + }, + { + title: "Agent Skills", + description: "Load modular instructions on demand. Skills activate when needed instead of bloating the system prompt. Define them as files or code, attach via plugin.", + code: `# Load skills on demand +from strands.vended_plugins.skills import ( + AgentSkills, Skill, +) + +plugin = AgentSkills(skills=[ + "./skills/pdf-processing", + "./skills/data-analysis", +]) + +agent = Agent(plugins=[plugin])`, + }, + // Give it context + { + title: "Conversation Memory", + description: "Sliding window, summarization, and session persistence out of the box. Manage context across long conversations without manual token counting.", + code: `# Manage context automatically +from strands.agent.conversation_manager import ( + SlidingWindowConversationManager, +) + +agent = Agent( + conversation_manager=SlidingWindowConversationManager( + window_size=5 + ), +)`, + }, + { + title: "Built-in Observability", + description: "OpenTelemetry traces, metrics, and logs with no extra instrumentation. See every tool call, model invocation, and token count.", + code: `# Traces with zero config +from strands import Agent + +agent = Agent(trace_attributes={ + "service": "my-app", + "env": "production", +})`, + }, + // Keep it honest + { + title: "Approve Before It Acts", + description: "Require human approval before sensitive tool calls. The agent pauses mid-task, waits for a response, then continues or cancels. No external workflow engine needed.", + code: `# Pause for approval before sending +from strands.hooks import BeforeToolCallEvent + +def require_approval(event: BeforeToolCallEvent): + if event.tool_use["name"] == "send_email": + event.interrupt( + "email_approval", + reason="Approve this email?" + ) + +agent = Agent(tools=[send_email]) +agent.add_hook(require_approval)`, + }, + { + title: "Evaluation SDK", + description: "Test your agent against scenarios before shipping. Define cases, pick evaluators, run experiments. Measure accuracy, tool selection, and output quality.", + code: `# Test agent behavior at scale +from strands_evals import Case, Experiment +from strands_evals.evaluators import OutputEvaluator + +cases = [ + Case(name="accuracy", + input="What is 2+2?", + expected_output="4"), +] + +experiment = Experiment( + cases=cases, + evaluators=[OutputEvaluator()], +) +reports = experiment.run_evaluations(my_agent)`, + }, +] diff --git a/src/pages/index.astro b/src/pages/index.astro index a7ac3ae1..72dcb8bc 100644 --- a/src/pages/index.astro +++ b/src/pages/index.astro @@ -1,51 +1,22 @@ --- /** - * Custom Landing Page for Strands Agents SDK - * - * This page uses a custom layout with the Starlight Header but without - * the full page layout, allowing for full-width content. + * Homepage for Strands Agents SDK + * + * Sections: Hero → Model-Driven → Steering → Feature Cards → Tabbed Examples → Testimonials → Footer */ import { getCollection, render } from 'astro:content' import { Image } from 'astro:assets' import LandingLayout from '../layouts/LandingLayout.astro' import Copyright from '../components/Copyright.astro' +import HeroSection from '../components/landing/HeroSection.astro' +import ModelDrivenSection from '../components/landing/ModelDrivenSection.astro' +import SteeringSection from '../components/landing/SteeringSection.astro' +import FeatureCards from '../components/landing/FeatureCards.astro' +import CredibilityStrip from '../components/landing/CredibilityStrip.astro' import curvePrimary from '../assets/curve-primary.svg' import curveSecondary from '../assets/curve-secondary.svg' -import iconZap from '../assets/icons/icon-zap.svg?raw' -import iconBot from '../assets/icons/icon-bot.svg?raw' -import iconLayers from '../assets/icons/icon-layers.svg?raw' -import iconBolt from '../assets/icons/icon-bolt.svg?raw' -// Get base path for links const base = import.meta.env.BASE_URL || '/' -const withBase = (path: string) => { - const normalizedBase = base.endsWith('/') ? base.slice(0, -1) : base - return normalizedBase + path -} - -// Features data -const features = [ - { - icon: iconZap, - title: "Model driven orchestration", - description: "Strands leverages model reasoning to plan, orchestrate tasks, and reflect on goals", - }, - { - icon: iconBot, - title: "Model & provider agnostic", - description: "Work with any LLM provider - Amazon Bedrock, OpenAI, Anthropic, local models. Switch providers without changing your code.", - }, - { - icon: iconLayers, - title: "Simple multi-agent primitives", - description: "Simple primitives for handoffs, swarms, and graph workflows with built-in support for A2A", - }, - { - icon: iconBolt, - title: "Best in-class AWS integrations", - description: "Native tools for AWS service interactions. Deploy easily into Bedrock AgentCore, EKS, Lambda, EC2, and more.", - }, -] // Testimonials from content collection const testimonialEntries = await getCollection('testimonials') @@ -70,39 +41,15 @@ const testimonials = await Promise.all(
- -
-
-
-

- Build production-ready,
- multi-agent AI systems
- in a few lines of code -

- -
-
- - -
-
- {features.map((feature) => ( -
-
-

{feature.title}

-

{feature.description}

-
- ))} -
-
+ + + + +
-
-
{testimonials.map((testimonial, index) => { @@ -140,7 +87,7 @@ const testimonials = await Promise.all( + + diff --git a/src/util/github.ts b/src/util/github.ts new file mode 100644 index 00000000..f4e70e03 --- /dev/null +++ b/src/util/github.ts @@ -0,0 +1,30 @@ +const REPOS = ['strands-agents/sdk-python', 'strands-agents/sdk-typescript'] +const FALLBACK = '5,800+' +const TIMEOUT_MS = 5000 + +export async function getStarCount(): Promise { + try { + const counts = await Promise.all( + REPOS.map(async (repo) => { + const controller = new AbortController() + const timeout = setTimeout(() => controller.abort(), TIMEOUT_MS) + try { + const res = await fetch(`https://api.github.com/repos/${repo}`, { + signal: controller.signal, + }) + if (!res.ok) return 0 + const data = await res.json() + return data.stargazers_count ?? 0 + } finally { + clearTimeout(timeout) + } + }) + ) + const total = counts.reduce((a: number, b: number) => a + b, 0) + if (total === 0) return FALLBACK + const rounded = Math.floor(total / 100) * 100 + return rounded.toLocaleString() + '+' + } catch { + return FALLBACK + } +}