Skip to main content

Python client for Detrix - debug-on-demand observability with zero overhead

Project description

Detrix Python Client

Debug-on-demand observability for Python applications with zero overhead when inactive.

Overview

The Detrix Python client enables your application to be observed by the Detrix daemon without:

  • Code modifications - No print statements or logging changes needed
  • Redeployment - Add metrics to running processes
  • Performance overhead - Zero cost when not actively observing

Installation

# Using uv (recommended)
uv add detrix-py

# Using pip
pip install detrix-py

Quick Start

1. Start the Detrix daemon

detrix serve --daemon

2. Initialize the client in your application

import detrix

# Initialize client - starts control plane, stays SLEEPING (zero overhead)
detrix.init(
    name="my-service",
    daemon_url="http://127.0.0.1:8090",
)

# Your application code runs normally...
def process_request(request):
    data = transform(request)
    return data

3. Enable observability when needed

# Wake up - starts debugger, registers with daemon
detrix.wake()

# Now the daemon can set observation points on any line
# No code changes needed!

# When done observing
detrix.sleep()

Try It

Run the end-to-end example that simulates an AI agent: starts a sample app, wakes it, adds metrics, and captures events.

# 1. Start the Detrix server
detrix serve --daemon

# 2. Run the agent simulation (from clients/python/)
uv run python examples/test_wake.py --daemon-port 8090

Other examples in examples/:

Example Description Run
basic_usage.py Init / wake / sleep cycle uv run python examples/basic_usage.py
trade_bot_detrix.py Long-running app with embedded client uv run python examples/trade_bot_detrix.py
test_wake.py Agent simulation (starts app, wakes, observes) uv run python examples/test_wake.py

API Reference

detrix.init(**kwargs)

Initialize the client. Starts the control plane HTTP server.

Parameters:

  • name (str): Connection name (default: "detrix-client-{pid}")
  • control_host (str): Control plane host (default: "127.0.0.1")
  • control_port (int): Control plane port (default: 0 = auto-assign)
  • debug_port (int): debugpy port (default: 0 = auto-assign on wake)
  • daemon_url (str): Detrix daemon URL (default: "http://127.0.0.1:8090")
  • start_state (str): Initial state "sleeping" or "warm" (default: "sleeping")
  • detrix_home (str): Path to Detrix home directory (default: ~/detrix)
  • health_check_timeout (float): Timeout for daemon health checks in seconds (default: 2.0)
  • register_timeout (float): Timeout for connection registration in seconds (default: 5.0)
  • unregister_timeout (float): Timeout for connection unregistration in seconds (default: 2.0)

detrix.status() -> dict

Get current client status.

Returns:

{
    "state": "sleeping" | "warm" | "awake",
    "name": "my-service-12345",
    "control_host": "127.0.0.1",
    "control_port": 9000,
    "debug_port": 5678,  # 0 if never awake
    "debug_port_active": True,  # True if debug port is actually open
    "daemon_url": "http://127.0.0.1:8090",
    "connection_id": "my-service-12345",  # None if not awake
}

detrix.wake(daemon_url: str = None) -> dict

Start debugger and register with daemon.

Parameters:

  • daemon_url (str): Override daemon URL (optional)

Raises:

  • DaemonError: If daemon is not reachable or URL is invalid

Returns:

{"status": "awake", "debug_port": 5678, "connection_id": "my-service-12345"}

detrix.sleep() -> dict

Stop debugger and unregister from daemon.

Returns:

{"status": "sleeping"}

detrix.shutdown()

Stop control server and cleanup. Call init() again to reinitialize.

State Machine

┌──────────┐         wake()          ┌───────┐
│ SLEEPING │ ──────────────────────►│ AWAKE │
└──────────┘                         └───────┘
    ▲                                   │
    │              sleep()               │
    └────────────────────────────────────┘
  • SLEEPING: No debugger loaded, zero overhead
  • AWAKE: debugpy listening, registered with daemon

Control Plane HTTP Endpoints

The client exposes a local HTTP server for management:

Endpoint Method Description
/detrix/health GET Health check
/detrix/status GET Get current status
/detrix/info GET Get process info
/detrix/wake POST Start debugger + register
/detrix/sleep POST Stop debugger + unregister

Environment Variables

Variable Description
DETRIX_NAME Default connection name
DETRIX_CONTROL_HOST Control plane host
DETRIX_CONTROL_PORT Control plane port
DETRIX_DEBUG_PORT debugpy port
DETRIX_DAEMON_URL Daemon URL
DETRIX_TOKEN Authentication token
DETRIX_HOME Detrix home directory
DETRIX_HEALTH_CHECK_TIMEOUT Timeout for daemon health checks (secs)
DETRIX_REGISTER_TIMEOUT Timeout for connection registration (secs)
DETRIX_UNREGISTER_TIMEOUT Timeout for connection unregistration (secs)

Security

Authentication

The control plane HTTP server has a security-first authentication model:

  • Localhost requests (127.0.0.1, ::1, localhost) are always allowed without authentication
  • Remote requests require a valid Bearer token

To enable remote access:

  1. Set DETRIX_TOKEN environment variable, or
  2. Create ~/detrix/auth-token file with the token

If no token is configured, remote requests are denied by default.

Remote Exposure Guidelines

The control plane is designed for localhost access only by default. If you need to expose it remotely:

  1. Always configure authentication - Set DETRIX_TOKEN or create ~/detrix/auth-token
  2. Use a reverse proxy - Place nginx, HAProxy, or similar in front for:
    • TLS termination (HTTPS)
    • Rate limiting
    • Access logging
    • IP allowlisting
  3. Restrict network access - Use firewall rules to limit which hosts can connect
  4. Protect the token file - Ensure ~/detrix/auth-token has restrictive permissions (chmod 600)

Example nginx configuration:

location /detrix/ {
    # Rate limiting
    limit_req zone=detrix burst=10 nodelay;

    # Proxy to control plane
    proxy_pass http://127.0.0.1:9000;
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
}

Note: The control plane exposes debugging capabilities. Unauthorized access could allow an attacker to inspect process state. Always treat it as a sensitive endpoint.

Error Handling

The client provides a hierarchy of exception types:

from detrix import DetrixError, ConfigError, DaemonError, DebuggerError

try:
    detrix.wake()
except DaemonError as e:
    print(f"Cannot reach daemon: {e}")
except DebuggerError as e:
    print(f"Debugger error: {e}")
except DetrixError as e:
    print(f"General error: {e}")
  • DetrixError: Base class for all Detrix errors
  • ConfigError: Configuration/initialization errors
  • DaemonError: Communication with daemon failed
  • DebuggerError: debugpy-related errors
  • ControlPlaneError: Control plane server errors

Note: DaemonConnectionError is kept as an alias for DaemonError for backward compatibility.

Known Limitations

1. debugpy Port Remains Open After Sleep

Issue: After calling sleep(), the debug port remains open until the process exits.

Cause: This is a limitation of debugpy itself - it does not support stopping its listener once started. See debugpy#895.

Impact:

  • The debug_port_active field in status remains True after sleep
  • Calling wake() after sleep() reuses the same port
  • The port is only freed when the process terminates

Workaround: This is expected behavior. If you need to fully release the port, you must restart the process.

2. Port Allocation Race Condition (Fixed)

Previous issue: Another process could claim a port between detection and binding.

Resolution: The client now passes port=0 directly to debugpy.listen(), which handles port allocation atomically at bind time. There is no window for another process to claim the port.

Note: If you explicitly specify a port via debug_port parameter or DETRIX_DEBUG_PORT, the standard TOCTOU limitation applies (another process could theoretically bind to that port first). Use port=0 for guaranteed atomic allocation.

Architecture

For detailed architecture documentation, see ARCHITECTURE.md.

Development

# Install dev dependencies
uv sync --all-extras

# Run tests
uv run pytest

# Type check
uv run mypy src/detrix

# Lint
uv run ruff check src/detrix

License

MIT

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

detrix_py-1.1.1.tar.gz (104.1 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

detrix_py-1.1.1-py3-none-any.whl (31.1 kB view details)

Uploaded Python 3

File details

Details for the file detrix_py-1.1.1.tar.gz.

File metadata

  • Download URL: detrix_py-1.1.1.tar.gz
  • Upload date:
  • Size: 104.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.10.6 {"installer":{"name":"uv","version":"0.10.6","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for detrix_py-1.1.1.tar.gz
Algorithm Hash digest
SHA256 0ad4d281a487a56c0107805e5188d9bde1f020a34f5a8f470d9e9babe8ccf72f
MD5 944bc698a386ef81e6652ff115a0fe32
BLAKE2b-256 881c5bc912b7bc1c246030e10cec4e21f5c20c8767e9b00b402c1390b96ac6c9

See more details on using hashes here.

File details

Details for the file detrix_py-1.1.1-py3-none-any.whl.

File metadata

  • Download URL: detrix_py-1.1.1-py3-none-any.whl
  • Upload date:
  • Size: 31.1 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.10.6 {"installer":{"name":"uv","version":"0.10.6","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for detrix_py-1.1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 20a17bc6f706ad3dd1eb2237af24cc6b76d6eb6d59aec0706539959a0eaf504f
MD5 9e08fe80681579af543029712e395b46
BLAKE2b-256 c5e4288c4544a9faf01614c667e2112eb6c86b9694728e61d2c3ba872b281522

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page