diff --git a/pkgs/website/astro.config.mjs b/pkgs/website/astro.config.mjs
index 37af3fad2..b4184a2be 100644
--- a/pkgs/website/astro.config.mjs
+++ b/pkgs/website/astro.config.mjs
@@ -152,15 +152,22 @@ export default defineConfig({
starlightLlmsTxt({
exclude: [
'index',
- '**/index',
+ // Navigation-only index files (pure CardGrid hubs with no unique content)
+ 'build/index',
+ 'concepts/index',
+ 'deploy/index',
+ 'reference/index',
+ 'tutorials/index',
+ 'comparisons/index',
+ // Tutorials (lengthy, patterns covered elsewhere)
'tutorials/ai-web-scraper/*',
- 'concepts/naming-steps',
- 'deploy/tune-flow-config',
- 'get-started/faq',
+ // News/blog and non-technical content
'news/**',
'hire/**',
- 'build/configuring-retries',
- 'build/delaying-steps',
+ 'author/**',
+ // Note: The following index files ARE included because they have valuable content:
+ // - build/starting-flows/index (comparison guide for starting flows)
+ // - reference/configuration/index (config philosophy and structure)
],
promote: [
'get-started/installation',
@@ -225,8 +232,12 @@ export default defineConfig({
link: '/build/organize-flow-code/',
},
{
- label: 'Configuring retries',
- link: '/build/configuring-retries/',
+ label: 'Retrying steps',
+ link: '/build/retrying-steps/',
+ },
+ {
+ label: 'Validation steps',
+ link: '/build/validation-steps/',
},
{
label: 'Delaying steps',
diff --git a/pkgs/website/src/content/docs/build/delaying-steps.mdx b/pkgs/website/src/content/docs/build/delaying-steps.mdx
index 45ae8e4a7..34703177f 100644
--- a/pkgs/website/src/content/docs/build/delaying-steps.mdx
+++ b/pkgs/website/src/content/docs/build/delaying-steps.mdx
@@ -10,7 +10,7 @@ import { Aside, CardGrid, LinkCard } from "@astrojs/starlight/components";
Use [`startDelay`](/reference/configuration/step-execution/#startdelay) to schedule steps for execution after a specified time period. Delays are relative to when the step's dependencies complete.
For detailed information about each configuration option, see the [Step Execution Options](/reference/configuration/step-execution/) reference.
@@ -82,8 +82,8 @@ new Flow({
description="Complete reference for all configuration options and their defaults"
/>
await fetchFromAPI(input.run.url))
+```
+
+### Permanent Failures
+
+Problems that will never succeed on retry:
+- Invalid input format (malformed email, negative numbers)
+- Missing required fields
+- Business rule violations
+- Schema validation errors
+
+Configure without retries:
+```typescript
+.step({
+ slug: 'validInput',
+ maxAttempts: 1, // No retries for validation
+}, (input) => {
+ if (!input.run.email) throw new Error('Email required');
+ return input.run;
+})
+```
+
+
+
+
+
+For detailed guidance on validation patterns, see [Validation Steps](/build/validation-steps/).
+
## Guiding Principle
**Set conservative flow-level defaults, override per-step as needed.**
@@ -56,6 +106,11 @@ new Flow({
## Learn More
+
({ slug: 'sendEmail', maxAttempts: 5 })
+ .step(
+ { slug: 'send' },
+ async (input) => {
+ if (!input.run.email.includes('@')) {
+ throw new Error('Invalid email'); // Retries 5 times!
+ }
+ return await sendEmail(input.run.email);
+ }
+ )
+```
+
+With explicit validation, failures stop immediately:
+
+```typescript
+// With validation - fails immediately on invalid input
+new Flow<{ email: string }>({ slug: 'sendEmail' })
+ .step(
+ { slug: 'validInput', maxAttempts: 1 },
+ (input) => {
+ if (!input.run.email.includes('@')) {
+ throw new Error('Invalid email');
+ }
+ return input.run;
+ }
+ )
+ .step(
+ { slug: 'send', dependsOn: ['validInput'], maxAttempts: 5 },
+ async (input) => await sendEmail(input.validInput.email)
+ )
+```
+
+## Keep Validation Synchronous
+
+Validation steps should be fast, synchronous functions that check input format and structure. Avoid async operations like database queries or API calls - those belong in separate steps with appropriate retry configuration.
+
+```typescript
+// Good: Fast, synchronous validation
+.step(
+ { slug: 'validOrder', maxAttempts: 1 },
+ (input) => {
+ const { amount, items } = input.run;
+
+ if (amount <= 0) throw new Error('amount must be positive');
+ if (!items?.length) throw new Error('items cannot be empty');
+
+ return input.run;
+ }
+)
+
+// Bad: Async checks in validation
+.step(
+ { slug: 'validCustomer', maxAttempts: 1 },
+ async (input) => {
+ // Database lookups belong in separate steps with retries
+ const exists = await checkCustomerExists(input.run.customerId);
+ if (!exists) throw new Error('Customer not found');
+ return input.run;
+ }
+)
+```
+
+
+
+## See Also
+
+- [Retrying Steps](/build/retrying-steps/) - Understanding failure types and retry policies
+- [Context Object](/concepts/context-object/) - Accessing validated input in dependent steps
diff --git a/pkgs/website/src/content/docs/index.mdx b/pkgs/website/src/content/docs/index.mdx
index 94c125fb0..d7bde6c08 100644
--- a/pkgs/website/src/content/docs/index.mdx
+++ b/pkgs/website/src/content/docs/index.mdx
@@ -372,7 +372,7 @@ npx pgflow@latest install
- Built-in retry logic with exponential backoff for flaky AI APIs. When OpenAI times out or rate-limits, only that step retries - your workflow continues. Configure max attempts and delays per step, no retry code needed. [Learn more →](/build/configuring-retries/)
+ Built-in retry logic with exponential backoff for flaky AI APIs. When OpenAI times out or rate-limits, only that step retries - your workflow continues. Configure max attempts and delays per step, no retry code needed. [Learn more →](/build/retrying-steps/)
diff --git a/pkgs/website/src/content/docs/reference/configuration/index.mdx b/pkgs/website/src/content/docs/reference/configuration/index.mdx
index 1799491ff..072d039c1 100644
--- a/pkgs/website/src/content/docs/reference/configuration/index.mdx
+++ b/pkgs/website/src/content/docs/reference/configuration/index.mdx
@@ -44,9 +44,9 @@ new Flow({ slug: 'my_flow' })