A persistent background daemon for pi agents. pi-daemon runs alongside your agent sessions and provides shared infrastructure that individual agents shouldn't have to manage themselves: collecting and exposing metrics, running scheduled jobs, and serving as a local coordination layer for the pi ecosystem.
Today:
- Accepts metric POST requests from agent extensions and exposes them as Prometheus metrics
Coming soon:
- Scheduled jobs — run tasks on a cron-like schedule independent of any active agent session
- RAG server — retrieval-augmented generation support for agent context and knowledge access
# Install dependencies
npm install
# Start the daemon in the foreground
npm run dev
# Or start in background
node --experimental-strip-types src/cli.ts start
# Check status
node --experimental-strip-types src/cli.ts status
# Stop
node --experimental-strip-types src/cli.ts stopCreate ~/.pi/daemon/config.json or set PI_DAEMON_CONFIG environment variable.
{
"server": {
"listenAddress": "127.0.0.1",
"port": 9876,
"maxPayloadSize": 1048576
},
"metrics": {
"enabled": true,
"retentionMinutes": 60,
"maxLabelCount": 10,
"maxMetricNames": 1000
},
"plugins": {
"metrics": true
},
"auth": {
"enabled": false
},
"logging": {
"level": "info"
}
}See examples/config.json for a full example.
pi-daemon exposes an HTTP API on 127.0.0.1:9876 by default.
GET /metrics
Returns metrics in Prometheus text format.
Response: 200 OK — text/plain; version=0.0.4; charset=utf-8
# HELP pi_compaction_tokens_before Counter metric: pi_compaction_tokens_before
# TYPE pi_compaction_tokens_before counter
pi_compaction_tokens_before{session="abc123",model="claude-sonnet-4"} 5000
POST /api/metrics
Content-Type: application/json
{
"name": "pi_compaction_tokens_before",
"type": "counter",
"labels": { "session": "abc123", "model": "claude-sonnet-4" },
"increment": 5000,
"timestamp": 1700000000000
}
Auth: Required when auth.enabled: true (Bearer token in Authorization header).
Response: 204 No Content on success, 400 Bad Request on validation error.
POST /api/metrics/batch
Content-Type: application/json
{
"requestId": "uuid-here",
"metrics": [
{ "name": "metric_a", "type": "counter", "increment": 1 },
{ "name": "metric_b", "type": "gauge", "set": 42 }
]
}
The optional requestId enables idempotency — if the same ID is sent within 5 minutes, the batch is rejected with 409 Conflict.
Response: 200 OK
{ "success": 2, "failed": [] }| Type | Field | Description |
|---|---|---|
counter |
increment (number, >= 0) |
Increment by value. Defaults to 1. |
gauge |
set (number) |
Set to this absolute value. |
gauge |
delta (number) |
Add this amount (can be negative). |
histogram |
observe (number) |
Record an observation. |
summary |
observe (number) |
Record an observation. |
Metric names must be valid Prometheus metric names. Label keys must match [a-zA-Z0-9_]+.
| Endpoint | Description |
|---|---|
GET /health |
Health check ({"status": "ok", "uptime": 12345.67, "version": "0.1.0"}) |
GET /config |
Current config (auth token redacted) |
POST /admin/reload |
Reload configuration ({"status": "reloaded"}) |
GET /admin/stats |
Runtime statistics (uptime, memory, pid, node version) |
GET /api/metrics/registry |
List registered metric names |
All errors return structured JSON:
{
"error": "Description of the error",
"code": "ERROR_CODE",
"details": {}
}| Code | Status | Description |
|---|---|---|
VALIDATION_ERROR |
400 | Request body failed validation |
INGESTION_ERROR |
400 | Metric could not be ingested |
AUTH_MISSING |
401 | Authorization header not provided |
AUTH_INVALID |
403 | Invalid auth token |
DUPLICATE_REQUEST |
409 | Batch with same requestId already processed |
RATE_LIMITED |
429 | Rate limit exceeded |
INTERNAL_ERROR |
500 | Unexpected server error |
The daemon exposes its own operational metrics on /metrics:
| Metric | Type | Description |
|---|---|---|
pi_daemon_requests_total |
counter | Total requests (labels: method, path, status) |
pi_daemon_request_duration_seconds |
histogram | Request latency |
pi_daemon_uptime_seconds |
gauge | Daemon uptime |
pi_daemon_memory_usage_bytes |
gauge | Memory usage (labels: type) |
The pi-daemon extension is a pi agent extension that integrates your agent sessions with a running pi-daemon instance. It listens to runtime events and sends metrics to the daemon via fire-and-forget POST requests.
// .pi/settings.json
{
"packages": ["pi-daemon"]
}Create ~/.pi/daemon-client/config.json to configure the extension. Set PI_DAEMON_CLIENT_CONFIG to override the default path.
{
"daemonUrl": "http://127.0.0.1:9876",
"auth": {
"token": "your-token"
},
"maxBufferSize": 50,
"flushIntervalMs": 5000,
"requestTimeoutMs": 2000,
"enabled": true,
"metrics": {
"compaction": true,
"sessions": true
}
}Config values are resolved in the following priority order (highest to lowest):
- Environment variables — override everything
- Config file —
~/.pi/daemon-client/config.json(orPI_DAEMON_CLIENT_CONFIG) - Defaults — built-in defaults
Each layer is deep-merged onto the previous one. Only fields present in a layer override the layer below it.
| Key | Default | Description |
|---|---|---|
daemonUrl |
http://127.0.0.1:9876 |
Daemon URL |
auth.token |
(none) | Bearer token for authenticated daemon endpoints |
maxBufferSize |
50 |
Max metrics to buffer before flushing |
flushIntervalMs |
5000 |
Flush interval (ms) |
requestTimeoutMs |
2000 |
HTTP request timeout (ms) |
enabled |
true |
Enable/disable the extension |
metrics.compaction |
true |
Send compaction metrics |
metrics.sessions |
true |
Send session metrics |
| Variable | Maps To |
|---|---|
PI_DAEMON_CLIENT_CONFIG |
Config file path (overrides default location) |
PI_DAEMON_URL |
daemonUrl |
PI_DAEMON_TOKEN |
auth.token |
PI_DAEMON_BUFFER_SIZE |
maxBufferSize |
PI_DAEMON_FLUSH_INTERVAL |
flushIntervalMs |
PI_DAEMON_TIMEOUT |
requestTimeoutMs |
PI_DAEMON_ENABLED |
enabled (set to "false" to disable) |
The extension automatically emits these metrics (all include session and model labels when available):
| Metric | Type | Description |
|---|---|---|
pi_compaction_tokens_before |
counter | Tokens before compaction |
pi_compaction_tokens_after |
counter | Tokens after compaction |
pi_compaction_tokens_saved |
counter | Tokens saved (before - after) |
pi_compaction_summary_length |
gauge | Length of the summary text |
pi_compaction_duration_seconds |
histogram | Compaction duration |
pi_compaction_total |
counter | Total compactions performed |
| Metric | Type | Description |
|---|---|---|
pi_sessions_started |
counter | Sessions started |
pi_sessions_ended |
counter | Sessions ended |
# Single metric
curl -X POST http://127.0.0.1:9876/api/metrics \
-H "Content-Type: application/json" \
-d '{"name":"my_counter","type":"counter","increment":1}'
# Batch
curl -X POST http://127.0.0.1:9876/api/metrics/batch \
-H "Content-Type: application/json" \
-d '{"metrics":[{"name":"a","type":"counter","increment":1},{"name":"b","type":"gauge","set":42}]}'
# With auth
curl -X POST http://127.0.0.1:9876/api/metrics \
-H "Content-Type: application/json" \
-H "Authorization: Bearer your-token-here" \
-d '{"name":"c","type":"counter","increment":1}'- Use batch ingestion for multiple metrics — it reduces HTTP overhead.
- Include meaningful labels — session, model, and source help with filtering.
- Keep label cardinality low — avoid unique values in label keys (e.g., timestamps, UUIDs).
- Use counters for cumulative counts — they are the most memory-efficient.
- Use histograms for distributions — latency, token counts, etc.
- Set a reasonable timeout — the default 2s prevents blocking the pi event loop.
Run pi-daemon as a managed system service with auto-restart and journald logging.
- Node.js 22.19.0+
- systemd (Linux)
1. Install the daemon:
# Global install (recommended)
npm install -g pi-daemon
# Or from source
git clone https://github.com/earendil-works/pi-daemon.git
cd pi-daemon
npm install
npm run build
npm install -g .2. Create config directory and config file:
sudo mkdir -p /etc/pi-daemon
sudo mkdir -p /var/lib/pi-daemon
sudo mkdir -p /var/log/pi-daemon
sudo cp contrib/pi-daemon.json /etc/pi-daemon/config.json3. Install the systemd service:
sudo cp contrib/pi-daemon.service /etc/systemd/system/
sudo systemctl daemon-reload4. Enable and start:
sudo systemctl enable --now pi-daemon.service
sudo systemctl status pi-daemon.service# Start / stop / restart
sudo systemctl start pi-daemon.service
sudo systemctl stop pi-daemon.service
sudo systemctl restart pi-daemon.service
# Reload configuration (SIGHUP)
sudo systemctl reload pi-daemon.service
# View logs
sudo journalctl -u pi-daemon.service -fEdit /etc/pi-daemon/config.json. Key settings:
| Setting | Default | Description |
|---|---|---|
server.listenAddress |
127.0.0.1 |
Bind address (keep 127.0.0.1 unless behind a reverse proxy) |
server.port |
9876 |
HTTP port |
auth.enabled |
false |
Enable Bearer token auth on POST endpoints |
logging.level |
info |
Log level (debug, info, warn, error) |
sudo systemctl stop pi-daemon.service
sudo systemctl disable pi-daemon.service
sudo rm /etc/systemd/system/pi-daemon.service
sudo systemctl daemon-reload
sudo rm -rf /etc/pi-daemon /var/lib/pi-daemon /var/log/pi-daemon
npm uninstall -g pi-daemonCurrent:
┌─────────────────┐ POST ┌──────────────────────────────┐
│ pi Agent │ ──────────► │ pi-daemon :9876 │
│ (Extension) │ │ │
│ │ │ • /api/metrics (ingest) │
│ • Compaction │ │ • /metrics (expose) │
│ • Session │ │ • /health │
│ events │ │ • /admin/* │
└─────────────────┘ └──────────────────────────────┘
▲
│ GET (scrape)
┌─────┴──────┐
│ Prometheus │
└────────────┘
Planned:
┌─────────────────┐ POST ┌──────────────────────────────┐
│ pi Agent │ ──────────► │ pi-daemon :9876 │
│ (Extension) │ │ │
│ │ │ • Metrics plugin │
│ • Compaction │ │ • Scheduled jobs (planned) │
│ • Session │ │ • RAG server (planned) │
│ events │ │ │
└─────────────────┘ └──────────────────────────────┘
▲
│ GET (scrape)
┌─────┴──────┐
│ Prometheus │
└────────────┘
- Default listen address is
127.0.0.1(localhost only) - Enable
auth.enabled: truefor Bearer token authentication on POST endpoints - When auth is enabled without a token, one is auto-generated
- Config file contains auth token in plaintext — ensure
chmod 600on the config file
MIT