skills/daemon-development/SKILL.md
Build daemon/background processes that start on boot, run continuously, and manage their own lifecycle. Covers macOS launchd (plist files, agents vs daemons), Linux systemd (unit files), Windows services, process supervision, logging, health checks, graceful shutdown, auto-restart, and AI-powered daemons that manage LLM API connections and rate limits. Activate on: "daemon", "background process", "launchd", "systemd", "service file", "plist", "launch agent", "launch daemon", "auto-start", "always running", "process supervisor", "pm2", "background service", "boot service", "AI daemon", "long-running process". NOT for: container orchestration (use devops-automator), cron jobs that run and exit (use task-scheduler), web server deployment (use backend-architect).
npx skillsauth add curiositech/windags-skills daemon-developmentInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Build long-running background processes that start reliably, run continuously, recover from failures, and shut down gracefully. Expert-level daemon architecture across macOS launchd, Linux systemd, and AI-powered services.
Platform Detected?
├─ macOS
│ ├─ Must run at boot (no user login): LaunchDaemon → /Library/LaunchDaemons/
│ ├─ User session required (GUI/files): LaunchAgent → ~/Library/LaunchAgents/
│ └─ System-wide user service: LaunchAgent → /Library/LaunchAgents/
├─ Linux
│ ├─ systemd available: systemd unit file → /etc/systemd/system/
│ ├─ Legacy SysV: init.d script (rare, avoid if possible)
│ └─ Container: s6 or built-in supervision
└─ Cross-platform dev
├─ Node.js app: pm2 for development, systemd/launchd for production
└─ Other languages: Direct systemd/launchd implementation
Daemon Startup Behavior?
├─ Simple process (doesn't fork)
│ ├─ systemd: Type=simple
│ └─ launchd: Standard plist (no special keys)
├─ Signals readiness when ready
│ ├─ systemd: Type=notify + sd_notify("READY=1")
│ └─ launchd: N/A (use health check instead)
├─ Forks child process (legacy)
│ ├─ systemd: Type=forking + PIDFile (avoid)
│ └─ launchd: Not supported (rewrite to not fork)
└─ Socket-activated
├─ systemd: Type=simple + [Socket] section
└─ launchd: Sockets dict in plist
Failure Recovery Strategy?
├─ Critical service (must always run)
│ ├─ systemd: Restart=always, RestartSec=5
│ └─ launchd: KeepAlive=true, ThrottleInterval=10
├─ Crash recovery only
│ ├─ systemd: Restart=on-failure
│ └─ launchd: KeepAlive={SuccessfulExit=false}
├─ Manual restart preferred
│ ├─ systemd: Restart=no
│ └─ launchd: KeepAlive=false
└─ Rate-limited restart
├─ systemd: StartLimitBurst=5, StartLimitIntervalSec=60
└─ launchd: ThrottleInterval=30 (built-in)
LLM API Connection Pattern?
├─ Single provider, token bucket
│ ├─ Token estimation: prompt_tokens + max_completion_tokens
│ ├─ Bucket refill: tokens_per_minute from provider limits
│ └─ Overflow: Queue requests with priority
├─ Multi-provider failover
│ ├─ Circuit breaker per provider (3 failures = 30s timeout)
│ ├─ Rate limit per provider independently
│ └─ Failover order: primary → secondary → queue
├─ Streaming responses
│ ├─ Reserve tokens optimistically
│ ├─ Adjust on actual_tokens in real-time
│ └─ Handle mid-stream rate limits gracefully
└─ Batch processing
├─ Group similar requests to maximize throughput
└─ Split large batches if they hit rate limits
SIGTERM Received?
├─ Web server daemon
│ ├─ 1. server.close() - stop accepting new connections
│ ├─ 2. Wait for active requests (timeout: TimeoutStopSec-5s)
│ ├─ 3. Close database connections
│ └─ 4. exit(0)
├─ Queue worker daemon
│ ├─ 1. Stop polling for new jobs
│ ├─ 2. Finish current job (timeout protection)
│ ├─ 3. Flush any pending state
│ └─ 4. exit(0)
├─ AI daemon
│ ├─ 1. Stop accepting new LLM requests
│ ├─ 2. Drain in-flight requests (respect provider timeouts)
│ ├─ 3. Save rate limit state to disk
│ └─ 4. Close provider connections, exit(0)
└─ Database/stateful daemon
├─ 1. Checkpoint/flush transactions
├─ 2. Close client connections gracefully
├─ 3. Release file locks
└─ 4. exit(0)
journalctl -u service shows start/crash/start pattern every few secondsRestartSec=10 (systemd) or ThrottleInterval=15 (launchd), implement startup validationps aux shows <defunct> processes, parent daemon still running but degradedps axo pid,ppid,stat,comm | grep Z shows zombie childrenwait() or waitpid(), use signal(SIGCHLD, SIG_IGN) if children are fire-and-forgetlsof -p <daemon_pid> | wc -l grows continuously, eventual EMFILE errorsLimitNOFILE in systemdSystemMaxUse, add log level controlsScenario: Building an AI daemon that processes user requests through OpenAI API, needs 99.9% uptime with graceful rate limit handling.
1. Initial Architecture Decision
# Decision: Multi-provider with circuit breakers
# Primary: OpenAI GPT-4, Secondary: Anthropic Claude, Tertiary: Local model
# systemd unit file choice
Type=notify # Daemon signals when fully initialized
Restart=on-failure
RestartSec=10
2. Rate Limiting Implementation
// Token bucket per provider (expert catches: different providers = different limits)
const rateLimiters = {
openai: new TokenBucket({ tokensPerMinute: 40000, burstCapacity: 8000 }),
anthropic: new TokenBucket({ tokensPerMinute: 25000, burstCapacity: 5000 }),
};
// Novice mistake: Request-based limiting
// Expert insight: LLM APIs are token-based, not request-based
async processRequest(req: LLMRequest) {
const estimatedTokens = this.estimateTokens(req.prompt, req.maxTokens);
await this.rateLimiters.openai.acquire(estimatedTokens);
// ... proceed with API call
}
3. Circuit Breaker Configuration
// Expert trade-off: Aggressive vs Conservative failover
const circuitBreaker = new CircuitBreaker({
failureThreshold: 3, // Conservative: 5+ for stable APIs, 3 for flaky ones
timeout: 30000, // Aggressive: 15s, Conservative: 60s
resetTimeout: 60000, // How long to wait before retry
});
// Decision point: When to fail over?
if (error.status === 429) {
// Rate limited: backoff on primary, don't fail over yet
await this.exponentialBackoff(provider, error.retryAfter);
} else if (error.status >= 500) {
// Server error: immediate failover to secondary
this.circuitBreaker.recordFailure('openai');
}
4. Graceful Shutdown Pattern
// Expert catches: Race conditions in shutdown
let shutdownInProgress = false;
process.on('SIGTERM', async () => {
if (shutdownInProgress) return; // Idempotent shutdown
shutdownInProgress = true;
console.log('SIGTERM received, draining connections...');
// 1. Stop accepting new requests
server.close();
// 2. Wait for in-flight requests (with timeout)
const drainTimeout = setTimeout(() => {
console.log('Drain timeout, force exit');
process.exit(1);
}, 25000); // systemd TimeoutStopSec=30, so exit by 25s
await Promise.all([
this.drainActiveRequests(),
this.flushRateLimitState(), // Save token bucket state to disk
]);
clearTimeout(drainTimeout);
process.exit(0);
});
5. Trade-offs and Decision Results
RestartSec=5 for quick recovery vs RestartSec=30 to avoid thrashingThis skill is NOT for:
devops-automatortask-schedulerbackend-architectbackground-job-orchestratordatabase-architectDelegate to other skills when:
backend-architect handles service mesh, load balancingdevops-automator handles deployment automationbackground-job-orchestrator handles queue-specific patternsalways-on-agent-architecture handles AI-specific lifecycle needsdata-ai
license: Apache-2.0 NOT for unrelated tasks outside this domain.
development
Use when designing caching strategies (cache-aside, write-through, write-behind), implementing distributed locks, building rate limiters, leaderboards, real-time streams (XADD/consumer groups), pub/sub, or tuning eviction policies. Triggers: thundering-herd on cache miss, dogpile on key expiry, Redlock vs SET-NX-PX choice, sliding-window rate limiter, hot-key on a single cluster slot, big-key blowup, MULTI/EXEC across slots, KEYS in production. NOT for Redis Cluster operations/admin (different domain), embedded KV (SQLite, leveldb), in-process LRU caches, or Memcached.
tools
Drawing the `'use client'` boundary correctly in React Server Components apps (Next.js App Router, RSC frameworks) — leaf-pushing, slot composition, serialization rules, and environment poisoning prevention. Grounded in react.dev and Next.js 16 docs.
development
Use when designing rate limiting for an API, choosing between token bucket / sliding window / leaky bucket / fixed window, implementing it in Redis, deciding edge (Cloudflare/Upstash) vs origin enforcement, sizing per-user vs per-IP vs per-endpoint quotas, returning the right 429 response with Retry-After, or fixing the boundary-burst bug in fixed-window limiters. Triggers: 429 too many requests, INCR + EXPIRE, ZADD + ZREMRANGEBYSCORE + ZCARD, X-RateLimit-Remaining header, Cloudflare WAF rate limiting rules, Upstash @upstash/ratelimit, leaky bucket shaping vs policing, distributed rate limiter consistency. NOT for DDoS mitigation specifically (different scale), CAPTCHA / bot management, full WAF design, or per-user quota billing.