Temporal awareness and time-effort estimation for AI agents.
AI agents have no concept of time. Their time intuition comes from LLM training data calibrated to human speed, not agent speed. This causes real problems:
- Bad timeouts: An agent sets a 60-minute timeout on a download that takes 65 minutes. It times out at 98% completion, losing all progress and doubling the total time.
- Wrong decisions: An agent chooses a 23-hour approach over a 9-hour approach because it estimates coding time at human speed — but the coding portion takes minutes, not hours, for an agent.
- No self-awareness: An agent has no idea how long it actually takes to complete tasks, how long it's been running, or when the last user interaction was.
OpenTime gives any AI agent the ability to:
- Track time — wall clock, elapsed time, stopwatches
- Record events — task start/end with automatic correlation IDs for overlapping tasks
- Learn duration estimates — per-agent, per-task-type statistics (mean, median, p95)
- Get timeout recommendations — "Based on your history, set a 45-second timeout for this task"
- Compare approaches — "The 'hard way' actually saves 17 hours given your coding speed"
- Passive tracking — automatically record tool usage durations via hooks (no agent action needed)
Each agent builds its own time-task database over time, learning its actual capabilities rather than relying on human-calibrated estimates.
pip install opentimeWith REST API support:
pip install opentime[rest]OpenTime works with any AI agent through three interfaces:
| Interface | Best For | Requires Agent Awareness? |
|---|---|---|
| MCP Server | Claude Code, Claude Desktop, Cursor, Windsurf, Cline | Yes (agent calls tools) |
| REST API | ChatGPT, Gemini, LangChain, custom agents | Yes (agent calls endpoints) |
| Hooks | Claude Code passive tracking | No (fully automatic) |
For any MCP-compatible client (Claude Code, Claude Desktop, Cursor, Windsurf, etc.):
{
"mcpServers": {
"opentime": {
"command": "opentime-mcp",
"env": {
"OPENTIME_DB_PATH": "~/.opentime/agent.db",
"OPENTIME_AGENT_ID": "my-agent"
}
}
}
}The agent gets access to 21 tools for time tracking, event recording, and duration statistics.
For any agent that can make HTTP calls — ChatGPT custom actions, Gemini function calling, LangChain tools, AutoGPT, CrewAI, or your own agents:
# Start the server
opentime-rest
# Or with custom settings
OPENTIME_DB_PATH=~/.opentime/agent.db OPENTIME_AGENT_ID=my-agent opentime-restThe API runs at http://127.0.0.1:8080 with interactive docs at /docs (Swagger UI).
Example: Record a task and get stats
# Start a task
curl -X POST http://localhost:8080/events/task-start \
-H "Content-Type: application/json" \
-d '{"task_type": "code_generation"}'
# Returns: {"event": {...}, "correlation_id": "abc123..."}
# End the task (pass the correlation_id back)
curl -X POST http://localhost:8080/events/task-end \
-H "Content-Type: application/json" \
-d '{"task_type": "code_generation", "correlation_id": "abc123..."}'
# Get duration statistics
curl http://localhost:8080/stats/durations/code_generation
# Returns: {"summary": {"count": 15, "mean_seconds": 8.2, "median_seconds": 7.5, ...}}
# Get a timeout recommendation
curl http://localhost:8080/stats/recommend-timeout/code_generation
# Returns: {"recommendation": {"recommended_seconds": 18.6, "percentile": 0.95, ...}}For fully automatic time tracking with zero agent involvement:
{
"hooks": {
"PreToolUse": [{
"matcher": "",
"hooks": [{"type": "command", "command": "python -m opentime.hooks.claude_code"}]
}],
"PostToolUse": [{
"matcher": "",
"hooks": [{"type": "command", "command": "python -m opentime.hooks.claude_code"}]
}],
"Stop": [{
"hooks": [{"type": "command", "command": "python -m opentime.hooks.claude_code"}]
}]
}
}Every tool call is automatically timed and recorded. Query the data later:
from opentime.db.connection import open_database
from opentime.core.stats import DurationStats
conn = open_database("~/.opentime/claude-code.db")
stats = DurationStats(conn, "claude-code")
# How long do Bash commands take?
print(stats.summarize("tool:Bash"))
# How long do file edits take?
print(stats.summarize("tool:Edit"))Clock (2 tools)
| Tool | Description |
|---|---|
clock_now |
Current UTC time as ISO 8601 |
clock_elapsed_since |
Seconds elapsed since a timestamp |
Stopwatch (5 tools)
| Tool | Description |
|---|---|
stopwatch_start |
Start a named stopwatch |
stopwatch_read |
Read elapsed time without stopping |
stopwatch_stop |
Stop and get final elapsed time |
stopwatch_list |
List all stopwatches |
stopwatch_delete |
Delete a stopwatch |
Events (6 tools)
| Tool | Description |
|---|---|
event_record |
Record a generic timestamped event |
event_task_start |
Start a task (returns correlation_id) |
event_task_end |
End a task (pass correlation_id to pair) |
event_list |
Query events with filters |
event_get |
Get a single event by ID |
event_active_tasks |
List started-but-not-ended tasks |
Statistics (6 tools)
| Tool | Description |
|---|---|
stats_duration |
Duration stats (mean, median, p95) for a task type |
stats_list_task_types |
List all task types with data |
stats_all |
Stats for all task types |
stats_recommend_timeout |
Recommend a timeout based on historical durations |
stats_check_timeout |
Check if a running task is at risk of timeout |
stats_compare_approaches |
Compare approaches using actual historical speed |
All MCP tools are mirrored as REST endpoints:
| Method | Endpoint | Description |
|---|---|---|
| GET | /health |
Health check |
| GET | /clock/now |
Current time |
| GET | /clock/elapsed?since= |
Elapsed time |
| POST | /stopwatch/{name}/start |
Start stopwatch |
| GET | /stopwatch/{name} |
Read stopwatch |
| POST | /stopwatch/{name}/stop |
Stop stopwatch |
| GET | /stopwatches |
List stopwatches |
| DELETE | /stopwatch/{name} |
Delete stopwatch |
| POST | /events |
Record event |
| POST | /events/task-start |
Start task |
| POST | /events/task-end |
End task |
| GET | /events/active |
Active tasks |
| GET | /events |
List events |
| GET | /events/{id} |
Get event |
| GET | /stats/durations/{task_type} |
Duration stats |
| GET | /stats/task-types |
List task types |
| GET | /stats/durations |
All stats |
| GET | /stats/recommend-timeout/{task_type} |
Timeout recommendation |
| GET | /stats/check-timeout/{task_type} |
Timeout risk check |
| POST | /stats/compare-approaches |
Compare approaches |
OpenTime is configured via environment variables:
| Variable | Default | Description |
|---|---|---|
OPENTIME_DB_PATH |
opentime.db |
Path to the SQLite database file |
OPENTIME_AGENT_ID |
default |
Unique identifier for this agent |
OPENTIME_HOST |
127.0.0.1 |
REST API host |
OPENTIME_PORT |
8080 |
REST API port |
MCP Server / REST API <- thin wrappers, no business logic
|
v
+------------------------+
| ClockService | <- stateless, in-memory stopwatches
| EventTracker |--> db.queries --> SQLite (per-agent)
| DurationStats |--> db.queries --> SQLite (per-agent)
+------------------------+
- Core layer (
opentime.core) — all business logic, fully decoupled from transport - DB layer (
opentime.db) — SQLite schema, connections, migrations, parameterized queries - MCP server (
opentime.mcp_server) — FastMCP tool registrations with lifespan-managed state - REST API (
opentime.rest_api) — FastAPI endpoints mirroring MCP tools - Hooks (
opentime.hooks) — passive tracking integrations (Claude Code)
# Setup
git clone https://github.com/transparentai-tech/OpenTime.git
cd OpenTime
python3 -m venv .venv && .venv/bin/pip install -e ".[all]"
# Tests
.venv/bin/pytest -v # 109 tests
# Lint
.venv/bin/ruff check src/ tests/MIT