Gerenciamento de estado entre etapas de execução
Persista variáveis, quadros de dados e bibliotecas importadas entre várias etapas de execução de código para que o agente possa aproveitar resultados anteriores sem repetir cálculos já realizados.
Gerenciamento de estado entre etapas de execução é uma aula grátis de AI Engineering Academy no CoddyKit. Esta é a aula 3 de 4. Você pode ler a aula completa abaixo gratuitamente — depois pratica ao vivo no navegador com um editor de código integrado e um tutor de IA 24/7. Faz parte do caminho de aprendizado de AI Engineering Academy, e seu progresso é sincronizado entre a web e o app CoddyKit. O curso de AI Engineering Academy inclui 4 aulas no total.
Partes desta aula ainda não foram traduzidas e aparecem em inglês.
The Statelessness Problem
Each Docker container execution starts with a fresh Python interpreter. Variables defined in iteration 1 do not exist in iteration 2. This statelessness forces the agent to re-compute or re-load everything from scratch on every execution step — unless you implement an explicit state management strategy to persist and restore state across iterations. Without it, multi-step analyses are impossible.
# Iteration 1 - works fine
df = pd.read_csv('data.csv') # df is in memory
df_cleaned = df.dropna()
print('Rows after cleaning:', len(df_cleaned))
# Iteration 2 - NEW container, df_cleaned is GONE
result = df_cleaned.groupby('category').sum() # NameError: df_cleaned is not defined
print(result) # This will fail!File-Based State Persistence
The simplest and most portable approach is to save state to files. At the end of each code block, the agent saves DataFrames, dictionaries, or other objects to files in the workspace directory. The next iteration loads them back. Parquet is ideal for DataFrames, JSON for dictionaries, and pickle for arbitrary Python objects (though pickle from untrusted code is a security risk).
import pandas as pd
import json
from pathlib import Path
WORKSPACE = Path('/workspace')
# Iteration 1: process and SAVE
df = pd.read_csv(WORKSPACE / 'raw_data.csv')
df_cleaned = df.dropna().reset_index(drop=True)
df_cleaned.to_parquet(WORKSPACE / 'cleaned.parquet') # save for next iteration
stats = {'rows': len(df_cleaned), 'columns': list(df_cleaned.columns)}
with open(WORKSPACE / 'stats.json', 'w') as f:
json.dump(stats, f)
print('Saved cleaned data:', len(df_cleaned), 'rows')
# Iteration 2: LOAD and continue
df_cleaned = pd.read_parquet(WORKSPACE / 'cleaned.parquet') # restore state
with open(WORKSPACE / 'stats.json') as f:
stats = json.load(f)
result = df_cleaned.groupby('category')['value'].sum()
print(result)Teaching the Agent File-Based State
You cannot just implement file-based state in your infrastructure — the LLM must also know about the convention and consistently use it. Include explicit instructions in the system prompt: after computing a result that will be needed later, always save it with a descriptive filename, and at the start of each iteration, always load the files that were produced in previous steps before proceeding.
STATE_MANAGEMENT_INSTRUCTIONS = '''
State persistence rules:
- You have a persistent workspace at /workspace/
- After computing any result you will need later, SAVE it to /workspace/
- DataFrames: use .to_parquet('/workspace/name.parquet')
- Dicts/lists: use json.dump to /workspace/name.json
- Text: write to /workspace/name.txt
- At the start of each code block, LOAD the files you need from previous steps
- Use clear, descriptive filenames like 'cleaned_data.parquet', not 'tmp1.parquet'
- When listing files, use: import os; print(os.listdir('/workspace/'))
'''Kernel-Based State with Persistent Python Processes
An alternative to file-based state is to keep a persistent Python process (like a Jupyter kernel) running between iterations and inject each code block into it with exec(). Variables defined in one iteration remain accessible in the next. This approach is faster and more natural, but requires running a persistent process per agent session rather than ephemeral containers.
import jupyter_client
class PersistentKernel:
def __init__(self):
km, self.kc = jupyter_client.manager.start_new_kernel(kernel_name='python3')
self.kc.wait_for_ready(timeout=30)
print('Kernel started')
def execute(self, code: str, timeout=60) -> tuple[str, str]:
msg_id = self.kc.execute(code)
outputs, errors = [], []
while True:
msg = self.kc.get_iopub_msg(timeout=timeout)
if msg['msg_type'] == 'stream':
if msg['content']['name'] == 'stdout':
outputs.append(msg['content']['text'])
else:
errors.append(msg['content']['text'])
if msg['msg_type'] == 'status' and msg['content']['execution_state'] == 'idle':
break
return ''.join(outputs), ''.join(errors)
def shutdown(self):
self.kc.shutdown()
# Variables persist across execute() calls!
kernel = PersistentKernel()
kernel.execute('x = 42') # x is now defined in kernel
output, _ = kernel.execute('print(x)') # prints 42 - state persists!
kernel.shutdown()Managing State With a State Object
For the agent orchestrator layer (outside the sandbox), maintain an explicit state object that tracks the current status of the agent's work: what files have been created, what has been computed, what step the agent is on, and what the final goal is. Pass a summary of this state object to the LLM in each iteration so it always knows where it is in the overall task.
from dataclasses import dataclass, field
from typing import Any
@dataclass
class ExecutionState:
task: str
iteration: int = 0
workspace_files: list[str] = field(default_factory=list)
computed_values: dict[str, Any] = field(default_factory=dict)
completed_steps: list[str] = field(default_factory=list)
last_output: str = ''
def to_context_summary(self) -> str:
return f'''Current task: {self.task}
Iteration: {self.iteration}
Completed steps: {', '.join(self.completed_steps) or 'None yet'}
Workspace files: {', '.join(self.workspace_files) or 'None yet'}
Key values: {self.computed_values}
Last output: {self.last_output[:500]}'''
def after_execution(self, output: str, step_name: str):
import os
self.workspace_files = os.listdir('/workspace')
self.completed_steps.append(step_name)
self.last_output = output
self.iteration += 1Injecting State Context into Prompts
Each time you call the LLM for the next code block, inject the current state context into the user message. This gives the LLM accurate information about what files are available, what has been computed, and what still needs to be done. Without this context, the LLM may try to re-create files that already exist or skip steps that are already complete.
def build_iteration_prompt(task: str, state: ExecutionState, last_observation: str) -> str:
return f'''ORIGINAL TASK: {task}
CURRENT STATE:
{state.to_context_summary()}
LAST EXECUTION OUTPUT:
{last_observation}
What is the next step? Write Python code to continue.
Remember:
- Load data from workspace files if needed
- Save any results you will need in future steps
- If the task is complete, say TASK COMPLETE and summarize the result'''
# Use in the main loop
for step_num in range(max_iterations):
context = build_iteration_prompt(task, state, last_observation)
response = llm.complete(messages + [{'role': 'user', 'content': context}])
code = extract_code_block(response)
if not code:
break # done
output, err = execute_in_sandbox(code)
state.after_execution(output, step_name=f'step_{step_num}')
last_observation = format_observation(output, err)Handling Large Intermediate Data
Data analysis agents often produce large intermediate datasets that are expensive to reload on every iteration. Apply lazy loading: only load the data you need for the current step. Use columnar formats like Parquet that support efficient column-selective reads. For truly large datasets (100MB+), keep a persistent kernel so the DataFrame lives in memory across iterations instead of being serialized and deserialized each time.
# Good: load only needed columns
df = pd.read_parquet('/workspace/full_data.parquet', columns=['date', 'revenue', 'region'])
# Bad: load everything even if you only need 2 columns
# df = pd.read_parquet('/workspace/full_data.parquet') # loads 50 columns you don't need
# Good: filter early before loading full dataset
df = pd.read_parquet('/workspace/full_data.parquet', filters=[('region', '=', 'EMEA')])
# For very large files, tell the LLM about data shape upfront
df_info = {'shape': (1_000_000, 50), 'size_mb': 850, 'columns': [...]}
# Include df_info in state so LLM plans accordinglyCheckpointing Long-Running Tasks
For tasks that span many iterations, implement checkpointing: periodically save the full agent state (completed steps, workspace files, current progress) to a durable store so the task can be resumed after an interruption. This is especially important for long data analysis jobs that might take 30+ minutes and could be interrupted by network issues, API errors, or server restarts.
import json
import time
def checkpoint_state(state: ExecutionState, checkpoint_id: str, db):
data = {
'task': state.task,
'iteration': state.iteration,
'completed_steps': state.completed_steps,
'workspace_files': state.workspace_files,
'timestamp': time.time()
}
db.execute(
'INSERT INTO agent_checkpoints (id, state) VALUES (?, ?) ON CONFLICT(id) DO UPDATE SET state=excluded.state',
(checkpoint_id, json.dumps(data))
)
print(f'Checkpoint saved at iteration {state.iteration}')
def resume_from_checkpoint(checkpoint_id: str, db) -> ExecutionState | None:
row = db.execute('SELECT state FROM agent_checkpoints WHERE id = ?', (checkpoint_id,)).fetchone()
if not row:
return None
data = json.loads(row[0])
state = ExecutionState(task=data['task'])
state.iteration = data['iteration']
state.completed_steps = data['completed_steps']
print(f'Resumed from iteration {state.iteration}')
return stateCleaning Up State After Completion
Agent workspaces accumulate files and can grow large over time. Always implement a cleanup phase that runs when a task completes or fails: delete intermediate files (cleaned.parquet, tmp_output.csv) and keep only the final output files the user cares about. For cloud storage, set lifecycle policies that automatically delete workspace files after a retention period.
import shutil
from pathlib import Path
INTERMEDIATE_PATTERNS = ['*.parquet', 'tmp_*.csv', 'step_*.json', 'debug_*.txt']
FINAL_OUTPUT_PATTERNS = ['report.pdf', 'final_*.csv', 'summary.json']
def cleanup_workspace(workspace_dir: str, keep_final=True):
workspace = Path(workspace_dir)
final_outputs = []
if keep_final:
for pattern in FINAL_OUTPUT_PATTERNS:
final_outputs.extend(workspace.glob(pattern))
# Move final outputs to output directory
output_dir = workspace.parent / 'outputs'
output_dir.mkdir(exist_ok=True)
for f in final_outputs:
shutil.move(str(f), str(output_dir / f.name))
# Delete the workspace
shutil.rmtree(workspace_dir)
print(f'Workspace cleaned up. Kept {len(final_outputs)} output files.')
return [str(f) for f in final_outputs]State Versioning for Debugging
When a code agent produces wrong results, you need to trace back through its execution history to find where it went wrong. Implement state versioning by saving a snapshot of the workspace after every iteration. This lets you replay the agent's execution, inspect intermediate states, and identify the exact iteration where the error occurred. Store only the diff (changed files) to save space.
Context Window vs External State
There is a fundamental tension in code agent design between putting state in the LLM context window (immediate but limited) and in external storage (unlimited but requires explicit management). The optimal strategy is: keep the current step's data in context, keep large intermediate results in files, keep the task goal and high-level plan in context, and keep raw data always on disk. The LLM manages the task; the filesystem manages the data.
Quick Check
Test your understanding of state management across code execution steps from this lesson.
Lesson Recap
In this lesson you learned: file-based state persistence using Parquet and JSON is the most portable way to share state between ephemeral container executions, persistent kernels eliminate the reload overhead by keeping a live Python process across iterations, and state context injection gives the LLM accurate knowledge of available files and completed steps at each iteration. Next up we build a full data analysis agent.
Perguntas Frequentes
A aula “Gerenciamento de estado entre etapas de execução” é grátis?
Sim — o texto completo de “Gerenciamento de estado entre etapas de execução” é grátis para ler aqui na web. Para praticá-la interativamente (um editor de código integrado e um tutor de IA 24/7) e desbloquear o restante do curso de AI Engineering Academy, atualize para CoddyKit PRO. O curso de AI Engineering Academy inclui 4 aulas no total.
O que vou aprender em “Gerenciamento de estado entre etapas de execução”?
Persista variáveis, quadros de dados e bibliotecas importadas entre várias etapas de execução de código para que o agente possa aproveitar resultados anteriores sem repetir cálculos já realizados. Você pratica AI Engineering Academy com código prático que executa diretamente no navegador, e um tutor de IA 24/7 responde suas dúvidas enquanto trabalha na aula.
Preciso ter experiência prévia para começar AI Engineering Academy?
Nenhuma experiência prévia é necessária. AI Engineering Academy no CoddyKit é estruturado para alunos iniciantes até avançados, então você pode começar aqui ou desde o início e aprender no seu ritmo. Esta é a aula 3 de 4.
Quanto tempo leva a aula “Gerenciamento de estado entre etapas de execução”?
A maioria das aulas CoddyKit leva cerca de 5–10 minutos. Cada uma é compacta e interativa, então você faz progresso constante e retoma exatamente de onde parou entre web e app.
Posso escrever e executar código nesta aula de AI Engineering Academy?
Sim. Cada aula de AI Engineering Academy inclui um editor de código integrado, então você escreve e executa código real direto no navegador e recebe feedback de IA instantaneamente — nenhuma configuração local necessária.
Todas as aulas deste curso
- O loop de execução de código
- Isolamento com Docker e RestrictedPython
- Gerenciamento de estado entre etapas de execução
- Criando um agente de análise de dados