Building an AI Agent With TokForge's 270+ API Endpoints
Learn to orchestrate TokForge from Python. Build agents that manage models, run conversations, track usage, and automate AI workflows on your phone.
TokForge is free. Android is live on Google Play; iPhone and iPad are in public beta on TestFlight. No account, and it works with the network off once a model is downloaded.
Reading on a computer? Check which models fit your phone, or see real measured speeds on the phone-speed leaderboard.
What You'll Build
By the end of this guide, you'll have a working Python AI agent that:
- Connects to TokForge running on your phone via HTTP API
- Manages models: list, load, and switch between them
- Creates and orchestrates multi-turn conversations with characters
- Sends prompts and reads responses with telemetry (token count, engine, timing fields)
- Monitors memory facts accumulated during conversations
- Polls generation status to see when a reply is still in progress
- Automates complex multi-character dialogues and workflows
Essentially, you'll treat TokForge on your phone as a remote AI backend, controlled entirely from your laptop via Python.
Prerequisites
- Python 3.7+ with pip installed
- requests library:
pip install requests - TokForge app installed on your phone with API enabled (see the API Walkthrough Guide)
- Network access: Phone and laptop on the same WiFi network, or ADB forwarding configured
- Access token from the app's developer API settings (required for every call except
/healthand/version)
Enable the API in Settings → Connections → Settings Mode: Advanced → Developer tools (Advanced) → Developer API server (Advanced).
The TokForge API Architecture
TokForge exposes its on-device control API as 270+ endpoints on Android. They fall into a handful of areas; here is a sample from each:
| Area | Purpose | Key Endpoints |
|---|---|---|
| Health | Service status and app version | /health, /version |
| Model catalogue | List downloaded models and browse the curated catalogue | /models/downloaded, /models/recommended |
| Models | Load, unload, query models | /models/{id}/load, /models/{id}, /control/unload-all |
| Generation | Check or stop a reply in progress | /control/generation-status, /control/stop-generation |
| Conversation | Multi-turn dialogue management | /conversations/new, /conversations/{id}/send |
| Memory | Character memory & facts | /memory/facts, /memory/search |
| Character | Character cards | /characters, /characters/import |
| Benchmark | Run speed tests and read saved results | /benchmark/run, /benchmark/results |
| Download | Manage model downloads | /models/recommended/{id}/download, /control/download-status, /control/cancel-download |
| Diagnostic | Logs, debugging, status | /debug/logcat, /debug/errors |
| Settings | App configuration | /settings, /settings/{key} |
Authentication: Every endpoint except a few status probes (such as /health and /version) requires the access token from the app, sent as a Bearer token in the Authorization header:
Authorization: Bearer YOUR_API_TOKEN
Request/Response Format: Requests use GET, POST or DELETE, and payloads are JSON. Chat replies also carry telemetry such as token count, engine and timing fields.
Step 1: Build Your API Client Class
Create a reusable TokForgeClient class to abstract HTTP calls. This makes your agent code clean and maintainable.
import requests
import json
from typing import Optional, Dict, Any
class TokForgeClient:
"""HTTP client for TokForge API with automatic Bearer token handling."""
def __init__(self, host: str, port: int = 8088, token: str = ""):
"""
Args:
host: IP or hostname of phone (e.g., "192.168.1.100")
port: TokForge API port (default 8088)
token: Access token from the app's developer API settings
"""
self.base_url = f"http://{host}:{port}"
self.token = token
self.headers = {"Content-Type": "application/json"}
if token:
self.headers["Authorization"] = f"Bearer {token}"
def _request(self, method: str, path: str, data: Optional[Dict] = None) -> Any:
"""Make HTTP request with error handling."""
url = f"{self.base_url}{path}"
try:
if method == "GET":
resp = requests.get(url, headers=self.headers, timeout=30)
elif method == "POST":
resp = requests.post(url, headers=self.headers, json=data or {}, timeout=30)
else:
raise ValueError(f"Unsupported method: {method}")
resp.raise_for_status()
return resp.json()
except requests.exceptions.RequestException as e:
print(f"API Error [{method} {path}]: {e}")
return {"error": str(e)}
def get(self, path: str) -> Any:
"""GET request."""
return self._request("GET", path)
def post(self, path: str, data: Optional[Dict] = None) -> Any:
"""POST request."""
return self._request("POST", path, data)
# High-level convenience methods
def health(self) -> Dict:
"""Check service health."""
return self.get("/health")
def models_downloaded(self) -> Any:
"""List all downloaded models (a JSON array)."""
return self.get("/models/downloaded")
def models_recommended(self) -> Any:
"""List catalogue models this phone can run (a JSON array)."""
return self.get("/models/recommended")
def load_model(self, model_id: int) -> Dict:
"""Load a downloaded model by its numeric id."""
return self.post(f"/models/{model_id}/load", {})
def unload_all(self) -> Dict:
"""Unload all loaded models."""
return self.post("/control/unload-all", {})
def characters(self) -> Any:
"""Get all character cards (a JSON array)."""
return self.get("/characters")
def new_conversation(self, character_id: int) -> Dict:
"""Start a new conversation with a character."""
return self.post("/conversations/new", {"character_id": character_id})
def send_message(self, conv_id: int, message: str) -> Dict:
"""Send a message in a conversation. Returns response with telemetry."""
return self.post(f"/conversations/{conv_id}/send", {"message": message})
def memory_facts(self, character_id: int) -> Any:
"""Get accumulated memory facts for a character (a JSON array)."""
return self.get(f"/memory/facts?character_id={character_id}")
def generation_status(self) -> Dict:
"""Check if generation is in progress."""
return self.get("/control/generation-status")
def settings(self) -> Dict:
"""Get app settings."""
return self.get("/settings")
def benchmark_results(self) -> Any:
"""Get saved benchmark results."""
return self.get("/benchmark/results")
Step 2: Build Your Agent Loop
Now create the core agent logic that orchestrates conversations and handles telemetry.
class TokForgeAgent:
"""AI Agent that orchestrates TokForge for multi-turn dialogue."""
def __init__(self, client: TokForgeClient, model_id: int, character_id: int):
self.client = client
self.model_id = model_id
self.character_id = character_id
self.conversation_id = None
self.turn_count = 0
self.total_tokens = 0
def setup(self) -> bool:
"""Initialize agent: check health, load model, start conversation."""
# 1. Check health
health = self.client.health()
if "error" in health:
print(f"❌ Health check failed: {health['error']}")
return False
print(f"✓ Service is healthy")
# 2. Load model
print(f"Loading model {self.model_id}...")
load_result = self.client.load_model(self.model_id)
if "error" in load_result:
print(f"❌ Failed to load model: {load_result['error']}")
return False
print(f"✓ Model loaded")
# 3. Start conversation
print(f"Starting conversation with character {self.character_id}...")
conv = self.client.new_conversation(self.character_id)
if "error" in conv or "conversation_id" not in conv:
print(f"❌ Failed to create conversation: {conv}")
return False
self.conversation_id = conv["conversation_id"]
print(f"✓ Conversation started: {self.conversation_id}")
return True
def send_prompt(self, prompt: str) -> Optional[str]:
"""Send a prompt and return the response."""
if not self.conversation_id:
print("❌ No active conversation")
return None
self.turn_count += 1
print(f"\n[Turn {self.turn_count}] You: {prompt}")
response_data = self.client.send_message(self.conversation_id, prompt)
if "error" in response_data:
print(f"❌ Error: {response_data['error']}")
return None
# Extract response and telemetry (top-level fields)
response_text = response_data.get("response", "")
token_count = response_data.get("token_count", 0)
backend = response_data.get("backend", "?")
print(f"Agent: {response_text}")
print(f" {token_count} tokens on {backend}")
self.total_tokens += token_count
return response_text
def get_memory(self) -> list:
"""Retrieve accumulated memory facts."""
facts = self.client.memory_facts(self.character_id)
return facts if isinstance(facts, list) else []
def print_stats(self):
"""Print conversation statistics."""
print(f"\n" + "="*50)
print(f"Conversation Summary")
print(f"="*50)
print(f"Turns: {self.turn_count}")
print(f"Total tokens: {self.total_tokens}")
memory = self.get_memory()
print(f"Memory facts: {len(memory)}")
print("="*50)
Step 3: Advanced Patterns
Multi-Character Orchestration
Switch between characters mid-session or run parallel conversations:
class MultiCharacterAgent:
"""Manage multiple character conversations simultaneously."""
def __init__(self, client: TokForgeClient, model_id: int):
self.client = client
self.model_id = model_id
self.conversations = {} # {character_id: conv_id}
def start_with(self, character_id: int) -> int:
"""Start conversation with a character."""
if character_id in self.conversations:
return self.conversations[character_id]
conv = self.client.new_conversation(character_id)
conv_id = conv.get("conversation_id")
if conv_id:
self.conversations[character_id] = conv_id
return conv_id
def send_to(self, character_id: int, message: str) -> Dict:
"""Send message to specific character."""
conv_id = self.conversations.get(character_id)
if not conv_id:
conv_id = self.start_with(character_id)
return self.client.send_message(conv_id, message)
Usage Monitoring Dashboard
Track metrics over time to optimize agent behavior. To compare speeds between phones, use the leaderboard.
import time
class PerformanceMonitor:
"""Track per-turn usage from send responses."""
def __init__(self):
self.metrics = []
def record(self, response: Dict):
"""Record one reply from /conversations/{id}/send."""
self.metrics.append({
"timestamp": time.time(),
"tokens": response.get("token_count", 0),
"elapsed_ms": response.get("elapsed_ms", 0),
"backend": response.get("backend")
})
def report(self):
"""Print usage summary."""
if not self.metrics:
print("No metrics recorded")
return
tokens = [m["tokens"] for m in self.metrics]
times = [m["elapsed_ms"] for m in self.metrics]
engines = sorted({str(m["backend"]) for m in self.metrics})
print(f"\nUsage Report ({len(self.metrics)} replies)")
print(f" Avg tokens per reply: {sum(tokens)/len(tokens):.1f}")
print(f" Avg reply time: {sum(times)/len(times)/1000:.1f} s")
print(f" Engines used: {', '.join(engines)}")
Auto Model Switching
Switch models based on task complexity:
class AdaptiveAgent:
"""Switch models based on task requirements."""
def __init__(self, client: TokForgeClient, fast_model: int, powerful_model: int):
self.client = client
self.fast = fast_model
self.powerful = powerful_model
self.current = None
def choose_model(self, prompt: str) -> int:
"""Select model based on prompt length/complexity."""
# Simple heuristic: complex prompts → powerful model
if len(prompt) > 200 or any(x in prompt.lower() for x in ["analyze", "complex", "detailed"]):
return self.powerful
return self.fast
def send(self, prompt: str) -> Dict:
"""Send with automatic model selection."""
model = self.choose_model(prompt)
if model != self.current:
print(f"Switching to {model}")
self.client.load_model(model)
self.current = model
# ... send prompt ...
Step 4: Full Working Example
Put it all together in a complete, runnable script:
#!/usr/bin/env python3
"""
Complete example: AI Agent controlling TokForge on your phone.
Run: python agent.py --host 192.168.1.100 --token YOUR_TOKEN --model 3 --character 2
"""
import argparse
import sys
def main():
parser = argparse.ArgumentParser(description="TokForge AI Agent")
parser.add_argument("--host", required=True, help="Phone IP (e.g., 192.168.1.100)")
parser.add_argument("--port", type=int, default=8088, help="API port")
parser.add_argument("--token", required=True, help="Access token from the app")
parser.add_argument("--model", type=int, required=True, help="Model id from /models/downloaded")
parser.add_argument("--character", type=int, required=True, help="Character id from /characters")
args = parser.parse_args()
# Initialize client and agent
client = TokForgeClient(args.host, args.port, args.token)
agent = TokForgeAgent(client, args.model, args.character)
# Setup
if not agent.setup():
print("Setup failed")
sys.exit(1)
# Interactive loop
monitor = PerformanceMonitor()
print("\nAgent ready. Type 'quit' to exit, 'stats' for summary.\n")
while True:
try:
user_input = input("You: ").strip()
if user_input.lower() == "quit":
break
elif user_input.lower() == "stats":
agent.print_stats()
monitor.report()
continue
elif not user_input:
continue
# Send prompt and collect telemetry
response = client.send_message(agent.conversation_id, user_input)
if "error" not in response:
print(f"Agent: {response.get('response', '')}")
monitor.record(response)
except KeyboardInterrupt:
print("\nExiting...")
break
except Exception as e:
print(f"Error: {e}")
# Final summary
agent.print_stats()
monitor.report()
if __name__ == "__main__":
main()
Example API Responses
Real field names, shortened. ... marks fields left out, and <number> stands in for values that depend on your phone.
Health Endpoint
Check service status. Works without a token. For the app version, call GET /version.
{
"status": "ok",
"device": { "model": "Pixel 9", ... },
"mnn_loaded": true,
"gguf_loaded": false,
"any_model_loaded": true,
"control_plane": { "uptime_ms": <number>, "request_count": <number>, ... },
...
}
Models Downloaded
List all downloaded models with metadata, as a JSON array.
[
{
"id": 3,
"name": "Qwen3.5 2B",
"size_bytes": <number>,
"model_type": "mnn",
"is_default": true,
"can_load_as_chat": true,
...
},
{
"id": 7,
"name": "Llama 3.1 8B",
"size_bytes": <number>,
"model_type": "mnn",
"is_default": false,
"can_load_as_chat": true,
...
}
]
Send Message (Conversation)
Send a message and get the reply with telemetry. The call returns when the reply is finished.
Request:
{
"message": "What is the capital of France?"
}
Response:
{
"status": "ok",
"conversation_id": 12,
"user_message_id": 341,
"assistant_message_id": 342,
"response": "The capital of France is Paris. It's located in the north-central part of the country and is known for its iconic landmarks like the Eiffel Tower and Notre-Dame Cathedral.",
"token_count": <number>,
"elapsed_ms": <number>,
"backend": "mnn",
"finish_reason": "stop",
...
}
Memory Facts
Retrieve accumulated memory for a character, most important first, as a JSON array.
[
{
"id": 17,
"character_id": 2,
"content": "User is interested in AI and machine learning",
"importance": <number>,
"is_pinned": false,
"created_at": <number>,
...
},
{
"id": 18,
"character_id": 2,
"content": "User prefers detailed technical explanations",
"importance": <number>,
"is_pinned": false,
"created_at": <number>,
...
}
]
Deployment & Best Practices
- Network Security: Use VPN or ADB forwarding if running over untrusted networks. Keep the access token private, and tap "Generate a new token" in the app if it ever leaks.
- Error Handling: The agent examples above include basic error checking. Wrap API calls in try-except and implement exponential backoff for retries.
- Token Management: Monitor total tokens generated; implement rate limiting if needed to avoid overwhelming your phone's resources.
- Model Lifecycle: Unload models when you are done to free memory on the phone. Use
POST /control/unload-allfor cleanup. - Concurrency: TokForge generates one reply at a time. Send turns one after another, and poll
/control/generation-statusto see whether a reply is still in progress. - Monitoring: Collect telemetry (token count, engine, timing fields) from every response. Use this to optimize model selection and prompt design. For speed comparisons between phones, see the leaderboard.
Next Steps
You now have the foundation for building sophisticated AI agents with TokForge. Here are natural extensions:
- API Reference: Explore the endpoints in the API documentation.
- API Walkthrough: Follow the API Walkthrough Guide for hands-on examples with curl and Python.
- Advanced Automation: Build specialized agents for specific tasks (summarization, Q&A, content generation, code review) using multi-character orchestration and model switching.
- Integration: Connect your agent to other services (Slack bots, webhooks, scheduled tasks) to create end-to-end automation pipelines.