<!--
hoody-daemon Subskill (sdk)
Auto-generated by Hoody Skills Generator
Generated: 2026-06-20T00:06:08.314Z
Model: mimo-v2.5-pro + fixer:z-ai/glm-5.1
Mode: sdk


Tokens: 7652

DO NOT EDIT MANUALLY - Changes will be overwritten on next generation
-->

# hoody-daemon Subskill

**Service Type**: Hoody Kit Service  
**Purpose**: Long-running process management via HTTP  
**Base URL**: `https://{projectId}-{containerId}-daemon-{serviceId}.{node}.containers.hoody.com`

---

## Overview

### What It Does

hoody-daemon manages long-running processes (programs) inside your container using supervisord. It provides HTTP endpoints to:

- **Configure** program definitions (add, edit, remove, reset)
- **Control** program lifecycle (start, stop, enable, disable)
- **Monitor** runtime status and logs
- **Launch ephemeral** programs via quick-start that auto-cleanup

### When to Use

| Scenario | Use hoody-daemon |
|----------|------------------|
| Custom background worker | ✅ Add persistent program |
| Temporary processing task | ✅ Quick-start ephemeral program |
| Application server with port range | ✅ Port-range program config |
| System service (nginx, apache2) | ❌ Use `systemctl` directly |
| One-off command execution | ❌ Use container exec |

### Service Philosophy

hoody-daemon embodies Hoody's "managed services" philosophy: declarative configuration with HTTP control. Programs are defined once, then managed via API. The daemon handles supervisord integration, log rotation, and port allocation automatically.

### Key Concepts

| Term | Description |
|------|-------------|
| **Program** | Persistent process definition stored in config |
| **Ephemeral Program** | Temporary process that auto-cleans on stop or reboot |
| **Port Range** | Programs that spawn multiple instances across ports |
| **Enabled/Disabled** | Whether program is registered with supervisord |
| **Boot** | Whether program starts automatically on container boot |

---

## Common Workflows

### 1. Health Check

Always verify service availability before operations:

```
const health = await client.daemon.health.check();
console.log(health.status); // "ok"
```

### 2. List All Programs

Retrieve all configured programs with optional filters:

```
// List all programs
const allPrograms = await client.daemon.programs.list();

// Filter by hoody_kit tag
const kitPrograms = await client.daemon.programs.list({ hoody_kit: 'my-kit' });

// Include runtime status in response
const withStatus = await client.daemon.programs.list({ include_status: 'true' });
```

### 3. Get Program Details

Retrieve full configuration for a specific program:

```
const program = await client.daemon.programs.get(42);
console.log(program.name, program.command, program.enabled);
```

### 4. Add a Custom Program

Create a new persistent daemon program:

```
const newProgram = await client.daemon.programs.add({
  name: 'my-worker',
  command: 'node /app/worker.js --concurrency=4',
  user: 'www-data',
  port_range: {
    start: 3000,
    end: 3010
  }
});
console.log('Created program ID:', newProgram.id);
```

**Key rules for custom programs:**
- `name` must be unique and cannot contain quotes
- `user` must exist on the system
- Port range max span: 1000 ports

### 5. Edit an Existing Program

Update program configuration (partial updates supported):

```
const updated = await client.daemon.programs.edit(42, {
  name: 'my-worker-v2',
  command: 'node /app/worker.js --concurrency=8',
  user: 'www-data',
  port_range: {
    start: 3000,
    end: 3010
  }
});
```

### 6. Enable/Disable Programs

Toggle program registration with supervisord:

```
// Enable a disabled program (registers with supervisord)
await client.daemon.control.enable(42);

// Disable a program (stops if running, removes from supervisord)
await client.daemon.control.disable(42);
```

### 7. Start/Stop Programs

Control running instances:

```
// Start a program
await client.daemon.control.start(42);

// Start specific port instance (for port-range programs)
await client.daemon.control.start(42, { port: 3001 });

// Stop a program
await client.daemon.control.stop(42);

// Stop all port instances
await client.daemon.control.stop(42, { all: true });

// Stop specific port
await client.daemon.control.stop(42, { port: 3001 });
```

### 8. Check Program Status

Monitor runtime state:

```
// All programs status
const allStatus = await client.daemon.status.getAll();

// Specific program status
const status = await client.daemon.status.get(42);

// Port-range program with specific port
const portStatus = await client.daemon.status.get(42, { port: 3001 });
```

### 9. Retrieve Logs

View program output:

```
// Get stdout logs (last 100 lines)
const logs = await client.daemon.status.getLogs(42, { type: 'stdout', lines: 100 });

// Get stderr logs
const errorLogs = await client.daemon.status.getLogs(42, { type: 'stderr', lines: 50 });
```

### 10. Remove a Program

Permanently delete a program (stops if running):

```
await client.daemon.programs.remove(42);
console.log('Program removed');
```

### 11. Quick-Start Ephemeral Programs

Launch temporary processes that auto-cleanup:

```
// Launch ephemeral program
const ephemeral = await client.daemon.quickStart.launch({
  command: 'python3 /tmp/process_batch.py --file=data.csv',
  user: 'www-data'
});
console.log('Ephemeral ID:', ephemeral.temporary_id);

// Check status
const status = await client.daemon.quickStart.getStatus(ephemeral.temporary_id);

// Get logs
const logs = await client.daemon.quickStart.getEphemeralLogs(ephemeral.temporary_id, { type: 'stdout' });

// Stop and cleanup
await client.daemon.quickStart.stop(ephemeral.temporary_id);
```

### 12. List Ephemeral Programs

```
const ephemeralList = await client.daemon.quickStart.list();
console.log(`${ephemeralList.length} ephemeral programs running`);
```

---

## Advanced Operations

### Complete Program Lifecycle Workflow

Full cycle: create → configure → verify → start → monitor → cleanup:

```
async function deployWorker(client: HoodyClient) {
  // 1. Add program
  const program = await client.daemon.programs.add({
    name: 'batch-processor',
    command: 'node /app/batch.js --queue=default',
    user: 'app',
    port_range: { start: 4000, end: 4010 }
  });

  // 2. Enable (registers with supervisord)
  await client.daemon.control.enable(program.id);

  // 3. Start
  await client.daemon.control.start(program.id, { port: 4000 });

  // 4. Verify running
  const status = await client.daemon.status.get(program.id, { port: 4000 });
  if (status.state !== 'RUNNING') {
    throw new Error(`Expected RUNNING, got ${status.state}`);
  }

  // 5. Monitor
  const logs = await client.daemon.status.getLogs(program.id, { type: 'stdout', lines: 20 });
  console.log('Recent output:', logs.lines);

  // 6. Cleanup when done
  await client.daemon.control.stop(program.id, { all: true });
  await client.daemon.programs.remove(program.id);
}
```

### Reset to Default State

Restore all programs to container setup defaults:

```
// WARNING: Stops all programs, removes custom configs
await client.daemon.programs.reset();
console.log('Reset to default programs');
```

### Ephemeral Data Processing Pipeline

Process multiple files with parallel ephemeral workers:

```
async function processFiles(client: HoodyClient, files: string[]) {
  const workers = [];

  for (const file of files) {
    const ephemeral = await client.daemon.quickStart.launch({
      command: `python3 /app/process.py --input=${file}`,
      user: 'worker'
    });
    workers.push(ephemeral.temporary_id);
  }

  // Monitor until all complete
  while (workers.length > 0) {
    for (const id of [...workers]) {
      const status = await client.daemon.quickStart.getStatus(id);
      if (status.state === 'STOPPED' || status.state === 'EXITED') {
        const logs = await client.daemon.quickStart.getEphemeralLogs(id, { type: 'stderr' });
        if (logs.lines.length > 0) {
          console.error(`Worker ${id} errors:`, logs.lines);
        }
        workers.splice(workers.indexOf(id), 1);
      }
    }
    await new Promise(r => setTimeout(r, 2000));
  }
}
```

### Error Recovery Pattern

Handle common failure scenarios:

```
async function safeStart(client: HoodyClient, programId: number) {
  try {
    await client.daemon.control.start(programId);
  } catch (err: any) {
    if (err.status === 409) {
      // Already running - stop then restart
      console.log('Program already running, restarting...');
      await client.daemon.control.stop(programId);
      await new Promise(r => setTimeout(r, 1000));
      await client.daemon.control.start(programId);
    } else if (err.status === 404) {
      // Program not found or disabled
      console.log('Enabling disabled program...');
      await client.daemon.control.enable(programId);
      await client.daemon.control.start(programId);
    } else {
      throw err;
    }
  }
}
```

### Performance Considerations

| Operation | Cost | Recommendation |
|-----------|------|----------------|
| `list()` with `include_status` | Higher | Use for dashboards only |
| `list()` without status | Lower | Use for config enumeration |
| `status.getAll()` | Single call | Use for monitoring dashboards |
| Ephemeral programs | Lightweight | OK for batch processing |

**Tips:**
- Avoid tight polling loops; 2-5 second intervals for status checks
- Port-range programs: query specific port instead of full range when possible

---

## Quick Reference

### Essential Endpoints

| Action | SDK Method | HTTP |
|--------|-----------|------|
| Health check | `client.daemon.health.check()` | GET |
| List programs | `client.daemon.programs.list()` | GET |
| Get program | `client.daemon.programs.get(id)` | GET |
| Add program | `client.daemon.programs.add(data)` | POST |
| Edit program | `client.daemon.programs.edit(id, data)` | POST |
| Remove program | `client.daemon.programs.remove(id)` | POST |
| Reset all | `client.daemon.programs.reset()` | POST |
| Enable | `client.daemon.control.enable(id)` | POST |
| Disable | `client.daemon.control.disable(id)` | POST |
| Start | `client.daemon.control.start(id, opts?)` | POST |
| Stop | `client.daemon.control.stop(id, opts?)` | POST |
| All status | `client.daemon.status.getAll()` | GET |
| Program status | `client.daemon.status.get(id, opts?)` | GET |
| Program logs | `client.daemon.status.getLogs(id, opts?)` | GET |
| List ephemeral | `client.daemon.quickStart.list()` | GET |
| Launch ephemeral | `client.daemon.quickStart.launch(data)` | POST |
| Ephemeral status | `client.daemon.quickStart.getStatus(id)` | GET |
| Ephemeral logs | `client.daemon.quickStart.getEphemeralLogs(id, opts?)` | GET |
| Stop ephemeral | `client.daemon.quickStart.stop(id)` | POST |

### Required Fields Reference

**Add/Edit Program:**
```
{
  name: string,           // Unique, no quotes
  command: string,        // Full command with args
  user: string,           // System user
  port_range: {
    start: number,        // Starting port
    end: number           // Max 1000 ports from start
  }
}
```

**Quick-Start:**
```
{
  command: string,        // Full command with args
  user: string            // System user
}
```

### Typical Response Shapes

**Health:**
```
{
  "status": "ok",
  "service": "daemon",
  "version": "1.0.0",
  "uptime": 3600
}
```

**Program:**
```
{
  "id": 42,
  "name": "my-worker",
  "command": "node /app/worker.js",
  "enabled": true,
  "boot": true,
  "user": "www-data",
  "port_range": { "start": 3000, "end": 3010 }
}
```

**Status:**
```
{
  "id": 42,
  "name": "my-worker",
  "state": "RUNNING",
  "pid": 1234,
  "uptime": 3600
}
```

**Ephemeral:**
```
{
  "temporary_id": "eph-abc123",
  "command": "python3 /tmp/script.py",
  "state": "RUNNING",
  "pid": 5678
}
```