From 114fcadb270573986b079b597ca49d505d041f50 Mon Sep 17 00:00:00 2001 From: Ryan Coleman Date: Mon, 9 Mar 2026 14:41:43 -0700 Subject: [PATCH 1/2] docs: restructure documentation information architecture MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Nav tabs reduced from 9 to 5: merged Labs and Contribute into Community, merged Python API and TypeScript API into a single API Reference tab with a sidebar language switcher, renamed User Guide to Docs. Blog tab will be added by a separate PR. Sidebar reorganized with a new task-first "Build" section (Adding Tools, Using MCP Tools, Multi-Agent Systems, Streaming Responses, Voice & Realtime, etc.) positioned above Concepts. Deploy promoted from last to after Model Providers. Homepage CTA now links directly to the Python quickstart, skipping the overview routing page. New "Why Strands?" page with model-driven vs workflow-driven comparison, code examples, and a Python/TypeScript feature matrix. Initial draft — content needs refinement. Sidebar labels now sourced from navigation.yml rather than page frontmatter, so nav labels can differ from page titles without touching individual content files. --- src/components/overrides/Sidebar.astro | 53 +++++- src/config/navbar.ts | 7 + src/config/navigation.yml | 185 ++++++++++---------- src/content/docs/user-guide/why-strands.mdx | 121 +++++++++++++ src/pages/index.astro | 2 +- src/pages/llms.txt.ts | 2 +- src/route-middleware.ts | 81 +++++---- src/sidebar.ts | 7 + test/sidebar.test.ts | 80 ++++----- 9 files changed, 363 insertions(+), 175 deletions(-) create mode 100644 src/content/docs/user-guide/why-strands.mdx diff --git a/src/components/overrides/Sidebar.astro b/src/components/overrides/Sidebar.astro index 8ffb96337..1c3148072 100644 --- a/src/components/overrides/Sidebar.astro +++ b/src/components/overrides/Sidebar.astro @@ -1,18 +1,69 @@ --- /** * Custom Sidebar component that mimics MkDocs Material theme's navigation.sections behavior. + * Includes an API language switcher (Python/TypeScript) that only appears on API pages. */ import MobileMenuFooter from 'virtual:starlight/components/MobileMenuFooter'; import SidebarPersister from '@astrojs/starlight/components/SidebarPersister.astro'; import SidebarSublist from './SidebarSublist.astro'; +import { pathWithBase } from '../../util/links'; -const { sidebar } = Astro.locals.starlightRoute; +const { sidebar, id: currentSlug } = Astro.locals.starlightRoute; +const isApiPage = currentSlug.startsWith('docs/api/'); +const isPython = currentSlug.startsWith('docs/api/python'); --- + {isApiPage && ( + + )}
+ + diff --git a/src/config/navbar.ts b/src/config/navbar.ts index 808fc9b19..c0447d143 100644 --- a/src/config/navbar.ts +++ b/src/config/navbar.ts @@ -20,6 +20,12 @@ export interface NavLink { * Example: href="/user-guide/quickstart/" but basePath="/user-guide/" */ basePath?: string + /** + * Additional base paths that should also be considered part of this nav section. + * Useful when a nav item encompasses multiple top-level sidebar sections. + * Example: Community nav item also covers /docs/labs/ and /docs/contribute/ + */ + additionalBasePaths?: string[] /** Set to true for external links (opens in new tab) */ external?: boolean } @@ -54,6 +60,7 @@ function transformNavLinks(links: NavLink[]): NavLink[] { ...link, href: withBase(link.href), ...(link.basePath ? { basePath: withBase(link.basePath) } : {}), + ...(link.additionalBasePaths ? { additionalBasePaths: link.additionalBasePaths.map(withBase) } : {}), } }) } diff --git a/src/config/navigation.yml b/src/config/navigation.yml index 63a0ead45..c09dd4b8e 100644 --- a/src/config/navigation.yml +++ b/src/config/navigation.yml @@ -12,8 +12,8 @@ navbar: - label: Home href: / - - label: User Guide - href: /docs/user-guide/quickstart/overview/ + - label: Docs + href: /docs/user-guide/quickstart/python/ basePath: /docs/user-guide/ - label: Examples href: /docs/examples/ @@ -21,49 +21,55 @@ navbar: - label: Community href: /docs/community/community-packages/ basePath: /docs/community/ - - label: Labs - href: /docs/labs/ - basePath: /docs/labs/ - - label: Blog - href: /blog/ - basePath: /blog/ - - label: Contribute ❤️ - href: /docs/contribute/ - basePath: /docs/contribute/ - - label: Python API + additionalBasePaths: + - /docs/labs/ + - /docs/contribute/ + - label: API Reference href: /docs/api/python/ - basePath: /docs/api/python/ - - label: TypeScript API - href: /docs/api/typescript/ - basePath: /docs/api/typescript/ + basePath: /docs/api/ sidebar: - - label: User Guide + - label: Docs items: - docs/index - - label: Quickstart + - label: Get Started items: - docs/user-guide/quickstart/overview - - docs/user-guide/quickstart/python - - docs/user-guide/quickstart/typescript + - label: "Quickstart: Python" + slug: docs/user-guide/quickstart/python + - label: "Quickstart: TypeScript" + slug: docs/user-guide/quickstart/typescript + - label: "Why Strands?" + slug: docs/user-guide/why-strands - docs/user-guide/build-with-ai + - label: Build + items: + - label: "Adding Tools" + slug: docs/user-guide/concepts/tools + - label: "Creating Custom Tools" + slug: docs/user-guide/concepts/tools/custom-tools + - label: "Using MCP Tools" + slug: docs/user-guide/concepts/tools/mcp-tools + - label: "Multi-Agent Systems" + slug: docs/user-guide/concepts/multi-agent/multi-agent-patterns + - label: "Structured Output" + slug: docs/user-guide/concepts/agents/structured-output + - label: "Streaming Responses" + slug: docs/user-guide/concepts/streaming + - label: "Voice & Realtime" + slug: docs/user-guide/concepts/bidirectional-streaming/quickstart - label: Concepts items: - - label: Agents - items: - - docs/user-guide/concepts/agents/agent-loop - - docs/user-guide/concepts/agents/state - - docs/user-guide/concepts/agents/session-management - - docs/user-guide/concepts/agents/prompts - - docs/user-guide/concepts/agents/retry-strategies - - docs/user-guide/concepts/agents/hooks - - docs/user-guide/concepts/agents/structured-output - - docs/user-guide/concepts/agents/conversation-management + - docs/user-guide/concepts/agents/agent-loop + - docs/user-guide/concepts/agents/state + - docs/user-guide/concepts/agents/session-management + - docs/user-guide/concepts/agents/prompts + - docs/user-guide/concepts/agents/hooks + - docs/user-guide/concepts/agents/conversation-management + - docs/user-guide/concepts/agents/retry-strategies + - docs/user-guide/concepts/interrupts - label: Tools items: - - docs/user-guide/concepts/tools - - docs/user-guide/concepts/tools/custom-tools - - docs/user-guide/concepts/tools/mcp-tools - docs/user-guide/concepts/tools/executors - docs/user-guide/concepts/tools/community-tools-package - label: Plugins @@ -71,25 +77,8 @@ sidebar: - docs/user-guide/concepts/plugins - docs/user-guide/concepts/plugins/skills - docs/user-guide/concepts/plugins/steering - - label: Model Providers - items: - - docs/user-guide/concepts/model-providers - - docs/user-guide/concepts/model-providers/amazon-bedrock - - docs/user-guide/concepts/model-providers/amazon-nova - - docs/user-guide/concepts/model-providers/anthropic - - docs/user-guide/concepts/model-providers/gemini - - docs/user-guide/concepts/model-providers/litellm - - docs/user-guide/concepts/model-providers/llamacpp - - docs/user-guide/concepts/model-providers/llamaapi - - docs/user-guide/concepts/model-providers/mistral - - docs/user-guide/concepts/model-providers/ollama - - docs/user-guide/concepts/model-providers/openai - - docs/user-guide/concepts/model-providers/sagemaker - - docs/user-guide/concepts/model-providers/writer - - docs/user-guide/concepts/model-providers/custom_model_provider - label: Streaming items: - - docs/user-guide/concepts/streaming - docs/user-guide/concepts/streaming/async-iterators - docs/user-guide/concepts/streaming/callback-handlers - label: Multi-agent @@ -99,11 +88,8 @@ sidebar: - docs/user-guide/concepts/multi-agent/swarm - docs/user-guide/concepts/multi-agent/graph - docs/user-guide/concepts/multi-agent/workflow - - docs/user-guide/concepts/multi-agent/multi-agent-patterns - - docs/user-guide/concepts/interrupts - label: Bidirectional Streaming items: - - docs/user-guide/concepts/bidirectional-streaming/quickstart - docs/user-guide/concepts/bidirectional-streaming/agent - label: Models items: @@ -118,6 +104,47 @@ sidebar: - label: Experimental items: - docs/user-guide/concepts/experimental/agent-config + - label: Model Providers + items: + - docs/user-guide/concepts/model-providers + - docs/user-guide/concepts/model-providers/amazon-bedrock + - docs/user-guide/concepts/model-providers/amazon-nova + - docs/user-guide/concepts/model-providers/anthropic + - docs/user-guide/concepts/model-providers/gemini + - docs/user-guide/concepts/model-providers/litellm + - docs/user-guide/concepts/model-providers/llamacpp + - docs/user-guide/concepts/model-providers/llamaapi + - docs/user-guide/concepts/model-providers/mistral + - docs/user-guide/concepts/model-providers/ollama + - docs/user-guide/concepts/model-providers/openai + - docs/user-guide/concepts/model-providers/sagemaker + - docs/user-guide/concepts/model-providers/writer + - docs/user-guide/concepts/model-providers/custom_model_provider + - docs/user-guide/concepts/model-providers/cohere + - docs/user-guide/concepts/model-providers/clova-studio + - docs/user-guide/concepts/model-providers/fireworksai + - docs/user-guide/concepts/model-providers/nebius-token-factory + - docs/user-guide/concepts/model-providers/xai + - label: Deploy + items: + - docs/user-guide/deploy/operating-agents-in-production + - label: Amazon Bedrock AgentCore + items: + - docs/user-guide/deploy/deploy_to_bedrock_agentcore + - docs/user-guide/deploy/deploy_to_bedrock_agentcore/python + - docs/user-guide/deploy/deploy_to_bedrock_agentcore/typescript + - docs/user-guide/deploy/deploy_to_aws_lambda + - docs/user-guide/deploy/deploy_to_aws_fargate + - docs/user-guide/deploy/deploy_to_aws_apprunner + - docs/user-guide/deploy/deploy_to_amazon_eks + - docs/user-guide/deploy/deploy_to_amazon_ec2 + - label: Docker + items: + - docs/user-guide/deploy/deploy_to_docker + - docs/user-guide/deploy/deploy_to_docker/python + - docs/user-guide/deploy/deploy_to_docker/typescript + - docs/user-guide/deploy/deploy_to_kubernetes + - docs/user-guide/deploy/deploy_to_terraform - label: Safety & Security items: - docs/user-guide/safety-security/responsible-ai @@ -155,26 +182,6 @@ sidebar: items: - docs/user-guide/evals-sdk/how-to/experiment_management - docs/user-guide/evals-sdk/how-to/serialization - - label: Deploy - items: - - docs/user-guide/deploy/operating-agents-in-production - - label: Amazon Bedrock AgentCore - items: - - docs/user-guide/deploy/deploy_to_bedrock_agentcore - - docs/user-guide/deploy/deploy_to_bedrock_agentcore/python - - docs/user-guide/deploy/deploy_to_bedrock_agentcore/typescript - - docs/user-guide/deploy/deploy_to_aws_lambda - - docs/user-guide/deploy/deploy_to_aws_fargate - - docs/user-guide/deploy/deploy_to_aws_apprunner - - docs/user-guide/deploy/deploy_to_amazon_eks - - docs/user-guide/deploy/deploy_to_amazon_ec2 - - label: Docker - items: - - docs/user-guide/deploy/deploy_to_docker - - docs/user-guide/deploy/deploy_to_docker/python - - docs/user-guide/deploy/deploy_to_docker/typescript - - docs/user-guide/deploy/deploy_to_kubernetes - - docs/user-guide/deploy/deploy_to_terraform - docs/user-guide/versioning-and-support - label: Examples @@ -229,25 +236,23 @@ sidebar: - docs/community/tools/strands-teams - docs/community/tools/strands-telegram - docs/community/tools/strands-telegram-listener - - - label: Labs - items: - - docs/labs - - label: Projects + - label: Labs items: - - docs/labs/robots - - docs/labs/robots-sim - - docs/labs/ai-functions - - - label: Contribute ❤️ - items: - - docs/contribute - - label: Contribution Types + - docs/labs + - label: Projects + items: + - docs/labs/robots + - docs/labs/robots-sim + - docs/labs/ai-functions + - label: Contribute items: - - docs/contribute/contributing/core-sdk - - docs/contribute/contributing/documentation - - docs/contribute/contributing/feature-proposals - - docs/contribute/contributing/extensions + - docs/contribute + - label: Contribution Types + items: + - docs/contribute/contributing/core-sdk + - docs/contribute/contributing/documentation + - docs/contribute/contributing/feature-proposals + - docs/contribute/contributing/extensions github: sections: diff --git a/src/content/docs/user-guide/why-strands.mdx b/src/content/docs/user-guide/why-strands.mdx new file mode 100644 index 000000000..6d1bb8ec9 --- /dev/null +++ b/src/content/docs/user-guide/why-strands.mdx @@ -0,0 +1,121 @@ +--- +title: Why Strands? +--- + +Most AI agent frameworks ask you to define every step your agent should take. Strands takes a different approach: **let the model decide**. + +## Model-Driven vs. Workflow-Driven + +In a **workflow-driven** framework, you build a graph of steps — nodes, edges, conditional branches — and the model fills in each step. You control the flow; the model provides the text. + +In a **model-driven** framework like Strands, you give the model a goal and a set of tools. The model decides which tools to call, in what order, and when it's done. The [agent loop](concepts/agents/agent-loop.md) handles the orchestration automatically. + +Here's the difference in code: + + + +```python +from strands import Agent +from strands_tools import calculator, web_search + +agent = Agent(tools=[calculator, web_search]) +agent("What is the population of Tokyo divided by the area of France?") +``` + +Three lines. The model figures out that it needs to search for two facts, then do the math. + + +```python +# Define the graph +graph = StateGraph() +graph.add_node("search_population", search_population) +graph.add_node("search_area", search_area) +graph.add_node("calculate", calculate) +graph.add_edge("search_population", "calculate") +graph.add_edge("search_area", "calculate") + +def search_population(state): + result = web_search("population of Tokyo") + state["population"] = parse_number(result) + return state + +def search_area(state): + result = web_search("area of France in km2") + state["area"] = parse_number(result) + return state + +def calculate(state): + state["answer"] = state["population"] / state["area"] + return state + +app = graph.compile() +app.invoke({"question": "..."}) +``` + +You had to anticipate the steps, wire the graph, and handle the state yourself. + + + +## When to Use Each Approach + +Model-driven development works best when: + +- **The steps aren't predictable.** If you don't know in advance what tools the agent needs to call or in what order, let the model figure it out. +- **You want to iterate quickly.** Changing behavior means changing the prompt or the tool set — not rewiring a graph. +- **You need flexibility.** The same agent can handle variations of a task without code changes. + +Workflow-driven frameworks can be a better fit when: + +- You need **deterministic, auditable** execution paths (e.g., compliance workflows). +- You want **fine-grained control** over every decision point. +- The task is **always the same sequence** of steps. + +Strands supports both styles. You can use the model-driven agent loop for flexible tasks and [graph workflows](concepts/multi-agent/graph.md) or [structured workflows](concepts/multi-agent/workflow.md) when you need explicit control. + +## What You Get + +- **Any model, any provider.** Amazon Bedrock, Anthropic, OpenAI, Gemini, Ollama, and [more](concepts/model-providers/index.md). Switch providers without changing your agent code. +- **Tools as plain functions.** Write a Python function with a docstring and it becomes a [tool](concepts/tools/custom-tools.md). No schemas, no boilerplate. +- **Multi-agent out of the box.** [Agents as tools](concepts/multi-agent/agents-as-tools.md), [swarms](concepts/multi-agent/swarm.md), [A2A protocol](concepts/multi-agent/agent-to-agent.md) — simple primitives that compose. +- **Production-ready deployment.** Deploy to [AWS Lambda](deploy/deploy_to_aws_lambda.md), [Fargate](deploy/deploy_to_aws_fargate.md), [EKS](deploy/deploy_to_amazon_eks.md), [Bedrock AgentCore](deploy/deploy_to_bedrock_agentcore/index.md), and more. +- **Built-in observability.** [Traces](../observability-evaluation/traces.md), [metrics](../observability-evaluation/metrics.md), and [logs](../observability-evaluation/logs.md) with OpenTelemetry integration. + +## Python & TypeScript + +Strands is available in both Python and TypeScript. The Python SDK is mature and production-ready. The TypeScript SDK is experimental with focus on core agent functionality. + +
+Feature comparison + +| Category | Feature | Python | TypeScript | +|----------|---------|:------:|:----------:| +| **Core** | Agent creation and invocation | ✅ | ✅ | +| | Streaming responses | ✅ | ✅ | +| | Structured output | ✅ | ❌ | +| **Model providers** | Amazon Bedrock | ✅ | ✅ | +| | OpenAI | ✅ | ✅ | +| | Anthropic | ✅ | ❌ | +| | Ollama | ✅ | ❌ | +| | LiteLLM | ✅ | ❌ | +| | Custom providers | ✅ | ✅ | +| **Tools** | Custom function tools | ✅ | ✅ | +| | MCP (Model Context Protocol) | ✅ | ✅ | +| | Built-in tools | 30+ via community package | 4 built-in | +| **Conversation** | Null manager | ✅ | ✅ | +| | Sliding window manager | ✅ | ✅ | +| | Summarizing manager | ✅ | ❌ | +| **Hooks** | Lifecycle hooks | ✅ | ✅ | +| **Multi-agent** | Swarms, workflows, graphs | ✅ | ❌ | +| | Agents as tools | ✅ | ❌ | +| **Session management** | File, S3, repository managers | ✅ | ❌ | +| **Observability** | OpenTelemetry integration | ✅ | ❌ | +| **Experimental** | Bidirectional streaming | ✅ | ❌ | + +
+ +## Next Steps + +- [Quickstart: Python](quickstart/python.md) — build your first agent +- [Quickstart: TypeScript](quickstart/typescript.md) — if you prefer TypeScript +- [Adding Tools](concepts/tools/index.md) — give your agent capabilities +- [Multi-Agent Systems](concepts/multi-agent/multi-agent-patterns.md) — coordinate multiple agents diff --git a/src/pages/index.astro b/src/pages/index.astro index df68429a9..8d9642670 100644 --- a/src/pages/index.astro +++ b/src/pages/index.astro @@ -80,7 +80,7 @@ const testimonials = await Promise.all( in a few lines of code
- Get Started + Get Started
diff --git a/src/pages/llms.txt.ts b/src/pages/llms.txt.ts index 844cbf03f..d7244842f 100644 --- a/src/pages/llms.txt.ts +++ b/src/pages/llms.txt.ts @@ -6,7 +6,7 @@ import { loadSidebarFromConfig, type StarlightSidebarItem } from '../sidebar' import path from 'node:path' // Sections to pull from sidebar (with their nav labels) -const SIDEBAR_SECTIONS = ['User Guide', 'Examples', 'Community'] +const SIDEBAR_SECTIONS = ['Docs', 'Examples', 'Community'] /** * Recursively extract links from sidebar items diff --git a/src/route-middleware.ts b/src/route-middleware.ts index fe84f9e42..eb660ba85 100644 --- a/src/route-middleware.ts +++ b/src/route-middleware.ts @@ -14,6 +14,7 @@ function isSidebarGroup(entry: SidebarEntry): entry is SidebarGroup { /** * Find which nav section the current page belongs to based on URL path. * Matches the most specific basePath (longest match wins). + * Also checks additionalBasePaths for nav items that span multiple sections. */ export function findCurrentNavSection(currentPath: string, links: NavLink[]): NavLink | undefined { let bestMatch: NavLink | undefined @@ -28,23 +29,36 @@ export function findCurrentNavSection(currentPath: string, links: NavLink[]): Na bestMatch = link bestMatchLength = basePath.length } + // Also check additionalBasePaths + if (link.additionalBasePaths) { + for (const additionalPath of link.additionalBasePaths) { + if (currentPath.startsWith(additionalPath) && additionalPath.length > bestMatchLength) { + bestMatch = link + bestMatchLength = additionalPath.length + } + } + } } return bestMatch } /** - * Filter sidebar entries to only include items matching a base path. + * Filter sidebar entries to only include items matching one or more base paths. * If the result is a single top-level group, unwrap it to return just its entries. */ -export function filterSidebarByBasePath(entries: SidebarEntry[], basePath: string): SidebarEntry[] { +export function filterSidebarByBasePath(entries: SidebarEntry[], basePath: string | string[]): SidebarEntry[] { + const basePaths = Array.isArray(basePath) ? basePath : [basePath] + + const matchesAnyBase = (href: string) => basePaths.some((bp) => href.startsWith(bp)) + const filtered = entries .map((entry) => { if (entry.type === 'link') { - return entry.href.startsWith(basePath) ? entry : null + return matchesAnyBase(entry.href) ? entry : null } if (entry.type === 'group') { - const filteredEntries = filterSidebarByBasePath(entry.entries, basePath) + const filteredEntries = filterSidebarByBasePath(entry.entries, basePaths) return filteredEntries.length > 0 ? { ...entry, entries: filteredEntries } : null } return null @@ -102,34 +116,8 @@ export const onRequest = defineRouteMiddleware(async (context) => { const currentPath = context.url.pathname const currentSlug = starlightRoute.id - // Check if we're on a Python API page - if (currentSlug.startsWith('docs/api/python')) { - const docs = await getCollection('docs') - const docInfos: DocInfo[] = docs.map((doc: { id: string; data: { title: unknown } }) => ({ - id: doc.id, - title: doc.data.title as string, - })) - - const pythonSidebar = buildPythonApiSidebar(docInfos, currentSlug) - - // Add index link at the top - pythonSidebar.unshift({ - type: 'link', - label: 'Overview', - href: pathWithBase('/docs/api/python/'), - isCurrent: currentSlug === 'docs/api/python', - badge: undefined, - attrs: {}, - }) - - const titlesByHref = await buildTitlesByHref() - starlightRoute.sidebar = pythonSidebar - starlightRoute.pagination = getPrevNextLinks(pythonSidebar, titlesByHref) - return - } - - // Check if we're on a TypeScript API page - if (currentSlug.startsWith('docs/api/typescript')) { + // Check if we're on an API page (Python or TypeScript) + if (currentSlug.startsWith('docs/api/python') || currentSlug.startsWith('docs/api/typescript')) { const docs = await getCollection('docs') const docInfos: DocInfo[] = docs.map((doc: { id: string; data: { title: unknown; category?: unknown } }) => ({ id: doc.id, @@ -137,21 +125,26 @@ export const onRequest = defineRouteMiddleware(async (context) => { category: doc.data.category as string | undefined, })) - const tsSidebar = buildTypeScriptApiSidebar(docInfos, currentSlug) + const isPython = currentSlug.startsWith('docs/api/python') + const apiSidebar = isPython + ? buildPythonApiSidebar(docInfos, currentSlug) + : buildTypeScriptApiSidebar(docInfos, currentSlug) // Add index link at the top - tsSidebar.unshift({ + const overviewHref = isPython ? '/docs/api/python/' : '/docs/api/typescript/' + const overviewSlug = isPython ? 'docs/api/python' : 'docs/api/typescript' + apiSidebar.unshift({ type: 'link', label: 'Overview', - href: pathWithBase('/docs/api/typescript/'), - isCurrent: currentSlug === 'docs/api/typescript', + href: pathWithBase(overviewHref), + isCurrent: currentSlug === overviewSlug, badge: undefined, attrs: {}, }) const titlesByHref = await buildTitlesByHref() - starlightRoute.sidebar = tsSidebar - starlightRoute.pagination = getPrevNextLinks(tsSidebar, titlesByHref) + starlightRoute.sidebar = apiSidebar + starlightRoute.pagination = getPrevNextLinks(apiSidebar, titlesByHref) return } @@ -164,19 +157,23 @@ export const onRequest = defineRouteMiddleware(async (context) => { return } + // Collect all base paths for this nav section (primary + additional) + const primaryBasePath = currentNav.basePath || currentNav.href + const allBasePaths = [primaryBasePath, ...(currentNav.additionalBasePaths || [])] + // Otherwise filter it down to the major section that we're in - const basePath = currentNav.basePath || currentNav.href - const filteredSidebar = filterSidebarByBasePath(sidebar, basePath) + const filteredSidebar = filterSidebarByBasePath(sidebar, allBasePaths) const expandedSidebar = expandFirstLevelGroups(filteredSidebar) starlightRoute.sidebar = expandedSidebar // Starlight pre-computes pagination from the full sidebar before our middleware runs. // Prune any prev/next links that fall outside the current nav section, then override // labels with actual page titles instead of sidebar nav labels. + const matchesAnyBase = (href: string) => allBasePaths.some((bp) => href.startsWith(bp)) const titlesByHref = await buildTitlesByHref() const { prev, next } = starlightRoute.pagination starlightRoute.pagination = { - prev: prev?.href.startsWith(basePath) ? { ...prev, label: titlesByHref.get(prev.href) ?? prev.label } : undefined, - next: next?.href.startsWith(basePath) ? { ...next, label: titlesByHref.get(next.href) ?? next.label } : undefined, + prev: prev && matchesAnyBase(prev.href) ? { ...prev, label: titlesByHref.get(prev.href) ?? prev.label } : undefined, + next: next && matchesAnyBase(next.href) ? { ...next, label: titlesByHref.get(next.href) ?? next.label } : undefined, } }) diff --git a/src/sidebar.ts b/src/sidebar.ts index bf73867c8..c26ecfe18 100644 --- a/src/sidebar.ts +++ b/src/sidebar.ts @@ -12,6 +12,7 @@ export type StarlightSidebarItem = interface NavConfigItem { label?: string items?: NavConfigItem[] + slug?: string // For labeled leaf items (e.g., { label: "Adding Tools", slug: "docs/user-guide/concepts/tools" }) } type NavConfigEntry = string | NavConfigItem @@ -82,6 +83,12 @@ function convertConfigItem(item: NavConfigEntry, ctx: ConvertContext): Starlight return { label: item.label, items: children } } + + // Object with label and slug (labeled leaf item) + if (item.label && item.slug) { + if (!contentExists(item.slug, ctx.contentDir)) return null + return { slug: item.slug, label: item.label } + } } return null diff --git a/test/sidebar.test.ts b/test/sidebar.test.ts index aa2536e8f..842ba719b 100644 --- a/test/sidebar.test.ts +++ b/test/sidebar.test.ts @@ -69,81 +69,81 @@ describe('Sidebar Generation from navigation.yml', () => { .filter((item): item is StarlightSidebarItem & { label: string } => 'label' in item) .map((item) => item.label) - expect(topLevelLabels).toContain('User Guide') + expect(topLevelLabels).toContain('Docs') expect(topLevelLabels).toContain('Examples') expect(topLevelLabels).toContain('Community') - expect(topLevelLabels).toContain('Labs') - expect(topLevelLabels).toContain('Contribute ❤️') }) it('should have collapsed groups at depth >= 1', () => { const sidebar = loadSidebarFromConfig(pathToNavigationYml) - // Find the User Guide section - const userGuide = sidebar.find( + // Find the Docs section + const docs = sidebar.find( (item): item is StarlightSidebarItem & { label: string; items: StarlightSidebarItem[] } => - 'label' in item && item.label === 'User Guide' + 'label' in item && item.label === 'Docs' ) - expect(userGuide).toBeDefined() - if (userGuide) { + expect(docs).toBeDefined() + if (docs) { // Top level should not be collapsed - expect(userGuide).not.toHaveProperty('collapsed') + expect(docs).not.toHaveProperty('collapsed') - // Find a nested group (like "Quickstart") - const quickstart = userGuide.items.find( - (item): item is StarlightSidebarItem & { label: string } => 'label' in item && item.label === 'Quickstart' + // Find a nested group (like "Get Started") + const getStarted = docs.items.find( + (item): item is StarlightSidebarItem & { label: string } => 'label' in item && item.label === 'Get Started' ) // Nested groups should be collapsed - expect(quickstart).toHaveProperty('collapsed', true) + expect(getStarted).toHaveProperty('collapsed', true) } }) - it('should have slugs without labels for file items', () => { + it('should support both labeled and unlabeled leaf items', () => { const sidebar = loadSidebarFromConfig(pathToNavigationYml) - // Find a leaf item (internal link) and verify it has slug but no label - function findLeafItem(items: StarlightSidebarItem[]): StarlightSidebarItem | null { + // Collect all leaf items + function findLeafItems(items: StarlightSidebarItem[]): StarlightSidebarItem[] { + const leaves: StarlightSidebarItem[] = [] for (const item of items) { if ('slug' in item && !('items' in item)) { - return item + leaves.push(item) } if ('items' in item) { - const found = findLeafItem(item.items as StarlightSidebarItem[]) - if (found) return found + leaves.push(...findLeafItems(item.items as StarlightSidebarItem[])) } } - return null + return leaves } - const leafItem = findLeafItem(sidebar) - expect(leafItem).toBeDefined() - expect(leafItem).toHaveProperty('slug') - expect(leafItem).not.toHaveProperty('label') + const leaves = findLeafItems(sidebar) + expect(leaves.length).toBeGreaterThan(0) + + // Some leaves should have labels (Build section items) + const labeled = leaves.filter((item) => 'label' in item) + expect(labeled.length).toBeGreaterThan(0) + + // Some leaves should not have labels (plain slug items) + const unlabeled = leaves.filter((item) => !('label' in item)) + expect(unlabeled.length).toBeGreaterThan(0) }) - it('should handle external links in sidebar', () => { + it('should include Labs and Contribute under Community', () => { const sidebar = loadSidebarFromConfig(pathToNavigationYml) - // Find the Contribute section which has an external link - const contribute = sidebar.find( - (item): item is StarlightSidebarItem & { label: string } => - 'label' in item && item.label === 'Contribute ❤️' + // Find the Community section + const community = sidebar.find( + (item): item is StarlightSidebarItem & { label: string; items: StarlightSidebarItem[] } => + 'label' in item && item.label === 'Community' ) - expect(contribute).toBeDefined() - if (contribute && 'items' in contribute) { - // Look for external links in the items - const externalLink = (contribute.items as StarlightSidebarItem[]).find( - (item): item is StarlightSidebarItem & { link: string } => 'link' in item && item.link?.startsWith('http') - ) + expect(community).toBeDefined() + if (community) { + const subLabels = community.items + .filter((item): item is StarlightSidebarItem & { label: string } => 'label' in item) + .map((item) => item.label) - // If there's an external link, it should have the right attributes - if (externalLink) { - expect(externalLink).toHaveProperty('attrs') - expect(externalLink.attrs).toHaveProperty('target', '_blank') - } + expect(subLabels).toContain('Labs') + expect(subLabels).toContain('Contribute') } }) }) From 5f6822f3194f57cce9baa350e922e9df297ceff4 Mon Sep 17 00:00:00 2001 From: Ryan Coleman Date: Thu, 12 Mar 2026 15:29:01 -0700 Subject: [PATCH 2/2] chore: temporarily remove why-strands page --- src/config/navigation.yml | 2 - src/content/docs/user-guide/why-strands.mdx | 121 -------------------- 2 files changed, 123 deletions(-) delete mode 100644 src/content/docs/user-guide/why-strands.mdx diff --git a/src/config/navigation.yml b/src/config/navigation.yml index c09dd4b8e..278c22350 100644 --- a/src/config/navigation.yml +++ b/src/config/navigation.yml @@ -39,8 +39,6 @@ sidebar: slug: docs/user-guide/quickstart/python - label: "Quickstart: TypeScript" slug: docs/user-guide/quickstart/typescript - - label: "Why Strands?" - slug: docs/user-guide/why-strands - docs/user-guide/build-with-ai - label: Build items: diff --git a/src/content/docs/user-guide/why-strands.mdx b/src/content/docs/user-guide/why-strands.mdx deleted file mode 100644 index 6d1bb8ec9..000000000 --- a/src/content/docs/user-guide/why-strands.mdx +++ /dev/null @@ -1,121 +0,0 @@ ---- -title: Why Strands? ---- - -Most AI agent frameworks ask you to define every step your agent should take. Strands takes a different approach: **let the model decide**. - -## Model-Driven vs. Workflow-Driven - -In a **workflow-driven** framework, you build a graph of steps — nodes, edges, conditional branches — and the model fills in each step. You control the flow; the model provides the text. - -In a **model-driven** framework like Strands, you give the model a goal and a set of tools. The model decides which tools to call, in what order, and when it's done. The [agent loop](concepts/agents/agent-loop.md) handles the orchestration automatically. - -Here's the difference in code: - - - -```python -from strands import Agent -from strands_tools import calculator, web_search - -agent = Agent(tools=[calculator, web_search]) -agent("What is the population of Tokyo divided by the area of France?") -``` - -Three lines. The model figures out that it needs to search for two facts, then do the math. - - -```python -# Define the graph -graph = StateGraph() -graph.add_node("search_population", search_population) -graph.add_node("search_area", search_area) -graph.add_node("calculate", calculate) -graph.add_edge("search_population", "calculate") -graph.add_edge("search_area", "calculate") - -def search_population(state): - result = web_search("population of Tokyo") - state["population"] = parse_number(result) - return state - -def search_area(state): - result = web_search("area of France in km2") - state["area"] = parse_number(result) - return state - -def calculate(state): - state["answer"] = state["population"] / state["area"] - return state - -app = graph.compile() -app.invoke({"question": "..."}) -``` - -You had to anticipate the steps, wire the graph, and handle the state yourself. - - - -## When to Use Each Approach - -Model-driven development works best when: - -- **The steps aren't predictable.** If you don't know in advance what tools the agent needs to call or in what order, let the model figure it out. -- **You want to iterate quickly.** Changing behavior means changing the prompt or the tool set — not rewiring a graph. -- **You need flexibility.** The same agent can handle variations of a task without code changes. - -Workflow-driven frameworks can be a better fit when: - -- You need **deterministic, auditable** execution paths (e.g., compliance workflows). -- You want **fine-grained control** over every decision point. -- The task is **always the same sequence** of steps. - -Strands supports both styles. You can use the model-driven agent loop for flexible tasks and [graph workflows](concepts/multi-agent/graph.md) or [structured workflows](concepts/multi-agent/workflow.md) when you need explicit control. - -## What You Get - -- **Any model, any provider.** Amazon Bedrock, Anthropic, OpenAI, Gemini, Ollama, and [more](concepts/model-providers/index.md). Switch providers without changing your agent code. -- **Tools as plain functions.** Write a Python function with a docstring and it becomes a [tool](concepts/tools/custom-tools.md). No schemas, no boilerplate. -- **Multi-agent out of the box.** [Agents as tools](concepts/multi-agent/agents-as-tools.md), [swarms](concepts/multi-agent/swarm.md), [A2A protocol](concepts/multi-agent/agent-to-agent.md) — simple primitives that compose. -- **Production-ready deployment.** Deploy to [AWS Lambda](deploy/deploy_to_aws_lambda.md), [Fargate](deploy/deploy_to_aws_fargate.md), [EKS](deploy/deploy_to_amazon_eks.md), [Bedrock AgentCore](deploy/deploy_to_bedrock_agentcore/index.md), and more. -- **Built-in observability.** [Traces](../observability-evaluation/traces.md), [metrics](../observability-evaluation/metrics.md), and [logs](../observability-evaluation/logs.md) with OpenTelemetry integration. - -## Python & TypeScript - -Strands is available in both Python and TypeScript. The Python SDK is mature and production-ready. The TypeScript SDK is experimental with focus on core agent functionality. - -
-Feature comparison - -| Category | Feature | Python | TypeScript | -|----------|---------|:------:|:----------:| -| **Core** | Agent creation and invocation | ✅ | ✅ | -| | Streaming responses | ✅ | ✅ | -| | Structured output | ✅ | ❌ | -| **Model providers** | Amazon Bedrock | ✅ | ✅ | -| | OpenAI | ✅ | ✅ | -| | Anthropic | ✅ | ❌ | -| | Ollama | ✅ | ❌ | -| | LiteLLM | ✅ | ❌ | -| | Custom providers | ✅ | ✅ | -| **Tools** | Custom function tools | ✅ | ✅ | -| | MCP (Model Context Protocol) | ✅ | ✅ | -| | Built-in tools | 30+ via community package | 4 built-in | -| **Conversation** | Null manager | ✅ | ✅ | -| | Sliding window manager | ✅ | ✅ | -| | Summarizing manager | ✅ | ❌ | -| **Hooks** | Lifecycle hooks | ✅ | ✅ | -| **Multi-agent** | Swarms, workflows, graphs | ✅ | ❌ | -| | Agents as tools | ✅ | ❌ | -| **Session management** | File, S3, repository managers | ✅ | ❌ | -| **Observability** | OpenTelemetry integration | ✅ | ❌ | -| **Experimental** | Bidirectional streaming | ✅ | ❌ | - -
- -## Next Steps - -- [Quickstart: Python](quickstart/python.md) — build your first agent -- [Quickstart: TypeScript](quickstart/typescript.md) — if you prefer TypeScript -- [Adding Tools](concepts/tools/index.md) — give your agent capabilities -- [Multi-Agent Systems](concepts/multi-agent/multi-agent-patterns.md) — coordinate multiple agents