How to Write Python Objects for AskRobots

By dan • February 14, 2026 • 4 min read

# How to Write Python Objects for AskRobots

This guide explains how to write Python objects that run on the AskRobots object-primitive system. Objects are small, self-contained Python modules that execute on-demand without deployment.

## Object Anatomy

Every object is a Python file with:
- Module-level docstring (description)
- Variable declarations for injected dependencies
- One or more HTTP method handlers (GET, POST, PUT, DELETE)

```python
"""
My Object - Brief description of what it does.
"""

# Injected at runtime (declare as None)
_state_manager = None
_runtime = None
_logger = None

def GET(request):
"""Handle GET requests"""
return {"message": "Hello from my object"}

def POST(request):
"""Handle POST requests with data"""
data = request.get('data', {})
return {"received": data}
```

## Injected Variables

These are automatically injected before execution:

| Variable | Type | Purpose |
|----------|------|---------|
| `_state_manager` | StateManager | Persistent key-value storage |
| `_runtime` | Runtime | Load and call other objects |
| `_logger` | Logger | Logging (info, warning, error) |
| `_user_id` | int | Current user's ID |
| `_object_id` | str | This object's ID |

Always declare them as `None` at module level:
```python
_state_manager = None
_runtime = None
```

## State Management

Use `_state_manager` for persistent storage:

```python
def POST(request):
# Get value (with default)
count = int(_state_manager.get('counter', '0'))

# Set value (always strings)
count += 1
_state_manager.set('counter', str(count))

# Get all state as dict
all_state = _state_manager.all()

# Reload from disk (for cross-object consistency)
_state_manager.reload()

return {"count": count}
```

State is stored in TSV files at `data/state/{object_id}/state.tsv`.

## Loading Other Objects

Use `_runtime.load_object()` for object composition:

```python
def GET(request):
# Load another object
deals_obj = _runtime.load_object('u_1_deals')

# Reload its state to get fresh data
deals_obj.state_manager.reload()

# Read its state
deals_json = deals_obj.state_manager.get('deals', '[]')
deals = json.loads(deals_json)

return {"deal_count": len(deals)}
```

Object IDs follow the pattern:
- `u_{user_id}_{name}` - User objects (e.g., `u_1_deals`)
- `basics_{name}` - System/example objects

## Allowed Imports

The sandbox allows these imports:

```python
import json
import re
import math
import datetime
import hashlib
import base64
import urllib.parse
import html
import random
import string
import collections
import itertools
import functools
import decimal
import uuid
import time
```

**Blocked imports** (security): `os`, `sys`, `subprocess`, `socket`, `requests`, `traceback`, `importlib`, `builtins`, `eval`, `exec`, `compile`.

## View Objects

View objects return HTML for browser display. The route `/v/{view_name}/` renders them:

```python
"""
My View - Renders HTML for the browser.
"""

def POST(request):
html = '''
<div class="retro-panel">
<h2>Welcome</h2>
<p>This is rendered from a Python object!</p>
</div>
'''
return {
'html': html,
'title': 'My Page Title'
}
```

Views use POST because they often need request context. Access via:
- Browser: `https://askrobots.com/v/my_view/`
- The system injects this into your base template

## Data Objects

Data objects store and manage data, called by views or other objects:

```python
"""
Deals Data - Stores deal records.
"""
import json

_state_manager = None

def GET(request):
"""Return all deals"""
deals = json.loads(_state_manager.get('deals', '[]'))
return {"deals": deals, "count": len(deals)}

def POST(request):
"""Add a new deal"""
data = request.get('data', {})
deals = json.loads(_state_manager.get('deals', '[]'))

new_deal = {
"id": str(len(deals) + 1),
"name": data.get('name', 'Untitled'),
"value": data.get('value', 0),
"stage": data.get('stage', 'lead')
}
deals.append(new_deal)

_state_manager.set('deals', json.dumps(deals))
return {"created": new_deal}
```

## Error Handling

Always wrap external calls in try/except:

```python
def GET(request):
try:
other = _runtime.load_object('u_1_data')
data = other.state_manager.get('items', '[]')
return {"items": json.loads(data)}
except Exception as e:
return {"error": str(e), "items": []}
```

## Request Object

The `request` parameter contains:

```python
{
"method": "POST", # HTTP method
"data": {...}, # POST/PUT body (parsed JSON)
"query_params": {...}, # URL query parameters
"user_id": 1, # Authenticated user ID
"headers": {...} # HTTP headers (limited)
}
```

## Best Practices

1. **Keep objects small** - Single responsibility, ~50-100 lines max
2. **Use composition** - Load other objects rather than duplicating code
3. **Always reload state** - Call `state_manager.reload()` when reading cross-object data
4. **Return JSON** - Except for views which return `{'html': ..., 'title': ...}`
5. **Handle errors gracefully** - Return error info rather than crashing
6. **Document with docstrings** - First line shows in object list

## Creating Objects

Via MCP (AI agents):
```
mcp__askrobots__create_object(
name="my_data",
code="...",
description="Stores my data"
)
```

Via Web UI:
- Navigate to Objects → Create Object
- Enter name (lowercase, underscores OK)
- Write code
- Save

## Testing Objects

Via MCP:
```
mcp__askrobots__execute_object(object_id="u_1_my_data", method="GET")
```

Via curl:
```bash
curl -X POST "https://askrobots.com/objects/u_1_my_data/execute/" \
-H "Authorization: Token YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{"method": "GET"}'
```

## Example: Counter Object

Complete working example:

```python
"""
Counter - Simple incrementing counter with reset.
"""
_state_manager = None

def GET(request):
"""Get current count"""
count = int(_state_manager.get('count', '0'))
return {"count": count}

def POST(request):
"""Increment counter"""
count = int(_state_manager.get('count', '0'))
count += 1
_state_manager.set('count', str(count))
return {"count": count, "action": "incremented"}

def DELETE(request):
"""Reset counter to zero"""
_state_manager.set('count', '0')
return {"count": 0, "action": "reset"}
```

---

*This is the 100x architecture: write Python, see it live instantly, no deploy needed.*