Skip to content

Data & Communication

ERA Agent provides powerful mechanisms for passing data to your code, storing state between executions, and communicating with external services. This guide covers everything you need to know about data handling.

FeaturePurposeStored WherePersistence
Session DataRuntime state, application dataDurable ObjectsSurvives runs
Session MetadataLabels, tags, configurationDurable ObjectsSurvives runs
Environment VariablesRuntime configurationPer-executionTemporary
FilesCode, assets, generated contentR2 StoragePersistent sessions only

Session data is the primary way to persist application state between code executions. Data is automatically injected before each run and extracted after.

  1. Before execution: Data is written to .session_data.json in the VM
  2. During execution: Your code reads/writes this file
  3. After execution: Updated data is saved back to the session
Terminal window
curl -X POST https://anewera.dev/api/sessions \
-H "Content-Type: application/json" \
-d '{
"language": "python",
"session_id": "data-demo",
"persistent": true,
"data": {
"counter": 0,
"users": ["alice", "bob"],
"config": {
"theme": "dark",
"max_items": 100
}
}
}'

Python Example:

import json
# Read session data
with open('.session_data.json', 'r') as f:
data = json.load(f)
counter = data.get('counter', 0)
users = data.get('users', [])
print(f"Counter: {counter}")
print(f"Users: {users}")

JavaScript/Node.js Example:

const fs = require('fs');
// Read session data
const data = JSON.parse(fs.readFileSync('.session_data.json', 'utf8'));
console.log('Counter:', data.counter);
console.log('Users:', data.users);

TypeScript Example:

import * as fs from 'fs';
interface SessionData {
counter: number;
users: string[];
config: {
theme: string;
max_items: number;
};
}
// Read session data with types
const data: SessionData = JSON.parse(
fs.readFileSync('.session_data.json', 'utf8')
);
console.log('Counter:', data.counter);
console.log('Theme:', data.config.theme);

Any changes you make to .session_data.json are automatically saved after execution:

Python Example:

import json
# Read current data
with open('.session_data.json', 'r') as f:
data = json.load(f)
# Update data
data['counter'] = data.get('counter', 0) + 1
data['users'].append('charlie')
data['last_run'] = '2024-01-15'
# Write back
with open('.session_data.json', 'w') as f:
json.dump(data, f, indent=2)
print(f"Counter incremented to: {data['counter']}")

JavaScript Example:

const fs = require('fs');
// Read, update, and write
const data = JSON.parse(fs.readFileSync('.session_data.json', 'utf8'));
data.counter = (data.counter || 0) + 1;
data.users.push('charlie');
data.last_run = new Date().toISOString();
fs.writeFileSync('.session_data.json', JSON.stringify(data, null, 2));
console.log(`Counter incremented to: ${data.counter}`);
Terminal window
# Run code
curl -X POST https://anewera.dev/api/sessions/data-demo/run \
-H "Content-Type: application/json" \
-d '{
"code": "import json\nwith open(\".session_data.json\", \"r\") as f: data=json.load(f)\ndata[\"counter\"]+=1\nwith open(\".session_data.json\", \"w\") as f: json.dump(data, f)"
}' | jq '.data'

Response includes updated data:

{
"exit_code": 0,
"stdout": "",
"stderr": "",
"session_id": "data-demo",
"data": {
"counter": 1,
"users": ["alice", "bob", "charlie"],
"config": {
"theme": "dark",
"max_items": 100
},
"last_run": "2024-01-15"
}
}

1. Create session with initial state:

Terminal window
SESSION_ID="counter-$(date +%s)"
curl -X POST https://anewera.dev/api/sessions \
-H "Content-Type: application/json" \
-d '{
"language": "python",
"session_id": "'$SESSION_ID'",
"persistent": true,
"data": {
"count": 0,
"history": []
}
}'

2. Increment counter:

Terminal window
curl -X POST https://anewera.dev/api/sessions/$SESSION_ID/run \
-H "Content-Type: application/json" \
-d '{
"code": "import json; from datetime import datetime; data = json.load(open(\".session_data.json\")); data[\"count\"] += 1; data[\"history\"].append(datetime.now().isoformat()); json.dump(data, open(\".session_data.json\", \"w\")); print(f\"Count: {data[\"count\"]}\")"
}'

Output:

{
"exit_code": 0,
"stdout": "Count: 1\n",
"data": {
"count": 1,
"history": ["2024-01-15T10:30:00"]
}
}

3. Run again - state persists:

Terminal window
# Run the same code again
curl -X POST https://anewera.dev/api/sessions/$SESSION_ID/run \
-H "Content-Type: application/json" \
-d '{
"code": "import json; from datetime import datetime; data = json.load(open(\".session_data.json\")); data[\"count\"] += 1; data[\"history\"].append(datetime.now().isoformat()); json.dump(data, open(\".session_data.json\", \"w\")); print(f\"Count: {data[\"count\"]}\")"
}'

Output:

{
"exit_code": 0,
"stdout": "Count: 2\n",
"data": {
"count": 2,
"history": ["2024-01-15T10:30:00", "2024-01-15T10:30:15"]
}
}

Pass runtime configuration without modifying session data.

Terminal window
curl -X POST https://anewera.dev/api/sessions/my-session/run \
-H "Content-Type: application/json" \
-d '{
"code": "import os; print(f\"API Key: {os.environ.get(\"API_KEY\")}\"); print(f\"Debug: {os.environ.get(\"DEBUG\")}\")",
"env": {
"API_KEY": "sk-test-12345",
"DEBUG": "true",
"MAX_RETRIES": "3"
}
}'

ERA Agent automatically provides these environment variables:

VariableDescriptionExample
ERA_SESSIONAlways “true”"true"
ERA_SESSION_IDCurrent session ID"my-session"
ERA_LANGUAGESession language"python"
ERA_BASE_URLAPI base URL"https://anewera.dev"
ERA_PROXY_URLProxy URL for this session"https://anewera.dev/proxy/my-session"

Example using ERA variables:

import os
session_id = os.environ.get('ERA_SESSION_ID')
language = os.environ.get('ERA_LANGUAGE')
base_url = os.environ.get('ERA_BASE_URL')
print(f"Running in session: {session_id}")
print(f"Language: {language}")
print(f"API: {base_url}")

Terminal window
curl -X POST https://anewera.dev/api/sessions \
-H "Content-Type: application/json" \
-d '{
"language": "python",
"session_id": "web-scraper",
"persistent": true,
"allowInternetAccess": true,
"setup": {
"pip": {
"requirements": "requests"
}
}
}'
import requests
import json
# GET request
response = requests.get('https://api.github.com/users/github')
print(f"Status: {response.status_code}")
print(f"User: {response.json()['name']}")
# POST request
data = {'title': 'Test', 'body': 'Content'}
response = requests.post('https://jsonplaceholder.typicode.com/posts', json=data)
print(f"Created: {response.json()['id']}")
# Save to session data
with open('.session_data.json', 'r') as f:
session_data = json.load(f)
session_data['last_fetch'] = response.json()
with open('.session_data.json', 'w') as f:
json.dump(session_data, f)
const axios = require('axios');
const fs = require('fs');
async function fetchData() {
// GET request
const response = await axios.get('https://api.github.com/users/github');
console.log('User:', response.data.name);
// POST request
const post = await axios.post('https://jsonplaceholder.typicode.com/posts', {
title: 'Test',
body: 'Content'
});
console.log('Created:', post.data.id);
// Save to session data
const data = JSON.parse(fs.readFileSync('.session_data.json', 'utf8'));
data.last_fetch = response.data;
fs.writeFileSync('.session_data.json', JSON.stringify(data, null, 2));
}
fetchData().catch(console.error);
import axios from 'axios';
import * as fs from 'fs';
interface GitHubUser {
name: string;
login: string;
public_repos: number;
}
async function fetchGitHubUser(username: string): Promise<void> {
const response = await axios.get<GitHubUser>(
`https://api.github.com/users/${username}`
);
console.log('User:', response.data.name);
console.log('Repos:', response.data.public_repos);
// Save to session data
const data = JSON.parse(fs.readFileSync('.session_data.json', 'utf8'));
data.github_data = response.data;
data.fetched_at = new Date().toISOString();
fs.writeFileSync('.session_data.json', JSON.stringify(data, null, 2));
}
fetchGitHubUser('github').catch(console.error);

Metadata is for labels, tags, and configuration - not runtime state.

MetadataData
Static configurationDynamic application state
Labels and tagsCounters, user data
Rarely changesChanges every run
Not injected into VMInjected as .session_data.json
API-only accessFile-based access
Terminal window
# Create with metadata
curl -X POST https://anewera.dev/api/sessions \
-H "Content-Type: application/json" \
-d '{
"language": "python",
"session_id": "tagged-session",
"persistent": true,
"metadata": {
"environment": "production",
"team": "data-science",
"project": "ml-pipeline",
"cost_center": "eng-ml"
}
}'
# Retrieve metadata
curl https://anewera.dev/api/sessions/tagged-session | jq '.metadata'

Response:

{
"environment": "production",
"team": "data-science",
"project": "ml-pipeline",
"cost_center": "eng-ml"
}

Track API call counts and timestamps:

import json
import time
from datetime import datetime
# Load session data
with open('.session_data.json', 'r') as f:
data = json.load(f)
# Initialize if needed
if 'api_calls' not in data:
data['api_calls'] = []
# Check rate limit (10 calls per minute)
now = time.time()
recent_calls = [c for c in data['api_calls'] if now - c < 60]
if len(recent_calls) >= 10:
print("Rate limit exceeded! Try again later.")
exit(1)
# Make API call
import requests
response = requests.get('https://api.example.com/data')
# Record call
data['api_calls'].append(now)
# Keep only last 100 calls
data['api_calls'] = data['api_calls'][-100:]
# Save
with open('.session_data.json', 'w') as f:
json.dump(data, f)
print(f"API call successful. Calls in last minute: {len(recent_calls) + 1}")
import json
import time
import requests
with open('.session_data.json', 'r') as f:
data = json.load(f)
if 'cache' not in data:
data['cache'] = {}
def get_cached_or_fetch(url, ttl=300):
now = time.time()
# Check cache
if url in data['cache']:
cached = data['cache'][url]
if now - cached['timestamp'] < ttl:
print(f"Cache hit for {url}")
return cached['data']
# Cache miss - fetch
print(f"Cache miss for {url}")
response = requests.get(url)
result = response.json()
# Update cache
data['cache'][url] = {
'data': result,
'timestamp': now
}
return result
# Use it
result = get_cached_or_fetch('https://api.github.com/users/github', ttl=600)
print(f"User: {result['name']}")
# Save updated cache
with open('.session_data.json', 'w') as f:
json.dump(data, f)
const fs = require('fs');
// Read state
const data = JSON.parse(fs.readFileSync('.session_data.json', 'utf8'));
// Initialize state machine
if (!data.state) {
data.state = 'idle';
data.history = [];
}
// State transitions
const transitions = {
'idle': ['processing'],
'processing': ['completed', 'failed'],
'completed': ['idle'],
'failed': ['idle', 'processing']
};
function transition(newState) {
if (!transitions[data.state].includes(newState)) {
throw new Error(`Invalid transition: ${data.state} -> ${newState}`);
}
data.history.push({
from: data.state,
to: newState,
timestamp: new Date().toISOString()
});
data.state = newState;
}
// Process event
const event = process.env.EVENT || 'start';
if (event === 'start' && data.state === 'idle') {
transition('processing');
console.log('Started processing');
} else if (event === 'complete' && data.state === 'processing') {
transition('completed');
console.log('Processing completed');
} else if (event === 'fail' && data.state === 'processing') {
transition('failed');
console.log('Processing failed');
} else if (event === 'reset') {
transition('idle');
console.log('Reset to idle');
}
// Save state
fs.writeFileSync('.session_data.json', JSON.stringify(data, null, 2));
console.log(`Current state: ${data.state}`);

Usage:

Terminal window
# Start processing
curl -X POST https://anewera.dev/api/sessions/state-machine/run \
-H "Content-Type: application/json" \
-d '{"code": "...", "env": {"EVENT": "start"}}'
# Complete
curl -X POST https://anewera.dev/api/sessions/state-machine/run \
-H "Content-Type: application/json" \
-d '{"code": "...", "env": {"EVENT": "complete"}}'

Terminal window
# ✅ Good: Runtime state in data
"data": {
"counter": 42,
"cache": {...},
"user_sessions": [...]
}
# ✅ Good: Static config in metadata
"metadata": {
"team": "eng",
"environment": "prod",
"version": "1.2.3"
}
# ❌ Bad: Large files in data (use R2 files instead)
"data": {
"large_dataset": "..." // Too large!
}
import json
with open('.session_data.json', 'r') as f:
data = json.load(f)
# Always provide defaults
counter = data.get('counter', 0)
users = data.get('users', [])
# Validate types
if not isinstance(counter, int):
counter = 0
if not isinstance(users, list):
users = []
const fs = require('fs');
let data = {};
try {
data = JSON.parse(fs.readFileSync('.session_data.json', 'utf8'));
} catch (error) {
console.log('No session data, using defaults');
data = {
counter: 0,
initialized: new Date().toISOString()
};
}
// ... use data
# ✅ Good: Bounded data
data['recent_logs'] = data.get('recent_logs', [])[-100:]
# ❌ Bad: Unbounded growth
data['logs'].append(new_log) # Will grow forever!
interface SessionData {
counter: number;
users: string[];
config: {
max_items: number;
theme: string;
};
last_run?: string;
}
// Type-safe access
const data: SessionData = JSON.parse(
fs.readFileSync('.session_data.json', 'utf8')
);

Problem: Data resets after each run

Solution: Ensure you’re writing to .session_data.json:

# ❌ Wrong: Setting local variable
data['counter'] = 42
# ✅ Correct: Writing to file
with open('.session_data.json', 'w') as f:
json.dump(data, f)

Problem: JSON.parse() or json.load() fails

Solution: Handle empty or corrupt data:

let data = {};
try {
const content = fs.readFileSync('.session_data.json', 'utf8');
data = JSON.parse(content);
} catch (error) {
console.warn('Failed to parse session data, using defaults');
data = {initialized: new Date().toISOString()};
}

Problem: Network requests timeout or fail

Solution: Ensure internet access is enabled:

Terminal window
# Check session settings
curl https://anewera.dev/api/sessions/my-session | jq '.allowInternetAccess'
# If false, recreate session with allowInternetAccess: true