Imagine an AI agent that doesn’t just respond to prompts but can actually write and execute code, interact with a virtual filesystem, and remember its past actions across multiple sessions. This isn’t science fiction; it’s the realm of “coding agents,” and they demand a fundamentally different architecture than simple Large Language Model (LLM) API wrappers.
In this chapter, we’ll peel back the layers of Flue’s agent harness to understand how it empowers these advanced coding agents. We’ll explore the critical concepts of sandboxed execution environments and persistent state, diving into why they’re essential for building intelligent, reliable, and secure AI systems. By the end, you’ll grasp how Flue structures these capabilities in TypeScript and be ready to build agents that can truly “think” and “act” in a controlled environment.
This chapter builds on the foundational Flue concepts we covered earlier, assuming you’re comfortable with defining basic agents and tools. Get ready to elevate your agent-building skills!
The Power of the Agent Harness: Sandboxed Execution
When we talk about “coding agents,” we’re envisioning AI systems that can do more than just generate text. They might need to:
- Write and run Python scripts to process data.
- Interact with a virtual filesystem to create, read, or modify files.
- Execute shell commands to manage dependencies or deploy applications.
Traditional LLM SDK wrappers are excellent for sending prompts and receiving responses, but they don’t offer the secure, controlled environment needed for such dynamic actions. This is where Flue’s agent harness architecture shines, particularly through its emphasis on sandboxed execution.
Why Sandboxed Execution is Critical
Allowing an AI to execute arbitrary code or shell commands directly on your infrastructure is a massive security risk. Think about it: an LLM might hallucinate a malicious command or misinterpret instructions, leading to unintended system access or data corruption.
📌 Key Idea: Sandboxed execution isolates the agent’s operational environment, preventing uncontrolled access to the host system.
Flue addresses this by providing an architectural pattern for integrating sandboxed environments. While the core Flue framework (TypeScript) itself doesn’t include a full-fledged sandbox runtime, it provides the structure and mechanisms for agents to interface with one. The idea is that your agent’s “tools” or “skills” can delegate execution to a secure, isolated process.
Figure 1: Flue Agent Interacting with a Sandboxed Execution Service
Flue’s Approach to Sandboxing
Flue, as an agent harness, defines how agents request actions that might involve sandboxed execution. It allows you to define tools that, when invoked by the agent, communicate with an external sandbox service. This service is responsible for:
- Isolation: Running code in a separate process, container, or virtual machine.
- Resource Limits: Constraining CPU, memory, and network access.
- Security: Preventing access to sensitive host resources or network endpoints.
- Input/Output Control: Managing what goes into the sandbox and what comes out.
For instance, if you wanted a Flue agent to write and execute Python code, you wouldn’t run Python directly within your Flue application. Instead, you’d create a Flue tool (e.g., pythonExecutor) that sends the Python code to a dedicated sandbox service (like a serverless function, a containerized environment, or a specialized sandbox API) for execution. The sandbox service then returns the output or any errors back to your Flue agent.
⚡ Real-world insight: Services like Cloudflare Workers (where Flue can be deployed) offer a highly isolated, serverless runtime environment that can act as a sandbox for certain types of operations, particularly JavaScript/TypeScript execution. For other languages or full filesystem access, you’d typically integrate with a dedicated sandbox platform.
Persistent State for Intelligent Agents
Beyond executing code, truly intelligent agents need memory. Not just short-term conversation history, but the ability to retain context, decisions, and even their internal “thoughts” across multiple interactions or even days. This is where persistent state comes into play.
Why Persistent State is Crucial
Consider a complex coding agent tasked with building a web application. It might:
- Generate initial project structure.
- Write a few files.
- Receive feedback from a user.
- Modify existing files based on that feedback.
- Remember the project’s current state (e.g., file contents, installed dependencies) to continue its work.
Without persistent state, each interaction would be like starting from scratch, making the agent incapable of handling multi-step, long-running tasks.
🧠 Important: Persistent state allows agents to maintain a long-term memory of their environment, progress, and internal reasoning, enabling complex, multi-turn interactions and long-running tasks.
Flue’s architecture accommodates persistent state, allowing you to design agents that can store and retrieve data related to their ongoing tasks. This state can include:
- Internal Monologue/Reasoning: The agent’s thought process.
- Intermediate Results: Data generated during a task.
- Environment Snapshot: The state of a virtual filesystem or database the agent is interacting with.
- User Preferences/Context: Information specific to the user’s ongoing session.
Managing Persistent State in Flue
Flue agents can access and modify a state object, which is automatically handled by the framework. When an agent runs within an AgentRouteHandler, this state can be stored in a backend data store (like a key-value store, database, or object storage) and loaded for subsequent invocations.
This allows an agent to:
- Read its past state at the beginning of an interaction.
- Update its state based on new information or actions.
- Persist the updated state at the end of the interaction.
This capability is fundamental for creating agents that can learn, adapt, and complete complex projects over time.
Architecting a Coding Agent with Flue
Let’s see how sandboxed execution and persistent state integrate into a Flue agent. We’ll outline a conceptual architecture for a simple “Code Interpreter Agent” that can execute JavaScript code in a controlled manner and remember previous code snippets.
Agent Structure with Sandboxing and State
Our coding agent will need:
- A Tool for Code Execution: This tool will take code as input and send it to our “sandbox” (for this example, we’ll simulate a sandbox, but in production, it would be a separate, secure service).
- Persistent State: To store the history of executed code or defined variables.
- Agent Logic: To decide when to use the code execution tool and how to update its state.
Figure 2: Flue Agent Architecture with Sandboxed Execution and Persistent State
Step-by-Step Implementation: Building a Simple Code Interpreter Agent
Let’s create a simplified Flue agent that can “execute” JavaScript code and remember a simple history. For the “sandbox,” we’ll simulate it locally for demonstration, but remember a real sandbox would be external.
Prerequisites: Ensure you have Node.js (v18.x or later as of 2026-06-03) and TypeScript set up. If you followed previous chapters, your Flue environment should be ready.
Step 1: Initialize Your Flue Project
If you haven’t already, create a new Flue project:
# Using npm
npm create flue@latest my-code-agent -- --template basic-agent
# Or using yarn
yarn create flue@latest my-code-agent --template basic-agent
cd my-code-agent
npm installStep 2: Define the Sandboxed Execution Tool
We’ll create a tool that simulates executing JavaScript code. In a real scenario, this tool would make an HTTP request to a dedicated sandbox service.
Open src/tools.ts and replace its content with the following:
// src/tools.ts
import { Tool } from '@flue/core';
// This is a *simulated* sandbox for demonstration purposes.
// IMPORTANT: In a production environment, this would call an external, secure sandbox service
// that runs in a truly isolated environment (e.g., a dedicated container, VM, or serverless function
// with strict resource and security policies).
// Directly executing untrusted code within your main application is a severe security risk.
const simulatedSandboxExecute = async (code: string): Promise<string> => {
console.log(`Executing simulated code:\n${code}`);
// Check for specific patterns to provide deterministic results for testing
if (code.includes('const a = 5;') && code.includes('const b = 10;') && code.includes('a + b;')) {
return '15';
} else if (code.includes('console.log("hello world");')) {
return 'hello world';
} else if (code.includes('2 * 3')) {
return '6';
} else if (code.includes('const myVar = 42;') && code.includes('myVar * 2;')) {
return '84';
} else if (code.includes('const anotherVar = 100;') && code.includes('anotherVar / 2;')) {
return '50';
}
// Fallback for any other code
return `Simulated execution successful. Output for: "${code.substring(0, Math.min(code.length, 50))}${code.length > 50 ? '...' : ''}"`;
};
/**
* A tool to execute JavaScript code in a simulated sandboxed environment.
* In a real application, this would interface with a secure external sandbox.
*/
export const executeJsCode: Tool = {
name: 'executeJsCode',
description: 'Executes JavaScript code in a simulated sandbox and returns the result.',
parameters: {
type: 'object',
properties: {
code: {
type: 'string',
description: 'The JavaScript code to execute.',
},
},
required: ['code'],
},
async run({ code }) {
console.log(`Tool 'executeJsCode' called with code: ${code}`);
const output = await simulatedSandboxExecute(code);
return `Execution Result: ${output}`;
},
};
export const tools = [executeJsCode];Explanation of src/tools.ts:
- We define
simulatedSandboxExecuteto represent our sandbox. For safety, this example uses conditional checks on the inputcodestring to return predefined results, rather than actually executing the code. This is crucial for security. - The
executeJsCodeTooltakes acodestring as a parameter. - Its
runmethod calls our simulated sandbox, logs the activity, and returns the result.
⚠️ What can go wrong: Directly executing untrusted code within your main application process is a massive security vulnerability. Always use a dedicated, secure, external sandbox for executing untrusted code. Our simulatedSandboxExecute function is purely for conceptual demonstration of the interface with a sandbox, not a secure implementation.
Step 3: Implement the Agent Logic with Persistent State
Now, let’s create our agent that uses this tool and manages its state. We’ll make it remember the last executed code snippet.
Open src/agent.ts and modify it:
// src/agent.ts
import { defineAgent, defineTool } from '@flue/core';
import { executeJsCode } from './tools'; // Import our new tool
// Define the shape of our agent's persistent state
interface AgentState {
lastExecutedCode: string | null;
executionHistory: string[];
}
// Our Code Interpreter Agent
export const codeInterpreterAgent = defineAgent({
name: 'CodeInterpreterAgent',
description: 'An agent that can execute JavaScript code and remembers its history.',
// Provide the tools this agent can use
tools: [executeJsCode],
// Define the initial state for new sessions
initialState: {
lastExecutedCode: null,
executionHistory: [],
} as AgentState, // Type assertion for initial state
// This is the core logic of our agent
async run({ prompt, tools, state, updateState }) {
// Cast state to our defined interface for type safety
const currentState = state as AgentState;
// Log current state for debugging
console.log('Current Agent State:', currentState);
// If the prompt contains "execute code", try to extract and run it
if (prompt.includes('execute code')) {
// A simple regex to extract code. A real agent might use a more robust parser or LLM for this.
const codeMatch = prompt.match(/```(?:javascript|js)\n([\s\S]*?)\n```/);
if (codeMatch && codeMatch[1]) {
const codeToExecute = codeMatch[1].trim();
// Use the executeJsCode tool
const result = await tools.executeJsCode({ code: codeToExecute });
// Update the agent's persistent state
const newHistory = [...currentState.executionHistory, `Code: ${codeToExecute}\nResult: ${result}`];
updateState({
lastExecutedCode: codeToExecute,
executionHistory: newHistory.slice(-5) // Keep last 5 entries
});
return `Code executed. Result:\n${result}\n\nI've remembered this.`;
} else {
return "Please provide the code in a JavaScript code block (e.g., ```js...```) for execution.";
}
}
// If the user asks about previous code
if (prompt.toLowerCase().includes('what was the last code') || prompt.toLowerCase().includes('last thing i ran')) {
if (currentState.lastExecutedCode) {
return `The last code I executed was:\n\`\`\`js\n${currentState.lastExecutedCode}\n\`\`\``;
} else {
return "I haven't executed any code yet in this session.";
}
}
// If the user asks for history
if (prompt.toLowerCase().includes('show me the history') || prompt.toLowerCase().includes('what have i run')) {
if (currentState.executionHistory.length > 0) {
return `Here's your execution history:\n\n${currentState.executionHistory.join('\n---\n')}`;
} else {
return "No execution history yet.";
}
}
// Default response if no specific action is triggered
return `Hello! I'm a Code Interpreter Agent. You can ask me to "execute code" by providing a JavaScript code block, or ask me "what was the last code" I ran.`;
},
});Explanation of src/agent.ts:
- We define an
AgentStateinterface to clearly structure our persistent data, includinglastExecutedCodeandexecutionHistory. defineAgentnow includestools: [executeJsCode]to make our sandbox tool available to the agent.initialStateprovides a default structure for new agent sessions, ensuringlastExecutedCodestarts asnullandexecutionHistoryis an empty array.- Inside the
runmethod, we accessstate(which holds the current persistent state) andupdateState(a function to modify and persist the state). - When code is executed, we extract the code using a simple regex, call the
executeJsCodetool, and then updatelastExecutedCodeandexecutionHistoryin the state usingupdateState. - We also add logic to respond to queries about the
lastExecutedCodeorexecutionHistory, demonstrating how the agent can retrieve its own memory for context.
Step 4: Expose the Agent via AgentRouteHandler
To make our agent accessible, we expose it using Flue’s AgentRouteHandler. This is typically done in src/index.ts.
Open src/index.ts and modify it to use our new agent:
// src/index.ts
import { AgentRouteHandler } from '@flue/core';
import { codeInterpreterAgent } from './agent'; // Import our new agent
// Create an AgentRouteHandler for our code interpreter agent
const handler = new AgentRouteHandler({
agent: codeInterpreterAgent,
// For local development, you might use a simple memory store.
// For production, you'd configure a persistent store (e.g., KV store, database).
// Flue framework provides adapters for various storage solutions.
// As of June 2026, Flue is designed to be highly adaptable to different
// environment-specific storage mechanisms, especially in serverless contexts.
// Example for Cloudflare Workers:
// stateStore: new CloudflareKVStateStore(MY_KV_NAMESPACE)
});
// Export the handler for deployment (e.g., to Cloudflare Workers)
export default handler;Explanation of src/index.ts:
- We import our
codeInterpreterAgent. - We instantiate
AgentRouteHandlerwith our agent. - The
stateStorecomment highlights that for production, you’d configure a robust, persistent storage solution. Flue is designed to integrate with various backend stores, often leveraging environment-specific options (like Cloudflare KV for Cloudflare Workers) to efficiently manage state across invocations.
Step 5: Test Your Agent Locally
You can test your agent using the Flue CLI or by sending HTTP requests.
Start the development server:
npm run devNow, you can send requests to your agent. You can use curl or a tool like Postman/Insomnia.
Example 1: Initial Prompt
curl -X POST http://localhost:8787/agent \
-H "Content-Type: application/json" \
-d '{"prompt": "Hello Flue agent!"}'Expected Output (approximate):
{
"response": "Hello! I'm a Code Interpreter Agent. You can ask me to \"execute code\" by providing a JavaScript code block, or ask me \"what was the last code\" I ran."
}Example 2: Execute Code
curl -X POST http://localhost:8787/agent \
-H "Content-Type: application/json" \
-d '{"prompt": "execute code\n```js\nconst a = 5;\nconst b = 10;\na + b;\n```"}'Expected Output (approximate):
{
"response": "Code executed. Result:\nExecution Result: 15\n\nI've remembered this."
}Example 3: Ask for Last Code (Demonstrates Persistent State)
curl -X POST http://localhost:8787/agent \
-H "Content-Type: application/json" \
-d '{"prompt": "What was the last code I ran?"}'Expected Output (approximate):
{
"response": "The last code I executed was:\n```js\nconst a = 5;\nconst b = 10;\na + b;\n```"
}Notice how the agent remembers the code from the previous interaction! This is persistent state in action.
Example 4: Ask for History
curl -X POST http://localhost:8787/agent \
-H "Content-Type: application/json" \
-d '{"prompt": "Show me the execution history"}'Expected Output (approximate):
{
"response": "Here's your execution history:\n\nCode: const a = 5;\nconst b = 10;\na + b;\nResult: Execution Result: 15"
}Mini-Challenge: Enhance Your Code Interpreter
Your agent can execute code and remember the last snippet. Can you make it a bit smarter?
Challenge: Modify the codeInterpreterAgent to allow the user to “define a variable” by providing a name and a value (e.g., “define variable myVar as 42”). The agent should store this variable in its persistent state and then, if a subsequent code execution uses myVar, the agent should prepend the variable definition to the code it sends to the executeJsCode tool.
Hint:
- Add a new field to your
AgentStateinterface, perhapsdefinedVariables: Record<string, string>. - Add logic in the
runmethod to parse “define variable” prompts and updatedefinedVariablesusingupdateState. - Before calling
tools.executeJsCode, check ifcurrentState.definedVariableshas any entries. If so, construct a string ofconst [variableName] = [variableValue];for each and prepend it to thecodeToExecutestring.
What to observe/learn: This challenge will solidify your understanding of how agents can manage complex internal state and dynamically modify their tool calls based on that state, moving towards more intelligent, context-aware behavior.
Common Pitfalls & Troubleshooting
Building agents with sandboxed execution and persistent state introduces new complexities.
- Sandbox Security:
- Pitfall: Assuming local execution of untrusted code is safe. It is never safe.
- Troubleshooting: Always design your
executeCodetool to communicate with a truly isolated and secure external sandbox service. This service should have strict resource limits, network egress controls, and execute code with minimal privileges. Ensure that the communication channel between your Flue agent and the sandbox is also secure, typically via authenticated API calls.
- State Management Overload:
- Pitfall: Storing too much data in the persistent state, or storing complex objects that are expensive to serialize/deserialize. This can lead to increased latency and storage costs.
- Troubleshooting: Be mindful of what you persist. Only store information that is absolutely necessary for the agent to maintain context across sessions. Consider using simpler data structures (strings, numbers, simple objects) and optimize for efficient storage and retrieval, especially in serverless environments like Cloudflare Workers where KV stores often have size limits per entry (e.g., 10MB per value in Cloudflare KV).
- Resource Limits in Sandboxed Environments:
- Pitfall: Your agent might try to execute long-running code or consume excessive memory within a serverless sandbox (e.g., Cloudflare Workers). These environments have strict CPU time (e.g., 50ms for CPU time in Cloudflare Workers) and memory limits.
- Troubleshooting: Design your sandbox service to handle and enforce these limits gracefully. If an agent’s task is inherently long-running (e.g., complex data processing, prolonged computation), consider offloading it to a dedicated background job system rather than a short-lived serverless function. Provide clear error messages back to the agent if limits are exceeded, allowing the agent to inform the user or try an alternative approach.
Summary
In this chapter, we took a significant leap into building advanced Flue agents. We explored:
- Sandboxed Execution: The critical need for isolated environments when agents need to execute code or interact with system resources, emphasizing Flue’s role in orchestrating these interactions through specialized tools. We learned why directly executing untrusted code is dangerous and why external sandbox services are essential for production.
- Persistent State: How agents can maintain long-term memory and context across interactions using Flue’s
stateandupdateStatemechanisms, enabling complex, multi-turn workflows and long-running tasks. - Practical Implementation: We walked through creating a simple code interpreter agent, demonstrating how to define a tool for (simulated) sandboxed execution and how to leverage persistent state to remember past actions and maintain conversational context.
By understanding and implementing these concepts, you’re now equipped to design and build more sophisticated and reliable AI agents using the Flue framework. These agents can go beyond simple conversational interfaces to become powerful, autonomous workers capable of complex, stateful tasks.
Next, we’ll delve into deployment strategies for these robust agents, focusing on production-minded considerations like reliability, scalability, and security when deploying to platforms like Cloudflare Workers.
References
- Flue — The Agent Harness Framework
- withastro/flue: The sandbox agent framework - GitHub
- flue/docs/deploy-cloudflare.md at main · withastro/flue - GitHub
- Cloudflare Workers Documentation
- Cloudflare Workers KV Documentation
This page is AI-assisted and reviewed. It references official documentation and recognized resources where relevant.