Documentation Index
Fetch the complete documentation index at: https://taskdaemon.mintlify.app/llms.txt
Use this file to discover all available pages before exploring further.
Raw Protocol
Write handlers in any language without an SDK using the raw stdin/stdout protocol.
Protocol Overview
TaskDaemon communicates with handlers via:
- Input: JSON task objects sent to stdin (one per line)
- Output: JSON result objects written to stdout (one per line)
The handler process stays alive and processes multiple tasks.
Each line on stdin is a JSON object:
{
"task_id": "550e8400-e29b-41d4-a716-446655440000",
"task_type": "process",
"task_data": {"key": "value"},
"attempt": 0
}
| Field | Type | Description |
|---|
task_id | string | Unique task identifier (UUID) |
task_type | string | Handler type from config |
task_data | object | Arbitrary input data from client |
attempt | number | Current attempt number (0-indexed) |
Write one JSON object per line to stdout.
Success Response
{"status": "success", "result": {"key": "value"}}
Error Response
{"status": "error", "error": "Error message", "retryable": false}
| Field | Type | Description |
|---|
status | string | "success" or "error" |
result | any | Result data (success only) |
error | string | Error message (error only) |
retryable | boolean | Whether to retry (error only, default: false) |
Example Implementations
Python (No SDK)
import json
import sys
for line in sys.stdin:
task = json.loads(line)
try:
# Process task
name = task["task_data"].get("name", "World")
result = {"message": f"Hello, {name}!"}
response = {"status": "success", "result": result}
except Exception as e:
response = {"status": "error", "error": str(e), "retryable": False}
print(json.dumps(response), flush=True)
Node.js (No SDK)
const readline = require('readline');
const rl = readline.createInterface({ input: process.stdin });
rl.on('line', (line) => {
const task = JSON.parse(line);
try {
const name = task.task_data.name || 'World';
const result = { message: `Hello, ${name}!` };
console.log(JSON.stringify({ status: 'success', result }));
} catch (e) {
console.log(JSON.stringify({ status: 'error', error: String(e), retryable: false }));
}
});
Ruby
require 'json'
STDOUT.sync = true
STDIN.each_line do |line|
task = JSON.parse(line)
begin
name = task['task_data']['name'] || 'World'
result = { message: "Hello, #{name}!" }
puts JSON.generate({ status: 'success', result: result })
rescue => e
puts JSON.generate({ status: 'error', error: e.message, retryable: false })
end
end
PHP
<?php
while ($line = fgets(STDIN)) {
$task = json_decode($line, true);
try {
$name = $task['task_data']['name'] ?? 'World';
$result = ['message' => "Hello, {$name}!"];
$response = ['status' => 'success', 'result' => $result];
} catch (Exception $e) {
$response = ['status' => 'error', 'error' => $e->getMessage(), 'retryable' => false];
}
echo json_encode($response) . "\n";
flush();
}
Bash
#!/bin/bash
while IFS= read -r line; do
name=$(echo "$line" | jq -r '.task_data.name // "World"')
echo "{\"status\": \"success\", \"result\": {\"message\": \"Hello, $name!\"}}"
done
Perl
use JSON;
use strict;
$| = 1; # Autoflush
while (my $line = <STDIN>) {
my $task = decode_json($line);
my $name = $task->{task_data}{name} // 'World';
print encode_json({
status => 'success',
result => { message => "Hello, $name!" }
}) . "\n";
}
Important Requirements
1. Flush Output
Always flush stdout after writing to ensure TaskDaemon receives the response immediately:
print(json.dumps(response), flush=True) # Python
// Node.js console.log auto-flushes
console.log(JSON.stringify(response));
STDOUT.sync = true # Ruby
2. One Response Per Task
Write exactly one JSON line per input line. Don’t write partial responses or multiple lines.
3. Keep Running
The handler process stays alive and processes multiple tasks. Don’t exit after one task:
# Wrong - exits after one task
task = json.loads(input())
print(json.dumps(result))
# Correct - keeps running
for line in sys.stdin:
task = json.loads(line)
print(json.dumps(result), flush=True)
4. Handle Errors Gracefully
Return error status instead of crashing:
try:
result = process(task)
response = {"status": "success", "result": result}
except Exception as e:
response = {"status": "error", "error": str(e), "retryable": False}
5. Use Unbuffered I/O
Configure your runtime for unbuffered or line-buffered I/O:
# Python
ENV PYTHONUNBUFFERED=1
CMD ["python", "-u", "handler.py"]
# Node.js (auto-flushes)
CMD ["node", "handler.js"]
# Ruby
CMD ["ruby", "-e", "$stdout.sync=true; load 'handler.rb'"]
Dockerfile Template
FROM your-base-image
COPY handler.ext /app/
WORKDIR /app
# Ensure unbuffered output (language-specific)
ENV PYTHONUNBUFFERED=1
CMD ["your-runtime", "handler.ext"]
Testing Handlers Locally
Test your handler without TaskDaemon:
echo '{"task_id":"test","task_type":"greet","task_data":{"name":"World"},"attempt":0}' | ./handler
Expected output:
{"status":"success","result":{"message":"Hello, World!"}}