Welcome to the second chapter of the Agents project tutorial!
In the previous chapter, Agent Definition & Validation, we defined the "Character Sheet" for our AI agent, the code-reviewer. Now that we have these definitions, we need a way to organize, view, and manage them.
Imagine you have created 20 different agents: a poet, a coder, a chef, a debugger, etc.
Without a Menu Controller, you would have no way to:
We need a central hubβlike a smartphone's home screenβthat lets us navigate between different tasks.
In this chapter, we will build the navigation logic that allows us to:
code-reviewer agent.The Menu Controller acts as the traffic police for the application. It relies on three main concepts:
Before diving into the code, let's visualize how the Menu Controller handles a user interaction.
Imagine a user wants to delete an agent. Here is how the Menu Controller routes that request.
The core logic lives in AgentsMenu.tsx. Let's look at how it manages the state and routing.
modeState)
The "brain" of the menu is a single state variable called modeState. It acts like a GPS coordinate for the UI.
// From AgentsMenu.tsx
type ModeState =
| { mode: 'list-agents'; source: string }
| { mode: 'create-agent' }
| { mode: 'edit-agent'; agent: AgentDefinition };
export function AgentsMenu({ tools, onExit }: Props) {
// This state determines which screen is visible
const [modeState, setModeState] = useState<ModeState>({
mode: 'list-agents',
source: 'all',
});
Explanation: When the app starts, mode is set to 'list-agents'. This tells the system to show the main dashboard first.
The Menu Controller checks modeState.mode and renders the corresponding component. This is the "Traffic Police" logic.
// From AgentsMenu.tsx (Simplified)
switch (modeState.mode) {
case 'list-agents':
return (
<AgentsList
onSelect={(agent) => setModeState({
mode: 'agent-menu',
agent: agent
})}
onCreateNew={() => setModeState({ mode: 'create-agent' })}
/>
);
case 'create-agent':
return <CreateAgentWizard onCancel={() => setModeState({ mode: 'list-agents' })} />;
case 'edit-agent':
return <AgentEditor agent={modeState.agent} />;
// ... cases for delete, view, etc.
}
Explanation:
list-agents, we show the List. We also pass functions to the list so it knows how to change the mode (e.g., onCreateNew switches mode to create-agent).create-agent, we hide the list and show the Wizard.AgentsList.tsx)
The controller delegates the actual drawing of items to AgentsList.tsx. This component iterates over the agent definitions we created in Chapter 1.
// From AgentsList.tsx
export function AgentsList({ agents, onSelect }) {
// We sort agents alphabetically
const sortedAgents = [...agents].sort(compareAgentsByName);
return (
<Box flexDirection="column">
{sortedAgents.map(agent => (
<Box key={agent.agentType}>
<Text>{agent.agentType}</Text>
</Box>
))}
</Box>
);
}
Explanation: This component receives the data (the list of agents) and the callbacks (what to do when clicked) from the Controller. It focuses purely on display.
AgentNavigationFooter.tsx)To make the app beginner-friendly, we always show keyboard instructions at the bottom.
// From AgentNavigationFooter.tsx
export function AgentNavigationFooter({ instructions }) {
const defaultText = "Press ββ to navigate Β· Enter to select Β· Esc to go back";
return (
<Box marginLeft={2}>
<Text dimColor>{instructions || defaultText}</Text>
</Box>
);
}
Explanation: This is a simple visual component included by the Menu Controller at the bottom of every screen to guide the user.
Let's trace exactly what happens in the code when we want to delete our code-reviewer:
AgentsMenu initializes with mode: 'list-agents'.AgentsList renders code-reviewer on the screen.code-reviewer.AgentsList calls onSelect.setModeState({ mode: 'agent-menu', agent: codeReviewer }) runs.switch statement sees 'agent-menu' and renders a generic <Select> menu with options: "View", "Edit", "Delete".setModeState({ mode: 'delete-confirm', ... }) runs.switch statement sees 'delete-confirm' and shows the confirmation dialog.In this chapter, we learned how to build a Menu Controller. This abstraction separates the Logic (state management and routing) from the Presentation (the actual list or dialogs).
This architecture allows our app to be flexible. We can easily add a "Settings" screen or a "Help" screen just by adding a new case to our switch statement, without rewriting the entire application.
Now that we can navigate to agents and select them, we need to handle what happens when we actually create or delete one. Where does that data go?
Next Chapter: File Persistence Layer
Generated by Code IQ