diff --git a/src/lib/posts/primitives-over-pipelines.mdx b/src/lib/posts/primitives-over-pipelines.mdx index d46140d..f4552ca 100644 --- a/src/lib/posts/primitives-over-pipelines.mdx +++ b/src/lib/posts/primitives-over-pipelines.mdx @@ -1,5 +1,5 @@ import { AUTHORS, CATEGORIES } from '~/lib/constants/blog' -import { PipelinePrimitivesFigure } from '~/ui/blog/primitives-over-pipelines' +import { AssemblyScenariosFigure, PipelinePrimitivesFigure } from '~/ui/blog/primitives-over-pipelines' export const metadata = { title: "Primitives over pipelines", @@ -50,6 +50,8 @@ Instead, primitive-oriented systems favor thin system prompts. A thin prompt def In other words, the system prompt encodes **taste, not state**. +> **Actionable insight:** "In other words, the system prompt encodes **taste, not state**." + The prompt defines how the agent should think and behave, while primitives supply the information required to act. This keeps the prompt small, keeps the system flexible, and ensures the agent reasons over fresh context rather than static assumptions. ## A practical example @@ -76,6 +78,11 @@ None of these dictate order. They simply expose capabilities. Given these primit - If multiple records are needed, it can parallelize retrieval before rendering. - If the answer is already known from context, it might skip listing entirely. +
+ + The same primitives can assemble different workflows at runtime. +
+ The same primitives support many different execution paths. This is the core advantage of primitive-oriented systems: the designer **defines capabilities, not trajectories**. Each primitive remains simple and predictable, but together they give the agent the freedom to construct the right workflow for each request. The result is a system that adapts to the user’s intent, avoids unnecessary steps, and reasons over the problem instead of replaying a predetermined script. @@ -87,3 +94,5 @@ The core problem is that the pipeline encodes one trajectory through the solutio The skeleton example is one instance of a broader pattern. When the system can navigate the space itself, prescribing the path gets in the way. We're used to designing systems where we control the flow; with capable models, the leverage is in giving them room to assemble. Don't try to anticipate every path. Give primitives and let the agent figure it out. + +> **Actionable insight:** "Don't try to anticipate every path. Give primitives and let the agent figure it out." diff --git a/src/ui/blog/primitives-over-pipelines/assembly-scenarios-figure.tsx b/src/ui/blog/primitives-over-pipelines/assembly-scenarios-figure.tsx new file mode 100644 index 0000000..36edb02 --- /dev/null +++ b/src/ui/blog/primitives-over-pipelines/assembly-scenarios-figure.tsx @@ -0,0 +1,267 @@ +'use client' + +import { Fragment, useCallback, useEffect, useState } from 'react' +import { cn } from '~/lib/utils/cn' +import { Button } from '~/ui/button' +import { Figure } from '~/ui/figure' +import { PauseIcon } from '~/ui/icons/pause' +import { PlayIcon } from '~/ui/icons/play' +import { RestartIcon } from '~/ui/icons/restart' + +type StepTone = 'sky' | 'amber' | 'violet' | 'emerald' + +type PrimitiveStep = { + id: string + label: string + tone: StepTone +} + +type Scenario = { + id: string + query: string + summary: string + steps: PrimitiveStep[] +} + +type PlaybackState = { + scenarioIndex: number + visibleSteps: number + holdTicks: number +} + +const SCENARIOS: Scenario[] = [ + { + id: 'subset-then-render', + query: 'Show me inventory cards for the top 3 items with details.', + summary: 'It might list first, retrieve a subset with get, then render.', + steps: [ + { id: 'list', label: 'list', tone: 'sky' }, + { id: 'get-subset', label: 'get (subset)', tone: 'amber' }, + { id: 'render', label: 'render[]', tone: 'violet' } + ] + }, + { + id: 'nested-data', + query: 'Show nested component data for this bundle and each child.', + summary: 'If the user asks for nested data, it may recurse and fetch deeper levels.', + steps: [ + { id: 'list', label: 'list', tone: 'sky' }, + { id: 'get-parent', label: 'get (level 1)', tone: 'amber' }, + { id: 'get-child', label: 'get (level 2)', tone: 'amber' }, + { id: 'render', label: 'render[]', tone: 'violet' } + ] + }, + { + id: 'parallel-retrieval', + query: 'Compare details for multiple records in one answer.', + summary: 'If multiple records are needed, it can parallelize retrieval before rendering.', + steps: [ + { id: 'list', label: 'list', tone: 'sky' }, + { id: 'get-parallel', label: 'get x N (parallel)', tone: 'emerald' }, + { id: 'render', label: 'render[]', tone: 'violet' } + ] + }, + { + id: 'context-hit', + query: 'The answer is already in context, render it directly.', + summary: 'If the answer is already known from context, it might skip listing entirely.', + steps: [{ id: 'render', label: 'render[]', tone: 'violet' }] + } +] + +const PRIMITIVE_SET = ['render_skeleton', 'list', 'get', 'render[]'] +const FIGURE_H = 300 +const STEP_INTERVAL_MS = 1050 +const HOLD_TICKS_MAX = 2 +const CONTROLS_LEFT = 8 +const CONTROLS_BOTTOM = 10 + +const STEP_TONE_STYLES: Record = { + amber: { + active: 'border-amber-500/45 bg-amber-500/15 text-amber-600 dark:text-amber-300', + inactive: 'border-amber-500/20 bg-amber-500/5 text-amber-500/45', + badge: 'bg-amber-500/20 text-amber-700 dark:text-amber-200' + }, + emerald: { + active: 'border-emerald-500/45 bg-emerald-500/15 text-emerald-600 dark:text-emerald-300', + inactive: 'border-emerald-500/20 bg-emerald-500/5 text-emerald-500/45', + badge: 'bg-emerald-500/20 text-emerald-700 dark:text-emerald-200' + }, + sky: { + active: 'border-sky-500/45 bg-sky-500/15 text-sky-600 dark:text-sky-300', + inactive: 'border-sky-500/20 bg-sky-500/5 text-sky-500/45', + badge: 'bg-sky-500/20 text-sky-700 dark:text-sky-200' + }, + violet: { + active: 'border-violet-500/45 bg-violet-500/15 text-violet-600 dark:text-violet-300', + inactive: 'border-violet-500/20 bg-violet-500/5 text-violet-500/45', + badge: 'bg-violet-500/20 text-violet-700 dark:text-violet-200' + } +} + +const getInitialPlaybackState = (): PlaybackState => ({ + holdTicks: 0, + scenarioIndex: 0, + visibleSteps: 1 +}) + +const getNextPlaybackState = (state: PlaybackState): PlaybackState => { + const scenario = SCENARIOS[state.scenarioIndex] + if (!scenario) return state + + if (state.visibleSteps < scenario.steps.length) + return { ...state, holdTicks: 0, visibleSteps: state.visibleSteps + 1 } + + if (state.holdTicks < HOLD_TICKS_MAX) return { ...state, holdTicks: state.holdTicks + 1 } + + const nextScenarioIndex = (state.scenarioIndex + 1) % SCENARIOS.length + return { + holdTicks: 0, + scenarioIndex: nextScenarioIndex, + visibleSteps: 1 + } +} + +const StepArrow = ({ visible }: { visible: boolean }) => ( + + Arrow right + + +) + +const WorkflowStep = ({ + index, + isVisible, + step +}: { + index: number + isVisible: boolean + step: PrimitiveStep +}) => { + const tone = STEP_TONE_STYLES[step.tone] + + return ( +
+ + {index + 1} + + {step.label} +
+ ) +} + +export const AssemblyScenariosFigure = () => { + const [playback, setPlayback] = useState(getInitialPlaybackState) + const [isPlaying, setIsPlaying] = useState(true) + + const reset = useCallback(() => { + setPlayback(getInitialPlaybackState()) + setIsPlaying(true) + }, []) + + const togglePlay = useCallback(() => { + setIsPlaying(prev => !prev) + }, []) + + useEffect(() => { + if (!isPlaying) return + + const timer = setInterval(() => { + setPlayback(prev => getNextPlaybackState(prev)) + }, STEP_INTERVAL_MS) + return () => clearInterval(timer) + }, [isPlaying]) + + const scenario = SCENARIOS[playback.scenarioIndex] ?? SCENARIOS[0] + if (!scenario) return null + + return ( +
+
+

+ Assembled primitives +

+ +

+ Query: "{scenario.query}" +

+ +

+ {scenario.summary} +

+ +
+
+ {scenario.steps.map((step, index) => { + const isVisible = index < playback.visibleSteps + const showArrow = index < playback.visibleSteps - 1 + const isLast = index === scenario.steps.length - 1 + + return ( + + + {isLast ? null : } + + ) + })} +
+
+ +
+
+ Primitive set: + {PRIMITIVE_SET.map(primitive => ( + + {primitive} + + ))} +
+
+ +
+ + + +
+
+
+ ) +} diff --git a/src/ui/blog/primitives-over-pipelines/index.ts b/src/ui/blog/primitives-over-pipelines/index.ts index 4e595af..ead9e58 100644 --- a/src/ui/blog/primitives-over-pipelines/index.ts +++ b/src/ui/blog/primitives-over-pipelines/index.ts @@ -1,2 +1,5 @@ -export { ListInspectFigure } from './list-inspect-figure' -export { PipelinePrimitivesFigure } from './pipeline-primitives-figure' +import { AssemblyScenariosFigure } from './assembly-scenarios-figure' +import { ListInspectFigure } from './list-inspect-figure' +import { PipelinePrimitivesFigure } from './pipeline-primitives-figure' + +export { AssemblyScenariosFigure, ListInspectFigure, PipelinePrimitivesFigure }